packages/ejson/ejson_tests.js
import { EJSON } from './ejson';
import EJSONTest from './custom_models_for_tests';
Tinytest.add('ejson - keyOrderSensitive', test => {
test.isTrue(EJSON.equals({
a: {b: 1, c: 2},
d: {e: 3, f: 4},
}, {
d: {f: 4, e: 3},
a: {c: 2, b: 1},
}));
test.isFalse(EJSON.equals({
a: {b: 1, c: 2},
d: {e: 3, f: 4},
}, {
d: {f: 4, e: 3},
a: {c: 2, b: 1},
}, {keyOrderSensitive: true}));
test.isFalse(EJSON.equals({
a: {b: 1, c: 2},
d: {e: 3, f: 4},
}, {
a: {c: 2, b: 1},
d: {f: 4, e: 3},
}, {keyOrderSensitive: true}));
test.isFalse(EJSON.equals({a: {}}, {a: {b: 2}}, {keyOrderSensitive: true}));
test.isFalse(EJSON.equals({a: {b: 2}}, {a: {}}, {keyOrderSensitive: true}));
});
Tinytest.add('ejson - nesting and literal', test => {
const d = new Date();
const obj = {$date: d};
const eObj = EJSON.toJSONValue(obj);
const roundTrip = EJSON.fromJSONValue(eObj);
test.equal(obj, roundTrip);
});
Tinytest.add('ejson - some equality tests', test => {
test.isTrue(EJSON.equals({a: 1, b: 2, c: 3}, {a: 1, c: 3, b: 2}));
test.isFalse(EJSON.equals({a: 1, b: 2}, {a: 1, c: 3, b: 2}));
test.isFalse(EJSON.equals({a: 1, b: 2, c: 3}, {a: 1, b: 2}));
test.isFalse(EJSON.equals({a: 1, b: 2, c: 3}, {a: 1, c: 3, b: 4}));
test.isFalse(EJSON.equals({a: {}}, {a: {b: 2}}));
test.isFalse(EJSON.equals({a: {b: 2}}, {a: {}}));
// XXX: Object and Array were previously mistaken, which is why
// we add some extra tests for them here
test.isTrue(EJSON.equals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]));
test.isFalse(EJSON.equals([1, 2, 3, 4, 5], [1, 2, 3, 4]));
test.isFalse(EJSON.equals([1,2,3,4], {0: 1, 1: 2, 2: 3, 3: 4}));
test.isFalse(EJSON.equals({0: 1, 1: 2, 2: 3, 3: 4}, [1,2,3,4]));
test.isFalse(EJSON.equals({}, []));
test.isFalse(EJSON.equals([], {}));
});
Tinytest.add('ejson - equality and falsiness', test => {
test.isTrue(EJSON.equals(null, null));
test.isTrue(EJSON.equals(undefined, undefined));
test.isFalse(EJSON.equals({foo: 'foo'}, null));
test.isFalse(EJSON.equals(null, {foo: 'foo'}));
test.isFalse(EJSON.equals(undefined, {foo: 'foo'}));
test.isFalse(EJSON.equals({foo: 'foo'}, undefined));
});
Tinytest.add('ejson - NaN and Inf', test => {
test.equal(EJSON.parse('{"$InfNaN": 1}'), Infinity);
test.equal(EJSON.parse('{"$InfNaN": -1}'), -Infinity);
test.isTrue(Number.isNaN(EJSON.parse('{"$InfNaN": 0}')));
test.equal(EJSON.parse(EJSON.stringify(Infinity)), Infinity);
test.equal(EJSON.parse(EJSON.stringify(-Infinity)), -Infinity);
test.isTrue(Number.isNaN(EJSON.parse(EJSON.stringify(NaN))));
test.isTrue(EJSON.equals(NaN, NaN));
test.isTrue(EJSON.equals(Infinity, Infinity));
test.isTrue(EJSON.equals(-Infinity, -Infinity));
test.isFalse(EJSON.equals(Infinity, -Infinity));
test.isFalse(EJSON.equals(Infinity, NaN));
test.isFalse(EJSON.equals(Infinity, 0));
test.isFalse(EJSON.equals(NaN, 0));
test.isTrue(EJSON.equals(
EJSON.parse('{"a": {"$InfNaN": 1}}'),
{a: Infinity}
));
test.isTrue(EJSON.equals(
EJSON.parse('{"a": {"$InfNaN": 0}}'),
{a: NaN}
));
});
Tinytest.add('ejson - clone', test => {
const cloneTest = (x, identical) => {
const y = EJSON.clone(x);
test.isTrue(EJSON.equals(x, y));
test.equal(x === y, !!identical);
};
cloneTest(null, true);
cloneTest(undefined, true);
cloneTest(42, true);
cloneTest('asdf', true);
cloneTest([1, 2, 3]);
cloneTest([1, 'fasdf', {foo: 42}]);
cloneTest({x: 42, y: 'asdf'});
function testCloneArgs(/*arguments*/) {
const clonedArgs = EJSON.clone(arguments);
test.equal(clonedArgs, [1, 2, 'foo', [4]]);
};
testCloneArgs(1, 2, 'foo', [4]);
});
Tinytest.add('ejson - stringify', test => {
test.equal(EJSON.stringify(null), 'null');
test.equal(EJSON.stringify(true), 'true');
test.equal(EJSON.stringify(false), 'false');
test.equal(EJSON.stringify(123), '123');
test.equal(EJSON.stringify('abc'), '"abc"');
test.equal(EJSON.stringify([1, 2, 3]),
'[1,2,3]'
);
test.equal(EJSON.stringify([1, 2, 3], {indent: true}),
'[\n 1,\n 2,\n 3\n]'
);
test.equal(EJSON.stringify([1, 2, 3], {canonical: false}),
'[1,2,3]'
);
test.equal(EJSON.stringify([1, 2, 3], {indent: true, canonical: false}),
'[\n 1,\n 2,\n 3\n]'
);
test.equal(EJSON.stringify([1, 2, 3], {indent: 4}),
'[\n 1,\n 2,\n 3\n]'
);
test.equal(EJSON.stringify([1, 2, 3], {indent: '--'}),
'[\n--1,\n--2,\n--3\n]'
);
test.equal(
EJSON.stringify(
{b: [2, {d: 4, c: 3}], a: 1},
{canonical: true}
),
'{"a":1,"b":[2,{"c":3,"d":4}]}'
);
test.equal(
EJSON.stringify(
{b: [2, {d: 4, c: 3}], a: 1},
{
indent: true,
canonical: true,
}
),
'{\n' +
' "a": 1,\n' +
' "b": [\n' +
' 2,\n' +
' {\n' +
' "c": 3,\n' +
' "d": 4\n' +
' }\n' +
' ]\n' +
'}'
);
test.equal(
EJSON.stringify(
{b: [2, {d: 4, c: 3}], a: 1},
{canonical: false}
),
'{"b":[2,{"d":4,"c":3}],"a":1}'
);
test.equal(
EJSON.stringify(
{b: [2, {d: 4, c: 3}], a: 1},
{indent: true, canonical: false}
),
'{\n' +
' "b": [\n' +
' 2,\n' +
' {\n' +
' "d": 4,\n' +
' "c": 3\n' +
' }\n' +
' ],\n' +
' "a": 1\n' +
'}'
);
test.throws(
() => {
const col = new Mongo.Collection('test');
EJSON.stringify(col)
},
/Converting circular structure to JSON/
);
});
Tinytest.add('ejson - parse', test => {
test.equal(EJSON.parse('[1,2,3]'), [1, 2, 3]);
test.throws(
() => { EJSON.parse(null); },
/argument should be a string/
);
});
Tinytest.add("ejson - regexp", test => {
test.equal(EJSON.stringify(/foo/gi), "{\"$regexp\":\"foo\",\"$flags\":\"gi\"}");
var d = new RegExp("foo", "gi");
var obj = { $regexp: "foo", $flags: "gi" };
var eObj = EJSON.toJSONValue(obj);
var roundTrip = EJSON.fromJSONValue(eObj);
test.equal(obj, roundTrip);
});
Tinytest.add('ejson - custom types', test => {
const testSameConstructors = (someObj, compareWith) => {
test.equal(someObj.constructor, compareWith.constructor);
if (typeof someObj === 'object') {
Object.keys(someObj).forEach(key => {
const value = someObj[key];
testSameConstructors(value, compareWith[key]);
});
}
};
const testReallyEqual = (someObj, compareWith) => {
test.equal(someObj, compareWith);
testSameConstructors(someObj, compareWith);
};
const testRoundTrip = (someObj) => {
const str = EJSON.stringify(someObj);
const roundTrip = EJSON.parse(str);
testReallyEqual(someObj, roundTrip);
};
const testCustomObject = (someObj) => {
testRoundTrip(someObj);
testReallyEqual(someObj, EJSON.clone(someObj));
};
const a = new EJSONTest.Address('Montreal', 'Quebec');
testCustomObject( {address: a} );
// Test that difference is detected even if they
// have similar toJSONValue results:
const nakedA = {city: 'Montreal', state: 'Quebec'};
test.notEqual(nakedA, a);
test.notEqual(a, nakedA);
const holder = new EJSONTest.Holder(nakedA);
test.equal(holder.toJSONValue(), a.toJSONValue()); // sanity check
test.notEqual(holder, a);
test.notEqual(a, holder);
const d = new Date();
const obj = new EJSONTest.Person('John Doe', d, a);
testCustomObject( obj );
// Test clone is deep:
const clone = EJSON.clone(obj);
clone.address.city = 'Sherbrooke';
test.notEqual( obj, clone );
});
// Verify objects with a property named "length" can be handled by the EJSON
// API properly (see https://github.com/meteor/meteor/issues/5175).
Tinytest.add('ejson - handle objects with properties named "length"', test => {
class Widget {
constructor() {
this.length = 10;
}
}
const widget = new Widget();
const toJsonWidget = EJSON.toJSONValue(widget);
test.equal(widget, toJsonWidget);
const fromJsonWidget = EJSON.fromJSONValue(widget);
test.equal(widget, fromJsonWidget);
const stringifiedWidget = EJSON.stringify(widget);
test.equal(stringifiedWidget, '{"length":10}');
const parsedWidget = EJSON.parse('{"length":10}');
test.equal({ length: 10 }, parsedWidget);
test.isFalse(EJSON.isBinary(widget));
const widget2 = new Widget();
test.isTrue(widget, widget2);
const clonedWidget = EJSON.clone(widget);
test.equal(widget, clonedWidget);
});