ts-data-forge 6.4.0 → 6.6.0
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/README.md +5 -3
- package/dist/array/impl/array-utils-element-access.d.mts +5 -5
- package/dist/array/impl/array-utils-element-access.mjs +4 -4
- package/dist/array/impl/array-utils-iterators.d.mts +1 -1
- package/dist/array/impl/array-utils-iterators.mjs +1 -1
- package/dist/array/impl/array-utils-modification.d.mts +4 -4
- package/dist/array/impl/array-utils-reducing-value.d.mts +5 -5
- package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
- package/dist/array/impl/array-utils-reducing-value.mjs +1 -0
- package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
- package/dist/array/impl/array-utils-search.d.mts +8 -8
- package/dist/array/impl/array-utils-set-op.d.mts.map +1 -1
- package/dist/array/impl/array-utils-set-op.mjs +3 -1
- package/dist/array/impl/array-utils-set-op.mjs.map +1 -1
- package/dist/array/impl/array-utils-size.d.mts +1 -1
- package/dist/array/impl/array-utils-size.mjs +1 -1
- package/dist/array/impl/array-utils-slice-clamped.d.mts +1 -1
- package/dist/array/impl/array-utils-slicing.d.mts +3 -3
- package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-validation.d.mts +7 -7
- package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-validation.mjs +22 -12
- package/dist/array/impl/array-utils-validation.mjs.map +1 -1
- package/dist/collections/imap-mapped.d.mts +5 -12
- package/dist/collections/imap-mapped.d.mts.map +1 -1
- package/dist/collections/imap-mapped.mjs.map +1 -1
- package/dist/collections/imap.d.mts +24 -28
- package/dist/collections/imap.d.mts.map +1 -1
- package/dist/collections/imap.mjs +1 -1
- package/dist/collections/imap.mjs.map +1 -1
- package/dist/collections/iset-mapped.d.mts +8 -14
- package/dist/collections/iset-mapped.d.mts.map +1 -1
- package/dist/collections/iset-mapped.mjs.map +1 -1
- package/dist/collections/iset.d.mts +9 -23
- package/dist/collections/iset.d.mts.map +1 -1
- package/dist/collections/iset.mjs +1 -0
- package/dist/collections/iset.mjs.map +1 -1
- package/dist/entry-point.mjs +1 -0
- package/dist/entry-point.mjs.map +1 -1
- package/dist/functional/optional/impl/optional-is-optional.d.mts +1 -1
- package/dist/functional/optional/impl/optional-is-optional.d.mts.map +1 -1
- package/dist/functional/optional/impl/optional-is-optional.mjs +6 -5
- package/dist/functional/optional/impl/optional-is-optional.mjs.map +1 -1
- package/dist/functional/optional/impl/optional-some.d.mts.map +1 -1
- package/dist/functional/optional/impl/optional-some.mjs.map +1 -1
- package/dist/functional/optional/impl/optional-zip.d.mts +1 -1
- package/dist/functional/optional/impl/optional-zip.mjs +1 -1
- package/dist/functional/result/impl/result-err.d.mts.map +1 -1
- package/dist/functional/result/impl/result-err.mjs.map +1 -1
- package/dist/functional/result/impl/result-is-result.d.mts +1 -1
- package/dist/functional/result/impl/result-is-result.mjs +1 -1
- package/dist/functional/result/impl/result-ok.d.mts.map +1 -1
- package/dist/functional/result/impl/result-ok.mjs.map +1 -1
- package/dist/functional/result/impl/result-unwrap-err-throw.mjs +1 -0
- package/dist/functional/result/impl/result-unwrap-err-throw.mjs.map +1 -1
- package/dist/functional/result/impl/result-unwrap-throw.mjs +1 -0
- package/dist/functional/result/impl/result-unwrap-throw.mjs.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-err.d.mts.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-err.mjs.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-is-ternary-result.d.mts +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-is-ternary-result.mjs +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-ok.d.mts.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-ok.mjs.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-unwrap-err-throw.mjs +1 -0
- package/dist/functional/ternary-result/impl/ternary-result-unwrap-err-throw.mjs.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-unwrap-throw.mjs +1 -0
- package/dist/functional/ternary-result/impl/ternary-result-unwrap-throw.mjs.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-unwrap-warn-throw.mjs +1 -0
- package/dist/functional/ternary-result/impl/ternary-result-unwrap-warn-throw.mjs.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-warn.d.mts.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-warn.mjs.map +1 -1
- package/dist/guard/has-key.d.mts +3 -1
- package/dist/guard/has-key.d.mts.map +1 -1
- package/dist/guard/has-key.mjs +6 -2
- package/dist/guard/has-key.mjs.map +1 -1
- package/dist/guard/is-non-null-object.d.mts.map +1 -1
- package/dist/guard/is-non-null-object.mjs +3 -1
- package/dist/guard/is-non-null-object.mjs.map +1 -1
- package/dist/guard/is-record.d.mts +2 -2
- package/dist/guard/is-record.mjs +2 -2
- package/dist/guard/is-type.d.mts +15 -15
- package/dist/guard/is-type.mjs +15 -15
- package/dist/guard/key-is-in.d.mts.map +1 -1
- package/dist/guard/key-is-in.mjs +3 -1
- package/dist/guard/key-is-in.mjs.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/dist/json/json.d.mts +3 -3
- package/dist/json/json.mjs +4 -3
- package/dist/json/json.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-safe-int.mjs +2 -2
- package/dist/number/branded-types/positive-safe-int.mjs +1 -1
- package/dist/number/branded-types/safe-int.mjs +2 -2
- package/dist/number/branded-types/safe-uint.mjs +1 -1
- package/dist/number/num.mjs +1 -1
- package/dist/object/object.d.mts +36 -8
- package/dist/object/object.d.mts.map +1 -1
- package/dist/object/object.mjs +28 -6
- package/dist/object/object.mjs.map +1 -1
- package/dist/others/fast-deep-equal.d.mts +8 -0
- package/dist/others/fast-deep-equal.d.mts.map +1 -0
- package/dist/others/fast-deep-equal.mjs +72 -0
- package/dist/others/fast-deep-equal.mjs.map +1 -0
- package/dist/others/index.d.mts +1 -0
- package/dist/others/index.d.mts.map +1 -1
- package/dist/others/index.mjs +1 -0
- package/dist/others/index.mjs.map +1 -1
- package/package.json +34 -29
- package/src/array/impl/array-utils-creation.test.mts +13 -10
- package/src/array/impl/array-utils-element-access.mts +5 -5
- package/src/array/impl/array-utils-element-access.test.mts +5 -5
- package/src/array/impl/array-utils-iterators.mts +1 -1
- package/src/array/impl/array-utils-iterators.test.mts +6 -6
- package/src/array/impl/array-utils-modification.mts +4 -4
- package/src/array/impl/array-utils-modification.test.mts +12 -12
- package/src/array/impl/array-utils-overload-type-error.test.mts +1 -1
- package/src/array/impl/array-utils-reducing-value.mts +6 -5
- package/src/array/impl/array-utils-reducing-value.test.mts +9 -9
- package/src/array/impl/array-utils-search.mts +8 -8
- package/src/array/impl/array-utils-search.test.mts +35 -35
- package/src/array/impl/array-utils-set-op.mts +1 -0
- package/src/array/impl/array-utils-set-op.test.mts +2 -2
- package/src/array/impl/array-utils-size.mts +1 -1
- package/src/array/impl/array-utils-size.test.mts +1 -1
- package/src/array/impl/array-utils-slice-clamped.mts +1 -1
- package/src/array/impl/array-utils-slice-clamped.test.mts +5 -5
- package/src/array/impl/array-utils-slicing.mts +3 -3
- package/src/array/impl/array-utils-slicing.test.mts +5 -5
- package/src/array/impl/array-utils-transformation.mts +1 -1
- package/src/array/impl/array-utils-transformation.test.mts +53 -53
- package/src/array/impl/array-utils-validation.mts +18 -10
- package/src/array/impl/array-utils-validation.test.mts +34 -29
- package/src/array/impl/array.test.mts +1 -1
- package/src/collections/imap-mapped.mts +4 -10
- package/src/collections/imap-mapped.test.mts +9 -9
- package/src/collections/imap.mts +24 -27
- package/src/collections/imap.test.mts +6 -6
- package/src/collections/iset-mapped.mts +12 -16
- package/src/collections/iset-mapped.test.mts +6 -6
- package/src/collections/iset.mts +14 -25
- package/src/collections/queue.test.mts +2 -1
- package/src/collections/stack.test.mts +3 -3
- package/src/functional/optional/impl/optional-is-optional.mts +6 -6
- package/src/functional/optional/impl/optional-some.mts +5 -4
- package/src/functional/optional/impl/optional-zip.mts +1 -1
- package/src/functional/result/impl/result-err.mts +5 -4
- package/src/functional/result/impl/result-is-result.mts +1 -1
- package/src/functional/result/impl/result-ok.mts +5 -4
- package/src/functional/result.test.mts +2 -2
- package/src/functional/ternary-result/impl/ternary-result-err.mts +5 -4
- package/src/functional/ternary-result/impl/ternary-result-is-ternary-result.mts +1 -1
- package/src/functional/ternary-result/impl/ternary-result-ok.mts +5 -4
- package/src/functional/ternary-result/impl/ternary-result-warn.mts +6 -5
- package/src/guard/has-key.mts +6 -2
- package/src/guard/is-error.test.mts +1 -1
- package/src/guard/is-non-empty-string.test.mts +1 -1
- package/src/guard/is-non-null-object.mts +1 -0
- package/src/guard/is-non-null-object.test.mts +4 -3
- package/src/guard/is-primitive.test.mts +1 -1
- package/src/guard/is-record.mts +2 -2
- package/src/guard/is-record.test.mts +2 -2
- package/src/guard/is-type.mts +15 -15
- package/src/guard/is-type.test.mts +2 -2
- package/src/guard/key-is-in.mts +3 -1
- package/src/json/json.mts +4 -3
- package/src/json/json.test.mts +20 -19
- package/src/number/branded-types/non-zero-safe-int.mts +2 -2
- package/src/number/branded-types/positive-safe-int.mts +1 -1
- package/src/number/branded-types/safe-int.mts +2 -2
- package/src/number/branded-types/safe-uint.mts +1 -1
- package/src/number/num.mts +1 -1
- package/src/object/object.mts +54 -8
- package/src/object/object.test.mts +70 -7
- package/src/others/cast-mutable.test.mts +3 -3
- package/src/others/cast-readonly.test.mts +10 -5
- package/src/others/fast-deep-equal.mts +98 -0
- package/src/others/fast-deep-equal.test.mts +771 -0
- package/src/others/index.mts +1 -0
- package/src/others/map-nullable.test.mts +8 -8
- package/src/others/memoize-function.test.mts +20 -8
- package/src/others/unknown-to-string.test.mts +8 -8
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
/* eslint-disable total-functions/no-unsafe-type-assertion */
|
|
2
|
+
/* eslint-disable guard-for-in */
|
|
3
|
+
/* eslint-disable require-unicode-regexp */
|
|
4
|
+
/* eslint-disable no-restricted-globals */
|
|
5
|
+
/* eslint-disable functional/no-class-inheritance */
|
|
6
|
+
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import { fastDeepEqual } from './fast-deep-equal.mjs';
|
|
9
|
+
|
|
10
|
+
const fastDeepEqualUntyped = (a: any, b: any): boolean => fastDeepEqual(a, b);
|
|
11
|
+
|
|
12
|
+
const func1 = (): void => {};
|
|
13
|
+
|
|
14
|
+
const func2 = (): void => {};
|
|
15
|
+
|
|
16
|
+
class MyMap<K, V> extends Map<K, V> {}
|
|
17
|
+
|
|
18
|
+
class MySet<T> extends Set<T> {}
|
|
19
|
+
|
|
20
|
+
const emptyObj = {} as const;
|
|
21
|
+
|
|
22
|
+
type TestCase = Readonly<{
|
|
23
|
+
name: string;
|
|
24
|
+
x: unknown;
|
|
25
|
+
y: unknown;
|
|
26
|
+
expected: '=' | '!=';
|
|
27
|
+
}>;
|
|
28
|
+
|
|
29
|
+
const testFn = ({ x, y, expected }: TestCase): void => {
|
|
30
|
+
{
|
|
31
|
+
const result = fastDeepEqualUntyped(x, y);
|
|
32
|
+
|
|
33
|
+
switch (expected) {
|
|
34
|
+
case '=':
|
|
35
|
+
assert.isTrue(result);
|
|
36
|
+
|
|
37
|
+
break;
|
|
38
|
+
|
|
39
|
+
case '!=':
|
|
40
|
+
assert.isFalse(result);
|
|
41
|
+
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
const resultReversed = fastDeepEqualUntyped(y, x);
|
|
48
|
+
|
|
49
|
+
switch (expected) {
|
|
50
|
+
case '=':
|
|
51
|
+
assert.isTrue(resultReversed);
|
|
52
|
+
|
|
53
|
+
break;
|
|
54
|
+
|
|
55
|
+
case '!=':
|
|
56
|
+
assert.isFalse(resultReversed);
|
|
57
|
+
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
describe(fastDeepEqual, () => {
|
|
64
|
+
describe('scalars', () => {
|
|
65
|
+
test.each([
|
|
66
|
+
{ name: 'equal numbers', x: 1, y: 1, expected: '=' },
|
|
67
|
+
{ name: 'not equal numbers', x: 1, y: 2, expected: '!=' },
|
|
68
|
+
{ name: 'number and array are not equal', x: 1, y: [], expected: '!=' },
|
|
69
|
+
{ name: '0 and null are not equal', x: 0, y: null, expected: '!=' },
|
|
70
|
+
{ name: 'equal strings', x: 'a', y: 'a', expected: '=' },
|
|
71
|
+
{ name: 'not equal strings', x: 'a', y: 'b', expected: '!=' },
|
|
72
|
+
{
|
|
73
|
+
name: 'empty string and null are not equal',
|
|
74
|
+
x: '',
|
|
75
|
+
y: null,
|
|
76
|
+
expected: '!=',
|
|
77
|
+
},
|
|
78
|
+
{ name: 'null is equal to null', x: null, y: null, expected: '=' },
|
|
79
|
+
{ name: 'equal booleans (true)', x: true, y: true, expected: '=' },
|
|
80
|
+
{ name: 'equal booleans (false)', x: false, y: false, expected: '=' },
|
|
81
|
+
{ name: 'not equal booleans', x: true, y: false, expected: '!=' },
|
|
82
|
+
{ name: '1 and true are not equal', x: 1, y: true, expected: '!=' },
|
|
83
|
+
{ name: '0 and false are not equal', x: 0, y: false, expected: '!=' },
|
|
84
|
+
{
|
|
85
|
+
name: 'NaN and NaN are equal',
|
|
86
|
+
x: Number.NaN,
|
|
87
|
+
y: Number.NaN,
|
|
88
|
+
expected: '=',
|
|
89
|
+
},
|
|
90
|
+
{ name: '0 and -0 are equal', x: 0, y: -0, expected: '=' },
|
|
91
|
+
{
|
|
92
|
+
name: 'Infinity and Infinity are equal',
|
|
93
|
+
x: Infinity,
|
|
94
|
+
y: Infinity,
|
|
95
|
+
expected: '=',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'Infinity and -Infinity are not equal',
|
|
99
|
+
x: Infinity,
|
|
100
|
+
y: -Infinity,
|
|
101
|
+
expected: '!=',
|
|
102
|
+
},
|
|
103
|
+
] as const satisfies readonly TestCase[])('$name', testFn);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('objects', () => {
|
|
107
|
+
test.each([
|
|
108
|
+
{ name: 'empty objects are equal', x: {}, y: {}, expected: '=' },
|
|
109
|
+
{
|
|
110
|
+
name: 'equal objects (same properties "order")',
|
|
111
|
+
x: { a: 1, b: '2' },
|
|
112
|
+
y: { a: 1, b: '2' },
|
|
113
|
+
expected: '=',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'equal objects (different properties "order")',
|
|
117
|
+
x: { a: 1, b: '2' },
|
|
118
|
+
y: { b: '2', a: 1 },
|
|
119
|
+
expected: '=',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'not equal objects (extra property)',
|
|
123
|
+
x: { a: 1, b: '2' },
|
|
124
|
+
y: { a: 1, b: '2', c: [] },
|
|
125
|
+
expected: '!=',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'not equal objects (different property values)',
|
|
129
|
+
x: { a: 1, b: '2', c: 3 },
|
|
130
|
+
y: { a: 1, b: '2', c: 4 },
|
|
131
|
+
expected: '!=',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'not equal objects (different properties)',
|
|
135
|
+
x: { a: 1, b: '2', c: 3 },
|
|
136
|
+
y: { a: 1, b: '2', d: 3 },
|
|
137
|
+
expected: '!=',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'equal objects (same sub-properties)',
|
|
141
|
+
x: { a: [{ b: 'c' }] },
|
|
142
|
+
y: { a: [{ b: 'c' }] },
|
|
143
|
+
expected: '=',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'not equal objects (different sub-property value)',
|
|
147
|
+
x: { a: [{ b: 'c' }] },
|
|
148
|
+
y: { a: [{ b: 'd' }] },
|
|
149
|
+
expected: '!=',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'not equal objects (different sub-property)',
|
|
153
|
+
x: { a: [{ b: 'c' }] },
|
|
154
|
+
y: { a: [{ c: 'c' }] },
|
|
155
|
+
expected: '!=',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'empty array and empty object are not equal',
|
|
159
|
+
x: {},
|
|
160
|
+
y: [],
|
|
161
|
+
expected: '!=',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'object with extra undefined properties are not equal #1',
|
|
165
|
+
x: {},
|
|
166
|
+
y: { foo: undefined },
|
|
167
|
+
expected: '!=',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'object with extra undefined properties are not equal #2',
|
|
171
|
+
x: { foo: undefined },
|
|
172
|
+
y: {},
|
|
173
|
+
expected: '!=',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: 'object with extra undefined properties are not equal #3',
|
|
177
|
+
x: { foo: undefined },
|
|
178
|
+
y: { bar: undefined },
|
|
179
|
+
expected: '!=',
|
|
180
|
+
},
|
|
181
|
+
{ name: 'nulls are equal', x: null, y: null, expected: '=' },
|
|
182
|
+
{
|
|
183
|
+
name: 'null and undefined are not equal',
|
|
184
|
+
x: null,
|
|
185
|
+
y: undefined,
|
|
186
|
+
expected: '!=',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'null and empty object are not equal',
|
|
190
|
+
x: null,
|
|
191
|
+
y: {},
|
|
192
|
+
expected: '!=',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'undefined and empty object are not equal',
|
|
196
|
+
x: undefined,
|
|
197
|
+
y: {},
|
|
198
|
+
expected: '!=',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: 'objects with different `toString` functions returning same values are equal',
|
|
202
|
+
x: { toString: () => 'Hello world!' } as any,
|
|
203
|
+
y: { toString: () => 'Hello world!' } as any,
|
|
204
|
+
expected: '=',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'objects with `toString` functions returning different values are not equal',
|
|
208
|
+
x: { toString: () => 'Hello world!' } as any,
|
|
209
|
+
y: { toString: () => 'Hi!' } as any,
|
|
210
|
+
expected: '!=',
|
|
211
|
+
},
|
|
212
|
+
] as const satisfies readonly TestCase[])('$name', testFn);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('arrays', () => {
|
|
216
|
+
test.each([
|
|
217
|
+
{ name: 'two empty arrays are equal', x: [], y: [], expected: '=' },
|
|
218
|
+
{ name: 'equal arrays', x: [1, 2, 3], y: [1, 2, 3], expected: '=' },
|
|
219
|
+
{
|
|
220
|
+
name: 'not equal arrays (different item)',
|
|
221
|
+
x: [1, 2, 3],
|
|
222
|
+
y: [1, 2, 4],
|
|
223
|
+
expected: '!=',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: 'not equal arrays (different length)',
|
|
227
|
+
x: [1, 2, 3],
|
|
228
|
+
y: [1, 2],
|
|
229
|
+
expected: '!=',
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: 'equal arrays of objects',
|
|
233
|
+
x: [{ a: 'a' }, { b: 'b' }],
|
|
234
|
+
y: [{ a: 'a' }, { b: 'b' }],
|
|
235
|
+
expected: '=',
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: 'not equal arrays of objects',
|
|
239
|
+
x: [{ a: 'a' }, { b: 'b' }],
|
|
240
|
+
y: [{ a: 'a' }, { b: 'c' }],
|
|
241
|
+
expected: '!=',
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: 'pseudo array and equivalent array are not equal',
|
|
245
|
+
x: { 0: 0, 1: 1, length: 2 },
|
|
246
|
+
y: [0, 1],
|
|
247
|
+
expected: '!=',
|
|
248
|
+
},
|
|
249
|
+
] as const satisfies readonly TestCase[])('$name', testFn);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe('Date objects', () => {
|
|
253
|
+
test.each([
|
|
254
|
+
{
|
|
255
|
+
name: 'equal date objects',
|
|
256
|
+
x: new Date('2017-06-16T21:36:48.362Z'),
|
|
257
|
+
y: new Date('2017-06-16T21:36:48.362Z'),
|
|
258
|
+
expected: '=',
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: 'not equal date objects',
|
|
262
|
+
x: new Date('2017-06-16T21:36:48.362Z'),
|
|
263
|
+
y: new Date('2017-01-01T00:00:00.000Z'),
|
|
264
|
+
expected: '!=',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: 'date and string are not equal',
|
|
268
|
+
x: new Date('2017-06-16T21:36:48.362Z'),
|
|
269
|
+
y: '2017-06-16T21:36:48.362Z',
|
|
270
|
+
expected: '!=',
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
name: 'date and object are not equal',
|
|
274
|
+
x: new Date('2017-06-16T21:36:48.362Z'),
|
|
275
|
+
y: {},
|
|
276
|
+
expected: '!=',
|
|
277
|
+
},
|
|
278
|
+
] as const satisfies readonly TestCase[])('$name', testFn);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe('RegExp objects', () => {
|
|
282
|
+
test.each([
|
|
283
|
+
{ name: 'equal RegExp objects', x: /foo/, y: /foo/, expected: '=' },
|
|
284
|
+
{
|
|
285
|
+
name: 'not equal RegExp objects (different pattern)',
|
|
286
|
+
x: /foo/,
|
|
287
|
+
y: /bar/,
|
|
288
|
+
expected: '!=',
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: 'not equal RegExp objects (different flags)',
|
|
292
|
+
x: /foo/,
|
|
293
|
+
y: /foo/i,
|
|
294
|
+
expected: '!=',
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: 'RegExp and string are not equal',
|
|
298
|
+
x: /foo/,
|
|
299
|
+
y: 'foo',
|
|
300
|
+
expected: '!=',
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'RegExp and object are not equal',
|
|
304
|
+
x: /foo/,
|
|
305
|
+
y: {},
|
|
306
|
+
expected: '!=',
|
|
307
|
+
},
|
|
308
|
+
] as const satisfies readonly TestCase[])('$name', testFn);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe('functions', () => {
|
|
312
|
+
test.each([
|
|
313
|
+
{ name: 'same function is equal', x: func1, y: func1, expected: '=' },
|
|
314
|
+
{
|
|
315
|
+
name: 'different functions are not equal',
|
|
316
|
+
x: func1,
|
|
317
|
+
y: func2,
|
|
318
|
+
expected: '!=',
|
|
319
|
+
},
|
|
320
|
+
] as const satisfies readonly TestCase[])('$name', testFn);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
describe('sample objects', () => {
|
|
324
|
+
test.each([
|
|
325
|
+
{
|
|
326
|
+
name: 'big object',
|
|
327
|
+
x: {
|
|
328
|
+
prop1: 'value1',
|
|
329
|
+
prop2: 'value2',
|
|
330
|
+
prop3: 'value3',
|
|
331
|
+
prop4: {
|
|
332
|
+
subProp1: 'sub value1',
|
|
333
|
+
subProp2: {
|
|
334
|
+
subSubProp1: 'sub sub value1',
|
|
335
|
+
subSubProp2: [1, 2, { prop2: 1, prop: 2 }, 4, 5],
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
prop5: 1000,
|
|
339
|
+
prop6: new Date(2016, 2, 10),
|
|
340
|
+
},
|
|
341
|
+
y: {
|
|
342
|
+
prop5: 1000,
|
|
343
|
+
prop3: 'value3',
|
|
344
|
+
prop1: 'value1',
|
|
345
|
+
prop2: 'value2',
|
|
346
|
+
prop6: new Date('2016/03/10'),
|
|
347
|
+
prop4: {
|
|
348
|
+
subProp2: {
|
|
349
|
+
subSubProp1: 'sub sub value1',
|
|
350
|
+
subSubProp2: [1, 2, { prop2: 1, prop: 2 }, 4, 5],
|
|
351
|
+
},
|
|
352
|
+
subProp1: 'sub value1',
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
expected: '=',
|
|
356
|
+
},
|
|
357
|
+
] as const satisfies readonly TestCase[])('$name', testFn);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
describe('bigint', () => {
|
|
361
|
+
test.each([
|
|
362
|
+
{ name: 'equal bigints', x: 1n, y: 1n, expected: '=' },
|
|
363
|
+
{ name: 'not equal bigints', x: 1n, y: 2n, expected: '!=' },
|
|
364
|
+
] as const satisfies readonly TestCase[])('$name', testFn);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe('Maps', () => {
|
|
368
|
+
const createMap = <K, V>(
|
|
369
|
+
obj: ReadonlyRecord<string, V>,
|
|
370
|
+
// transformer-ignore-next-line convert-to-readonly
|
|
371
|
+
MapClass: new () => Map<K, V> = Map,
|
|
372
|
+
): ReadonlyMap<K, V> => {
|
|
373
|
+
const mut_m = new MapClass();
|
|
374
|
+
|
|
375
|
+
for (const key in obj) {
|
|
376
|
+
mut_m.set(key as unknown as K, obj[key] as V);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return mut_m;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
test.each([
|
|
383
|
+
{
|
|
384
|
+
name: 'empty maps are equal',
|
|
385
|
+
x: new Map(),
|
|
386
|
+
y: new Map(),
|
|
387
|
+
expected: '=',
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
name: 'empty maps of different class are not equal',
|
|
391
|
+
x: new Map(),
|
|
392
|
+
y: new MyMap(),
|
|
393
|
+
expected: '!=',
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: 'equal maps (same key "order")',
|
|
397
|
+
x: createMap({ a: 1, b: '2' }),
|
|
398
|
+
y: createMap({ a: 1, b: '2' }),
|
|
399
|
+
expected: '=',
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: 'not equal maps (same key "order" - instances of different classes)',
|
|
403
|
+
x: createMap({ a: 1, b: '2' }),
|
|
404
|
+
y: createMap({ a: 1, b: '2' }, MyMap),
|
|
405
|
+
expected: '!=',
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: 'equal maps (different key "order")',
|
|
409
|
+
x: createMap({ a: 1, b: '2' }),
|
|
410
|
+
y: createMap({ b: '2', a: 1 }),
|
|
411
|
+
expected: '=',
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: 'equal maps (different key "order" - instances of the same subclass)',
|
|
415
|
+
x: createMap({ a: 1, b: '2' }, MyMap),
|
|
416
|
+
y: createMap({ b: '2', a: 1 }, MyMap),
|
|
417
|
+
expected: '=',
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
name: 'not equal maps (extra key)',
|
|
421
|
+
x: createMap({ a: 1, b: '2' }),
|
|
422
|
+
y: createMap({ a: 1, b: '2', c: [] }),
|
|
423
|
+
expected: '!=',
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: 'not equal maps (different key value)',
|
|
427
|
+
x: createMap({ a: 1, b: '2', c: 3 }),
|
|
428
|
+
y: createMap({ a: 1, b: '2', c: 4 }),
|
|
429
|
+
expected: '!=',
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: 'not equal maps (different keys)',
|
|
433
|
+
x: createMap({ a: 1, b: '2', c: 3 }),
|
|
434
|
+
y: createMap({ a: 1, b: '2', d: 3 }),
|
|
435
|
+
expected: '!=',
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
name: 'equal maps (same sub-keys)',
|
|
439
|
+
x: new Map([['a', [createMap({ b: 'c' })]]]),
|
|
440
|
+
y: new Map([['a', [createMap({ b: 'c' })]]]),
|
|
441
|
+
expected: '=',
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: 'not equal maps (different sub-key value)',
|
|
445
|
+
x: new Map([['a', [createMap({ b: 'c' })]]]),
|
|
446
|
+
y: new Map([['a', [createMap({ b: 'd' })]]]),
|
|
447
|
+
expected: '!=',
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
name: 'not equal maps (different sub-key)',
|
|
451
|
+
x: new Map([['a', [createMap({ b: 'c' })]]]),
|
|
452
|
+
y: new Map([['a', [createMap({ c: 'c' })]]]),
|
|
453
|
+
expected: '!=',
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
name: 'empty map and empty object are equal',
|
|
457
|
+
x: {},
|
|
458
|
+
y: new Map(),
|
|
459
|
+
expected: '!=',
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
name: 'empty map and empty array are not equal',
|
|
463
|
+
x: [],
|
|
464
|
+
y: new Map(),
|
|
465
|
+
expected: '!=',
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: 'map with extra undefined key is not equal #1',
|
|
469
|
+
x: createMap({}),
|
|
470
|
+
y: createMap({ foo: undefined }),
|
|
471
|
+
expected: '!=',
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: 'map with extra undefined key is not equal #2',
|
|
475
|
+
x: createMap({ foo: undefined }),
|
|
476
|
+
y: createMap({}),
|
|
477
|
+
expected: '!=',
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: 'map with extra undefined key is not equal #3',
|
|
481
|
+
x: createMap({ foo: undefined }),
|
|
482
|
+
y: createMap({ bar: undefined }),
|
|
483
|
+
expected: '!=',
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: 'map and a pseudo map are not equal',
|
|
487
|
+
x: createMap({}),
|
|
488
|
+
y: {
|
|
489
|
+
constructor: Map,
|
|
490
|
+
size: 0,
|
|
491
|
+
has: () => true,
|
|
492
|
+
get: () => 1,
|
|
493
|
+
},
|
|
494
|
+
expected: '!=',
|
|
495
|
+
},
|
|
496
|
+
] as const satisfies readonly TestCase[])('$name', testFn);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
describe('Sets', () => {
|
|
500
|
+
const createSet = <T,>(
|
|
501
|
+
arr: readonly T[],
|
|
502
|
+
// transformer-ignore-next-line convert-to-readonly
|
|
503
|
+
SetClass: new () => Set<T> = Set,
|
|
504
|
+
): ReadonlySet<T> => {
|
|
505
|
+
const mut_s = new SetClass();
|
|
506
|
+
|
|
507
|
+
for (const value of arr) {
|
|
508
|
+
mut_s.add(value);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return mut_s;
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
test.each([
|
|
515
|
+
{
|
|
516
|
+
name: 'empty sets are equal',
|
|
517
|
+
x: new Set(),
|
|
518
|
+
y: new Set(),
|
|
519
|
+
expected: '=',
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
name: 'empty sets of different class are not equal',
|
|
523
|
+
x: new Set(),
|
|
524
|
+
y: new MySet(),
|
|
525
|
+
expected: '!=',
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
name: 'equal sets (same value "order")',
|
|
529
|
+
x: createSet(['a', 'b']),
|
|
530
|
+
y: createSet(['a', 'b']),
|
|
531
|
+
expected: '=',
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
name: 'not equal sets (same value "order" - instances of different classes)',
|
|
535
|
+
x: createSet(['a', 'b']),
|
|
536
|
+
y: createSet(['a', 'b'], MySet),
|
|
537
|
+
expected: '!=',
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
name: 'equal sets (different value "order")',
|
|
541
|
+
x: createSet(['a', 'b']),
|
|
542
|
+
y: createSet(['b', 'a']),
|
|
543
|
+
expected: '=',
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: 'equal sets (different value "order" - instances of the same subclass)',
|
|
547
|
+
x: createSet(['a', 'b'], MySet),
|
|
548
|
+
y: createSet(['b', 'a'], MySet),
|
|
549
|
+
expected: '=',
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
name: 'not equal sets (extra value)',
|
|
553
|
+
x: createSet(['a', 'b']),
|
|
554
|
+
y: createSet(['a', 'b', 'c']),
|
|
555
|
+
expected: '!=',
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
name: 'not equal sets (different values)',
|
|
559
|
+
x: createSet(['a', 'b', 'c']),
|
|
560
|
+
y: createSet(['a', 'b', 'd']),
|
|
561
|
+
expected: '!=',
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
name: 'not equal sets (different instances of objects)',
|
|
565
|
+
x: createSet(['a', {}]),
|
|
566
|
+
y: createSet(['a', {}]),
|
|
567
|
+
expected: '!=',
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
name: 'equal sets (same instances of objects)',
|
|
571
|
+
x: createSet(['a', emptyObj]),
|
|
572
|
+
y: createSet(['a', emptyObj]),
|
|
573
|
+
expected: '=',
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
name: 'empty set and empty object are not equal',
|
|
577
|
+
x: {},
|
|
578
|
+
y: new Set(),
|
|
579
|
+
expected: '!=',
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
name: 'empty set and empty array are not equal',
|
|
583
|
+
x: [],
|
|
584
|
+
y: new Set(),
|
|
585
|
+
expected: '!=',
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
name: 'set with extra undefined value is not equal #1',
|
|
589
|
+
x: createSet([]),
|
|
590
|
+
y: createSet([undefined]),
|
|
591
|
+
expected: '!=',
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
name: 'set with extra undefined value is not equal #2',
|
|
595
|
+
x: createSet([undefined]),
|
|
596
|
+
y: createSet([]),
|
|
597
|
+
expected: '!=',
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
name: 'set and pseudo set are not equal',
|
|
601
|
+
x: new Set(),
|
|
602
|
+
y: {
|
|
603
|
+
constructor: Set,
|
|
604
|
+
size: 0,
|
|
605
|
+
has: () => true,
|
|
606
|
+
},
|
|
607
|
+
expected: '!=',
|
|
608
|
+
},
|
|
609
|
+
] as const satisfies readonly TestCase[])('$name', testFn);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
describe('Typed arrays', () => {
|
|
613
|
+
test.each([
|
|
614
|
+
{
|
|
615
|
+
name: 'two empty arrays of the same class are equal',
|
|
616
|
+
x: new Int32Array([]),
|
|
617
|
+
y: new Int32Array([]),
|
|
618
|
+
expected: '=',
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
name: 'two empty arrays of the different class are not equal',
|
|
622
|
+
x: new Int32Array([]),
|
|
623
|
+
y: new Int16Array([]),
|
|
624
|
+
expected: '!=',
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
name: 'equal arrays',
|
|
628
|
+
x: new Int32Array([1, 2, 3]),
|
|
629
|
+
y: new Int32Array([1, 2, 3]),
|
|
630
|
+
expected: '=',
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
name: 'equal BigUint64Array arrays',
|
|
634
|
+
x: new BigUint64Array([1n, 2n, 3n]),
|
|
635
|
+
y: new BigUint64Array([1n, 2n, 3n]),
|
|
636
|
+
expected: '=',
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
name: 'not equal BigUint64Array arrays',
|
|
640
|
+
x: new BigUint64Array([1n, 2n, 3n]),
|
|
641
|
+
y: new BigUint64Array([1n, 2n, 4n]),
|
|
642
|
+
expected: '!=',
|
|
643
|
+
},
|
|
644
|
+
] as const satisfies readonly TestCase[])('$name', testFn);
|
|
645
|
+
|
|
646
|
+
test.each([
|
|
647
|
+
{
|
|
648
|
+
name: 'not equal arrays (same items, different class)',
|
|
649
|
+
x: new Int32Array([1, 2, 3]),
|
|
650
|
+
y: new Int16Array([1, 2, 3]),
|
|
651
|
+
expected: '!=',
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: 'not equal arrays (different item)',
|
|
655
|
+
x: new Int32Array([1, 2, 3]),
|
|
656
|
+
y: new Int32Array([1, 2, 4]),
|
|
657
|
+
expected: '!=',
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
name: 'not equal arrays (different length)',
|
|
661
|
+
x: new Int32Array([1, 2, 3]),
|
|
662
|
+
y: new Int32Array([1, 2]),
|
|
663
|
+
expected: '!=',
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: 'pseudo array and equivalent typed array are not equal',
|
|
667
|
+
x: { 0: 1, 1: 2, length: 2, constructor: Int32Array },
|
|
668
|
+
y: new Int32Array([1, 2]),
|
|
669
|
+
expected: '!=',
|
|
670
|
+
},
|
|
671
|
+
] as const satisfies readonly TestCase[])('$name', testFn);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
describe('React component', () => {
|
|
675
|
+
let mut_renderCount = 0;
|
|
676
|
+
|
|
677
|
+
class ChildWithShouldComponentUpdate extends React.Component {
|
|
678
|
+
override shouldComponentUpdate(nextProps: any): boolean {
|
|
679
|
+
// this.props.children contains React elements with circular references to their owner (Container)
|
|
680
|
+
return !fastDeepEqualUntyped(this.props, nextProps);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
override render(): null {
|
|
684
|
+
mut_renderCount += 1;
|
|
685
|
+
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
describe('element (with circular references)', () => {
|
|
691
|
+
test('React elements with _owner can be compared without infinite loop', () => {
|
|
692
|
+
// Create React elements that contain _owner property (circular reference)
|
|
693
|
+
const element1 = React.createElement(
|
|
694
|
+
'div',
|
|
695
|
+
{ className: 'test' },
|
|
696
|
+
'content',
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
const element2 = React.createElement(
|
|
700
|
+
'div',
|
|
701
|
+
{ className: 'test' },
|
|
702
|
+
'content',
|
|
703
|
+
);
|
|
704
|
+
|
|
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);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
test('React elements with different props are not equal', () => {
|
|
715
|
+
const element1 = React.createElement('div', { className: 'test1' });
|
|
716
|
+
|
|
717
|
+
const element2 = React.createElement('div', { className: 'test2' });
|
|
718
|
+
|
|
719
|
+
assert.isFalse(fastDeepEqualUntyped(element1, element2));
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
test('shouldComponentUpdate works correctly with circular references', () => {
|
|
723
|
+
mut_renderCount = 0;
|
|
724
|
+
|
|
725
|
+
// Create a child component instance
|
|
726
|
+
const child = new ChildWithShouldComponentUpdate({
|
|
727
|
+
children: [
|
|
728
|
+
React.createElement('h1', { key: 'h1' }, 'Title'),
|
|
729
|
+
React.createElement('h2', { key: 'h2' }, 'Subtitle'),
|
|
730
|
+
],
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
// Initial render
|
|
734
|
+
child.render();
|
|
735
|
+
|
|
736
|
+
assert.strictEqual(mut_renderCount, 1);
|
|
737
|
+
|
|
738
|
+
// Update with same props structure - should not re-render
|
|
739
|
+
const sameProps = {
|
|
740
|
+
children: [
|
|
741
|
+
React.createElement('h1', { key: 'h1' }, 'Title'),
|
|
742
|
+
React.createElement('h2', { key: 'h2' }, 'Subtitle'),
|
|
743
|
+
],
|
|
744
|
+
} as const;
|
|
745
|
+
|
|
746
|
+
if (child.shouldComponentUpdate(sameProps)) {
|
|
747
|
+
child.render();
|
|
748
|
+
}
|
|
749
|
+
|
|
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);
|
|
753
|
+
|
|
754
|
+
// Update with different props - should re-render
|
|
755
|
+
const differentProps = {
|
|
756
|
+
children: [
|
|
757
|
+
React.createElement('h1', { key: 'h1' }, 'New Title'),
|
|
758
|
+
React.createElement('h2', { key: 'h2' }, 'Subtitle'),
|
|
759
|
+
],
|
|
760
|
+
} as const;
|
|
761
|
+
|
|
762
|
+
if (child.shouldComponentUpdate(differentProps)) {
|
|
763
|
+
child.render();
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Should have rendered at least once more
|
|
767
|
+
assert.isTrue(mut_renderCount >= 2);
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
});
|