"use strict"; var valueToString = require("@sinonjs/commons").valueToString; var indexOf = require("@sinonjs/commons").prototypes.string.indexOf; var forEach = require("@sinonjs/commons").prototypes.array.forEach; var type = require("type-detect"); var engineCanCompareMaps = typeof Array.from === "function"; var deepEqual = require("./deep-equal").use(match); // eslint-disable-line no-use-before-define var isArrayType = require("./is-array-type"); var isSubset = require("./is-subset"); var createMatcher = require("./create-matcher"); /** * Returns true when `array` contains all of `subset` as defined by the `compare` * argument * * @param {Array} array An array to search for a subset * @param {Array} subset The subset to find in the array * @param {Function} compare A comparison function * @returns {boolean} [description] * @private */ function arrayContains(array, subset, compare) { if (subset.length === 0) { return true; } var i, l, j, k; for (i = 0, l = array.length; i < l; ++i) { if (compare(array[i], subset[0])) { for (j = 0, k = subset.length; j < k; ++j) { if (i + j >= l) { return false; } if (!compare(array[i + j], subset[j])) { return false; } } return true; } } return false; } /* eslint-disable complexity */ /** * Matches an object with a matcher (or value) * * @alias module:samsam.match * @param {object} object The object candidate to match * @param {object} matcherOrValue A matcher or value to match against * @returns {boolean} true when `object` matches `matcherOrValue` */ function match(object, matcherOrValue) { if (matcherOrValue && typeof matcherOrValue.test === "function") { return matcherOrValue.test(object); } switch (type(matcherOrValue)) { case "bigint": case "boolean": case "number": case "symbol": return matcherOrValue === object; case "function": return matcherOrValue(object) === true; case "string": var notNull = typeof object === "string" || Boolean(object); return ( notNull && indexOf( valueToString(object).toLowerCase(), matcherOrValue.toLowerCase() ) >= 0 ); case "null": return object === null; case "undefined": return typeof object === "undefined"; case "Date": /* istanbul ignore else */ if (type(object) === "Date") { return object.getTime() === matcherOrValue.getTime(); } /* istanbul ignore next: this is basically the rest of the function, which is covered */ break; case "Array": case "Int8Array": case "Uint8Array": case "Uint8ClampedArray": case "Int16Array": case "Uint16Array": case "Int32Array": case "Uint32Array": case "Float32Array": case "Float64Array": return ( isArrayType(matcherOrValue) && arrayContains(object, matcherOrValue, match) ); case "Map": /* istanbul ignore next: this is covered by a test, that is only run in IE, but we collect coverage information in node*/ if (!engineCanCompareMaps) { throw new Error( "The JavaScript engine does not support Array.from and cannot reliably do value comparison of Map instances" ); } return ( type(object) === "Map" && arrayContains( Array.from(object), Array.from(matcherOrValue), match ) ); default: break; } switch (type(object)) { case "null": return false; case "Set": return isSubset(matcherOrValue, object, match); default: break; } /* istanbul ignore else */ if (matcherOrValue && typeof matcherOrValue === "object") { if (matcherOrValue === object) { return true; } if (typeof object !== "object") { return false; } var prop; // eslint-disable-next-line guard-for-in for (prop in matcherOrValue) { var value = object[prop]; if ( typeof value === "undefined" && typeof object.getAttribute === "function" ) { value = object.getAttribute(prop); } if ( matcherOrValue[prop] === null || typeof matcherOrValue[prop] === "undefined" ) { if (value !== matcherOrValue[prop]) { return false; } } else if ( typeof value === "undefined" || !deepEqual(value, matcherOrValue[prop]) ) { return false; } } return true; } /* istanbul ignore next */ throw new Error("Matcher was an unknown or unsupported type"); } /* eslint-enable complexity */ forEach(Object.keys(createMatcher), function(key) { match[key] = createMatcher[key]; }); module.exports = match;