123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- 'use strict';
- /* globals Symbol: false, Uint8Array: false, WeakMap: false */
- /*!
- * deep-eql
- * Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com>
- * MIT Licensed
- */
- var type = require('type-detect');
- function FakeMap() {
- this._key = 'chai/deep-eql__' + Math.random() + Date.now();
- }
- FakeMap.prototype = {
- get: function get(key) {
- return key[this._key];
- },
- set: function set(key, value) {
- if (Object.isExtensible(key)) {
- Object.defineProperty(key, this._key, {
- value: value,
- configurable: true,
- });
- }
- },
- };
- var MemoizeMap = typeof WeakMap === 'function' ? WeakMap : FakeMap;
- /*!
- * Check to see if the MemoizeMap has recorded a result of the two operands
- *
- * @param {Mixed} leftHandOperand
- * @param {Mixed} rightHandOperand
- * @param {MemoizeMap} memoizeMap
- * @returns {Boolean|null} result
- */
- function memoizeCompare(leftHandOperand, rightHandOperand, memoizeMap) {
- // Technically, WeakMap keys can *only* be objects, not primitives.
- if (!memoizeMap || isPrimitive(leftHandOperand) || isPrimitive(rightHandOperand)) {
- return null;
- }
- var leftHandMap = memoizeMap.get(leftHandOperand);
- if (leftHandMap) {
- var result = leftHandMap.get(rightHandOperand);
- if (typeof result === 'boolean') {
- return result;
- }
- }
- return null;
- }
- /*!
- * Set the result of the equality into the MemoizeMap
- *
- * @param {Mixed} leftHandOperand
- * @param {Mixed} rightHandOperand
- * @param {MemoizeMap} memoizeMap
- * @param {Boolean} result
- */
- function memoizeSet(leftHandOperand, rightHandOperand, memoizeMap, result) {
- // Technically, WeakMap keys can *only* be objects, not primitives.
- if (!memoizeMap || isPrimitive(leftHandOperand) || isPrimitive(rightHandOperand)) {
- return;
- }
- var leftHandMap = memoizeMap.get(leftHandOperand);
- if (leftHandMap) {
- leftHandMap.set(rightHandOperand, result);
- } else {
- leftHandMap = new MemoizeMap();
- leftHandMap.set(rightHandOperand, result);
- memoizeMap.set(leftHandOperand, leftHandMap);
- }
- }
- /*!
- * Primary Export
- */
- module.exports = deepEqual;
- module.exports.MemoizeMap = MemoizeMap;
- /**
- * Assert deeply nested sameValue equality between two objects of any type.
- *
- * @param {Mixed} leftHandOperand
- * @param {Mixed} rightHandOperand
- * @param {Object} [options] (optional) Additional options
- * @param {Array} [options.comparator] (optional) Override default algorithm, determining custom equality.
- * @param {Array} [options.memoize] (optional) Provide a custom memoization object which will cache the results of
- complex objects for a speed boost. By passing `false` you can disable memoization, but this will cause circular
- references to blow the stack.
- * @return {Boolean} equal match
- */
- function deepEqual(leftHandOperand, rightHandOperand, options) {
- // If we have a comparator, we can't assume anything; so bail to its check first.
- if (options && options.comparator) {
- return extensiveDeepEqual(leftHandOperand, rightHandOperand, options);
- }
- var simpleResult = simpleEqual(leftHandOperand, rightHandOperand);
- if (simpleResult !== null) {
- return simpleResult;
- }
- // Deeper comparisons are pushed through to a larger function
- return extensiveDeepEqual(leftHandOperand, rightHandOperand, options);
- }
- /**
- * Many comparisons can be canceled out early via simple equality or primitive checks.
- * @param {Mixed} leftHandOperand
- * @param {Mixed} rightHandOperand
- * @return {Boolean|null} equal match
- */
- function simpleEqual(leftHandOperand, rightHandOperand) {
- // Equal references (except for Numbers) can be returned early
- if (leftHandOperand === rightHandOperand) {
- // Handle +-0 cases
- return leftHandOperand !== 0 || 1 / leftHandOperand === 1 / rightHandOperand;
- }
- // handle NaN cases
- if (
- leftHandOperand !== leftHandOperand && // eslint-disable-line no-self-compare
- rightHandOperand !== rightHandOperand // eslint-disable-line no-self-compare
- ) {
- return true;
- }
- // Anything that is not an 'object', i.e. symbols, functions, booleans, numbers,
- // strings, and undefined, can be compared by reference.
- if (isPrimitive(leftHandOperand) || isPrimitive(rightHandOperand)) {
- // Easy out b/c it would have passed the first equality check
- return false;
- }
- return null;
- }
- /*!
- * The main logic of the `deepEqual` function.
- *
- * @param {Mixed} leftHandOperand
- * @param {Mixed} rightHandOperand
- * @param {Object} [options] (optional) Additional options
- * @param {Array} [options.comparator] (optional) Override default algorithm, determining custom equality.
- * @param {Array} [options.memoize] (optional) Provide a custom memoization object which will cache the results of
- complex objects for a speed boost. By passing `false` you can disable memoization, but this will cause circular
- references to blow the stack.
- * @return {Boolean} equal match
- */
- function extensiveDeepEqual(leftHandOperand, rightHandOperand, options) {
- options = options || {};
- options.memoize = options.memoize === false ? false : options.memoize || new MemoizeMap();
- var comparator = options && options.comparator;
- // Check if a memoized result exists.
- var memoizeResultLeft = memoizeCompare(leftHandOperand, rightHandOperand, options.memoize);
- if (memoizeResultLeft !== null) {
- return memoizeResultLeft;
- }
- var memoizeResultRight = memoizeCompare(rightHandOperand, leftHandOperand, options.memoize);
- if (memoizeResultRight !== null) {
- return memoizeResultRight;
- }
- // If a comparator is present, use it.
- if (comparator) {
- var comparatorResult = comparator(leftHandOperand, rightHandOperand);
- // Comparators may return null, in which case we want to go back to default behavior.
- if (comparatorResult === false || comparatorResult === true) {
- memoizeSet(leftHandOperand, rightHandOperand, options.memoize, comparatorResult);
- return comparatorResult;
- }
- // To allow comparators to override *any* behavior, we ran them first. Since it didn't decide
- // what to do, we need to make sure to return the basic tests first before we move on.
- var simpleResult = simpleEqual(leftHandOperand, rightHandOperand);
- if (simpleResult !== null) {
- // Don't memoize this, it takes longer to set/retrieve than to just compare.
- return simpleResult;
- }
- }
- var leftHandType = type(leftHandOperand);
- if (leftHandType !== type(rightHandOperand)) {
- memoizeSet(leftHandOperand, rightHandOperand, options.memoize, false);
- return false;
- }
- // Temporarily set the operands in the memoize object to prevent blowing the stack
- memoizeSet(leftHandOperand, rightHandOperand, options.memoize, true);
- var result = extensiveDeepEqualByType(leftHandOperand, rightHandOperand, leftHandType, options);
- memoizeSet(leftHandOperand, rightHandOperand, options.memoize, result);
- return result;
- }
- function extensiveDeepEqualByType(leftHandOperand, rightHandOperand, leftHandType, options) {
- switch (leftHandType) {
- case 'String':
- case 'Number':
- case 'Boolean':
- case 'Date':
- // If these types are their instance types (e.g. `new Number`) then re-deepEqual against their values
- return deepEqual(leftHandOperand.valueOf(), rightHandOperand.valueOf());
- case 'Promise':
- case 'Symbol':
- case 'function':
- case 'WeakMap':
- case 'WeakSet':
- return leftHandOperand === rightHandOperand;
- case 'Error':
- return keysEqual(leftHandOperand, rightHandOperand, [ 'name', 'message', 'code' ], options);
- case 'Arguments':
- case 'Int8Array':
- case 'Uint8Array':
- case 'Uint8ClampedArray':
- case 'Int16Array':
- case 'Uint16Array':
- case 'Int32Array':
- case 'Uint32Array':
- case 'Float32Array':
- case 'Float64Array':
- case 'Array':
- return iterableEqual(leftHandOperand, rightHandOperand, options);
- case 'RegExp':
- return regexpEqual(leftHandOperand, rightHandOperand);
- case 'Generator':
- return generatorEqual(leftHandOperand, rightHandOperand, options);
- case 'DataView':
- return iterableEqual(new Uint8Array(leftHandOperand.buffer), new Uint8Array(rightHandOperand.buffer), options);
- case 'ArrayBuffer':
- return iterableEqual(new Uint8Array(leftHandOperand), new Uint8Array(rightHandOperand), options);
- case 'Set':
- return entriesEqual(leftHandOperand, rightHandOperand, options);
- case 'Map':
- return entriesEqual(leftHandOperand, rightHandOperand, options);
- case 'Temporal.PlainDate':
- case 'Temporal.PlainTime':
- case 'Temporal.PlainDateTime':
- case 'Temporal.Instant':
- case 'Temporal.ZonedDateTime':
- case 'Temporal.PlainYearMonth':
- case 'Temporal.PlainMonthDay':
- return leftHandOperand.equals(rightHandOperand);
- case 'Temporal.Duration':
- return leftHandOperand.total('nanoseconds') === rightHandOperand.total('nanoseconds');
- case 'Temporal.TimeZone':
- case 'Temporal.Calendar':
- return leftHandOperand.toString() === rightHandOperand.toString();
- default:
- return objectEqual(leftHandOperand, rightHandOperand, options);
- }
- }
- /*!
- * Compare two Regular Expressions for equality.
- *
- * @param {RegExp} leftHandOperand
- * @param {RegExp} rightHandOperand
- * @return {Boolean} result
- */
- function regexpEqual(leftHandOperand, rightHandOperand) {
- return leftHandOperand.toString() === rightHandOperand.toString();
- }
- /*!
- * Compare two Sets/Maps for equality. Faster than other equality functions.
- *
- * @param {Set} leftHandOperand
- * @param {Set} rightHandOperand
- * @param {Object} [options] (Optional)
- * @return {Boolean} result
- */
- function entriesEqual(leftHandOperand, rightHandOperand, options) {
- // IE11 doesn't support Set#entries or Set#@@iterator, so we need manually populate using Set#forEach
- if (leftHandOperand.size !== rightHandOperand.size) {
- return false;
- }
- if (leftHandOperand.size === 0) {
- return true;
- }
- var leftHandItems = [];
- var rightHandItems = [];
- leftHandOperand.forEach(function gatherEntries(key, value) {
- leftHandItems.push([ key, value ]);
- });
- rightHandOperand.forEach(function gatherEntries(key, value) {
- rightHandItems.push([ key, value ]);
- });
- return iterableEqual(leftHandItems.sort(), rightHandItems.sort(), options);
- }
- /*!
- * Simple equality for flat iterable objects such as Arrays, TypedArrays or Node.js buffers.
- *
- * @param {Iterable} leftHandOperand
- * @param {Iterable} rightHandOperand
- * @param {Object} [options] (Optional)
- * @return {Boolean} result
- */
- function iterableEqual(leftHandOperand, rightHandOperand, options) {
- var length = leftHandOperand.length;
- if (length !== rightHandOperand.length) {
- return false;
- }
- if (length === 0) {
- return true;
- }
- var index = -1;
- while (++index < length) {
- if (deepEqual(leftHandOperand[index], rightHandOperand[index], options) === false) {
- return false;
- }
- }
- return true;
- }
- /*!
- * Simple equality for generator objects such as those returned by generator functions.
- *
- * @param {Iterable} leftHandOperand
- * @param {Iterable} rightHandOperand
- * @param {Object} [options] (Optional)
- * @return {Boolean} result
- */
- function generatorEqual(leftHandOperand, rightHandOperand, options) {
- return iterableEqual(getGeneratorEntries(leftHandOperand), getGeneratorEntries(rightHandOperand), options);
- }
- /*!
- * Determine if the given object has an @@iterator function.
- *
- * @param {Object} target
- * @return {Boolean} `true` if the object has an @@iterator function.
- */
- function hasIteratorFunction(target) {
- return typeof Symbol !== 'undefined' &&
- typeof target === 'object' &&
- typeof Symbol.iterator !== 'undefined' &&
- typeof target[Symbol.iterator] === 'function';
- }
- /*!
- * Gets all iterator entries from the given Object. If the Object has no @@iterator function, returns an empty array.
- * This will consume the iterator - which could have side effects depending on the @@iterator implementation.
- *
- * @param {Object} target
- * @returns {Array} an array of entries from the @@iterator function
- */
- function getIteratorEntries(target) {
- if (hasIteratorFunction(target)) {
- try {
- return getGeneratorEntries(target[Symbol.iterator]());
- } catch (iteratorError) {
- return [];
- }
- }
- return [];
- }
- /*!
- * Gets all entries from a Generator. This will consume the generator - which could have side effects.
- *
- * @param {Generator} target
- * @returns {Array} an array of entries from the Generator.
- */
- function getGeneratorEntries(generator) {
- var generatorResult = generator.next();
- var accumulator = [ generatorResult.value ];
- while (generatorResult.done === false) {
- generatorResult = generator.next();
- accumulator.push(generatorResult.value);
- }
- return accumulator;
- }
- /*!
- * Gets all own and inherited enumerable keys from a target.
- *
- * @param {Object} target
- * @returns {Array} an array of own and inherited enumerable keys from the target.
- */
- function getEnumerableKeys(target) {
- var keys = [];
- for (var key in target) {
- keys.push(key);
- }
- return keys;
- }
- function getEnumerableSymbols(target) {
- var keys = [];
- var allKeys = Object.getOwnPropertySymbols(target);
- for (var i = 0; i < allKeys.length; i += 1) {
- var key = allKeys[i];
- if (Object.getOwnPropertyDescriptor(target, key).enumerable) {
- keys.push(key);
- }
- }
- return keys;
- }
- /*!
- * Determines if two objects have matching values, given a set of keys. Defers to deepEqual for the equality check of
- * each key. If any value of the given key is not equal, the function will return false (early).
- *
- * @param {Mixed} leftHandOperand
- * @param {Mixed} rightHandOperand
- * @param {Array} keys An array of keys to compare the values of leftHandOperand and rightHandOperand against
- * @param {Object} [options] (Optional)
- * @return {Boolean} result
- */
- function keysEqual(leftHandOperand, rightHandOperand, keys, options) {
- var length = keys.length;
- if (length === 0) {
- return true;
- }
- for (var i = 0; i < length; i += 1) {
- if (deepEqual(leftHandOperand[keys[i]], rightHandOperand[keys[i]], options) === false) {
- return false;
- }
- }
- return true;
- }
- /*!
- * Recursively check the equality of two Objects. Once basic sameness has been established it will defer to `deepEqual`
- * for each enumerable key in the object.
- *
- * @param {Mixed} leftHandOperand
- * @param {Mixed} rightHandOperand
- * @param {Object} [options] (Optional)
- * @return {Boolean} result
- */
- function objectEqual(leftHandOperand, rightHandOperand, options) {
- var leftHandKeys = getEnumerableKeys(leftHandOperand);
- var rightHandKeys = getEnumerableKeys(rightHandOperand);
- var leftHandSymbols = getEnumerableSymbols(leftHandOperand);
- var rightHandSymbols = getEnumerableSymbols(rightHandOperand);
- leftHandKeys = leftHandKeys.concat(leftHandSymbols);
- rightHandKeys = rightHandKeys.concat(rightHandSymbols);
- if (leftHandKeys.length && leftHandKeys.length === rightHandKeys.length) {
- if (iterableEqual(mapSymbols(leftHandKeys).sort(), mapSymbols(rightHandKeys).sort()) === false) {
- return false;
- }
- return keysEqual(leftHandOperand, rightHandOperand, leftHandKeys, options);
- }
- var leftHandEntries = getIteratorEntries(leftHandOperand);
- var rightHandEntries = getIteratorEntries(rightHandOperand);
- if (leftHandEntries.length && leftHandEntries.length === rightHandEntries.length) {
- leftHandEntries.sort();
- rightHandEntries.sort();
- return iterableEqual(leftHandEntries, rightHandEntries, options);
- }
- if (leftHandKeys.length === 0 &&
- leftHandEntries.length === 0 &&
- rightHandKeys.length === 0 &&
- rightHandEntries.length === 0) {
- return true;
- }
- return false;
- }
- /*!
- * Returns true if the argument is a primitive.
- *
- * This intentionally returns true for all objects that can be compared by reference,
- * including functions and symbols.
- *
- * @param {Mixed} value
- * @return {Boolean} result
- */
- function isPrimitive(value) {
- return value === null || typeof value !== 'object';
- }
- function mapSymbols(arr) {
- return arr.map(function mapSymbol(entry) {
- if (typeof entry === 'symbol') {
- return entry.toString();
- }
- return entry;
- });
- }
|