From 2b736a1ab82b9df23d8b7d75348e49da35fa3c74 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Wed, 30 Jan 2019 23:36:54 -0600 Subject: [PATCH 1/9] Compare the values of object properties with symbolic keys. --- lib/deep-equal.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/deep-equal.js b/lib/deep-equal.js index 012b0f6..2c1458b 100644 --- a/lib/deep-equal.js +++ b/lib/deep-equal.js @@ -18,6 +18,11 @@ var getTime = Date.prototype.getTime; var hasOwnProperty = Object.prototype.hasOwnProperty; var indexOf = Array.prototype.indexOf; var keys = Object.keys; +var getOwnPropertySymbols = Object.getOwnPropertySymbols; + +function keysAndSymbols(obj) { + return keys(obj).concat(getOwnPropertySymbols(obj)); +} /** * @name samsam.deepEqual @@ -105,8 +110,8 @@ function deepEqualCyclic(first, second, match) { var class1 = getClass(obj1); var class2 = getClass(obj2); - var keys1 = keys(obj1); - var keys2 = keys(obj2); + var keys1 = keysAndSymbols(obj1); + var keys2 = keysAndSymbols(obj2); var name1 = getClassName(obj1); var name2 = getClassName(obj2); From 95b37d4df1914964e9498e4478020d0d2efac235 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Thu, 28 Feb 2019 22:02:15 -0600 Subject: [PATCH 2/9] -Assert deep equal if the actual object contains additional symbolic properties that are missing from the expectation. -Added unit tests. -Added myself to the authors list. --- AUTHORS | 1 + lib/deep-equal.js | 11 ++++------- lib/deep-equal.test.js | 21 +++++++++++++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index 35200a9..4b0a569 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,3 +10,4 @@ Magnar Sveen Mathias Schreck Olle Jonsson Gunnar André Reinseth +Brandon Evans diff --git a/lib/deep-equal.js b/lib/deep-equal.js index 2c1458b..ce6f851 100644 --- a/lib/deep-equal.js +++ b/lib/deep-equal.js @@ -20,10 +20,6 @@ var indexOf = Array.prototype.indexOf; var keys = Object.keys; var getOwnPropertySymbols = Object.getOwnPropertySymbols; -function keysAndSymbols(obj) { - return keys(obj).concat(getOwnPropertySymbols(obj)); -} - /** * @name samsam.deepEqual * @param Object first @@ -110,10 +106,11 @@ function deepEqualCyclic(first, second, match) { var class1 = getClass(obj1); var class2 = getClass(obj2); - var keys1 = keysAndSymbols(obj1); - var keys2 = keysAndSymbols(obj2); + var keys1 = keys(obj1); + var keys2 = keys(obj2); var name1 = getClassName(obj1); var name2 = getClassName(obj2); + var keysAndSymbols1 = keys1.concat(getOwnPropertySymbols(obj1)); if (isArguments(obj1) || isArguments(obj2)) { if (obj1.length !== obj2.length) { @@ -138,7 +135,7 @@ function deepEqualCyclic(first, second, match) { return isSubset(obj1, obj2, deepEqual); } - return every.call(keys1, function(key) { + return every.call(keysAndSymbols1, function(key) { if (!hasOwnProperty.call(obj2, key)) { return false; } diff --git a/lib/deep-equal.test.js b/lib/deep-equal.test.js index ca278cf..370358c 100644 --- a/lib/deep-equal.test.js +++ b/lib/deep-equal.test.js @@ -26,6 +26,7 @@ describe("deepEqual", function() { var func = function() {}; var obj = {}; var arr = []; + var symbol = Symbol("id"); var date = new Date(); var sameDate = new Date(date.getTime()); var sameDateWithProp = new Date(date.getTime()); @@ -364,6 +365,26 @@ describe("deepEqual", function() { assert.isFalse(checkDeep); }); + it("returns false if object has different symbolic properties", function() { + var checkDeep = samsam.deepEqual({ [symbol]: 42 }, { [symbol]: 43 }); + assert.isFalse(checkDeep); + }); + + it("returns true if object has same symbolic properties", function() { + var checkDeep = samsam.deepEqual({ [symbol]: 42 }, { [symbol]: 42 }); + assert.isTrue(checkDeep); + }); + + it("returns false if object missing expected symbolic properties", function() { + var checkDeep = samsam.deepEqual({ [symbol]: 42 }, {}); + assert.isFalse(checkDeep); + }); + + it("returns true if object contains additional symbolic properties", function() { + var checkDeep = samsam.deepEqual({}, { [symbol]: 42 }); + assert.isTrue(checkDeep); + }); + it("returns false if object to null", function() { var checkDeep = samsam.deepEqual({}, null); assert.isFalse(checkDeep); From 7bd5f6e4417d28d44ff3c1b3e1c669650aa4da03 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Thu, 28 Feb 2019 22:34:07 -0600 Subject: [PATCH 3/9] -Ignore a Symbol.iterator key if the expectation object is an arguments object. -Fixed linting errors. --- lib/deep-equal.js | 10 +++++++++- lib/deep-equal.test.js | 22 ++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/lib/deep-equal.js b/lib/deep-equal.js index ce6f851..0bb4cd1 100644 --- a/lib/deep-equal.js +++ b/lib/deep-equal.js @@ -111,8 +111,16 @@ function deepEqualCyclic(first, second, match) { var name1 = getClassName(obj1); var name2 = getClassName(obj2); var keysAndSymbols1 = keys1.concat(getOwnPropertySymbols(obj1)); + var obj1IsArguments = isArguments(obj1); + var obj2IsArguments = isArguments(obj2); - if (isArguments(obj1) || isArguments(obj2)) { + if (obj1IsArguments) { + keysAndSymbols1 = keysAndSymbols1.filter(function(keyOrSymbol) { + return keyOrSymbol !== Symbol.iterator; + }); + } + + if (obj1IsArguments || obj2IsArguments) { if (obj1.length !== obj2.length) { return false; } diff --git a/lib/deep-equal.test.js b/lib/deep-equal.test.js index 370358c..e5aea58 100644 --- a/lib/deep-equal.test.js +++ b/lib/deep-equal.test.js @@ -366,22 +366,36 @@ describe("deepEqual", function() { }); it("returns false if object has different symbolic properties", function() { - var checkDeep = samsam.deepEqual({ [symbol]: 42 }, { [symbol]: 43 }); + var obj1 = {}; + var obj2 = {}; + obj1[symbol] = 42; + obj2[symbol] = 43; + var checkDeep = samsam.deepEqual(obj1, obj2); assert.isFalse(checkDeep); }); it("returns true if object has same symbolic properties", function() { - var checkDeep = samsam.deepEqual({ [symbol]: 42 }, { [symbol]: 42 }); + var obj1 = {}; + var obj2 = {}; + obj1[symbol] = 42; + obj2[symbol] = 42; + var checkDeep = samsam.deepEqual(obj1, obj2); assert.isTrue(checkDeep); }); it("returns false if object missing expected symbolic properties", function() { - var checkDeep = samsam.deepEqual({ [symbol]: 42 }, {}); + var obj1 = {}; + var obj2 = {}; + obj1[symbol] = 42; + var checkDeep = samsam.deepEqual(obj1, obj2); assert.isFalse(checkDeep); }); it("returns true if object contains additional symbolic properties", function() { - var checkDeep = samsam.deepEqual({}, { [symbol]: 42 }); + var obj1 = {}; + var obj2 = {}; + obj2[symbol] = 42; + var checkDeep = samsam.deepEqual(obj1, obj2); assert.isTrue(checkDeep); }); From 67dc08b3c8ca9886a369d37598817cac5c93231f Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Fri, 1 Mar 2019 01:23:06 -0600 Subject: [PATCH 4/9] Reversed my logic. The first parameter is the actual, while the second is the expected. --- lib/deep-equal.js | 10 +++++----- lib/deep-equal.test.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/deep-equal.js b/lib/deep-equal.js index 0bb4cd1..e422472 100644 --- a/lib/deep-equal.js +++ b/lib/deep-equal.js @@ -110,12 +110,12 @@ function deepEqualCyclic(first, second, match) { var keys2 = keys(obj2); var name1 = getClassName(obj1); var name2 = getClassName(obj2); - var keysAndSymbols1 = keys1.concat(getOwnPropertySymbols(obj1)); + var keysAndSymbols2 = keys1.concat(getOwnPropertySymbols(obj2)); var obj1IsArguments = isArguments(obj1); var obj2IsArguments = isArguments(obj2); - if (obj1IsArguments) { - keysAndSymbols1 = keysAndSymbols1.filter(function(keyOrSymbol) { + if (obj2IsArguments) { + keysAndSymbols2 = keysAndSymbols2.filter(function(keyOrSymbol) { return keyOrSymbol !== Symbol.iterator; }); } @@ -143,8 +143,8 @@ function deepEqualCyclic(first, second, match) { return isSubset(obj1, obj2, deepEqual); } - return every.call(keysAndSymbols1, function(key) { - if (!hasOwnProperty.call(obj2, key)) { + return every.call(keysAndSymbols2, function(key) { + if (!hasOwnProperty.call(obj1, key)) { return false; } diff --git a/lib/deep-equal.test.js b/lib/deep-equal.test.js index e5aea58..964279c 100644 --- a/lib/deep-equal.test.js +++ b/lib/deep-equal.test.js @@ -386,7 +386,7 @@ describe("deepEqual", function() { it("returns false if object missing expected symbolic properties", function() { var obj1 = {}; var obj2 = {}; - obj1[symbol] = 42; + obj2[symbol] = 42; var checkDeep = samsam.deepEqual(obj1, obj2); assert.isFalse(checkDeep); }); @@ -394,7 +394,7 @@ describe("deepEqual", function() { it("returns true if object contains additional symbolic properties", function() { var obj1 = {}; var obj2 = {}; - obj2[symbol] = 42; + obj1[symbol] = 42; var checkDeep = samsam.deepEqual(obj1, obj2); assert.isTrue(checkDeep); }); From c4188da6f8f5250d5061e0b7bb84b8b0796bd588 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Fri, 1 Mar 2019 18:27:15 -0600 Subject: [PATCH 5/9] Remove the special case for arguments objects. --- lib/deep-equal.js | 10 +--------- lib/deep-equal.test.js | 8 ++++---- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/lib/deep-equal.js b/lib/deep-equal.js index e422472..49cddaf 100644 --- a/lib/deep-equal.js +++ b/lib/deep-equal.js @@ -111,16 +111,8 @@ function deepEqualCyclic(first, second, match) { var name1 = getClassName(obj1); var name2 = getClassName(obj2); var keysAndSymbols2 = keys1.concat(getOwnPropertySymbols(obj2)); - var obj1IsArguments = isArguments(obj1); - var obj2IsArguments = isArguments(obj2); - if (obj2IsArguments) { - keysAndSymbols2 = keysAndSymbols2.filter(function(keyOrSymbol) { - return keyOrSymbol !== Symbol.iterator; - }); - } - - if (obj1IsArguments || obj2IsArguments) { + if (isArguments(obj1) || isArguments(obj2)) { if (obj1.length !== obj2.length) { return false; } diff --git a/lib/deep-equal.test.js b/lib/deep-equal.test.js index 964279c..7dadf73 100644 --- a/lib/deep-equal.test.js +++ b/lib/deep-equal.test.js @@ -449,12 +449,12 @@ describe("deepEqual", function() { assert.isFalse(checkDeep); }); - it("returns true if arguments to array", function() { + it("returns false if arguments to array", function() { var gather = function() { return arguments; }; var checkDeep = samsam.deepEqual([1, 2, {}, []], gather(1, 2, {}, [])); - assert.isTrue(checkDeep); + assert.isFalse(checkDeep); }); it("returns true if array to arguments", function() { @@ -465,13 +465,13 @@ describe("deepEqual", function() { assert.isTrue(checkDeep); }); - it("returns true if arguments to array like object", function() { + it("returns false if arguments to array like object", function() { var gather = function() { return arguments; }; var arrayLike = { length: 4, "0": 1, "1": 2, "2": {}, "3": [] }; var checkDeep = samsam.deepEqual(arrayLike, gather(1, 2, {}, [])); - assert.isTrue(checkDeep); + assert.isFalse(checkDeep); }); it("returns true for same error", function() { From aac1b138c5e5678cc1c30d4902a757d293bcb35d Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Sat, 2 Mar 2019 08:31:16 -0600 Subject: [PATCH 6/9] Renamed first to actual and second to expectation. Fixed the key and symbol concatenation. Support IE11. --- lib/deep-equal.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/deep-equal.js b/lib/deep-equal.js index 49cddaf..85e4cdd 100644 --- a/lib/deep-equal.js +++ b/lib/deep-equal.js @@ -110,7 +110,10 @@ function deepEqualCyclic(first, second, match) { var keys2 = keys(obj2); var name1 = getClassName(obj1); var name2 = getClassName(obj2); - var keysAndSymbols2 = keys1.concat(getOwnPropertySymbols(obj2)); + var symbols2 = typeof Object.getOwnPropertySymbols === "function" + ? getOwnPropertySymbols(obj2) + : []; + var keysAndSymbols2 = keys2.concat(symbols2); if (isArguments(obj1) || isArguments(obj2)) { if (obj1.length !== obj2.length) { From 6605fa155afdc7d46430dc38f2e4d30bad0d09f3 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Sat, 2 Mar 2019 08:33:21 -0600 Subject: [PATCH 7/9] Staged full commit. --- lib/deep-equal.js | 185 ++++++++++++++++++++++++++-------------------- 1 file changed, 104 insertions(+), 81 deletions(-) diff --git a/lib/deep-equal.js b/lib/deep-equal.js index 85e4cdd..abea178 100644 --- a/lib/deep-equal.js +++ b/lib/deep-equal.js @@ -22,8 +22,8 @@ var getOwnPropertySymbols = Object.getOwnPropertySymbols; /** * @name samsam.deepEqual - * @param Object first - * @param Object second + * @param Object actual + * @param Object expectation * * Deep equal comparison. Two values are "deep equal" if: * @@ -31,140 +31,158 @@ var getOwnPropertySymbols = Object.getOwnPropertySymbols; * - They are both date objects representing the same time * - They are both arrays containing elements that are all deepEqual * - They are objects with the same set of properties, and each property - * in ``first`` is deepEqual to the corresponding property in ``second`` + * in ``actual`` is deepEqual to the corresponding property in ``expectation`` * * Supports cyclic objects. */ -function deepEqualCyclic(first, second, match) { +function deepEqualCyclic(actual, expectation, match) { // used for cyclic comparison // contain already visited objects - var objects1 = []; - var objects2 = []; + var actualObjects = []; + var expectationObjects = []; // contain pathes (position in the object structure) // of the already visited objects // indexes same as in objects arrays - var paths1 = []; - var paths2 = []; + var actualPaths = []; + var expectationPaths = []; // contains combinations of already compared objects // in the manner: { "$1['ref']$2['ref']": true } var compared = {}; // does the recursion for the deep equal check - return (function deepEqual(obj1, obj2, path1, path2) { + return (function deepEqual( + actualObj, + expectationObj, + actualPath, + expectationPath + ) { // If both are matchers they must be the same instance in order to be // considered equal If we didn't do that we would end up running one // matcher against the other - if (match && match.isMatcher(obj2)) { - if (match.isMatcher(obj1)) { - return obj1 === obj2; + if (match && match.isMatcher(expectationObj)) { + if (match.isMatcher(actualObj)) { + return actualObj === expectationObj; } - return obj2.test(obj1); + return expectationObj.test(actualObj); } - var type1 = typeof obj1; - var type2 = typeof obj2; + var actualType = typeof actualObj; + var expectationType = typeof expectationObj; // == null also matches undefined if ( - obj1 === obj2 || - isNaN(obj1) || - isNaN(obj2) || - obj1 == null || - obj2 == null || - type1 !== "object" || - type2 !== "object" + actualObj === expectationObj || + isNaN(actualObj) || + isNaN(expectationObj) || + actualObj == null || + expectationObj == null || + actualType !== "object" || + expectationType !== "object" ) { - return identical(obj1, obj2); + return identical(actualObj, expectationObj); } // Elements are only equal if identical(expected, actual) - if (isElement(obj1) || isElement(obj2)) { + if (isElement(actualObj) || isElement(expectationObj)) { return false; } - var isDate1 = isDate(obj1); - var isDate2 = isDate(obj2); - if (isDate1 || isDate2) { + var isActualDate = isDate(actualObj); + var isExpectationDate = isDate(expectationObj); + if (isActualDate || isExpectationDate) { if ( - !isDate1 || - !isDate2 || - getTime.call(obj1) !== getTime.call(obj2) + !isActualDate || + !isExpectationDate || + getTime.call(actualObj) !== getTime.call(expectationObj) ) { return false; } } - if (obj1 instanceof RegExp && obj2 instanceof RegExp) { - if (valueToString(obj1) !== valueToString(obj2)) { + if (actualObj instanceof RegExp && expectationObj instanceof RegExp) { + if (valueToString(actualObj) !== valueToString(expectationObj)) { return false; } } - if (obj1 instanceof Error && obj2 instanceof Error) { - return obj1 === obj2; + if (actualObj instanceof Error && expectationObj instanceof Error) { + return actualObj === expectationObj; } - var class1 = getClass(obj1); - var class2 = getClass(obj2); - var keys1 = keys(obj1); - var keys2 = keys(obj2); - var name1 = getClassName(obj1); - var name2 = getClassName(obj2); - var symbols2 = typeof Object.getOwnPropertySymbols === "function" - ? getOwnPropertySymbols(obj2) - : []; - var keysAndSymbols2 = keys2.concat(symbols2); - - if (isArguments(obj1) || isArguments(obj2)) { - if (obj1.length !== obj2.length) { + var actualClass = getClass(actualObj); + var expectationClass = getClass(expectationObj); + var actualKeys = keys(actualObj); + var expectationKeys = keys(expectationObj); + var actualName = getClassName(actualObj); + var expectationName = getClassName(expectationObj); + var expectationSymbols = + typeof Object.getOwnPropertySymbols === "function" + ? getOwnPropertySymbols(expectationObj) + : []; + var expectationKeysAndSymbols = expectationKeys.concat( + expectationSymbols + ); + + if (isArguments(actualObj) || isArguments(expectationObj)) { + if (actualObj.length !== expectationObj.length) { return false; } } else { if ( - type1 !== type2 || - class1 !== class2 || - keys1.length !== keys2.length || - (name1 && name2 && name1 !== name2) + actualType !== expectationType || + actualClass !== expectationClass || + actualKeys.length !== expectationKeys.length || + (actualName && + expectationName && + actualName !== expectationName) ) { return false; } } - if (isSet(obj1) || isSet(obj2)) { - if (!isSet(obj1) || !isSet(obj2) || obj1.size !== obj2.size) { + if (isSet(actualObj) || isSet(expectationObj)) { + if ( + !isSet(actualObj) || + !isSet(expectationObj) || + actualObj.size !== expectationObj.size + ) { return false; } - return isSubset(obj1, obj2, deepEqual); + return isSubset(actualObj, expectationObj, deepEqual); } - return every.call(keysAndSymbols2, function(key) { - if (!hasOwnProperty.call(obj1, key)) { + return every.call(expectationKeysAndSymbols, function(key) { + if (!hasOwnProperty.call(actualObj, key)) { return false; } - var value1 = obj1[key]; - var value2 = obj2[key]; - var isObject1 = isObject(value1); - var isObject2 = isObject(value2); + var actualValue = actualObj[key]; + var expectationValue = expectationObj[key]; + var actualObject = isObject(actualValue); + var expectationObject = isObject(expectationValue); // determines, if the objects were already visited // (it's faster to check for isObject first, than to // get -1 from getIndex for non objects) - var index1 = isObject1 ? indexOf.call(objects1, value1) : -1; - var index2 = isObject2 ? indexOf.call(objects2, value2) : -1; + var actualIndex = actualObject + ? indexOf.call(actualObjects, actualValue) + : -1; + var expectationIndex = expectationObject + ? indexOf.call(expectationObjects, expectationValue) + : -1; // determines the new paths of the objects // - for non cyclic objects the current path will be extended // by current property name // - for cyclic objects the stored path is taken - var newPath1 = - index1 !== -1 - ? paths1[index1] - : path1 + "[" + JSON.stringify(key) + "]"; - var newPath2 = - index2 !== -1 - ? paths2[index2] - : path2 + "[" + JSON.stringify(key) + "]"; - var combinedPath = newPath1 + newPath2; + var newActualPath = + actualIndex !== -1 + ? actualPaths[actualIndex] + : actualPath + "[" + JSON.stringify(key) + "]"; + var newExpectationPath = + expectationIndex !== -1 + ? expectationPaths[expectationIndex] + : expectationPath + "[" + JSON.stringify(key) + "]"; + var combinedPath = newActualPath + newExpectationPath; // stop recursion if current objects are already compared if (compared[combinedPath]) { @@ -172,27 +190,32 @@ function deepEqualCyclic(first, second, match) { } // remember the current objects and their paths - if (index1 === -1 && isObject1) { - objects1.push(value1); - paths1.push(newPath1); + if (actualIndex === -1 && actualObject) { + actualObjects.push(actualValue); + actualPaths.push(newActualPath); } - if (index2 === -1 && isObject2) { - objects2.push(value2); - paths2.push(newPath2); + if (expectationIndex === -1 && expectationObject) { + expectationObjects.push(expectationValue); + expectationPaths.push(newExpectationPath); } // remember that the current objects are already compared - if (isObject1 && isObject2) { + if (actualObject && expectationObject) { compared[combinedPath] = true; } // End of cyclic logic - // neither value1 nor value2 is a cycle + // neither actualValue nor expectationValue is a cycle // continue with next level - return deepEqual(value1, value2, newPath1, newPath2); + return deepEqual( + actualValue, + expectationValue, + newActualPath, + newExpectationPath + ); }); - })(first, second, "$1", "$2"); + })(actual, expectation, "$1", "$2"); } deepEqualCyclic.use = function(match) { From 70d5f0bf0a29c6dabab25363cc0d5ef6d342896d Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Wed, 6 Mar 2019 17:04:17 -0600 Subject: [PATCH 8/9] Updated documentation. --- docs/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index e8c7a2d..28c99d8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -53,7 +53,7 @@ observably distinguishable. `NaN` is compared to itself. -### `deepEqual(obj1, obj2)` +### `deepEqual(actual, expectation)` Deep equal comparison. Two values are "deep equal" if: @@ -61,7 +61,7 @@ Deep equal comparison. Two values are "deep equal" if: * They are both date objects representing the same time * They are both arrays containing elements that are all deepEqual * They are objects with the same set of properties, and each property - in `obj1` is deepEqual to the corresponding property in `obj2` + in `actual` is deepEqual to the corresponding property in `expectation` ### `match(object, matcher)` From cef5959ce99377c9ce08a70629ecf648af7e9c4f Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Wed, 6 Mar 2019 17:05:58 -0600 Subject: [PATCH 9/9] Added section to documentation explaining symbolic properties. --- docs/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.md b/docs/index.md index 28c99d8..a054883 100644 --- a/docs/index.md +++ b/docs/index.md @@ -63,6 +63,8 @@ Deep equal comparison. Two values are "deep equal" if: * They are objects with the same set of properties, and each property in `actual` is deepEqual to the corresponding property in `expectation` + * `actual` can have [symbolic properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) that are missing from `expectation` + ### `match(object, matcher)`