UNPKG

d2-ui

Version:
498 lines (459 loc) 15.8 kB
// tests stringify() /*global require console exports */ // set to true to show performance stats var DEBUG = false; var assert = require('assert'); var JSON5 = require('../lib/json5'); // Test JSON5.stringify() by comparing its output for each case with // native JSON.stringify(). The only differences will be in how object keys are // handled. var simpleCases = [ null, 9, -9, +9, +9.878, '', "''", '999', '9aa', 'aaa', 'aa a', 'aa\na', 'aa\\a', '\'', '\\\'', '\\"', undefined, true, false, {}, [], function(){}, Date.now(), new Date(Date.now()) ]; exports.stringify = {}; exports.stringify.simple = function test() { for (var i=0; i<simpleCases.length; i++) { assertStringify(simpleCases[i]); } }; exports.stringify.oddities = function test() { assertStringify(Function); assertStringify(Date); assertStringify(Object); assertStringify(NaN); assertStringify(Infinity); assertStringify(10e6); assertStringify(19.3223e6); assertStringify(077); assertStringify(0x99); assertStringify(/aa/); assertStringify(new RegExp('aa')); assertStringify(new Number(7)); assertStringify(new String(7)); assertStringify(new String("")); assertStringify(new String("abcde")); assertStringify(new String(new String("abcde"))); assertStringify(new Boolean(true)); assertStringify(new Boolean()); }; exports.stringify.arrays = function test() { assertStringify([""]); assertStringify([1, 2]); assertStringify([undefined]); assertStringify([1, 'fasds']); assertStringify([1, '\n\b\t\f\r\'']); assertStringify([1, 'fasds', ['fdsafsd'], null]); assertStringify([1, 'fasds', ['fdsafsd'], null, function(aaa) { return 1; }, false ]); assertStringify([1, 'fasds', ['fdsafsd'], undefined, function(aaa) { return 1; }, false ]); }; exports.stringify.objects = function test() { assertStringify({a:1, b:2}); assertStringify({"":1, b:2}); assertStringify({9:1, b:2}); assertStringify({"9aaa":1, b:2}); assertStringify({aaaa:1, bbbb:2}); assertStringify({a$a_aa:1, bbbb:2}); assertStringify({"a$a_aa":1, 'bbbb':2}); assertStringify({"a$a_aa":[1], 'bbbb':{a:2}}); assertStringify({"a$22222_aa":[1], 'bbbb':{aaaa:2, name:function(a,n,fh,h) { return 'nuthin'; }, foo: undefined}}); assertStringify({"a$222222_aa":[1], 'bbbb':{aaaa:2, name:'other', foo: undefined}}); assertStringify({"a$222222_aa":[1, {}, undefined, function() { }, { jjj: function() { } }], 'bbbb':{aaaa:2, name:'other', foo: undefined}}); // using same obj multiple times var innerObj = {a: 9, b:6}; assertStringify({a : innerObj, b: innerObj, c: [innerObj, innerObj, innerObj]}); }; exports.stringify.oddKeys = function test() { assertStringify({"this is a crazy long key":1, 'bbbb':2}); assertStringify({"":1, 'bbbb':2}); assertStringify({"s\ns":1, 'bbbb':2}); assertStringify({'\n\b\t\f\r\'\\':1, 'bbbb':2}); assertStringify({undefined:1, 'bbbb':2}); assertStringify({'\x00':'\x00'}); }; // we expect errors from all of these tests. The errors should match exports.stringify.circular = function test() { var obj = { }; obj.obj = obj; assertStringify(obj, null, true); var obj2 = {inner1: {inner2: {}}}; obj2.inner1.inner2.obj = obj2; assertStringify(obj2, null, true); var obj3 = {inner1: {inner2: []}}; obj3.inner1.inner2[0] = obj3; assertStringify(obj3, null, true); }; exports.stringify.replacerType = function test() { var assertStringifyJSON5ThrowsExceptionForReplacer = function(replacer) { assert.throws( function() { JSON5.stringify(null, replacer); }, /Replacer must be a function or an array/ ); }; assertStringifyJSON5ThrowsExceptionForReplacer('string'); assertStringifyJSON5ThrowsExceptionForReplacer(123); assertStringifyJSON5ThrowsExceptionForReplacer({}); }; exports.stringify.replacer = {}; exports.stringify.replacer.function = {}; exports.stringify.replacer.function.simple = function test() { function replacerTestFactory(expectedValue) { return function() { var lastKey = null, lastValue = null, numCalls = 0, replacerThis; return { replacer: function(key, value) { lastKey = key; lastValue = value; numCalls++; replacerThis = this; return value; }, assert: function() { assert.equal(numCalls, 1, "Replacer should be called exactly once for " + expectedValue); assert.equal(lastKey, ""); assert.deepEqual(replacerThis, {"":expectedValue}); var expectedValueToJson = expectedValue; if (expectedValue && expectedValue['toJSON']) { expectedValueToJson = expectedValue.toJSON(); } assert.equal(lastValue, expectedValueToJson); } } } } for (var i=0; i<simpleCases.length; i++) { assertStringify(simpleCases[i], replacerTestFactory(simpleCases[i])); } }; exports.stringify.replacer.function.complexObject = function test() { var obj = { "": "emptyPropertyName", one: 'string', two: 123, three: ['array1', 'array2'], four: {nested_one:'anotherString'}, five: new Date(), six: Date.now(), seven: null, eight: true, nine: false, ten: [NaN, Infinity, -Infinity], eleven: function() {} }; var expectedKeys = [ '', // top level object '', // First key 'one', 'two', 'three', 0, 1, // array keys 'four', 'nested_one', // nested object keys 'five', 'six', 'seven', 'eight', 'nine', 'ten', 0, 1, 2, // array keys 'eleven' ]; var expectedHolders = [ {"": obj}, obj, obj, obj, obj, obj.three, obj.three, obj, obj.four, obj, obj, obj, obj, obj, obj, obj.ten, obj.ten, obj.ten, obj ]; var ReplacerTest = function() { var seenKeys = []; var seenHolders = []; return { replacer: function(key, value) { seenKeys.push(key); seenHolders.push(this); if (typeof(value) == "object") { return value; } return 'replaced ' + (value ? value.toString() : ''); }, assert: function() { assert.deepEqual(seenKeys, expectedKeys); assert.deepEqual(seenHolders, expectedHolders); } } }; assertStringify(obj, ReplacerTest); }; exports.stringify.replacer.function.replacingWithUndefined = function test() { var obj = { shouldSurvive: 'one', shouldBeRemoved: 'two' }; var ReplacerTest = function() { return { replacer: function(key, value) { if (key === 'shouldBeRemoved') { return undefined; } else { return value; } }, assert: function() { /* no-op */ } } }; assertStringify(obj, ReplacerTest); }; exports.stringify.replacer.function.replacingArrayValueWithUndefined = function test() { var obj = ['should survive', 'should be removed']; var ReplacerTest = function() { return { replacer: function(key, value) { if (value === 'should be removed') { return undefined; } else { return value; } }, assert: function() { /* no-op */ } } }; assertStringify(obj, ReplacerTest); }; exports.stringify.replacer.array = {}; exports.stringify.replacer.array.simple = function test() { var ReplacerTest = function() { return { replacer: [], assert: function() { /* no-op */ } } }; for (var i=0; i<simpleCases.length; i++) { assertStringify(simpleCases[i], ReplacerTest); } }; exports.stringify.replacer.array.emptyStringProperty = function test() { var obj = {'': 'keep', 'one': 'remove'}; var ReplacerTest = function() { return { replacer: [''], assert: function() {/* no-op */} } }; assertStringify(obj, ReplacerTest); }; exports.stringify.replacer.array.complexObject = function test() { var obj = { "": "emptyPropertyName", one: 'string', one_remove: 'string', two: 123, two_remove: 123, three: ['array1', 'array2'], three_remove: ['array1', 'array2'], four: {nested_one:'anotherString', nested_one_remove: 'anotherString'}, four_remove: {nested_one:'anotherString', nested_one_remove: 'anotherString'}, five: new Date(), five_remove: new Date(), six: Date.now(), six_remove: Date.now(), seven: null, seven_remove: null, eight: true, eight_remove: true, nine: false, nine_remove: false, ten: [NaN, Infinity, -Infinity], ten_remove: [NaN, Infinity, -Infinity], eleven: function() {}, eleven_remove: function() {} }; var ReplacerTest = function() { return { replacer: [ 'one', 'two', 'three', 'four', 'nested_one', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 0 ], assert: function() {/* no-op */} } }; assertStringify(obj, ReplacerTest); }; exports.stringify.toJSON = function test() { var customToJSONObject = { name: 'customToJSONObject', toJSON: function() { return 'custom-to-json-object-serialization'; } }; assertStringify(customToJSONObject); var customToJSONPrimitive = "Some string"; customToJSONPrimitive.toJSON = function() { return 'custom-to-json-string-serialization'; }; assertStringify(customToJSONPrimitive); var object = { customToJSONObject: customToJSONObject }; assertStringify(object); // Returning an object with a toJSON function does *NOT* have that toJSON function called: it is omitted var nested = { name: 'nested', toJSON: function() { return customToJSONObject; } }; assertStringify(nested); var count = 0; function createObjectSerialisingTo(value) { count++; return { name: 'obj-' + count, toJSON: function() { return value; } }; } assertStringify(createObjectSerialisingTo(null)); assertStringify(createObjectSerialisingTo(undefined)); assertStringify(createObjectSerialisingTo([])); assertStringify(createObjectSerialisingTo({})); assertStringify(createObjectSerialisingTo(12345)); assertStringify(createObjectSerialisingTo(true)); assertStringify(createObjectSerialisingTo(new Date())); assertStringify(createObjectSerialisingTo(function(){})); }; function stringifyJSON5(obj, replacer, space) { var start, res, end; try { start = new Date(); res = JSON5.stringify(obj, replacer, space); end = new Date(); } catch (e) { res = e.message; end = new Date(); } if (DEBUG) { console.log('JSON5.stringify time: ' + (end-start)); console.log(res); } return res; } function stringifyJSON(obj, replacer, space) { var start, res, end; try { start = new Date(); res = JSON.stringify(obj, replacer, space); end = new Date(); // now remove all quotes from keys where appropriate // first recursively find all key names var keys = []; function findKeys(key, innerObj) { if (innerObj && innerObj.toJSON && typeof innerObj.toJSON === "function") { innerObj = innerObj.toJSON(); } if (replacer) { if (typeof replacer === 'function') { innerObj = replacer(key, innerObj); } else if (key !== '' && replacer.indexOf(key) < 0) { return; } } if (JSON5.isWord(key) && typeof innerObj !== 'function' && typeof innerObj !== 'undefined') { keys.push(key); } if (typeof innerObj === 'object') { if (Array.isArray(innerObj)) { for (var i = 0; i < innerObj.length; i++) { findKeys(i, innerObj[i]); } } else if (innerObj !== null) { for (var prop in innerObj) { if (innerObj.hasOwnProperty(prop)) { findKeys(prop, innerObj[prop]); } } } } } findKeys('', obj); // now replace each key in the result var last = 0; for (var i = 0; i < keys.length; i++) { // not perfect since we can match on parts of the previous value that // matches the key, but we can design our test around that. last = res.indexOf('"' + keys[i] + '"', last); if (last === -1) { // problem with test framework console.log("Couldn't find: " + keys[i]); throw new Error("Couldn't find: " + keys[i]); } res = res.substring(0, last) + res.substring(last+1, last + keys[i].length+1) + res.substring(last + keys[i].length + 2, res.length); last += keys[i].length; } } catch (e) { res = e.message; end = new Date(); } if (DEBUG) { console.log('JSON.stringify time: ' + (end-start)); } return res; } function assertStringify(obj, replacerTestConstructor, expectError) { if (!replacerTestConstructor) { replacerTestConstructor = function(){ return {replacer: null, assert: function(){}}; }; } var testStringsEqual = function(obj, indent) { var j5ReplacerTest = replacerTestConstructor(); var jReplacerTest = replacerTestConstructor(); var j5, j; j5 = stringifyJSON5(obj, j5ReplacerTest.replacer, indent); j = stringifyJSON(obj, jReplacerTest.replacer, indent); assert.equal(j5, j); j5ReplacerTest.assert(); }; var indents = [ undefined, " ", " ", " ", "\t", "this is an odd indent", 5, 20, '\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t' ]; for (var i=0; i<indents.length; i++) { testStringsEqual(obj, indents[i]); } if (!expectError) { // no point in round tripping if there is an error var origStr = JSON5.stringify(obj), roundTripStr; if (origStr !== "undefined" && typeof origStr !== "undefined") { try { roundTripStr = JSON5.stringify(JSON5.parse(origStr)); } catch (e) { console.log(e); console.log(origStr); throw e; } assert.equal(origStr, roundTripStr); } } }