@dungarees/core 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/util.js CHANGED
@@ -36,107 +36,89 @@ export const toCamelCase = (segments) => {
36
36
  };
37
37
  export const kebabCase2camelCase = (kebabCase) => toCamelCase(fromKebabCase(kebabCase));
38
38
  export const camelCase2kebabCase = (camelCase) => toKebabCase(fromCamelCase(camelCase));
39
+ // `null` has to be excluded explicitly: `typeof null` is `'object'`, so without this a null value
40
+ // reaches the key walks below and throws instead of simply not matching.
41
+ const isKeyedObject = (input) => typeof input === 'object' && input !== null;
42
+ const isDataViewEqual = (a, b) => {
43
+ if (a.byteLength !== b.byteLength) {
44
+ return false;
45
+ }
46
+ for (let index = a.byteLength; index-- !== 0;) {
47
+ if (a.getUint8(index) !== b.getUint8(index)) {
48
+ return false;
49
+ }
50
+ }
51
+ return true;
52
+ };
53
+ // `valueOf` and `toString` exist on every object, so the only useful question is whether this one
54
+ // replaced them with something that describes its value.
55
+ const hasOwnValueOf = (value) => value.valueOf !== Object.prototype.valueOf && typeof value.valueOf === 'function';
56
+ // Called through a parameter whose own `toString` is declared, so the call is not read as a
57
+ // stringification of a plain object.
58
+ const toOwnString = (value) => value.toString();
59
+ const hasOwnToString = (value) => value.toString !== Object.prototype.toString && typeof value.toString === 'function';
39
60
  export const isDeepEqual = (a, b) => {
40
61
  // in order to support circular references we have to keep track of visited objects.
41
62
  // for that reason we have to create new function for each invocation.
42
63
  const visited = new WeakMap();
43
- // eslint-disable-next-line complexity
44
64
  const inner = (a, b) => {
45
65
  // in case strict equality - there is nothing to check anymore.
46
66
  if (a === b) {
47
67
  return true;
48
68
  }
49
- // in case any of values is not an object, there is nothing to do, except to check strict equality.
50
- if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {
69
+ // in case any of values is not an object, there is nothing to do, except to check strict
70
+ // equality.
71
+ if (!isKeyedObject(a) || !isKeyedObject(b)) {
51
72
  // looks weird, but it is most efficient way to test NaN.
52
- // otherwise we have to involve Number.isNaN, which causes context switch and therefore is slower.
53
- // eslint-disable-next-line no-self-compare
73
+ // otherwise we have to involve Number.isNaN, which causes context switch and therefore is
74
+ // slower.
54
75
  return a !== a && b !== b;
55
76
  }
56
77
  // if constructors are different, objects are definitely not equal.
57
78
  if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
58
79
  return false;
59
80
  }
60
- const { constructor } = a;
61
- if (constructor === Date) {
81
+ if (a instanceof Date && b instanceof Date) {
62
82
  return a.getTime() === b.getTime();
63
83
  }
64
- if (constructor === RegExp) {
84
+ if (a instanceof RegExp && b instanceof RegExp) {
65
85
  return a.source === b.source && a.flags === b.flags;
66
86
  }
67
- if (constructor === Set) {
68
- if (a.size !== b.size) {
69
- return false;
70
- }
71
- for (const value of a) {
72
- if (!b.has(value)) {
73
- return false;
74
- }
75
- }
76
- return true;
87
+ if (a instanceof Set && b instanceof Set) {
88
+ return a.size === b.size && [...a].every((value) => b.has(value));
77
89
  }
78
- if (constructor === ArrayBuffer) {
79
- a = new DataView(a);
80
- b = new DataView(b);
81
- }
82
- if (constructor === DataView || ArrayBuffer.isView(a)) {
83
- // this is a TypedArray.
84
- if (constructor !== DataView) {
85
- a = new DataView(a.buffer);
86
- b = new DataView(b.buffer);
87
- }
88
- if (a.byteLength !== b.byteLength)
89
- return false;
90
- for (let i = a.byteLength; i-- !== 0;) {
91
- if (a.getUint8(i) !== b.getUint8(i)) {
92
- return false;
93
- }
94
- }
95
- return true;
90
+ if (a instanceof ArrayBuffer && b instanceof ArrayBuffer) {
91
+ return isDataViewEqual(new DataView(a), new DataView(b));
92
+ }
93
+ if (a instanceof DataView && b instanceof DataView) {
94
+ return isDataViewEqual(a, b);
95
+ }
96
+ // this is a TypedArray.
97
+ if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
98
+ return isDataViewEqual(new DataView(a.buffer), new DataView(b.buffer));
96
99
  }
97
100
  // Check circular references
98
- if (visited.has(a) && visited.get(a) === b) {
101
+ if (visited.get(a) === b) {
99
102
  return true;
100
103
  }
101
104
  visited.set(a, b);
102
- if (constructor === Array) {
103
- if (a.length !== b.length) {
104
- return false;
105
- }
106
- for (let i = a.length; i-- !== 0;) {
107
- if (!inner(a[i], b[i])) {
108
- return false;
109
- }
110
- }
111
- return true;
105
+ if (Array.isArray(a) && Array.isArray(b)) {
106
+ return a.length === b.length && a.every((item, index) => inner(item, b[index]));
112
107
  }
113
- if (constructor === Map) {
114
- if (a.size !== b.size) {
115
- return false;
116
- }
117
- for (const entry of a) {
118
- if (!b.has(entry[0]) || !inner(entry[1], b.get(entry[0]))) {
119
- return false;
120
- }
121
- }
122
- return true;
108
+ if (a instanceof Map && b instanceof Map) {
109
+ return (a.size === b.size && [...a].every(([key, value]) => b.has(key) && inner(value, b.get(key))));
123
110
  }
124
- // at this point, we've handled all possible data containers and we can compare objects as plain.
125
- if (a.valueOf !== Object.prototype.valueOf && typeof a.valueOf === 'function' && typeof b.valueOf === 'function') {
111
+ // at this point, we've handled all possible data containers and we can compare objects as
112
+ // plain.
113
+ if (hasOwnValueOf(a) && hasOwnValueOf(b)) {
126
114
  return a.valueOf() === b.valueOf();
127
115
  }
128
- if (a.toString !== Object.prototype.toString && typeof a.toString === 'function' && typeof b.toString === 'function') {
129
- return a.toString() === b.toString();
116
+ if (hasOwnToString(a) && hasOwnToString(b)) {
117
+ return toOwnString(a) === toOwnString(b);
130
118
  }
131
119
  const aKeys = Object.keys(a);
132
- let key;
133
- for (let l = aKeys.length; l-- !== 0;) {
134
- key = aKeys[l];
135
- if (!Object.hasOwn(b, key) || !inner(a[key], b[key])) {
136
- return false;
137
- }
138
- }
139
- return Object.keys(b).length === aKeys.length;
120
+ return (aKeys.length === Object.keys(b).length &&
121
+ aKeys.every((key) => Object.hasOwn(b, key) && inner(a[key], b[key])));
140
122
  };
141
123
  return inner(a, b);
142
124
  };
@@ -144,19 +126,17 @@ export const deepEqualPartial = (actual, expected) => {
144
126
  if (actual === undefined || actual === null) {
145
127
  return true;
146
128
  }
147
- const isObject = (input) => typeof input === 'object';
148
- if (!isObject(expected)) {
129
+ if (!isKeyedObject(expected)) {
149
130
  return expected === actual;
150
131
  }
151
- if (!isObject(actual)) {
132
+ if (!isKeyedObject(actual)) {
152
133
  return false;
153
134
  }
154
135
  return Object.keys(actual).every((key) => {
155
- const val = actual[key];
156
- if (val instanceof Object) {
157
- return deepEqualPartial(expected[key], val);
158
- }
159
- return actual[key] === expected[key];
136
+ const value = actual[key];
137
+ return value instanceof Object
138
+ ? deepEqualPartial(expected[key], value)
139
+ : value === expected[key];
160
140
  });
161
141
  };
162
142
  export const findByPattern = (patterns, itemToMatch) => patterns
@@ -210,7 +190,7 @@ export const assertTypeByGuard = ({ value, guard, message, }) => {
210
190
  }
211
191
  return value;
212
192
  };
213
- export const assertPredicate = ({ value, predicate, message, }) => {
193
+ export const assertPredicate = ({ value, predicate, message }) => {
214
194
  if (!predicate(value)) {
215
195
  throw new Error(getErrorMessage(message, value));
216
196
  }
@@ -222,15 +202,55 @@ export const assertImpossible = (message) => {
222
202
  const getErrorMessage = (message, value) => typeof message === 'function' ? message(value) : message;
223
203
  export const unPrototypeProperties = (obj, keys) => {
224
204
  const propertyEntries = keys.map((key) => {
225
- const value = isFunction(obj[key]) ? (...args) => obj[key](...args) : obj[key];
226
- return [key, value];
205
+ const property = obj[key];
206
+ return [key, isFunction(property) ? property.bind(obj) : property];
227
207
  });
228
208
  return Object.fromEntries(propertyEntries);
229
209
  };
230
210
  const isFunction = (value) => value instanceof Function;
231
- export const mapConst = (array) => (transformer) => {
232
- return array.map((item) => transformer(item));
211
+ export const boolFromThrow = (fn) => {
212
+ try {
213
+ fn();
214
+ return true;
215
+ }
216
+ catch {
217
+ return false;
218
+ }
219
+ };
220
+ export const boolFromThrowAsync = async (fn) => {
221
+ try {
222
+ await fn();
223
+ return true;
224
+ }
225
+ catch {
226
+ return false;
227
+ }
233
228
  };
229
+ export function mapConst(array, transformer) {
230
+ return transformer === undefined
231
+ ? (t) => {
232
+ return array.map((item, index) => t(item, index));
233
+ }
234
+ : array.map((item, index) => transformer(item, index));
235
+ }
236
+ export function mapConstKeysToEntries(array, transformer) {
237
+ return transformer === undefined
238
+ ? (t) => {
239
+ return array.map((item, index) => [
240
+ item,
241
+ t(item, index),
242
+ ]);
243
+ }
244
+ : array.map((item, index) => [
245
+ item,
246
+ transformer(item, index),
247
+ ]);
248
+ }
234
249
  export const objectFromConstEntries = (entries) => {
235
250
  return Object.fromEntries(entries);
236
251
  };
252
+ export function mapObjectFromKeys(keys, transformer) {
253
+ return transformer === undefined
254
+ ? (t) => objectFromConstEntries(mapConstKeysToEntries(keys)(t))
255
+ : objectFromConstEntries(mapConstKeysToEntries(keys, transformer));
256
+ }
package/util.test.js CHANGED
@@ -1,23 +1,22 @@
1
- import { assertDefined, assertPredicate, assertTypeByGuard, assertImpossible, camelCase2kebabCase, capitalize, deepEqualPartial, findByPattern, isDefined, join, kebabCase2camelCase, makeObjectFromStringLiteral, mapConst, optionalPatternToList, pluralize, split, unPrototypeProperties, objectFromConstEntries, } from './util.js';
2
- import { assert } from 'tsafe';
3
- import { expect, test } from 'vitest';
1
+ import { assertDefined, assertImpossible, assertPredicate, assertTypeByGuard, boolFromThrow, boolFromThrowAsync, camelCase2kebabCase, capitalize, deepEqualPartial, findByPattern, isDeepEqual, isDefined, join, kebabCase2camelCase, makeObjectFromStringLiteral, mapConst, mapConstKeysToEntries, mapObjectFromKeys, objectFromConstEntries, optionalPatternToList, pluralize, split, unPrototypeProperties, } from './util.js';
2
+ import { expect, expectTypeOf, test } from 'vitest';
4
3
  test('makeObjectFromStringLiteral', () => {
5
4
  const obj = makeObjectFromStringLiteral('key', 1);
6
5
  expect(obj['key']).toBe(1);
7
6
  });
8
7
  test('split is working in a typesafe way', () => {
9
8
  const splitted = split('a.a.a', '.');
10
- assert();
9
+ expectTypeOf().toEqualTypeOf();
11
10
  expect(splitted).toEqual(['a', 'a', 'a']);
12
11
  });
13
12
  test('join is working in a typesafe way', () => {
14
13
  const joined = join(['a', 'a', 'a'], '.');
15
- assert();
14
+ expectTypeOf().toEqualTypeOf();
16
15
  expect(joined).toBe('a.a.a');
17
16
  });
18
17
  test('capitalize', () => {
19
18
  const capitalized = capitalize('apple');
20
- assert();
19
+ expectTypeOf().toEqualTypeOf();
21
20
  expect(capitalized).toBe('Apple');
22
21
  });
23
22
  test('pluralize', () => {
@@ -34,14 +33,14 @@ test('kebabCase2camelCase', () => {
34
33
  const camelCase = 'firstSecondThird';
35
34
  const output = kebabCase2camelCase(kebabCase);
36
35
  expect(output).toBe(camelCase);
37
- assert();
36
+ expectTypeOf().toEqualTypeOf();
38
37
  });
39
38
  test('camelCase2kebabCase', () => {
40
39
  const kebabCase = 'first-second-third';
41
40
  const camelCase = 'firstSecondThird';
42
41
  const output = camelCase2kebabCase(camelCase);
43
42
  expect(output).toBe(kebabCase);
44
- assert();
43
+ expectTypeOf().toEqualTypeOf();
45
44
  });
46
45
  test('deepEqualPartial', () => {
47
46
  expect(deepEqualPartial({}, {})).toBe(true);
@@ -52,18 +51,22 @@ test('deepEqualPartial', () => {
52
51
  expect(deepEqualPartial({ foo: { baz: 'bar' } }, { foo: { baz: 'bar' }, quux: 'kill me' })).toBe(true);
53
52
  expect(deepEqualPartial({ foo: { baz: 'baz' } }, { foo: { baz: 'bar' }, quux: 'kill me' })).toBe(false);
54
53
  });
54
+ test('deepEqualPartial treats a null expected as unequal rather than throwing', () => {
55
+ expect(deepEqualPartial({ baz: 54 }, null)).toBe(false);
56
+ expect(deepEqualPartial({ baz: 54 }, undefined)).toBe(false);
57
+ expect(deepEqualPartial(null, null)).toBe(true);
58
+ });
55
59
  test('findByPattern', () => {
56
- // eslint-disable-next-line
57
60
  const value1 = findByPattern([], '');
58
- assert();
61
+ expectTypeOf().toEqualTypeOf();
59
62
  expect(value1).toBeUndefined();
60
63
  // @ts-expect-error itemToMatch has to follow the pattern type
61
64
  findByPattern([], {});
62
65
  // @ts-expect-error itemToMatch type has to be a serializable type
63
66
  const _ = (findByPattern);
64
- assert(_);
67
+ expect(_).toBeDefined();
65
68
  const value2 = findByPattern([{ pattern: 'ab', value: 1 }], 'ab');
66
- assert();
69
+ expectTypeOf().toEqualTypeOf();
67
70
  expect(value2).toBe(1);
68
71
  const value3 = findByPattern([{ pattern: 'ab', value: 1 }], 'no-match');
69
72
  expect(value3).toBe(undefined);
@@ -103,12 +106,12 @@ test('findByPattern', () => {
103
106
  });
104
107
  test('optionalPatternToList', () => {
105
108
  const list1 = optionalPatternToList(1);
106
- assert();
109
+ expectTypeOf().toEqualTypeOf();
107
110
  expect(list1).toEqual([{ value: 1 }]);
108
111
  const list2 = optionalPatternToList([{ value: 1 }]);
109
- assert();
112
+ expectTypeOf().toEqualTypeOf();
110
113
  expect(list2).toEqual([{ value: 1 }]);
111
- assert();
114
+ expectTypeOf().toEqualTypeOf();
112
115
  });
113
116
  test('isDefined', () => {
114
117
  expect(isDefined(0)).toStrictEqual(true);
@@ -164,7 +167,7 @@ test('unPrototypeProperties', () => {
164
167
  }
165
168
  const instance = new Test(1);
166
169
  const noPrototype = unPrototypeProperties(instance, ['method']);
167
- assert();
170
+ expectTypeOf().toEqualTypeOf();
168
171
  expect(noPrototype.method()).toBe(1);
169
172
  const method = noPrototype.method;
170
173
  expect(method()).toBe(1);
@@ -172,23 +175,87 @@ test('unPrototypeProperties', () => {
172
175
  expect(Object.hasOwn(noPrototype, 'method2')).toBe(false);
173
176
  const clone = { ...noPrototype };
174
177
  expect(clone.method()).toBe(1);
175
- // @ts-expect-error method2 is not a property of clone
176
- expect(() => clone.method2()).toThrow('method2 is not a function');
178
+ expect(Object.hasOwn(clone, 'method2')).toBe(false);
177
179
  // @ts-expect-error method3 is not a property of clone
178
180
  unPrototypeProperties(clone, ['method3']);
179
181
  });
180
182
  test('mapConst', () => {
181
183
  const input = ['a', 'b', 'c'];
182
184
  const output = mapConst(input)((value) => `${value}-const`);
183
- assert();
185
+ expectTypeOf().toEqualTypeOf();
184
186
  expect(output).toEqual(['a-const', 'b-const', 'c-const']);
185
187
  });
188
+ test('mapConst expets a lambda with a correct return type', () => {
189
+ const input = ['a', 'b', 'c'];
190
+ // @ts-expect-error The return type of the lambda should depend on the input value
191
+ mapConst(input)(() => 1);
192
+ });
186
193
  test('mapConst infers input type from array', () => {
187
194
  const input = ['a', 'b', 'c'];
188
- mapConst(input)((value) => {
189
- assert();
195
+ const output = [];
196
+ mapConst(input)((value, index) => {
197
+ expectTypeOf().toEqualTypeOf();
198
+ expectTypeOf(index).toEqualTypeOf();
199
+ output.push(index);
200
+ return `${value}-const`;
201
+ });
202
+ expect(output).toEqual([0, 1, 2]);
203
+ });
204
+ test('mapConst non-curried infers input type from array', () => {
205
+ const input = ['a', 'b', 'c'];
206
+ const output = [];
207
+ const result = mapConst(input, (value, index) => {
208
+ expectTypeOf().toEqualTypeOf();
209
+ expectTypeOf(index).toEqualTypeOf();
210
+ output.push(index);
211
+ return 1;
212
+ });
213
+ expectTypeOf().toEqualTypeOf();
214
+ expect(result).toEqual([1, 1, 1]);
215
+ expect(output).toEqual([0, 1, 2]);
216
+ });
217
+ test('mapConstKeysToEntries infers input type from array', () => {
218
+ const input = ['a', 'b', 'c'];
219
+ const output = mapConstKeysToEntries(input)((value) => `${value}-const`);
220
+ expectTypeOf().toEqualTypeOf();
221
+ expect(output).toEqual([
222
+ ['a', 'a-const'],
223
+ ['b', 'b-const'],
224
+ ['c', 'c-const'],
225
+ ]);
226
+ });
227
+ test('mapConstKeysToEntries expets a lambda with a correct return type', () => {
228
+ const input = ['a', 'b', 'c'];
229
+ // @ts-expect-error The return type of the lambda should depend on the input value
230
+ mapConstKeysToEntries(input)(() => 1);
231
+ });
232
+ test('mapConstKeysToEntries infers input type from array', () => {
233
+ const input = ['a', 'b', 'c'];
234
+ const output = [];
235
+ const a = mapConstKeysToEntries(input, (value, index) => {
236
+ expectTypeOf().toEqualTypeOf();
237
+ expectTypeOf(index).toEqualTypeOf();
238
+ output.push(index);
239
+ return 1;
240
+ });
241
+ expectTypeOf().toEqualTypeOf();
242
+ expect(a).toEqual([
243
+ ['a', 1],
244
+ ['b', 1],
245
+ ['c', 1],
246
+ ]);
247
+ expect(output).toEqual([0, 1, 2]);
248
+ });
249
+ test('mapConstKeysToEntries infers input type from array', () => {
250
+ const input = ['a', 'b', 'c'];
251
+ const output = [];
252
+ mapConstKeysToEntries(input)((value, index) => {
253
+ expectTypeOf().toEqualTypeOf();
254
+ expectTypeOf(index).toEqualTypeOf();
255
+ output.push(index);
190
256
  return `${value}-const`;
191
257
  });
258
+ expect(output).toEqual([0, 1, 2]);
192
259
  });
193
260
  test('objectFromConstEntries', () => {
194
261
  const entries = [
@@ -197,6 +264,156 @@ test('objectFromConstEntries', () => {
197
264
  ['c', 3],
198
265
  ];
199
266
  const obj = objectFromConstEntries(entries);
200
- assert();
267
+ expectTypeOf().toEqualTypeOf();
201
268
  expect(obj).toEqual({ a: 1, b: 2, c: 3 });
202
269
  });
270
+ test('mapObjectFromKeys', () => {
271
+ const keys = ['a', 'b', 'c'];
272
+ const obj = mapObjectFromKeys(keys)((key, index) => {
273
+ expectTypeOf().toEqualTypeOf();
274
+ expectTypeOf(index).toEqualTypeOf();
275
+ return `${key}-const`;
276
+ });
277
+ expectTypeOf().toEqualTypeOf();
278
+ expect(obj).toEqual({ a: 'a-const', b: 'b-const', c: 'c-const' });
279
+ });
280
+ test('mapObjectFromKeys non-curried infers input type from array', () => {
281
+ const keys = ['a', 'b', 'c'];
282
+ const obj = mapObjectFromKeys(keys, (key, index) => {
283
+ expectTypeOf().toEqualTypeOf();
284
+ expectTypeOf(index).toEqualTypeOf();
285
+ return 1;
286
+ });
287
+ expectTypeOf().toEqualTypeOf();
288
+ expect(obj).toEqual({ a: 1, b: 1, c: 1 });
289
+ });
290
+ test('boolFromThrow returns true when function does not throw', () => {
291
+ expect(boolFromThrow(() => { })).toBe(true);
292
+ });
293
+ test('boolFromThrow returns false when function throws', () => {
294
+ expect(boolFromThrow(() => {
295
+ throw new Error('fail');
296
+ })).toBe(false);
297
+ });
298
+ test('boolFromThrowAsync returns true when async function does not throw', async () => {
299
+ await expect(boolFromThrowAsync(async () => { })).resolves.toBe(true);
300
+ });
301
+ test('boolFromThrowAsync returns false when async function rejects', async () => {
302
+ await expect(boolFromThrowAsync(async () => {
303
+ throw new Error('fail');
304
+ })).resolves.toBe(false);
305
+ });
306
+ test('isDeepEqual compares primitives by strict equality, with NaN equal to itself', () => {
307
+ expect(isDeepEqual(1, 1)).toBe(true);
308
+ expect(isDeepEqual(1, 2)).toBe(false);
309
+ expect(isDeepEqual('a', 'a')).toBe(true);
310
+ expect(isDeepEqual(1, '1')).toBe(false);
311
+ expect(isDeepEqual(NaN, NaN)).toBe(true);
312
+ expect(isDeepEqual(undefined, undefined)).toBe(true);
313
+ expect(isDeepEqual(null, null)).toBe(true);
314
+ expect(isDeepEqual(null, {})).toBe(false);
315
+ expect(isDeepEqual({}, null)).toBe(false);
316
+ });
317
+ test('isDeepEqual compares plain objects by their own keys', () => {
318
+ expect(isDeepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true);
319
+ expect(isDeepEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false);
320
+ expect(isDeepEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false);
321
+ expect(isDeepEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } })).toBe(true);
322
+ expect(isDeepEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 2 } } })).toBe(false);
323
+ });
324
+ test('isDeepEqual compares arrays element-wise', () => {
325
+ expect(isDeepEqual([1, 2, 3], [1, 2, 3])).toBe(true);
326
+ expect(isDeepEqual([1, 2], [1, 2, 3])).toBe(false);
327
+ expect(isDeepEqual([{ a: 1 }], [{ a: 1 }])).toBe(true);
328
+ expect(isDeepEqual([{ a: 1 }], [{ a: 2 }])).toBe(false);
329
+ expect(isDeepEqual([], [])).toBe(true);
330
+ });
331
+ test('isDeepEqual treats a different prototype as unequal', () => {
332
+ class A {
333
+ constructor(x) {
334
+ this.x = x;
335
+ }
336
+ }
337
+ class B {
338
+ constructor(x) {
339
+ this.x = x;
340
+ }
341
+ }
342
+ expect(isDeepEqual(new A(1), new B(1))).toBe(false);
343
+ expect(isDeepEqual([1], { 0: 1 })).toBe(false);
344
+ });
345
+ test('isDeepEqual compares Dates by time', () => {
346
+ expect(isDeepEqual(new Date(1000), new Date(1000))).toBe(true);
347
+ expect(isDeepEqual(new Date(1000), new Date(2000))).toBe(false);
348
+ });
349
+ test('isDeepEqual compares RegExps by source and flags', () => {
350
+ expect(isDeepEqual(/ab+/g, /ab+/g)).toBe(true);
351
+ expect(isDeepEqual(/ab+/g, /ab+/i)).toBe(false);
352
+ expect(isDeepEqual(/ab+/g, /ac+/g)).toBe(false);
353
+ });
354
+ test('isDeepEqual compares Sets by size and membership', () => {
355
+ expect(isDeepEqual(new Set([1, 2]), new Set([1, 2]))).toBe(true);
356
+ expect(isDeepEqual(new Set([1, 2]), new Set([2, 1]))).toBe(true);
357
+ expect(isDeepEqual(new Set([1, 2]), new Set([1, 3]))).toBe(false);
358
+ expect(isDeepEqual(new Set([1]), new Set([1, 2]))).toBe(false);
359
+ });
360
+ test('isDeepEqual compares Maps by size and entries', () => {
361
+ expect(isDeepEqual(new Map([['a', 1]]), new Map([['a', 1]]))).toBe(true);
362
+ expect(isDeepEqual(new Map([['a', 1]]), new Map([['a', 2]]))).toBe(false);
363
+ expect(isDeepEqual(new Map([['a', 1]]), new Map([['b', 1]]))).toBe(false);
364
+ expect(isDeepEqual(new Map([['a', { b: 1 }]]), new Map([['a', { b: 1 }]]))).toBe(true);
365
+ });
366
+ test('isDeepEqual compares ArrayBuffers and typed arrays byte by byte', () => {
367
+ const bufferOf = (bytes) => new Uint8Array(bytes).buffer;
368
+ expect(isDeepEqual(bufferOf([1, 2, 3]), bufferOf([1, 2, 3]))).toBe(true);
369
+ expect(isDeepEqual(bufferOf([1, 2, 3]), bufferOf([1, 2, 4]))).toBe(false);
370
+ expect(isDeepEqual(bufferOf([1, 2]), bufferOf([1, 2, 3]))).toBe(false);
371
+ expect(isDeepEqual(new Uint8Array([1, 2]), new Uint8Array([1, 2]))).toBe(true);
372
+ expect(isDeepEqual(new Uint8Array([1, 2]), new Uint8Array([1, 3]))).toBe(false);
373
+ expect(isDeepEqual(new DataView(bufferOf([9])), new DataView(bufferOf([9])))).toBe(true);
374
+ expect(isDeepEqual(new DataView(bufferOf([9])), new DataView(bufferOf([8])))).toBe(false);
375
+ });
376
+ test('isDeepEqual survives circular references', () => {
377
+ const a = { name: 'a' };
378
+ a.self = a;
379
+ const b = { name: 'a' };
380
+ b.self = b;
381
+ expect(isDeepEqual(a, b)).toBe(true);
382
+ const c = { name: 'c' };
383
+ c.self = c;
384
+ expect(isDeepEqual(a, c)).toBe(false);
385
+ });
386
+ test('isDeepEqual prefers a custom valueOf when one is defined', () => {
387
+ class Money {
388
+ constructor(cents) {
389
+ this.cents = cents;
390
+ }
391
+ valueOf() {
392
+ return this.cents;
393
+ }
394
+ }
395
+ expect(isDeepEqual(new Money(100), new Money(100))).toBe(true);
396
+ expect(isDeepEqual(new Money(100), new Money(200))).toBe(false);
397
+ });
398
+ test('isDeepEqual falls back to a custom toString when one is defined', () => {
399
+ class Tag {
400
+ constructor(label) {
401
+ this.label = label;
402
+ }
403
+ toString() {
404
+ return this.label;
405
+ }
406
+ }
407
+ expect(isDeepEqual(new Tag('x'), new Tag('x'))).toBe(true);
408
+ expect(isDeepEqual(new Tag('x'), new Tag('y'))).toBe(false);
409
+ });
410
+ test('isDeepEqual compares subclasses of built-ins by their built-in semantics', () => {
411
+ class MySet extends Set {
412
+ }
413
+ class MyMap extends Map {
414
+ }
415
+ expect(isDeepEqual(new MySet([1]), new MySet([1]))).toBe(true);
416
+ expect(isDeepEqual(new MySet([1]), new MySet([2]))).toBe(false);
417
+ expect(isDeepEqual(new MyMap([['a', 1]]), new MyMap([['a', 1]]))).toBe(true);
418
+ expect(isDeepEqual(new MyMap([['a', 1]]), new MyMap([['a', 2]]))).toBe(false);
419
+ });