ts-data-forge 14.9.0 → 14.10.1

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.
Files changed (94) hide show
  1. package/README.md +5 -5
  2. package/dist/array/impl/array-utils-element-access.d.mts.map +1 -1
  3. package/dist/array/impl/array-utils-element-access.mjs +4 -4
  4. package/dist/array/impl/array-utils-element-access.mjs.map +1 -1
  5. package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts +8 -8
  6. package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts.map +1 -1
  7. package/dist/array/impl/array-utils-length-bounded-array-cast.mjs +28 -28
  8. package/dist/array/impl/array-utils-length-bounded-array-cast.mjs.map +1 -1
  9. package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts.map +1 -1
  10. package/dist/array/impl/array-utils-length-bounded-array-guard.mjs +4 -4
  11. package/dist/array/impl/array-utils-length-bounded-array-guard.mjs.map +1 -1
  12. package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts +3 -4
  13. package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts.map +1 -1
  14. package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs +28 -29
  15. package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs.map +1 -1
  16. package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts.map +1 -1
  17. package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs +3 -3
  18. package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs.map +1 -1
  19. package/dist/array/impl/array-utils-modification.d.mts +1 -1
  20. package/dist/array/impl/array-utils-modification.d.mts.map +1 -1
  21. package/dist/array/impl/array-utils-modification.mjs +15 -15
  22. package/dist/array/impl/array-utils-modification.mjs.map +1 -1
  23. package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
  24. package/dist/array/impl/array-utils-reducing-value.mjs +12 -12
  25. package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
  26. package/dist/array/impl/array-utils-search.d.mts.map +1 -1
  27. package/dist/array/impl/array-utils-search.mjs +56 -56
  28. package/dist/array/impl/array-utils-search.mjs.map +1 -1
  29. package/dist/array/impl/array-utils-slicing.d.mts.map +1 -1
  30. package/dist/array/impl/array-utils-slicing.mjs +3 -3
  31. package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
  32. package/dist/array/impl/array-utils-transformation.d.mts +25 -25
  33. package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
  34. package/dist/array/impl/array-utils-transformation.mjs +22 -22
  35. package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
  36. package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
  37. package/dist/array/impl/array-utils-validation.mjs +2 -2
  38. package/dist/array/impl/array-utils-validation.mjs.map +1 -1
  39. package/dist/collections/imap-mapped.d.mts +2 -2
  40. package/dist/collections/imap.d.mts +3 -4
  41. package/dist/collections/imap.d.mts.map +1 -1
  42. package/dist/collections/imap.mjs +0 -1
  43. package/dist/collections/imap.mjs.map +1 -1
  44. package/dist/collections/iset-mapped.d.mts +1 -2
  45. package/dist/collections/iset-mapped.d.mts.map +1 -1
  46. package/dist/collections/iset-mapped.mjs.map +1 -1
  47. package/dist/collections/iset.d.mts +4 -5
  48. package/dist/collections/iset.d.mts.map +1 -1
  49. package/dist/collections/iset.mjs +0 -1
  50. package/dist/collections/iset.mjs.map +1 -1
  51. package/dist/number/num.d.mts +2 -2
  52. package/dist/number/num.mjs.map +1 -1
  53. package/dist/object/object.d.mts +2 -2
  54. package/dist/others/fast-deep-equal.d.mts.map +1 -1
  55. package/dist/others/fast-deep-equal.mjs +10 -7
  56. package/dist/others/fast-deep-equal.mjs.map +1 -1
  57. package/dist/others/index.d.mts +1 -0
  58. package/dist/others/index.d.mts.map +1 -1
  59. package/dist/others/index.mjs +1 -0
  60. package/dist/others/index.mjs.map +1 -1
  61. package/dist/others/replace-equal-deep.d.mts +95 -0
  62. package/dist/others/replace-equal-deep.d.mts.map +1 -0
  63. package/dist/others/replace-equal-deep.mjs +156 -0
  64. package/dist/others/replace-equal-deep.mjs.map +1 -0
  65. package/dist/string/str.d.mts +2 -2
  66. package/package.json +12 -12
  67. package/src/array/impl/array-utils-element-access.mts +11 -11
  68. package/src/array/impl/array-utils-length-bounded-array-cast.mts +69 -69
  69. package/src/array/impl/array-utils-length-bounded-array-guard.mts +21 -21
  70. package/src/array/impl/array-utils-length-bounded-array-guard.test.mts +2 -2
  71. package/src/array/impl/array-utils-length-bounded-tuple-cast.mts +64 -65
  72. package/src/array/impl/array-utils-length-bounded-tuple-guard.mts +15 -15
  73. package/src/array/impl/array-utils-modification.mts +54 -54
  74. package/src/array/impl/array-utils-reducing-value.mts +55 -55
  75. package/src/array/impl/array-utils-search.mts +114 -114
  76. package/src/array/impl/array-utils-slicing.mts +16 -16
  77. package/src/array/impl/array-utils-transformation.mts +96 -96
  78. package/src/array/impl/array-utils-transformation.test.mts +2 -2
  79. package/src/array/impl/array-utils-validation.mts +19 -19
  80. package/src/array/impl/array-utils-validation.test.mts +1 -1
  81. package/src/collections/imap-mapped.mts +2 -2
  82. package/src/collections/imap.mts +3 -4
  83. package/src/collections/imap.test.mts +1 -1
  84. package/src/collections/iset-mapped.mts +1 -2
  85. package/src/collections/iset.mts +4 -5
  86. package/src/collections/iset.test.mts +1 -1
  87. package/src/number/num.mts +10 -10
  88. package/src/object/object.mts +2 -2
  89. package/src/others/fast-deep-equal.mts +17 -10
  90. package/src/others/fast-deep-equal.test.mts +141 -29
  91. package/src/others/index.mts +1 -0
  92. package/src/others/replace-equal-deep.mts +225 -0
  93. package/src/others/replace-equal-deep.test.mts +348 -0
  94. package/src/string/str.mts +2 -2
