1 | var Sinon = require("sinon")
|
---|
2 | var stringify = require("..")
|
---|
3 | function jsonify(obj) { return JSON.stringify(obj, null, 2) }
|
---|
4 |
|
---|
5 | describe("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 |
|
---|
244 | function bangString(key, value) {
|
---|
245 | return typeof value == "string" ? value + "!" : value
|
---|
246 | }
|
---|