| '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 getMap(key) { |
| return key[this._key]; |
| }, |
| set: function setMap(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': |
| case 'Error': |
| return leftHandOperand === rightHandOperand; |
| 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); |
| 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; |
| } |
| |
| /*! |
| * 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); |
| if (leftHandKeys.length && leftHandKeys.length === rightHandKeys.length) { |
| leftHandKeys.sort(); |
| rightHandKeys.sort(); |
| if (iterableEqual(leftHandKeys, rightHandKeys) === 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'; |
| } |