@@ -125,8 +125,8 @@ type IMapMappedInterface<K, V, KM extends MapSetKeyType> = Readonly<{
125
125
  // Reducing a value
126
126
 
127
127
  /**
128
- * Checks if all elements in the map satisfy a predicate. Also supports a
129
- * type predicate overload that narrows the type of values in the map if the
128
+ * Checks if all elements in the map satisfy a predicate. Also supports a type
129
+ * predicate overload that narrows the type of values in the map if the
130
130
  * predicate returns true for all elements.
131
131
  *
132
132
  * @example
@@ -96,8 +96,8 @@ type IMapInterface<K extends MapSetKeyType, V> = Readonly<{
96
96
  // Reducing a value
97
97
 
98
98
  /**
99
- * Checks if all elements in the map satisfy a predicate. Also supports a
100
- * type predicate overload that narrows the type of values in the map if the
99
+ * Checks if all elements in the map satisfy a predicate. Also supports a type
100
+ * predicate overload that narrows the type of values in the map if the
101
101
  * predicate returns true for all elements.
102
102
  *
103
103
  * @example
@@ -656,8 +656,7 @@ export namespace IMap {
656
656
  *
657
657
  * Two IMaps are considered equal if they have the same size and contain
658
658
  * exactly the same key-value pairs. The order of entries does not matter for
659
- * equality comparison. Values are compared using JavaScript's `===`
660
- * operator.
659
+ * equality comparison. Values are compared using JavaScript's `===` operator.
661
660
  *
662
661
  * **Performance:** O(n) where n is the size of the smaller map.
663
662
  *
@@ -81,7 +81,7 @@ describe('IMap.create', () => {
81
81
  });
82
82
 
83
83
  test('should create map from JavaScript Map', () => {
84
- const jsMap = new Map([
84
+ const jsMap = new Map<string, number>([
85
85
  ['a', 1],
86
86
  ['b', 2],
87
87
  ]);
@@ -138,8 +138,7 @@ type ISetMappedInterface<K, KM extends MapSetKeyType> = Readonly<{
138
138
 
139
139
  /**
140
140
  * Checks if all elements in the set satisfy a type predicate. Narrows the
141
- * type of elements in the set if the predicate returns true for all
142
- * elements.
141
+ * type of elements in the set if the predicate returns true for all elements.
143
142
  *
144
143
  *
145
144
  * @example
@@ -89,9 +89,9 @@ type ISetInterface<K extends MapSetKeyType> = Readonly<{
89
89
  // Reducing a value
90
90
 
91
91
  /**
92
- * Checks if all elements in the set satisfy a predicate. Also supports a
93
- * type predicate overload that narrows the type of elements in the set if
94
- * the predicate returns true for all elements.
92
+ * Checks if all elements in the set satisfy a predicate. Also supports a type
93
+ * predicate overload that narrows the type of elements in the set if the
94
+ * predicate returns true for all elements.
95
95
  *
96
96
  * @example
97
97
  *
@@ -539,8 +539,7 @@ export namespace ISet {
539
539
  *
540
540
  * This factory function accepts any iterable of elements, including arrays,
541
541
  * JavaScript Sets, other ISets, or custom iterables. Duplicate elements in
542
- * the input iterable will be automatically deduplicated in the resulting
543
- * set.
542
+ * the input iterable will be automatically deduplicated in the resulting set.
544
543
  *
545
544
  * **Performance:** O(n) where n is the number of elements in the iterable.
546
545
  *
@@ -39,7 +39,7 @@ describe('ISet.create', () => {
39
39
  });
40
40
 
41
41
  test('should create set from JavaScript Set', () => {
42
- const jsSet = new Set(['a', 'b', 'c']);
42
+ const jsSet = new Set<string>(['a', 'b', 'c']);
43
43
 
44
44
  const set = ISet.create(jsSet);
45
45
 
@@ -120,8 +120,8 @@ export namespace Num {
120
120
  *
121
121
  * assert.strictEqual(Result.unwrapOk(Num.safeParseInt(' ')), undefined);
122
122
  *
123
- * // A value that overflows to Infinity is not an integer either, even
124
- * // though native `parseInt` reads a 1 out of it.
123
+ * // A value that overflows to Infinity is not an integer either, even though
124
+ * // native `parseInt` reads a 1 out of it.
125
125
  *
126
126
  * assert.strictEqual(Number.parseInt('1e400', 10), 1);
127
127
  *
@@ -135,10 +135,10 @@ export namespace Num {
135
135
  */
136
136
  export const safeParseInt = (s: string): Result<Int, Error> =>
137
137
  // The parsing itself is `SafeNumber.parseInteger` (Sumi D-49 (c)): one
138
- // implementation, in the package that owns the standard-library
139
- // boundary. What stays here is this function's own contract — the `Int`
140
- // brand on the success side, and an `Error` rather than a tagged record
141
- // on the failure side.
138
+ // implementation, in the package that owns the standard-library boundary.
139
+ // What stays here is this function's own contract — the `Int` brand on the
140
+ // success side, and an `Error` rather than a tagged record on the failure
141
+ // side.
142
142
  Result.match(SafeNumber.parseInteger(s), {
143
143
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
144
144
  ok: (n) => Result.ok(n as Int),
@@ -213,10 +213,10 @@ export namespace Num {
213
213
  */
214
214
  export const safeParseFloat = (s: string): Result<FiniteNumber, Error> =>
215
215
  // The parsing itself is `SafeNumber.parse` (Sumi D-49 (c)): one
216
- // implementation, in the package that owns the standard-library
217
- // boundary. What stays here is this function's own contract — the
218
- // `FiniteNumber` brand on the success side, and an `Error` rather than a
219
- // tagged record on the failure side.
216
+ // implementation, in the package that owns the standard-library boundary.
217
+ // What stays here is this function's own contract — the `FiniteNumber`
218
+ // brand on the success side, and an `Error` rather than a tagged record on
219
+ // the failure side.
220
220
  Result.match(SafeNumber.parse(s), {
221
221
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
222
222
  ok: (n) => Result.ok(n as FiniteNumber),
@@ -337,8 +337,8 @@ export namespace Obj {
337
337
  * This is what `Object.fromEntries(Object.entries(record).map(...))` cannot
338
338
  * express: an entries array carries no evidence that it still covers every
339
339
  * key of the record it came from, so its element type is all the return type
340
- * can be built from. Going through a mapped type instead states the
341
- * invariant that a value-only transform preserves the key set.
340
+ * can be built from. Going through a mapped type instead states the invariant
341
+ * that a value-only transform preserves the key set.
342
342
  *
343
343
  * **Key handling**: keys reach the callback the way `Object.entries` yields
344
344
  * them, so a numeric key `1` arrives as `'1'`. Symbol-keyed properties are
@@ -67,12 +67,20 @@ export const fastDeepEqual = <T,>(a: T, b: T): boolean => {
67
67
  return isRegExp(b) && a.source === b.source && a.flags === b.flags;
68
68
  }
69
69
 
70
- if (a.valueOf !== Object.prototype.valueOf) {
71
- return a.valueOf() === b.valueOf();
70
+ // A null-prototype object (`Object.create(null)`, `Object.groupBy`) has
71
+ // neither method, so it falls through to the key comparison.
72
+ if (
73
+ typeof a.valueOf === 'function' &&
74
+ a.valueOf !== Object.prototype.valueOf
75
+ ) {
76
+ return typeof b.valueOf === 'function' && a.valueOf() === b.valueOf();
72
77
  }
73
78
 
74
- if (a.toString !== Object.prototype.toString) {
75
- return a.toString() === b.toString();
79
+ if (
80
+ typeof a.toString === 'function' &&
81
+ a.toString !== Object.prototype.toString
82
+ ) {
83
+ return typeof b.toString === 'function' && a.toString() === b.toString();
76
84
  }
77
85
 
78
86
  const aKeys: readonly string[] = Object.keys(a);
@@ -84,12 +92,11 @@ export const fastDeepEqual = <T,>(a: T, b: T): boolean => {
84
92
  aKeys.every(
85
93
  (key) =>
86
94
  hasKey(b, key) &&
87
- // React-specific: avoid traversing React elements' _owner.
88
- // _owner contains circular references
89
- // and is not needed when comparing the actual elements (and not their owners)
90
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
91
- !(key === '_owner' && a['$$typeof']) &&
92
- fastDeepEqual(a[key], b[key]),
95
+ // React-specific: a React element (marked by a symbol `$$typeof`)
96
+ // has its _owner skipped, i.e. treated as equal. It holds circular
97
+ // references and is not part of what the element renders.
98
+ ((key === '_owner' && typeof a['$$typeof'] === 'symbol') ||
99
+ fastDeepEqual(a[key], b[key])),
93
100
  )
94
101
  );
95
102
  }
@@ -203,6 +203,18 @@ describe(fastDeepEqual, () => {
203
203
  y: { toString: () => 'Hello world!' } as any,
204
204
  expected: '=',
205
205
  },
206
+ {
207
+ name: 'objects whose own `valueOf` and `toString` are not functions are compared by key',
208
+ x: { valueOf: 1, toString: 'a' } as any,
209
+ y: { valueOf: 1, toString: 'a' } as any,
210
+ expected: '=',
211
+ },
212
+ {
213
+ name: 'objects whose own `valueOf` that is not a function differs are not equal',
214
+ x: { valueOf: 1 } as any,
215
+ y: { valueOf: 2 } as any,
216
+ expected: '!=',
217
+ },
206
218
  {
207
219
  name: 'objects with `toString` functions returning different values are not equal',
208
220
  x: { toString: () => 'Hello world!' } as any,
@@ -212,6 +224,54 @@ describe(fastDeepEqual, () => {
212
224
  ] as const satisfies readonly TestCase[])('$name', testFn);
213
225
  });
214
226
 
227
+ describe('null-prototype objects', () => {
228
+ const withNullPrototype = (
229
+ entries: ReadonlyRecord<string, unknown>,
230
+ ): unknown => Object.setPrototypeOf({ ...entries }, null);
231
+
232
+ const groupByParity = (xs: readonly number[]): unknown =>
233
+ Object.groupBy(xs, (x) => (x % 2 === 0 ? 'even' : 'odd'));
234
+
235
+ test.each([
236
+ {
237
+ name: 'empty null-prototype objects are equal',
238
+ x: Object.create(null),
239
+ y: Object.create(null),
240
+ expected: '=',
241
+ },
242
+ {
243
+ name: 'null-prototype objects with the same entries are equal',
244
+ x: withNullPrototype({ a: 1, b: [2] }),
245
+ y: withNullPrototype({ a: 1, b: [2] }),
246
+ expected: '=',
247
+ },
248
+ {
249
+ name: 'null-prototype objects with different entries are not equal',
250
+ x: withNullPrototype({ a: 1 }),
251
+ y: withNullPrototype({ a: 2 }),
252
+ expected: '!=',
253
+ },
254
+ {
255
+ name: 'a null-prototype object and a plain object are not equal',
256
+ x: Object.create(null),
257
+ y: {},
258
+ expected: '!=',
259
+ },
260
+ {
261
+ name: 'Object.groupBy results with the same groups are equal',
262
+ x: groupByParity([1, 2, 3]),
263
+ y: groupByParity([1, 2, 3]),
264
+ expected: '=',
265
+ },
266
+ {
267
+ name: 'Object.groupBy results with different groups are not equal',
268
+ x: groupByParity([1, 2, 3]),
269
+ y: groupByParity([1, 2, 4]),
270
+ expected: '!=',
271
+ },
272
+ ] as const satisfies readonly TestCase[])('$name', testFn);
273
+ });
274
+
215
275
  describe('arrays', () => {
216
276
  test.each([
217
277
  { name: 'two empty arrays are equal', x: [], y: [], expected: '=' },
@@ -382,13 +442,13 @@ describe(fastDeepEqual, () => {
382
442
  test.each([
383
443
  {
384
444
  name: 'empty maps are equal',
385
- x: new Map(),
386
- y: new Map(),
445
+ x: new Map<unknown, unknown>(),
446
+ y: new Map<unknown, unknown>(),
387
447
  expected: '=',
388
448
  },
389
449
  {
390
450
  name: 'empty maps of different class are not equal',
391
- x: new Map(),
451
+ x: new Map<unknown, unknown>(),
392
452
  y: new MyMap(),
393
453
  expected: '!=',
394
454
  },
@@ -436,32 +496,44 @@ describe(fastDeepEqual, () => {
436
496
  },
437
497
  {
438
498
  name: 'equal maps (same sub-keys)',
439
- x: new Map([['a', [createMap({ b: 'c' })]]]),
440
- y: new Map([['a', [createMap({ b: 'c' })]]]),
499
+ x: new Map<string, readonly ReadonlyMap<unknown, string>[]>([
500
+ ['a', [createMap({ b: 'c' })]],
501
+ ]),
502
+ y: new Map<string, readonly ReadonlyMap<unknown, string>[]>([
503
+ ['a', [createMap({ b: 'c' })]],
504
+ ]),
441
505
  expected: '=',
442
506
  },
443
507
  {
444
508
  name: 'not equal maps (different sub-key value)',
445
- x: new Map([['a', [createMap({ b: 'c' })]]]),
446
- y: new Map([['a', [createMap({ b: 'd' })]]]),
509
+ x: new Map<string, readonly ReadonlyMap<unknown, string>[]>([
510
+ ['a', [createMap({ b: 'c' })]],
511
+ ]),
512
+ y: new Map<string, readonly ReadonlyMap<unknown, string>[]>([
513
+ ['a', [createMap({ b: 'd' })]],
514
+ ]),
447
515
  expected: '!=',
448
516
  },
449
517
  {
450
518
  name: 'not equal maps (different sub-key)',
451
- x: new Map([['a', [createMap({ b: 'c' })]]]),
452
- y: new Map([['a', [createMap({ c: 'c' })]]]),
519
+ x: new Map<string, readonly ReadonlyMap<unknown, string>[]>([
520
+ ['a', [createMap({ b: 'c' })]],
521
+ ]),
522
+ y: new Map<string, readonly ReadonlyMap<unknown, string>[]>([
523
+ ['a', [createMap({ c: 'c' })]],
524
+ ]),
453
525
  expected: '!=',
454
526
  },
455
527
  {
456
528
  name: 'empty map and empty object are equal',
457
529
  x: {},
458
- y: new Map(),
530
+ y: new Map<unknown, unknown>(),
459
531
  expected: '!=',
460
532
  },
461
533
  {
462
534
  name: 'empty map and empty array are not equal',
463
535
  x: [],
464
- y: new Map(),
536
+ y: new Map<unknown, unknown>(),
465
537
  expected: '!=',
466
538
  },
467
539
  {
@@ -514,13 +586,13 @@ describe(fastDeepEqual, () => {
514
586
  test.each([
515
587
  {
516
588
  name: 'empty sets are equal',
517
- x: new Set(),
518
- y: new Set(),
589
+ x: new Set<unknown>(),
590
+ y: new Set<unknown>(),
519
591
  expected: '=',
520
592
  },
521
593
  {
522
594
  name: 'empty sets of different class are not equal',
523
- x: new Set(),
595
+ x: new Set<unknown>(),
524
596
  y: new MySet(),
525
597
  expected: '!=',
526
598
  },
@@ -575,13 +647,13 @@ describe(fastDeepEqual, () => {
575
647
  {
576
648
  name: 'empty set and empty object are not equal',
577
649
  x: {},
578
- y: new Set(),
650
+ y: new Set<unknown>(),
579
651
  expected: '!=',
580
652
  },
581
653
  {
582
654
  name: 'empty set and empty array are not equal',
583
655
  x: [],
584
- y: new Set(),
656
+ y: new Set<unknown>(),
585
657
  expected: '!=',
586
658
  },
587
659
  {
@@ -598,7 +670,7 @@ describe(fastDeepEqual, () => {
598
670
  },
599
671
  {
600
672
  name: 'set and pseudo set are not equal',
601
- x: new Set(),
673
+ x: new Set<unknown>(),
602
674
  y: {
603
675
  constructor: Set,
604
676
  size: 0,
@@ -702,15 +774,58 @@ describe(fastDeepEqual, () => {
702
774
  'content',
703
775
  );
704
776
 
705
- // These elements have the same structure but different _owner references
706
- // fastDeepEqual should skip _owner comparison and not cause infinite loop
707
- // The result depends on whether other properties are equal
708
- const result = fastDeepEqualUntyped(element1, element2);
709
-
710
- // The important thing is that it doesn't throw or hang
711
- assert.isBoolean(result);
777
+ assert.isTrue(fastDeepEqualUntyped(element1, element2));
712
778
  });
713
779
 
780
+ test.each([
781
+ {
782
+ name: 'element-shaped objects with different _owner are equal',
783
+ x: {
784
+ $$typeof: Symbol.for('react.element'),
785
+ type: 'div',
786
+ key: null,
787
+ props: { className: 'x' },
788
+ _owner: { name: 'A' },
789
+ },
790
+ y: {
791
+ $$typeof: Symbol.for('react.element'),
792
+ type: 'div',
793
+ key: null,
794
+ props: { className: 'x' },
795
+ _owner: { name: 'B' },
796
+ },
797
+ expected: '=',
798
+ },
799
+ {
800
+ name: 'element-shaped objects with different props are not equal',
801
+ x: {
802
+ $$typeof: Symbol.for('react.element'),
803
+ type: 'div',
804
+ props: { className: 'x' },
805
+ _owner: null,
806
+ },
807
+ y: {
808
+ $$typeof: Symbol.for('react.element'),
809
+ type: 'div',
810
+ props: { className: 'y' },
811
+ _owner: null,
812
+ },
813
+ expected: '!=',
814
+ },
815
+ {
816
+ name: '_owner is compared on an object without $$typeof',
817
+ x: { _owner: { name: 'A' } },
818
+ y: { _owner: { name: 'B' } },
819
+ expected: '!=',
820
+ },
821
+ {
822
+ name: '_owner is compared when $$typeof is falsy',
823
+ x: { $$typeof: null, _owner: { name: 'A' } },
824
+ y: { $$typeof: null, _owner: { name: 'B' } },
825
+ expected: '!=',
826
+ },
827
+ ] as const satisfies readonly TestCase[])('$name', testFn);
828
+
714
829
  test('React elements with different props are not equal', () => {
715
830
  const element1 = React.createElement('div', { className: 'test1' });
716
831
 
@@ -747,9 +862,7 @@ describe(fastDeepEqual, () => {
747
862
  child.render();
748
863
  }
749
864
 
750
- // Should still be 1 or 2 depending on React element comparison
751
- // The important thing is it doesn't throw or hang
752
- assert.isTrue(mut_renderCount >= 1);
865
+ assert.strictEqual(mut_renderCount, 1);
753
866
 
754
867
  // Update with different props - should re-render
755
868
  const differentProps = {
@@ -763,8 +876,7 @@ describe(fastDeepEqual, () => {
763
876
  child.render();
764
877
  }
765
878
 
766
- // Should have rendered at least once more
767
- assert.isTrue(mut_renderCount >= 2);
879
+ assert.strictEqual(mut_renderCount, 2);
768
880
  });
769
881
  });
770
882
  });
@@ -6,5 +6,6 @@ export * from './if-then.mjs';
6
6
  export * from './map-nullable.mjs';
7
7
  export * from './memoize-function.mjs';
8
8
  export * from './panic-error.mjs';
9
+ export * from './replace-equal-deep.mjs';
9
10
  export * from './tuple.mjs';
10
11
  export * from './unknown-to-string.mjs';