123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- "use strict";
-
- var valueToString = require("@sinonjs/commons").valueToString;
- var className = require("@sinonjs/commons").className;
- var typeOf = require("@sinonjs/commons").typeOf;
- var arrayProto = require("@sinonjs/commons").prototypes.array;
- var objectProto = require("@sinonjs/commons").prototypes.object;
- var mapForEach = require("@sinonjs/commons").prototypes.map.forEach;
-
- var getClass = require("./get-class");
- var identical = require("./identical");
- var isArguments = require("./is-arguments");
- var isDate = require("./is-date");
- var isElement = require("./is-element");
- var isMap = require("./is-map");
- var isNaN = require("./is-nan");
- var isObject = require("./is-object");
- var isSet = require("./is-set");
- var isSubset = require("./is-subset");
-
- var concat = arrayProto.concat;
- var every = arrayProto.every;
- var push = arrayProto.push;
-
- var getTime = Date.prototype.getTime;
- var hasOwnProperty = objectProto.hasOwnProperty;
- var indexOf = arrayProto.indexOf;
- var keys = Object.keys;
- var getOwnPropertySymbols = Object.getOwnPropertySymbols;
-
- /**
- * Deep equal comparison. Two values are "deep equal" when:
- *
- * - They are equal, according to samsam.identical
- * - 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 ``actual`` is deepEqual to the corresponding property in ``expectation``
- *
- * Supports cyclic objects.
- *
- * @alias module:samsam.deepEqual
- * @param {*} actual The object to examine
- * @param {*} expectation The object actual is expected to be equal to
- * @param {object} match A value to match on
- * @returns {boolean} Returns true when actual and expectation are considered equal
- */
- function deepEqualCyclic(actual, expectation, match) {
- // used for cyclic comparison
- // contain already visited objects
- var actualObjects = [];
- var expectationObjects = [];
- // contain pathes (position in the object structure)
- // of the already visited objects
- // indexes same as in objects arrays
- 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
- // eslint-disable-next-line complexity
- 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(expectationObj)) {
- if (match.isMatcher(actualObj)) {
- return actualObj === expectationObj;
- }
- return expectationObj.test(actualObj);
- }
-
- var actualType = typeof actualObj;
- var expectationType = typeof expectationObj;
-
- if (
- actualObj === expectationObj ||
- isNaN(actualObj) ||
- isNaN(expectationObj) ||
- actualObj === null ||
- expectationObj === null ||
- actualObj === undefined ||
- expectationObj === undefined ||
- actualType !== "object" ||
- expectationType !== "object"
- ) {
- return identical(actualObj, expectationObj);
- }
-
- // Elements are only equal if identical(expected, actual)
- if (isElement(actualObj) || isElement(expectationObj)) {
- return false;
- }
-
- var isActualDate = isDate(actualObj);
- var isExpectationDate = isDate(expectationObj);
- if (isActualDate || isExpectationDate) {
- if (
- !isActualDate ||
- !isExpectationDate ||
- getTime.call(actualObj) !== getTime.call(expectationObj)
- ) {
- return false;
- }
- }
-
- if (actualObj instanceof RegExp && expectationObj instanceof RegExp) {
- if (valueToString(actualObj) !== valueToString(expectationObj)) {
- return false;
- }
- }
-
- if (actualObj instanceof Promise && expectationObj instanceof Promise) {
- return actualObj === expectationObj;
- }
-
- if (actualObj instanceof Error && expectationObj instanceof Error) {
- return actualObj === expectationObj;
- }
-
- var actualClass = getClass(actualObj);
- var expectationClass = getClass(expectationObj);
- var actualKeys = keys(actualObj);
- var expectationKeys = keys(expectationObj);
- var actualName = className(actualObj);
- var expectationName = className(expectationObj);
- var expectationSymbols =
- typeOf(getOwnPropertySymbols) === "function"
- ? getOwnPropertySymbols(expectationObj)
- : /* istanbul ignore next: cannot collect coverage for engine that doesn't support Symbol */
- [];
- var expectationKeysAndSymbols = concat(
- expectationKeys,
- expectationSymbols
- );
-
- if (isArguments(actualObj) || isArguments(expectationObj)) {
- if (actualObj.length !== expectationObj.length) {
- return false;
- }
- } else {
- if (
- actualType !== expectationType ||
- actualClass !== expectationClass ||
- actualKeys.length !== expectationKeys.length ||
- (actualName &&
- expectationName &&
- actualName !== expectationName)
- ) {
- return false;
- }
- }
-
- if (isSet(actualObj) || isSet(expectationObj)) {
- if (
- !isSet(actualObj) ||
- !isSet(expectationObj) ||
- actualObj.size !== expectationObj.size
- ) {
- return false;
- }
-
- return isSubset(actualObj, expectationObj, deepEqual);
- }
-
- if (isMap(actualObj) || isMap(expectationObj)) {
- if (
- !isMap(actualObj) ||
- !isMap(expectationObj) ||
- actualObj.size !== expectationObj.size
- ) {
- return false;
- }
-
- var mapsDeeplyEqual = true;
- mapForEach(actualObj, function (value, key) {
- mapsDeeplyEqual =
- mapsDeeplyEqual &&
- deepEqualCyclic(value, expectationObj.get(key));
- });
-
- return mapsDeeplyEqual;
- }
-
- return every(expectationKeysAndSymbols, function (key) {
- if (!hasOwnProperty(actualObj, key)) {
- return false;
- }
-
- 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 actualIndex = actualObject
- ? indexOf(actualObjects, actualValue)
- : -1;
- var expectationIndex = expectationObject
- ? indexOf(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 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]) {
- return true;
- }
-
- // remember the current objects and their paths
- if (actualIndex === -1 && actualObject) {
- push(actualObjects, actualValue);
- push(actualPaths, newActualPath);
- }
- if (expectationIndex === -1 && expectationObject) {
- push(expectationObjects, expectationValue);
- push(expectationPaths, newExpectationPath);
- }
-
- // remember that the current objects are already compared
- if (actualObject && expectationObject) {
- compared[combinedPath] = true;
- }
-
- // End of cyclic logic
-
- // neither actualValue nor expectationValue is a cycle
- // continue with next level
- return deepEqual(
- actualValue,
- expectationValue,
- newActualPath,
- newExpectationPath
- );
- });
- })(actual, expectation, "$1", "$2");
- }
-
- deepEqualCyclic.use = function (match) {
- return function deepEqual(a, b) {
- return deepEqualCyclic(a, b, match);
- };
- };
-
- module.exports = deepEqualCyclic;
|