1
// Automated tests should only cover js API. Use a manual test for native API
2
Y.JSON.useNativeStringify = false;
4
var suite = new Y.Test.Suite("Y.JSON.stringify (JavaScript implementation)");
6
suite.add(new Y.Test.Case({
11
test_failOnStringifyCyclicalRef1 : true,
12
test_failOnStringifyCyclicalRef2 : true,
13
test_failOnStringifyCyclicalRef3 : true
18
Y.one("body").append('<form id="testbed" action="">' +
19
'<h3>Form used for field value extraction, stringification</h3>' +
20
'<input type="text" id="empty_text">' +
21
'<input type="text" id="text" value="text">' +
22
'<input type="radio" name="radio" id="unchecked_radio" value="unchecked">' +
23
'<input type="radio" name="radio" id="checked_radio" value="radio" checked="checked">' +
24
'<input type="checkbox" name="box" id="unchecked_box" value="unchecked">' +
25
'<input type="checkbox" name="box" id="checked_box" value="box" checked="checked">' +
26
'<textarea id="empty_textarea"></textarea>' +
27
'<textarea id="textarea">textarea</textarea>' +
28
'<select id="select">' +
29
'<option value="unselected">Unselected</option>' +
30
'<option value="selected" selected="selected">Selected</option>' +
32
'<select id="multiple_select" multiple="multiple" size="3">' +
33
'<option value="unselected">Unselected</option>' +
34
'<option value="selected" selected="selected">Selected</option>' +
35
'<option value="selected also" selected="selected">Selected also</option>' +
37
'<button id="button" type="button">content; no value</button>' +
38
'<button id="button_with_value" type="button" value="button value">content and value</button>' +
39
'<button id="button_submit" type="submit">content; no value</button>' +
40
'<button id="button_submit_with_value" type="submit" value="submit button value">content and value</button>' +
41
'<input type="button" id="input_button" value="input button">' +
42
'<input type="submit" id="input_submit" value="input submit">' +
43
'<!--input type="image" id="input_image" src="404.png" value="input image"-->' +
47
tearDown: function () {
48
Y.one("#testbed").remove(true);
51
test_stringifyNatives: function () {
52
Y.Assert.areSame('[true,false,null,-0.12345,"string",{"object with one member":["array with one element"]}]',
53
Y.JSON.stringify([true,false,null,-0.12345,"string",{"object with one member":["array with one element"]}]));
56
test_stringifyEscapes: function () {
57
Y.Assert.areSame('["\\\\","\\\\\'","\\\\\\\"","\\\\\\\"","\\b","\\b","\\\\b","\\\\\\b","\\\\\\b","\\b","\\\\x08","\\\\\\b","\\\\\\\\x08","\\n","\\\\n","\\\\\\n"]',
58
Y.JSON.stringify(['\\','\\\'', "\\\"", '\\"', "\b", '\b', "\\b","\\\b", '\\\b', "\x08", "\\x08", "\\\x08","\\\\x08", "\n", "\\n", "\\\n"]));
61
test_stringifyObject : function () {
62
// stringify sorts the keys
63
Y.Assert.areSame('{"one":1,"two":true,"three":false,"four":null,"five":"String with\\nnewline","six":{"nested":-0.12345}}',
64
Y.JSON.stringify({one:1,two:true,three:false,four:null,five:"String with\nnewline",six : {nested:-0.12345}}));
67
test_failOnStringifyCyclicalRef1 : function () {
68
var o = { key: 'value' };
71
// Should throw an error
73
Y.log("Stringified Object with cyclical reference, but should have failed.","warn","TestRunner");
76
test_failOnStringifyCyclicalRef2 : function () {
80
// Should throw an error
82
Y.log("Stringified Array with cyclical reference, but should have failed.","warn","TestRunner");
85
test_failOnStringifyCyclicalRef3 : function () {
86
var o = [1,2,3,{key:"value",nest:[4,5,6,{foo:"bar"}]}];
87
o[4] = o[3].x = o[3].nest[4] =
88
o[3].nest[3].y = o[3].nest[3].z = o;
90
// Should throw an error
92
Y.log("Stringified Object with cyclical reference, but should have failed.","warn","TestRunner");
95
test_stringifyFunction : function () {
96
Y.Assert.areSame('{"arr":[null]}',
98
functions : function (are,ignored) {},
99
arr : [ function () {} ]
103
test_stringifyRegex : function () {
104
Y.Assert.areSame('{"regex":{},"arr":[{}]}',
106
regex : /are treated as objects/,
107
arr : [ new RegExp("in array") ]
111
test_stringifyUndefined : function () {
112
Y.Assert.areSame('{"arr":[null]}',
119
test_stringifyDate : function () {
120
// native toJSON method should be called if available
121
var d = new Date(Date.UTC(1946,6,6)),
122
ref = d.toJSON ? d.toJSON() : "1946-07-06T00:00:00Z";
124
Y.Assert.areSame('{"dt":"'+ref+'"}', Y.JSON.stringify({dt : d}));
127
test_stringifyFormValue : function () {
128
function $(id) { return document.getElementById(id); }
131
empty_text : $('empty_text').value,
132
text : $('text').value,
133
unchecked_radio : $('unchecked_radio').value,
134
checked_radio : $('checked_radio').value,
135
unchecked_box : $('unchecked_box').value,
136
checked_box : $('checked_box').value,
137
empty_textarea : $('empty_textarea').value,
138
textarea : $('textarea').value,
139
select : $('select').value,
140
multiple_select : $('multiple_select').value,
141
// Buttons commented out for now because IE reports values
143
//button : $('button').value,
144
//button_with_value : $('button_with_value').value,
145
//button_submit : $('button_submit').value,
146
//button_submit_with_value: $('button_submit_with_value').value,
147
input_button : $('input_button').value,
148
input_submit : $('input_submit').value//,
149
//input_image : $('input_image').value
152
Y.Assert.areSame('{'+
155
'"unchecked_radio":"unchecked",'+
156
'"checked_radio":"radio",'+
157
'"unchecked_box":"unchecked",'+
158
'"checked_box":"box",'+
159
'"empty_textarea":"",'+
160
'"textarea":"textarea",'+
161
'"select":"selected",'+
162
'"multiple_select":"selected",'+
164
//'"button_with_value":"button value",'+
165
//'"button_submit":"",'+
166
//'"button_submit_with_value":"submit button value",'+
167
'"input_button":"input button",'+
168
'"input_submit":"input submit"}',
169
//'"input_image":"input image"}',
170
Y.JSON.stringify(data));
175
suite.add(new Y.Test.Case({
178
test_emptyWhitelistArray : function () {
179
Y.Assert.areSame('{}',
180
Y.JSON.stringify({foo:1,bar:[1,2,3],baz:true},[]));
182
Y.Assert.areSame('[1,true,null,{},["string",null,{}]]',
187
},["string",undefined,/some regex/]
191
test_whitelistArray : function () {
192
Y.Assert.areSame('{"foo":[1,2,3,{"foo":"FOO"}]}',
204
Y.Assert.areSame('{"foo":[1,2,3,{"foo":"FOO","baz":true}],"baz":true}',
218
baz : true},["foo","baz"]));
223
// REMOVED for spec compatibility
224
test_whitelistObject : function () {
225
// (undocumented) supports an obj literal as well
226
Y.Assert.areSame('{"foo":[1,2,3,{"foo":"FOO","baz":true}],"baz":true}',
241
}, {foo : true, baz : false})); // values ignored
247
suite.add(new Y.Test.Case({
250
test_falseyIndents : function () {
251
Y.Assert.areSame('{"foo0":[2,{"bar":[4,{"baz":[6,{"deep enough":7}]}]}]}',
262
/* Commented out because FF3.5 has infinite loop bug for neg indents
263
* Fixed in FF for next version.
264
Y.Assert.areSame('{"foo-4":[2,{"bar":[4,{"baz":[6,{"deep enough":7}]}]}]}',
276
Y.Assert.areSame('{"foo_false":[2,{"bar":[4,{"baz":[6,{"deep enough":7}]}]}]}',
287
Y.Assert.areSame('{"foo_empty":[2,{"bar":[4,{"baz":[6,{"deep enough":7}]}]}]}',
300
test_indentNumber : function () {
301
Y.Assert.areSame("{\n" +
311
" \"deep enough\": 7\n" +
329
test_indentString : function () {
330
Y.Assert.areSame("{\n" +
334
"XoXoXo\"bar\": [\n" +
337
"XoXoXoXoXo\"baz\": [\n" +
340
"XoXoXoXoXoXoXo\"deep enough\": 7\n" +
360
suite.add(new Y.Test.Case({
363
test_toJSON_on_object: function () {
364
Y.Assert.areSame('"toJSON"',
365
Y.JSON.stringify({ toJSON: function () { return "toJSON"; } }));
367
// TODO: complex object with toJSON
370
test_toJSON_on_proto: function () {
372
A.prototype.toJSON = function () { return "A"; };
375
B.prototype = new A();
382
C.prototype = new B();
384
Y.Assert.areSame('"A"', Y.JSON.stringify(new C()));
388
suite.add(new Y.Test.Case({
391
test_replacer : function () {
392
// replacer applies to even simple value stringifications
393
Y.Assert.areSame("20",
394
Y.JSON.stringify(10,function (k,v) {
395
return typeof(v) === 'number' ? v * 2 : v;
398
// replacer is applied to every nested property as well.
399
// can modify the host object en route
400
// executes from the context of the key:value container
401
Y.Assert.areSame('{"num":2,"alpha":"ABC","obj":{"nested_num":100,"alpha":"abc"},"arr":[2,null,4]}',
406
change: "to a function",
418
if (k === 'change') {
419
// this property should then be ignored
420
return function () {};
421
} else if (k === 'ignore') {
422
// this property should then be ignored
424
} else if (t === 'number') {
425
// undefined returned to arrays should become null
426
return v % 7 ? v * 2 : undefined;
428
// this refers to the object containing the key:value
429
} else if (t === 'string' && (this.toUpper)) {
430
// modify the object during stringification
432
return v.toUpperCase();
438
// replacer works in conjunction with indent
439
Y.Assert.areSame("{\n_\"num\": 2,\n_\"alpha\": \"ABC\",\n_\"obj\": {\n__\"nested_num\": 100,\n__\"alpha\": \"abc\"\n_},\n_\"arr\": [\n__2,\n__null,\n__4\n_]\n}",
444
change: "to a function",
456
if (k === 'change') {
457
// this property should then be ignored
458
return function () {};
459
} else if (k === 'ignore') {
460
// this property should then be ignored
462
} else if (t === 'number') {
463
// undefined returned to arrays should become null
464
return v % 7 ? v * 2 : undefined;
466
// this refers to the object containing the key:value
467
} else if (t === 'string' && (this.toUpper)) {
468
// modify the object during stringification
470
return v.toUpperCase();
477
test_replacer_after_toJSON : function () {
478
Y.Assert.areSame('{"a":"ABC"}',
479
Y.JSON.stringify({a:{ toJSON: function () { return "abc"; } } },
481
return typeof v === 'string' ? v.toUpperCase() : v;
484
// Date instances in ES5 have toJSON that outputs toISOString, which
485
// means the replacer should never receive a Date instance
486
var str = Y.JSON.stringify([new Date()], function (k,v) {
487
return (v instanceof Date) ? "X" : v;
489
Y.Assert.areSame(-1, str.indexOf("X"), "Date incorrectly received by replacer");
492
test_replacer_returning_Date : function () {
494
ref = Y.JSON.stringify(d);
496
Y.Assert.areSame('{"dt":'+ref+',"date_from_replacer":{}}',
497
Y.JSON.stringify({ dt: d, date_from_replacer: 1 },
499
return typeof v === 'number' ? d : v;
504
Y.Test.Runner.add(suite);