diff --git a/.eslintrc b/.eslintrc index 76f35a6..16910ed 100644 --- a/.eslintrc +++ b/.eslintrc @@ -10,6 +10,10 @@ "max-statements-per-line": [2, { "max": 2 }], "strict": 1, }, + "globals": { + "WeakMap": false, + "WeakSet": false, + }, "overrides": [ { "files": "example/**", diff --git a/index.js b/index.js index 41c203e..be9f948 100644 --- a/index.js +++ b/index.js @@ -7,11 +7,17 @@ var isArray = require('isarray'); var isDate = require('is-date-object'); var whichBoxedPrimitive = require('which-boxed-primitive'); var callBound = require('es-abstract/helpers/callBound'); +var whichCollection = require('which-collection'); +var getIterator = require('es-get-iterator'); var $getTime = callBound('Date.prototype.getTime'); var gPO = Object.getPrototypeOf; var $objToString = callBound('Object.prototype.toString'); +var $mapHas = callBound('Map.prototype.has', true); +var $mapGet = callBound('Map.prototype.get', true); +var $setHas = callBound('Set.prototype.has', true); + function deepEqual(actual, expected, options) { var opts = options || {}; @@ -62,7 +68,7 @@ function isBuffer(x) { } function objEquiv(a, b, opts) { - /* eslint max-statements: [2, 70], max-lines-per-function: [2, 80] */ + /* eslint max-statements: [2, 100], max-lines-per-function: [2, 120], max-depth: [2, 5] */ var i, key; if (typeof a !== typeof b) { return false; } @@ -138,6 +144,41 @@ function objEquiv(a, b, opts) { if (!deepEqual(a[key], b[key], opts)) { return false; } } + var aCollection = whichCollection(a); + var bCollection = whichCollection(b); + if (aCollection !== bCollection) { + return false; + } + if (aCollection === 'Map' || aCollection === 'Set') { + var iA = getIterator(a); + var iB = getIterator(b); + var resultA; + var resultB; + if (aCollection === 'Map') { // aCollection === bCollection + var aWithBKey; + var bWithAKey; + while ((resultA = iA.next()) && (resultB = iB.next()) && !resultA.done && !resultB.done) { + if (!$mapHas(a, resultB.value[0]) || !$mapHas(b, resultA.value[0])) { return false; } + if (resultA.value[0] === resultB.value[0]) { // optimization: keys are the same, no need to look up values + if (!deepEqual(resultA.value[1], resultB.value[1])) { return false; } + } else { + aWithBKey = $mapGet(a, resultB.value[0]); + bWithAKey = $mapGet(b, resultA.value[0]); + if (!deepEqual(resultA.value[1], bWithAKey) || !deepEqual(resultB.value[1], aWithBKey)) { + return false; + } + } + } + } else if (aCollection === 'Set') { // aCollection === bCollection + while ((resultA = iA.next()) && (resultB = iB.next()) && !resultA.done && !resultB.done) { + if (!$setHas(a, resultB.value) || !$setHas(b, resultA.value)) { return false; } + } + } + if (resultA && resultB && resultA.done !== resultB.done) { + return false; + } + } + return true; } diff --git a/package.json b/package.json index 5e9a2b8..936f8bc 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "es-abstract": "^1.16.2", + "es-get-iterator": "^1.0.1", "is-arguments": "^1.0.4", "is-date-object": "^1.0.1", "is-regex": "^1.0.4", @@ -37,7 +38,8 @@ "object-is": "^1.0.1", "object-keys": "^1.1.1", "regexp.prototype.flags": "^1.2.0", - "which-boxed-primitive": "^1.0.1" + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.0" }, "devDependencies": { "@ljharb/eslint-config": "^15.0.2", diff --git a/test/cmp.js b/test/cmp.js index 16b005f..7d55324 100644 --- a/test/cmp.js +++ b/test/cmp.js @@ -12,6 +12,103 @@ test('equal', function (t) { true, false ); + + t.end(); +}); + +test('Maps', { skip: typeof Map !== 'function' }, function (t) { + t.deepEqualTest( + new Map([['a', 1], ['b', 2]]), + new Map([['b', 2], ['a', 1]]), + 'two equal Maps', + true, + true + ); + + t.deepEqualTest( + new Map([['a', [1, 2]]]), + new Map([['a', [2, 1]]]), + 'two Maps with inequal values on the same key', + false, + false + ); + + t.deepEqualTest( + new Map([['a', 1]]), + new Map([['b', 1]]), + 'two inequal Maps', + false, + false + ); + + t.end(); +}); + +test('WeakMaps', { skip: typeof WeakMap !== 'function' }, function (t) { + t.deepEqualTest( + new WeakMap([[Object, null], [Function, true]]), + new WeakMap([[Function, true], [Object, null]]), + 'two equal WeakMaps', + true, + true + ); + + t.deepEqualTest( + new WeakMap([[Object, null]]), + new WeakMap([[Object, true]]), + 'two WeakMaps with inequal values on the same key', + true, + true + ); + + t.deepEqualTest( + new WeakMap([[Object, null], [Function, true]]), + new WeakMap([[Object, null]]), + 'two inequal WeakMaps', + true, + true + ); + + t.end(); +}); + +test('Sets', { skip: typeof Set !== 'function' }, function (t) { + t.deepEqualTest( + new Set(['a', 1, 'b', 2]), + new Set(['b', 2, 'a', 1]), + 'two equal Sets', + true, + true + ); + + t.deepEqualTest( + new Set(['a', 1]), + new Set(['b', 1]), + 'two inequal Sets', + false, + false + ); + + t.end(); +}); + +test('WeakSets', { skip: typeof WeakSet !== 'function' }, function (t) { + t.deepEqualTest( + new WeakSet([Object, Function]), + new WeakSet([Function, Object]), + 'two equal WeakSets', + true, + true + ); + + t.deepEqualTest( + new WeakSet([Object, Function]), + new WeakSet([Object]), + 'two inequal WeakSets', + true, + true + ); + t.end(); }); @@ -23,6 +120,7 @@ test('not equal', function (t) { false, false ); + t.end(); });