blob: 1fb17b891e1b6d6f7fea806e42cc49c6f9138b0e [file] [log] [blame]
Paul Lewis40e28672020-11-27 09:51:371"use strict";
2
3var valueToString = require("@sinonjs/commons").valueToString;
4var indexOf = require("@sinonjs/commons").prototypes.string.indexOf;
5var forEach = require("@sinonjs/commons").prototypes.array.forEach;
6var type = require("type-detect");
7
8var engineCanCompareMaps = typeof Array.from === "function";
9var deepEqual = require("./deep-equal").use(match); // eslint-disable-line no-use-before-define
10var isArrayType = require("./is-array-type");
11var isSubset = require("./is-subset");
12var createMatcher = require("./create-matcher");
13
14/**
15 * Returns true when `array` contains all of `subset` as defined by the `compare`
16 * argument
17 *
18 * @param {Array} array An array to search for a subset
19 * @param {Array} subset The subset to find in the array
20 * @param {Function} compare A comparison function
21 * @returns {boolean} [description]
22 * @private
23 */
24function arrayContains(array, subset, compare) {
25 if (subset.length === 0) {
26 return true;
27 }
28 var i, l, j, k;
29 for (i = 0, l = array.length; i < l; ++i) {
30 if (compare(array[i], subset[0])) {
31 for (j = 0, k = subset.length; j < k; ++j) {
32 if (i + j >= l) {
33 return false;
34 }
35 if (!compare(array[i + j], subset[j])) {
36 return false;
37 }
38 }
39 return true;
40 }
41 }
42 return false;
43}
44
45/* eslint-disable complexity */
46/**
47 * Matches an object with a matcher (or value)
48 *
49 * @alias module:samsam.match
50 * @param {object} object The object candidate to match
51 * @param {object} matcherOrValue A matcher or value to match against
52 * @returns {boolean} true when `object` matches `matcherOrValue`
53 */
54function match(object, matcherOrValue) {
55 if (matcherOrValue && typeof matcherOrValue.test === "function") {
56 return matcherOrValue.test(object);
57 }
58
59 switch (type(matcherOrValue)) {
60 case "bigint":
61 case "boolean":
62 case "number":
63 case "symbol":
64 return matcherOrValue === object;
65 case "function":
66 return matcherOrValue(object) === true;
67 case "string":
68 var notNull = typeof object === "string" || Boolean(object);
69 return (
70 notNull &&
71 indexOf(
72 valueToString(object).toLowerCase(),
73 matcherOrValue.toLowerCase()
74 ) >= 0
75 );
76 case "null":
77 return object === null;
78 case "undefined":
79 return typeof object === "undefined";
80 case "Date":
81 /* istanbul ignore else */
82 if (type(object) === "Date") {
83 return object.getTime() === matcherOrValue.getTime();
84 }
85 /* istanbul ignore next: this is basically the rest of the function, which is covered */
86 break;
87 case "Array":
88 case "Int8Array":
89 case "Uint8Array":
90 case "Uint8ClampedArray":
91 case "Int16Array":
92 case "Uint16Array":
93 case "Int32Array":
94 case "Uint32Array":
95 case "Float32Array":
96 case "Float64Array":
97 return (
98 isArrayType(matcherOrValue) &&
99 arrayContains(object, matcherOrValue, match)
100 );
101 case "Map":
102 /* istanbul ignore next: this is covered by a test, that is only run in IE, but we collect coverage information in node*/
103 if (!engineCanCompareMaps) {
104 throw new Error(
105 "The JavaScript engine does not support Array.from and cannot reliably do value comparison of Map instances"
106 );
107 }
108
109 return (
110 type(object) === "Map" &&
111 arrayContains(
112 Array.from(object),
113 Array.from(matcherOrValue),
114 match
115 )
116 );
117 default:
118 break;
119 }
120
121 switch (type(object)) {
122 case "null":
123 return false;
124 case "Set":
125 return isSubset(matcherOrValue, object, match);
126 default:
127 break;
128 }
129
130 /* istanbul ignore else */
131 if (matcherOrValue && typeof matcherOrValue === "object") {
132 if (matcherOrValue === object) {
133 return true;
134 }
135 if (typeof object !== "object") {
136 return false;
137 }
138 var prop;
139 // eslint-disable-next-line guard-for-in
140 for (prop in matcherOrValue) {
141 var value = object[prop];
142 if (
143 typeof value === "undefined" &&
144 typeof object.getAttribute === "function"
145 ) {
146 value = object.getAttribute(prop);
147 }
148 if (
149 matcherOrValue[prop] === null ||
150 typeof matcherOrValue[prop] === "undefined"
151 ) {
152 if (value !== matcherOrValue[prop]) {
153 return false;
154 }
155 } else if (
156 typeof value === "undefined" ||
157 !deepEqual(value, matcherOrValue[prop])
158 ) {
159 return false;
160 }
161 }
162 return true;
163 }
164
165 /* istanbul ignore next */
166 throw new Error("Matcher was an unknown or unsupported type");
167}
168/* eslint-enable complexity */
169
Tim van der Lippe61fe6852021-09-13 12:21:16170forEach(Object.keys(createMatcher), function (key) {
Paul Lewis40e28672020-11-27 09:51:37171 match[key] = createMatcher[key];
172});
173
174module.exports = match;