stringify_test.js (7550B)
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 }