UNPKG

12.4 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6exports.emptyObject = emptyObject;
7exports.isOneline =
8 exports.isError =
9 exports.pathAsArray =
10 exports.partition =
11 exports.sparseArrayEquality =
12 exports.arrayBufferEquality =
13 exports.typeEquality =
14 exports.subsetEquality =
15 exports.iterableEquality =
16 exports.getObjectSubset =
17 exports.getPath =
18 void 0;
19
20var _jestGetType = require('jest-get-type');
21
22var _jasmineUtils = require('./jasmineUtils');
23
24var global = (function () {
25 if (typeof globalThis !== 'undefined') {
26 return globalThis;
27 } else if (typeof global !== 'undefined') {
28 return global;
29 } else if (typeof self !== 'undefined') {
30 return self;
31 } else if (typeof window !== 'undefined') {
32 return window;
33 } else {
34 return Function('return this')();
35 }
36})();
37
38var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
39
40/**
41 * Checks if `hasOwnProperty(object, key)` up the prototype chain, stopping at `Object.prototype`.
42 */
43const hasPropertyInObject = (object, key) => {
44 const shouldTerminate =
45 !object || typeof object !== 'object' || object === Object.prototype;
46
47 if (shouldTerminate) {
48 return false;
49 }
50
51 return (
52 Object.prototype.hasOwnProperty.call(object, key) ||
53 hasPropertyInObject(Object.getPrototypeOf(object), key)
54 );
55};
56
57const getPath = (object, propertyPath) => {
58 if (!Array.isArray(propertyPath)) {
59 propertyPath = pathAsArray(propertyPath);
60 }
61
62 if (propertyPath.length) {
63 const lastProp = propertyPath.length === 1;
64 const prop = propertyPath[0];
65 const newObject = object[prop];
66
67 if (!lastProp && (newObject === null || newObject === undefined)) {
68 // This is not the last prop in the chain. If we keep recursing it will
69 // hit a `can't access property X of undefined | null`. At this point we
70 // know that the chain has broken and we can return right away.
71 return {
72 hasEndProp: false,
73 lastTraversedObject: object,
74 traversedPath: []
75 };
76 }
77
78 const result = getPath(newObject, propertyPath.slice(1));
79
80 if (result.lastTraversedObject === null) {
81 result.lastTraversedObject = object;
82 }
83
84 result.traversedPath.unshift(prop);
85
86 if (lastProp) {
87 // Does object have the property with an undefined value?
88 // Although primitive values support bracket notation (above)
89 // they would throw TypeError for in operator (below).
90 result.hasEndProp =
91 newObject !== undefined ||
92 (!(0, _jestGetType.isPrimitive)(object) && prop in object);
93
94 if (!result.hasEndProp) {
95 result.traversedPath.shift();
96 }
97 }
98
99 return result;
100 }
101
102 return {
103 lastTraversedObject: null,
104 traversedPath: [],
105 value: object
106 };
107}; // Strip properties from object that are not present in the subset. Useful for
108// printing the diff for toMatchObject() without adding unrelated noise.
109
110/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
111
112exports.getPath = getPath;
113
114const getObjectSubset = (object, subset, seenReferences = new WeakMap()) => {
115 /* eslint-enable @typescript-eslint/explicit-module-boundary-types */
116 if (Array.isArray(object)) {
117 if (Array.isArray(subset) && subset.length === object.length) {
118 // The map method returns correct subclass of subset.
119 return subset.map((sub, i) => getObjectSubset(object[i], sub));
120 }
121 } else if (object instanceof Date) {
122 return object;
123 } else if (isObject(object) && isObject(subset)) {
124 if (
125 (0, _jasmineUtils.equals)(object, subset, [
126 iterableEquality,
127 subsetEquality
128 ])
129 ) {
130 // Avoid unnecessary copy which might return Object instead of subclass.
131 return subset;
132 }
133
134 const trimmed = {};
135 seenReferences.set(object, trimmed);
136 Object.keys(object)
137 .filter(key => hasPropertyInObject(subset, key))
138 .forEach(key => {
139 trimmed[key] = seenReferences.has(object[key])
140 ? seenReferences.get(object[key])
141 : getObjectSubset(object[key], subset[key], seenReferences);
142 });
143
144 if (Object.keys(trimmed).length > 0) {
145 return trimmed;
146 }
147 }
148
149 return object;
150};
151
152exports.getObjectSubset = getObjectSubset;
153const IteratorSymbol = Symbol.iterator;
154
155const hasIterator = object => !!(object != null && object[IteratorSymbol]);
156/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
157
158const iterableEquality = (
159 a,
160 b,
161 /* eslint-enable @typescript-eslint/explicit-module-boundary-types */
162 aStack = [],
163 bStack = []
164) => {
165 if (
166 typeof a !== 'object' ||
167 typeof b !== 'object' ||
168 Array.isArray(a) ||
169 Array.isArray(b) ||
170 !hasIterator(a) ||
171 !hasIterator(b)
172 ) {
173 return undefined;
174 }
175
176 if (a.constructor !== b.constructor) {
177 return false;
178 }
179
180 let length = aStack.length;
181
182 while (length--) {
183 // Linear search. Performance is inversely proportional to the number of
184 // unique nested structures.
185 // circular references at same depth are equal
186 // circular reference is not equal to non-circular one
187 if (aStack[length] === a) {
188 return bStack[length] === b;
189 }
190 }
191
192 aStack.push(a);
193 bStack.push(b);
194
195 const iterableEqualityWithStack = (a, b) =>
196 iterableEquality(a, b, [...aStack], [...bStack]);
197
198 if (a.size !== undefined) {
199 if (a.size !== b.size) {
200 return false;
201 } else if (
202 (0, _jasmineUtils.isA)('Set', a) ||
203 (0, _jasmineUtils.isImmutableUnorderedSet)(a)
204 ) {
205 let allFound = true;
206
207 for (const aValue of a) {
208 if (!b.has(aValue)) {
209 let has = false;
210
211 for (const bValue of b) {
212 const isEqual = (0, _jasmineUtils.equals)(aValue, bValue, [
213 iterableEqualityWithStack
214 ]);
215
216 if (isEqual === true) {
217 has = true;
218 }
219 }
220
221 if (has === false) {
222 allFound = false;
223 break;
224 }
225 }
226 } // Remove the first value from the stack of traversed values.
227
228 aStack.pop();
229 bStack.pop();
230 return allFound;
231 } else if (
232 (0, _jasmineUtils.isA)('Map', a) ||
233 (0, _jasmineUtils.isImmutableUnorderedKeyed)(a)
234 ) {
235 let allFound = true;
236
237 for (const aEntry of a) {
238 if (
239 !b.has(aEntry[0]) ||
240 !(0, _jasmineUtils.equals)(aEntry[1], b.get(aEntry[0]), [
241 iterableEqualityWithStack
242 ])
243 ) {
244 let has = false;
245
246 for (const bEntry of b) {
247 const matchedKey = (0, _jasmineUtils.equals)(aEntry[0], bEntry[0], [
248 iterableEqualityWithStack
249 ]);
250 let matchedValue = false;
251
252 if (matchedKey === true) {
253 matchedValue = (0, _jasmineUtils.equals)(aEntry[1], bEntry[1], [
254 iterableEqualityWithStack
255 ]);
256 }
257
258 if (matchedValue === true) {
259 has = true;
260 }
261 }
262
263 if (has === false) {
264 allFound = false;
265 break;
266 }
267 }
268 } // Remove the first value from the stack of traversed values.
269
270 aStack.pop();
271 bStack.pop();
272 return allFound;
273 }
274 }
275
276 const bIterator = b[IteratorSymbol]();
277
278 for (const aValue of a) {
279 const nextB = bIterator.next();
280
281 if (
282 nextB.done ||
283 !(0, _jasmineUtils.equals)(aValue, nextB.value, [
284 iterableEqualityWithStack
285 ])
286 ) {
287 return false;
288 }
289 }
290
291 if (!bIterator.next().done) {
292 return false;
293 } // Remove the first value from the stack of traversed values.
294
295 aStack.pop();
296 bStack.pop();
297 return true;
298};
299
300exports.iterableEquality = iterableEquality;
301
302const isObject = a => a !== null && typeof a === 'object';
303
304const isObjectWithKeys = a =>
305 isObject(a) &&
306 !(a instanceof Error) &&
307 !(a instanceof Array) &&
308 !(a instanceof Date);
309
310const subsetEquality = (object, subset) => {
311 // subsetEquality needs to keep track of the references
312 // it has already visited to avoid infinite loops in case
313 // there are circular references in the subset passed to it.
314 const subsetEqualityWithContext =
315 (seenReferences = new WeakMap()) =>
316 (object, subset) => {
317 if (!isObjectWithKeys(subset)) {
318 return undefined;
319 }
320
321 return Object.keys(subset).every(key => {
322 if (isObjectWithKeys(subset[key])) {
323 if (seenReferences.has(subset[key])) {
324 return (0, _jasmineUtils.equals)(object[key], subset[key], [
325 iterableEquality
326 ]);
327 }
328
329 seenReferences.set(subset[key], true);
330 }
331
332 const result =
333 object != null &&
334 hasPropertyInObject(object, key) &&
335 (0, _jasmineUtils.equals)(object[key], subset[key], [
336 iterableEquality,
337 subsetEqualityWithContext(seenReferences)
338 ]); // The main goal of using seenReference is to avoid circular node on tree.
339 // It will only happen within a parent and its child, not a node and nodes next to it (same level)
340 // We should keep the reference for a parent and its child only
341 // Thus we should delete the reference immediately so that it doesn't interfere
342 // other nodes within the same level on tree.
343
344 seenReferences.delete(subset[key]);
345 return result;
346 });
347 };
348
349 return subsetEqualityWithContext()(object, subset);
350}; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
351
352exports.subsetEquality = subsetEquality;
353
354const typeEquality = (a, b) => {
355 if (a == null || b == null || a.constructor === b.constructor) {
356 return undefined;
357 }
358
359 return false;
360};
361
362exports.typeEquality = typeEquality;
363
364const arrayBufferEquality = (a, b) => {
365 if (!(a instanceof ArrayBuffer) || !(b instanceof ArrayBuffer)) {
366 return undefined;
367 }
368
369 const dataViewA = new DataView(a);
370 const dataViewB = new DataView(b); // Buffers are not equal when they do not have the same byte length
371
372 if (dataViewA.byteLength !== dataViewB.byteLength) {
373 return false;
374 } // Check if every byte value is equal to each other
375
376 for (let i = 0; i < dataViewA.byteLength; i++) {
377 if (dataViewA.getUint8(i) !== dataViewB.getUint8(i)) {
378 return false;
379 }
380 }
381
382 return true;
383};
384
385exports.arrayBufferEquality = arrayBufferEquality;
386
387const sparseArrayEquality = (a, b) => {
388 if (!Array.isArray(a) || !Array.isArray(b)) {
389 return undefined;
390 } // A sparse array [, , 1] will have keys ["2"] whereas [undefined, undefined, 1] will have keys ["0", "1", "2"]
391
392 const aKeys = Object.keys(a);
393 const bKeys = Object.keys(b);
394 return (
395 (0, _jasmineUtils.equals)(a, b, [iterableEquality, typeEquality], true) &&
396 (0, _jasmineUtils.equals)(aKeys, bKeys)
397 );
398};
399
400exports.sparseArrayEquality = sparseArrayEquality;
401
402const partition = (items, predicate) => {
403 const result = [[], []];
404 items.forEach(item => result[predicate(item) ? 0 : 1].push(item));
405 return result;
406};
407
408exports.partition = partition;
409
410const pathAsArray = propertyPath => {
411 // will match everything that's not a dot or a bracket, and "" for consecutive dots.
412 const pattern = RegExp('[^.[\\]]+|(?=(?:\\.)(?:\\.|$))', 'g');
413 const properties = []; // Because the regex won't match a dot in the beginning of the path, if present.
414
415 if (propertyPath[0] === '.') {
416 properties.push('');
417 }
418
419 propertyPath.replace(pattern, match => {
420 properties.push(match);
421 return match;
422 });
423 return properties;
424}; // Copied from https://github.com/graingert/angular.js/blob/a43574052e9775cbc1d7dd8a086752c979b0f020/src/Angular.js#L685-L693
425
426exports.pathAsArray = pathAsArray;
427
428const isError = value => {
429 switch (Object.prototype.toString.call(value)) {
430 case '[object Error]':
431 case '[object Exception]':
432 case '[object DOMException]':
433 return true;
434
435 default:
436 return value instanceof Error;
437 }
438};
439
440exports.isError = isError;
441
442function emptyObject(obj) {
443 return obj && typeof obj === 'object' ? !Object.keys(obj).length : false;
444}
445
446const MULTILINE_REGEXP = /[\r\n]/;
447
448const isOneline = (expected, received) =>
449 typeof expected === 'string' &&
450 typeof received === 'string' &&
451 (!MULTILINE_REGEXP.test(expected) || !MULTILINE_REGEXP.test(received));
452
453exports.isOneline = isOneline;