UNPKG

@ngard/tiny-isequal

Version:

A minimal-weight utility similar to lodash.isequal

802 lines (655 loc) 22.9 kB
const { isEqual } = require("./index"); const { expect } = require("chai"); const vm = require("vm"); var symbol1 = Symbol("a"), symbol2 = Symbol("b"); const noop = () => {}; var root = (typeof global == "object" && global) || this; describe("should compare primitives", function() { [ [1, 1, true, "1 should equal 1"], [1, 2, false, "1 should not equal 2"], [1, Object(1), true, "1 should equal Object(1)"], [Object(1), Object(1), true, "Object(1) should equal Object(1)"], [Object(1), Object(2), false, "Object(1) should not equal Object(2)"], [1, Number(1), true, "1 should equal Number(1)"], [Number(1), Number(1), true, "Number(1) should equal Number(1)"], [Number(1), Number(4), false, "Number(1) should not equal Number(4)"], [1, "1", false, "1 should not equal '1'"], [-0, -0, true, "-0 should equal -0"], [0, 0, true, "0 should equal 0"], [0, -0, true, "0 should equal -0"], [0, null, false, "0 should not equal null"], [NaN, NaN, true, "NaN should equal NaN"], [NaN, Object(NaN), true, "NaN should equal Object(NaN)"], [Object(NaN), Object(NaN), true, "Object(NaN) should equal Object(NaN)"], [NaN, "a", false, "NaN should not equal 'a'"], [NaN, Infinity, false, "NaN should not equal Infinity"], ["a", "a", true, "'a' should equal 'a'"], [ "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz", true, "'abcdefghijklmnopqrstuvwxyz' should equal 'abcdefghijklmnopqrstuvwxyz'" ], ["a", Object("a"), true, "'a' should equal Object('a')"], [Object("a"), Object("a"), true, "Object('a') should equal Object('a')"], ["a", String("a"), true, "'a' should equal String('a')"], [String("a"), String("a"), true, "String('a') should equal String('a')"], ["a", "b", false, "'a' should not equal 'b'"], ["ab", ["a", "b"], false, "'ab' should not equal ['a','b']"], [true, true, true, "true should equal true"], [true, Object(true), true, "true should equal Object(true)"], [ Object(true), Object(true), true, "Object(true) should equal Object(true)" ], [true, Boolean(true), true, "true should equal Boolean(true)"], [ Boolean(true), Boolean(true), true, "Boolean(true) should equal Boolean(true)" ], [true, 1, false, "true should not equal '1'"], [true, "a", false, "true should not equal 'a'"], [false, false, true, "false should equal false"], [false, Object(false), true, "false should equal Object(false)"], [ Object(false), Object(false), true, "Object(false) should equal Object(false)" ], [false, Boolean(false), true, "false should equal Boolean(false)"], [ Boolean(false), Boolean(false), true, "Boolean(false) should equal Boolean(false)" ], [false, 0, false, "false should not be equal to 0"], [false, "", false, "false should not be equal to ''"], [null, null, true, "null should be equal to null"], [null, Object(null), false, "null should not be equal to Object(null)"], [null, undefined, false, "null should not be equal to undefined"], [null, {}, false, "null should not be equal to {},"], [null, "", false, "null should not be equal to ''"], [undefined, undefined, true, "undefined should be equal to undefined"], [undefined, null, false, "undefined should not be equal to null"], [undefined, "", false, "undefined should not be equal to ''"], [symbol1, symbol1, true, "symbol1 should be equal to symbol1"], [ symbol1, Object(symbol1), true, "symbol1 should be equal to Object(symbol1)" ], [ Object(symbol1), Object(symbol1), true, "Object(symbol1) should be equal to Object(symbol1)" ], [symbol1, symbol2, false, "symbol1 should not be equal to symbol2"] ].forEach(([v1, v2, expected, testCase]) => { var v1String = v1 && v1.description ? "Symbol(" + v1.description + ")" : v1 === "" ? '""' : "" + v1; var v2String = v2 && v2.description ? "Symbol(" + v2.description + ")" : v2 === "" ? '""' : "" + v2; it(testCase, function() { expect(isEqual(v1, v2)).to.equal( expected, "isEqual(" + v1String + ", " + v2String + ") failed" ); expect(isEqual(v2, v1)).to.equal( expected, "isEqual(" + v2String + ", " + v1String + ") failed" ); }); }); it("should avoid common type coercions (==)", function() { expect(isEqual(true, Object(false))).to.equal(false); expect(isEqual(Object(false), Object(0))).to.equal(false); expect(isEqual(false, Object(""))).to.equal(false); expect(isEqual(Object(36), Object("36"))).to.equal(false); expect(isEqual(0, "")).to.equal(false); expect(isEqual(1, true)).to.equal(false); expect(isEqual(1337756400000, new Date(2012, 4, 23))).to.equal(false); expect(isEqual("36", 36)).to.equal(false); expect(isEqual(36, "36")).to.equal(false); }); }); describe("should compare arrays", function() { it("should correctly compare arrays of primitives", function() { var array1 = [true, null, 1, "a", undefined], array2 = [true, null, 1, "a", undefined], array3 = [false, null, 1, "a", undefined]; expect(isEqual(array1, array2)).to.equal(true); expect(isEqual(array1, array3)).to.equal(false); }); it("should correctly compare arrays whose values change but references do not", function() { var array1 = [true, null, 1, "a", undefined], array2 = [true, null, 1, "a", undefined]; expect(isEqual(array1, array2)).to.equal(true); array2[0] = false; expect(isEqual(array1, array2)).to.equal(false); }); it("should correctly compare arrays of objects", function() { var array1 = [[1, 2, 3], new Date(2012, 4, 23), /x/, { e: 1 }]; var array2 = [[1, 2, 3], new Date(2012, 4, 23), /x/, { e: 1 }]; var array3 = [[1, 2, 3], new Date(2012, 4, 24), /x/i, { e: 1 }]; expect(isEqual(array1, array2)).to.equal(true); expect(isEqual(array1, array3)).to.equal(false); }); it("should compare arrays with circular references", function() { var array1 = [], array2 = []; array1.push(array1); array2.push(array2); expect(isEqual(array1, array2)).to.equal(true); array1.push("b"); array2.push("b"); expect(isEqual(array1, array2)).to.equal(true); array1.push("c"); array2.push("d"); expect(isEqual(array1, array2)).to.equal(false); array1 = ["a", "b", "c"]; array1[1] = array1; array2 = ["a", ["a", "b", "c"], "c"]; expect(isEqual(array1, array2)).to.equal(false); }); it("should have transitive equivalence for circular references of arrays", function() { var array1 = [], array2 = [array1], array3 = [array2]; array1[0] = array1; expect(isEqual(array1, array2)).to.equal(true); expect(isEqual(array2, array3)).to.equal(true); expect(isEqual(array1, array3)).to.equal(true); }); it("should compare sparse arrays", function() { var array = Array(1); expect(isEqual(array, Array(1))).to.equal(true); expect(isEqual(array, [undefined])).to.equal(false); expect(isEqual(array, Array(2))).to.equal(false); }); it("should compare arrays created from different realms (different constructors)", function() { var xArraySame = vm.runInNewContext("new Array(1, 2, 3)"); var xArrayDiff = vm.runInNewContext("new Array(1, 2, 3, 4)"); var array = new Array(1, 2, 3); expect(xArraySame instanceof Array).to.equal(false); // make sure it's from another realm expect(isEqual(array, xArraySame)).to.equal(true); expect(isEqual(array, xArrayDiff)).to.equal(false); }); it("should correctly compare arrays of equivalent elements", function() { var array1 = [ Object(1), false, Object("a"), /x/, new Date(2012, 4, 23), ["a", "b", [Object("c")]], { a: 1 } ]; var array2 = [ 1, Object(false), "a", /x/, new Date(2012, 4, 23), ["a", Object("b"), ["c"]], { a: 1 } ]; expect(isEqual(array1, array2)).to.equal(true); }); it("should correctly compare arrays that have the same elements in different orders", function() { var array1 = [1, 2, 3]; var array2 = [3, 2, 1]; expect(isEqual(array1, array2)).to.equal(false); }); it("should correctly compare arrays of different lengths, even if their maximal sub-arrays are equal", function() { var array1 = [1, 2]; var array2 = [1, 2, 3]; expect(isEqual(array1, array2)).to.equal(false); expect(isEqual(array2, array1)).to.equal(false); }); it("should correctly compare arrays whose length property was manipulated", function() { // it is important that the third element of array1 is undefined because empty slots // are coerced to undefined when directly accessed, e.g. array2[2] === undefined var array1 = [1, 2, undefined]; var array2 = [1, 2]; array2.length = 3; expect(isEqual(array1, array2)).to.equal(false); }); it("should compare `arguments` objects", function() { var args1 = (function() { return arguments; })(), args2 = (function() { return arguments; })(), args3 = (function() { return arguments; })(1, 2); expect(isEqual(args1, args2)).to.equal(true); expect(isEqual(args1, args3)).to.equal(false); }); it("should compare array buffers", function() { if (ArrayBuffer) { var buffer = new Int8Array([-1]).buffer; expect(isEqual(buffer, new Uint8Array([255]).buffer)).to.equal(true); expect(isEqual(buffer, new ArrayBuffer(1))).to.equal(false); } }); it("should compare array views", function() { var typedArrays = [ "Float32Array", "Float64Array", "Int8Array", "Int16Array", "Int32Array", "Uint8Array", "Uint8ClampedArray", "Uint16Array", "Uint32Array" ]; /** Used to check whether methods support array views. */ var arrayViews = typedArrays.concat("DataView"); var pairs = arrayViews.map(function(type, viewIndex) { var otherType = arrayViews[(viewIndex + 1) % arrayViews.length], CtorA = root[type], CtorB = root[otherType], bufferA = new ArrayBuffer(8), bufferB = new ArrayBuffer(8), bufferC = new ArrayBuffer(16); return [ new CtorA(bufferA), new CtorA(bufferA), new CtorB(bufferB), new CtorB(bufferC) ]; }); pairs.map(function(pair) { expect(isEqual(pair[0], pair[1]), "reflection failed").to.equal(true); expect(isEqual(pair[0], pair[2]), "similarity failed").to.equal(false); expect(isEqual(pair[2], pair[3]), "difference failed").to.equal(false); }); }); it("should compare arrays with additional properties", function() { var array1 = [1, 2, 3], array2 = [1, 2, 3]; array1.secretProperty = "I am not a normal array"; expect(isEqual(array1, array2)).to.equal(false); array2.secretProperty = "I am not a normal array"; expect(isEqual(array1, array2)).to.equal(true); }); }); describe("should compare objects", function() { it("should compare plain objects", function() { var object1 = { a: true, b: null, c: 1, d: "a", e: undefined }, object2 = { a: true, b: null, c: 1, d: "a", e: undefined }; expect(isEqual(object1, object2)).to.equal(true); object1 = { a: [1, 2, 3], b: new Date(2012, 4, 23), c: /x/, d: { e: 1 } }; object2 = { a: [1, 2, 3], b: new Date(2012, 4, 23), c: /x/, d: { e: 1 } }; expect(isEqual(object1, object2)).to.equal(true); object1 = { a: 1, b: 2, c: 3 }; object2 = { a: 3, b: 2, c: 1 }; expect(isEqual(object1, object2)).to.equal(false); object1 = { a: 1, b: 2, c: 3 }; object2 = { d: 1, e: 2, f: 3 }; expect(isEqual(object1, object2)).to.equal(false); object1 = { a: 1, b: 2 }; object2 = { a: 1, b: 2, c: 3 }; expect(isEqual(object1, object2)).to.equal(false); }); it("should compare objects regardless of key order", function() { var object1 = { a: 1, b: 2, c: 3 }, object2 = { c: 3, a: 1, b: 2 }; expect(isEqual(object1, object2)).to.equal(true); }); it("should compare nested objects", function() { var object1 = { a: [1, 2, 3], b: true, c: Object(1), d: "a", e: { f: ["a", Object("b"), "c"], g: Object(false), h: new Date(2012, 4, 23), i: noop, j: "a" } }; var object2 = { a: [1, Object(2), 3], b: Object(true), c: 1, d: Object("a"), e: { f: ["a", "b", "c"], g: false, h: new Date(2012, 4, 23), i: noop, j: "a" } }; expect(isEqual(object1, object2)).to.equal(true); }); it("should compare object instances", function() { function Foo() { this.a = 1; } Foo.prototype.a = 1; function Bar() { this.a = 1; } Bar.prototype.a = 2; expect(isEqual(new Foo(), new Foo())).to.equal(true); expect(isEqual(new Foo(), new Bar())).to.equal(false); expect(isEqual({ a: 1 }, new Foo())).to.equal(false); expect(isEqual({ a: 2 }, new Bar())).to.equal(false); }); it("should compare objects with constructor properties", function() { expect(isEqual({ constructor: 1 }, { constructor: 1 })).to.equal(true); expect(isEqual({ constructor: 1 }, { constructor: "1" })).to.equal(false); expect(isEqual({ constructor: [1] }, { constructor: [1] })).to.equal(true); expect(isEqual({ constructor: [1] }, { constructor: ["1"] })).to.equal( false ); expect(isEqual({ constructor: Object }, {})).to.equal(false); }); it("should compare objects with circular references", function() { var object1 = {}, object2 = {}; object1.a = object1; object2.a = object2; expect(isEqual(object1, object2)).to.equal(true); object1.b = 0; object2.b = Object(0); expect(isEqual(object1, object2)).to.equal(true); object1.c = Object(1); object2.c = Object(2); expect(isEqual(object1, object2)).to.equal(false); object1 = { a: 1, b: 2, c: 3 }; object1.b = object1; object2 = { a: 1, b: { a: 1, b: 2, c: 3 }, c: 3 }; expect(isEqual(object1, object2)).to.equal(false); }); it("should have transitive equivalence for circular references of objects", function() { var object1 = {}, object2 = { a: object1 }, object3 = { a: object2 }; object1.a = object1; expect(isEqual(object1, object2)).to.equal(true); expect(isEqual(object2, object3)).to.equal(true); expect(isEqual(object1, object3)).to.equal(true); }); it("should compare objects with multiple circular references", function() { var array1 = [{}], array2 = [{}]; (array1[0].a = array1).push(array1); (array2[0].a = array2).push(array2); expect(isEqual(array1, array2)).to.equal(true); array1[0].b = 0; array2[0].b = Object(0); expect(isEqual(array1, array2)).to.equal(true); array1[0].c = Object(1); array2[0].c = Object(2); expect(isEqual(array1, array2)).to.equal(false); }); it("should compare objects with complex circular references", function() { var object1 = { foo: { b: { c: { d: {} } } }, bar: { a: 2 } }; var object2 = { foo: { b: { c: { d: {} } } }, bar: { a: 2 } }; object1.foo.b.c.d = object1; object1.bar.b = object1.foo.b; object2.foo.b.c.d = object2; object2.bar.b = object2.foo.b; expect(isEqual(object1, object2)).to.equal(true); }); it("should compare objects with shared property values", function() { var object1 = { a: [1, 2] }; var object2 = { a: [1, 2], b: [1, 2] }; object1.b = object1.a; expect(isEqual(object1, object2)).to.equal(true); }); it("should compare objects created by `Object.create(null)`", function() { var object1 = Object.create(null); object1.a = 1; var object2 = Object.create(null); object2.a = 1; var object3 = { a: 1 }; expect(isEqual(object1, object2)).to.equal(true); expect(isEqual(object1, object3)).to.equal(false); }); it("should compare objects whose toString was overwritten", function() { var object1 = { 0: "element", length: 1, toString: function() { return "[object Array]"; } }; var object2 = ["element"]; expect(isEqual(object1, object2)).to.equal(false); expect(isEqual(object2, object1)).to.equal(false); }); it("should compare date objects", function() { var date = new Date(2012, 4, 23); expect(isEqual(date, new Date(2012, 4, 23)), "same date").to.equal(true); expect(isEqual(new Date("a"), new Date("b")), "invalid dates").to.equal( true ); expect(isEqual(date, new Date(2013, 3, 25)), "different dates").to.equal( false ); expect( isEqual(date, { getTime: () => +date }), "a date-like object" ).to.equal(false); }); it("should compare error objects", function() { // every error is unique var pairs = [ "Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError" ].map(function(type, index, errorTypes) { var otherType = errorTypes[++index % errorTypes.length], CtorA = root[type], CtorB = root[otherType]; return [new CtorA("a"), new CtorA("a"), new CtorB("a"), new CtorB("b")]; }); pairs.map(function(pair) { expect(isEqual(pair[0], pair[1])).to.equal(false); expect(isEqual(pair[0], pair[2])).to.equal(false); expect(isEqual(pair[2], pair[3])).to.equal(false); }); }); it("should compare functions", function() { function a() { return 1 + 2; } function b() { return 1 + 2; } var object = { method1: function foo() { return 1 + 2; }, method2: function foo() { return 1 + 2; } }; expect(isEqual(a, a)).to.equal(true); expect(isEqual(a, b)).to.equal(false); expect(isEqual(object.method1, object.method2)).to.equal(true); object.method1.secret = "I am no longer the same as method2"; expect(isEqual(object.method1, object.method2)).to.equal(false); }); it("should compare maps", function() { if (Map) { var map1 = new Map(), map2 = new Map(); expect(isEqual(map1, map2)).to.equal(true); map1.set("a", 1); map2.set("b", 2); expect(isEqual(map1, map2)).to.equal(false); // insertion order matters in maps map1.set("b", 2); map2.set("a", 1); expect(isEqual(map1, map2)).to.equal(false); map1.delete("a"); map1.set("a", 1); expect(isEqual(map1, map2)).to.equal(true); map2.delete("a"); expect(isEqual(map1, map2)).to.equal(false); map1.clear(); map2.clear(); } }); it("should compare maps with additional properties", function() { if (Map) { var map1 = new Map(), map2 = new Map(); map1.set("a", 1); map2.set("a", 1); map1.secretProperty = "I am not a normal map"; expect(isEqual(map1, map2)).to.equal(false); map2.secretProperty = "I am not a normal map"; expect(isEqual(map1, map2)).to.equal(true); } }); it("should compare maps with circular references", function() { if (Map) { var map1 = new Map(), map2 = new Map(); map1.set("a", map1); map2.set("a", map2); expect(isEqual(map1, map2)).to.equal(true); map1.set("b", 1); map2.set("b", 2); expect(isEqual(map1, map2)).to.equal(false); } }); it("should compare promises by reference", function() { var promise = Promise.resolve(1); if (promise) { [[promise, Promise.resolve(1)], [promise]].forEach(function(promises) { var promise1 = promises[0], promise2 = promises[1]; expect(isEqual(promise1, promise2)).to.equal(false); expect(isEqual(promise1, promise1)).to.equal(true); }); } }); it("should compare regexes", function() { expect(isEqual(/x/gim, /x/gim)).to.equal(true); expect(isEqual(/x/gim, /x/gim)).to.equal(true); expect(isEqual(/x/gi, /x/g)).to.equal(false); expect(isEqual(/x/, /y/)).to.equal(false); expect( isEqual(/x/g, { global: true, ignoreCase: false, multiline: false, source: "x" }) ).to.equal(false); }); it("should compare sets", function() { if (Set) { [[new Set(), new Set()]].forEach(function(sets) { var set1 = sets[0], set2 = sets[1]; expect(isEqual(set1, set2)).to.equal(true); set1.add(1); set2.add(2); expect(isEqual(set1, set2)).to.equal(false); // insertion order matters set1.add(2); set2.add(1); expect(isEqual(set1, set2)).to.equal(false); set1.delete(1); set1.add(1); expect(isEqual(set1, set2)).to.equal(true); set2.delete(1); expect(isEqual(set1, set2)).to.equal(false); set1.clear(); set2.clear(); }); } }); it("should compare sets with circular references", function() { if (Set) { var set1 = new Set(), set2 = new Set(); set1.add(set1); set2.add(set2); expect(isEqual(set1, set2)).to.equal(true); set1.add(1); set2.add(2); expect(isEqual(set1, set2)).to.equal(false); } }); it("should compare symbol properties", function() { if (Symbol) { var object1 = { a: 1 }, object2 = { a: 1 }; object1[symbol1] = { a: { b: 2 } }; object2[symbol1] = { a: { b: 2 } }; expect(isEqual(object1, object2)).to.equal(true); object2[symbol1] = { a: 1 }; expect(isEqual(object1, object2)).to.equal(false); delete object2[symbol1]; object2[Symbol("a")] = { a: { b: 2 } }; expect(isEqual(object1, object2)).to.equal(false); } }); it("should not error on DOM elements", function() { if (root.document) { var element1 = document.createElement("div"), element2 = element1.cloneNode(true); expect(isEqual(element1, element2)).to.equal(false); } }); it("should return `false` for objects with custom `toString` methods", function() { var primitive, object = { toString: function() { return primitive; } }, values = [true, null, 1, "a", undefined]; values.forEach(function(value) { primitive = value; expect(isEqual(object, value)).to.equal(false); }); }); });