stringify_test.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. var Sinon = require("sinon")
  2. var stringify = require("..")
  3. function jsonify(obj) { return JSON.stringify(obj, null, 2) }
  4. describe("Stringify", function() {
  5. it("must stringify circular objects", function() {
  6. var obj = {name: "Alice"}
  7. obj.self = obj
  8. var json = stringify(obj, null, 2)
  9. json.must.eql(jsonify({name: "Alice", self: "[Circular ~]"}))
  10. })
  11. it("must stringify circular objects with intermediaries", function() {
  12. var obj = {name: "Alice"}
  13. obj.identity = {self: obj}
  14. var json = stringify(obj, null, 2)
  15. json.must.eql(jsonify({name: "Alice", identity: {self: "[Circular ~]"}}))
  16. })
  17. it("must stringify circular objects deeper", function() {
  18. var obj = {name: "Alice", child: {name: "Bob"}}
  19. obj.child.self = obj.child
  20. stringify(obj, null, 2).must.eql(jsonify({
  21. name: "Alice",
  22. child: {name: "Bob", self: "[Circular ~.child]"}
  23. }))
  24. })
  25. it("must stringify circular objects deeper with intermediaries", function() {
  26. var obj = {name: "Alice", child: {name: "Bob"}}
  27. obj.child.identity = {self: obj.child}
  28. stringify(obj, null, 2).must.eql(jsonify({
  29. name: "Alice",
  30. child: {name: "Bob", identity: {self: "[Circular ~.child]"}}
  31. }))
  32. })
  33. it("must stringify circular objects in an array", function() {
  34. var obj = {name: "Alice"}
  35. obj.self = [obj, obj]
  36. stringify(obj, null, 2).must.eql(jsonify({
  37. name: "Alice", self: ["[Circular ~]", "[Circular ~]"]
  38. }))
  39. })
  40. it("must stringify circular objects deeper in an array", function() {
  41. var obj = {name: "Alice", children: [{name: "Bob"}, {name: "Eve"}]}
  42. obj.children[0].self = obj.children[0]
  43. obj.children[1].self = obj.children[1]
  44. stringify(obj, null, 2).must.eql(jsonify({
  45. name: "Alice",
  46. children: [
  47. {name: "Bob", self: "[Circular ~.children.0]"},
  48. {name: "Eve", self: "[Circular ~.children.1]"}
  49. ]
  50. }))
  51. })
  52. it("must stringify circular arrays", function() {
  53. var obj = []
  54. obj.push(obj)
  55. obj.push(obj)
  56. var json = stringify(obj, null, 2)
  57. json.must.eql(jsonify(["[Circular ~]", "[Circular ~]"]))
  58. })
  59. it("must stringify circular arrays with intermediaries", function() {
  60. var obj = []
  61. obj.push({name: "Alice", self: obj})
  62. obj.push({name: "Bob", self: obj})
  63. stringify(obj, null, 2).must.eql(jsonify([
  64. {name: "Alice", self: "[Circular ~]"},
  65. {name: "Bob", self: "[Circular ~]"}
  66. ]))
  67. })
  68. it("must stringify repeated objects in objects", function() {
  69. var obj = {}
  70. var alice = {name: "Alice"}
  71. obj.alice1 = alice
  72. obj.alice2 = alice
  73. stringify(obj, null, 2).must.eql(jsonify({
  74. alice1: {name: "Alice"},
  75. alice2: {name: "Alice"}
  76. }))
  77. })
  78. it("must stringify repeated objects in arrays", function() {
  79. var alice = {name: "Alice"}
  80. var obj = [alice, alice]
  81. var json = stringify(obj, null, 2)
  82. json.must.eql(jsonify([{name: "Alice"}, {name: "Alice"}]))
  83. })
  84. it("must call given decycler and use its output", function() {
  85. var obj = {}
  86. obj.a = obj
  87. obj.b = obj
  88. var decycle = Sinon.spy(function() { return decycle.callCount })
  89. var json = stringify(obj, null, 2, decycle)
  90. json.must.eql(jsonify({a: 1, b: 2}, null, 2))
  91. decycle.callCount.must.equal(2)
  92. decycle.thisValues[0].must.equal(obj)
  93. decycle.args[0][0].must.equal("a")
  94. decycle.args[0][1].must.equal(obj)
  95. decycle.thisValues[1].must.equal(obj)
  96. decycle.args[1][0].must.equal("b")
  97. decycle.args[1][1].must.equal(obj)
  98. })
  99. it("must call replacer and use its output", function() {
  100. var obj = {name: "Alice", child: {name: "Bob"}}
  101. var replacer = Sinon.spy(bangString)
  102. var json = stringify(obj, replacer, 2)
  103. json.must.eql(jsonify({name: "Alice!", child: {name: "Bob!"}}))
  104. replacer.callCount.must.equal(4)
  105. replacer.args[0][0].must.equal("")
  106. replacer.args[0][1].must.equal(obj)
  107. replacer.thisValues[1].must.equal(obj)
  108. replacer.args[1][0].must.equal("name")
  109. replacer.args[1][1].must.equal("Alice")
  110. replacer.thisValues[2].must.equal(obj)
  111. replacer.args[2][0].must.equal("child")
  112. replacer.args[2][1].must.equal(obj.child)
  113. replacer.thisValues[3].must.equal(obj.child)
  114. replacer.args[3][0].must.equal("name")
  115. replacer.args[3][1].must.equal("Bob")
  116. })
  117. it("must call replacer after describing circular references", function() {
  118. var obj = {name: "Alice"}
  119. obj.self = obj
  120. var replacer = Sinon.spy(bangString)
  121. var json = stringify(obj, replacer, 2)
  122. json.must.eql(jsonify({name: "Alice!", self: "[Circular ~]!"}))
  123. replacer.callCount.must.equal(3)
  124. replacer.args[0][0].must.equal("")
  125. replacer.args[0][1].must.equal(obj)
  126. replacer.thisValues[1].must.equal(obj)
  127. replacer.args[1][0].must.equal("name")
  128. replacer.args[1][1].must.equal("Alice")
  129. replacer.thisValues[2].must.equal(obj)
  130. replacer.args[2][0].must.equal("self")
  131. replacer.args[2][1].must.equal("[Circular ~]")
  132. })
  133. it("must call given decycler and use its output for nested objects",
  134. function() {
  135. var obj = {}
  136. obj.a = obj
  137. obj.b = {self: obj}
  138. var decycle = Sinon.spy(function() { return decycle.callCount })
  139. var json = stringify(obj, null, 2, decycle)
  140. json.must.eql(jsonify({a: 1, b: {self: 2}}))
  141. decycle.callCount.must.equal(2)
  142. decycle.args[0][0].must.equal("a")
  143. decycle.args[0][1].must.equal(obj)
  144. decycle.args[1][0].must.equal("self")
  145. decycle.args[1][1].must.equal(obj)
  146. })
  147. it("must use decycler's output when it returned null", function() {
  148. var obj = {a: "b"}
  149. obj.self = obj
  150. obj.selves = [obj, obj]
  151. function decycle() { return null }
  152. stringify(obj, null, 2, decycle).must.eql(jsonify({
  153. a: "b",
  154. self: null,
  155. selves: [null, null]
  156. }))
  157. })
  158. it("must use decycler's output when it returned undefined", function() {
  159. var obj = {a: "b"}
  160. obj.self = obj
  161. obj.selves = [obj, obj]
  162. function decycle() {}
  163. stringify(obj, null, 2, decycle).must.eql(jsonify({
  164. a: "b",
  165. selves: [null, null]
  166. }))
  167. })
  168. it("must throw given a decycler that returns a cycle", function() {
  169. var obj = {}
  170. obj.self = obj
  171. var err
  172. function identity(key, value) { return value }
  173. try { stringify(obj, null, 2, identity) } catch (ex) { err = ex }
  174. err.must.be.an.instanceof(TypeError)
  175. })
  176. describe(".getSerialize", function() {
  177. it("must stringify circular objects", function() {
  178. var obj = {a: "b"}
  179. obj.circularRef = obj
  180. obj.list = [obj, obj]
  181. var json = JSON.stringify(obj, stringify.getSerialize(), 2)
  182. json.must.eql(jsonify({
  183. "a": "b",
  184. "circularRef": "[Circular ~]",
  185. "list": ["[Circular ~]", "[Circular ~]"]
  186. }))
  187. })
  188. // This is the behavior as of Mar 3, 2015.
  189. // The serializer function keeps state inside the returned function and
  190. // so far I'm not sure how to not do that. JSON.stringify's replacer is not
  191. // called _after_ serialization.
  192. xit("must return a function that could be called twice", function() {
  193. var obj = {name: "Alice"}
  194. obj.self = obj
  195. var json
  196. var serializer = stringify.getSerialize()
  197. json = JSON.stringify(obj, serializer, 2)
  198. json.must.eql(jsonify({name: "Alice", self: "[Circular ~]"}))
  199. json = JSON.stringify(obj, serializer, 2)
  200. json.must.eql(jsonify({name: "Alice", self: "[Circular ~]"}))
  201. })
  202. })
  203. })
  204. function bangString(key, value) {
  205. return typeof value == "string" ? value + "!" : value
  206. }