From 81075b120e86b5c8703c4c7ad0c541651b0df87f Mon Sep 17 00:00:00 2001 From: Luke Edwards Date: Tue, 11 Aug 2020 13:04:27 -0700 Subject: [PATCH] feat: add "lite" mode; - Drop Set/Map support - Maintains IE support --- .gitignore | 4 +- package.json | 7 +- src/lite.js | 29 +++++ test/lite.js | 302 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 340 insertions(+), 2 deletions(-) create mode 100644 src/lite.js create mode 100644 test/lite.js diff --git a/.gitignore b/.gitignore index 9c94371..1506892 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ node_modules *-lock.* *.lock *.log -dist + +/dist +/lite diff --git a/package.json b/package.json index 43ed640..0e00b89 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,13 @@ }, "files": [ "*.d.ts", - "dist" + "dist", + "lite" ], + "modes": { + "lite": "src/lite.js", + "default": "src/index.js" + }, "keywords": [ "deep", "deep-equal", diff --git a/src/lite.js b/src/lite.js new file mode 100644 index 0000000..5820d67 --- /dev/null +++ b/src/lite.js @@ -0,0 +1,29 @@ +var has = Object.prototype.hasOwnProperty; + +export function dequal(foo, bar) { + var ctor, len; + if (foo === bar) return true; + + if (foo && bar && (ctor=foo.constructor) === bar.constructor) { + if (ctor === Date) return foo.getTime() === bar.getTime(); + if (ctor === RegExp) return foo.toString() === bar.toString(); + + if (ctor === Array) { + if ((len=foo.length) === bar.length) { + while (len-- && dequal(foo[len], bar[len])); + } + return len === -1; + } + + if (!ctor || typeof foo === 'object') { + len = 0; + for (ctor in foo) { + if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false; + if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false; + } + return Object.keys(bar).length === len; + } + } + + return foo !== foo && bar !== bar; +} diff --git a/test/lite.js b/test/lite.js new file mode 100644 index 0000000..6d47caf --- /dev/null +++ b/test/lite.js @@ -0,0 +1,302 @@ +import { suite } from 'uvu'; +import * as assert from 'uvu/assert'; +import { dequal } from '../src/lite'; + +function same(a, b) { + assert.is(dequal(a, b), true); +} + +function different(a, b) { + assert.is(dequal(a, b), false); +} + +const API = suite('exports'); + +API('exports', () => { + assert.type(dequal, 'function'); +}); + +API.run(); + +// --- + +const scalars = suite('scalars'); + +scalars('scalars', () => { + same(1, 1); + different(1, 2); + different(1, []); + different(1, '1'); + same(Infinity, Infinity); + different(Infinity, -Infinity); + different(NaN, undefined); + different(NaN, null); + same(NaN, NaN); + different(1, -1); + same(0, -0); + + same(null, null); + same(void 0, undefined); + same(undefined, undefined); + different(null, undefined); + different('', null); + different(0, null); + + same(true, true); + same(false, false); + different(true, false); + different(0, false); + different(1, true); + + same('a', 'a'); + different('a', 'b'); +}); + +scalars.run(); + +// --- + +const Objects = suite('Object'); + +Objects('Objects', () => { + same({}, {}); + same({ a:1, b:2 }, { a:1, b:2 }); + same({ b:2, a:1 }, { a:1, b:2 }); + + different({ a:1, b:2, c:[] }, { a:1, b:2 }); + different({ a:1, b:2 }, { a:1, b:2, c:[] }); + different({ a:1, c:3 }, { a:1, b:2 }); + + same({ a:[{ b:1 }] }, { a:[{ b:1 }] }); + different({ a:[{ b:2 }] }, { a:[{ b:1 }] }); + different({ a:[{ c:1 }] }, { a:[{ b:1 }] }); + + different([], {}); + different({}, []); + different({}, null); + different({}, undefined); + + different({ a:void 0 }, {}); + different({}, { a:undefined }); + different({ a:undefined }, { b:undefined }); +}); + +Objects('dictionary', () => { + const foo = Object.create(null); + const bar = Object.create(null); + same(foo, bar); + + foo.hello = 'world'; + different(foo, bar); +}); + +Objects.run(); + +// --- + +const Arrays = suite('Array'); + +Arrays('Arrays', () => { + same([], []); + same([1,2,3], [1,2,3]); + different([1,2,4], [1,2,3]); + different([1,2], [1,2,3]); + + same([{ a:1 }, { b:2 }], [{ a:1 }, { b:2 }]); + different([{ a:2 }, { b:2 }], [{ a:1 }, { b:2 }]); + + different({ '0':0, '1':1, length:2 }, [0, 1]); +}); + +Arrays.run(); + +// --- + +const Dates = suite('Date'); + +Dates('Dates', () => { + same( + new Date('2015-05-01T22:16:18.234Z'), + new Date('2015-05-01T22:16:18.234Z') + ); + + different( + new Date('2015-05-01T22:16:18.234Z'), + new Date('2017-01-01T00:00:00.000Z') + ); + + different( + new Date('2015-05-01T22:16:18.234Z'), + '2015-05-01T22:16:18.234Z' + ); + + different( + new Date('2015-05-01T22:16:18.234Z'), + 1430518578234 + ); + + different( + new Date('2015-05-01T22:16:18.234Z'), + {} + ); +}); + +Dates.run(); + +// --- + +const RegExps = suite('RegExp'); + +RegExps('RegExps', () => { + same(/foo/, /foo/); + same(/foo/i, /foo/i); + + different(/foo/, /bar/); + different(/foo/, /foo/i); + + different(/foo/, 'foo'); + different(/foo/, {}); +}); + +RegExps.run(); + +// --- + +const Functions = suite('Function'); + +Functions('Functions', () => { + let foo = () => {}; + let bar = () => {}; + + same(foo, foo); + different(foo, bar); + different(foo, () => {}); +}); + +Functions.run(); + +// --- + +const Classes = suite('class'); + +Classes('class', () => { + class Foobar {} + same(new Foobar, new Foobar); +}); + +// @see https://github.com/lukeed/klona/issues/14 +Classes('prototype', () => { + function Test () {} + Test.prototype.val = 42; + + same(new Test, new Test); +}); + +Classes('constructor properties', () => { + function Test (num) { + this.value = num; + } + + Test.prototype.val = 42; + + same(new Test(123), new Test(123)); + different(new Test(0), new Test(123)); +}); + +Classes('constructor properties :: class', () => { + class Test { + constructor(num) { + this.value = num; + } + } + + same(new Test, new Test); + same(new Test(123), new Test(123)); + different(new Test, new Test(123)); +}); + +Classes('constructor properties :: defaults', () => { + class Test { + constructor(num = 123) { + this.value = num; + } + } + + same(new Test(456), new Test(456)); + same(new Test(123), new Test); +}); + +Classes('accessors', () => { + class Test { + get val() { + return 42; + } + } + + same(new Test, new Test); +}); + +Classes('values but not prototype', () => { + class Item { + constructor() { + this.foo = 1; + this.bar = 2; + } + } + + const hello = new Item; + const world = { + foo: 1, + bar: 2, + }; + + assert.is( + JSON.stringify(hello), + JSON.stringify(world) + ); + + different(hello, world); + + hello.foo = world.foo; + hello.bar = world.bar; + + different(hello, world); +}); + +Classes.run(); + +// --- + +const kitchen = suite('kitchen'); + +kitchen('kitchen sink', () => { + same({ + prop1: 'value1', + prop2: 'value2', + prop3: 'value3', + prop4: { + subProp1: 'sub value1', + subProp2: { + subSubProp1: 'sub sub value1', + subSubProp2: [1, 2, {prop2: 1, prop: 2}, 4, 5] + } + }, + prop5: 1000, + prop6: new Date(2016, 2, 10) + }, { + prop5: 1000, + prop3: 'value3', + prop1: 'value1', + prop2: 'value2', + prop6: new Date('2016/03/10'), + prop4: { + subProp2: { + subSubProp1: 'sub sub value1', + subSubProp2: [1, 2, {prop2: 1, prop: 2}, 4, 5] + }, + subProp1: 'sub value1' + } + }); +}); + +kitchen.run();