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