@esportsplus/reactivity 0.34.0 → 0.36.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.
- package/README.md +9 -7
- package/bench/reactive/array.bench.ts +17 -17
- package/build/compiler/array.d.ts +2 -2
- package/build/compiler/array.js +10 -52
- package/build/compiler/constants.d.ts +4 -4
- package/build/compiler/constants.js +20 -4
- package/build/compiler/index.js +25 -38
- package/build/compiler/object.d.ts +2 -2
- package/build/compiler/object.js +5 -16
- package/build/compiler/primitives.d.ts +6 -2
- package/build/compiler/primitives.js +31 -38
- package/build/compiler/types.d.ts +3 -1
- package/build/constants.d.ts +2 -2
- package/build/constants.js +2 -2
- package/build/reactive/array.d.ts +1 -1
- package/build/reactive/array.js +26 -15
- package/build/reactive/index.js +5 -14
- package/build/reactive/object.js +1 -1
- package/build/system.d.ts +1 -4
- package/build/system.js +105 -83
- package/build/types.d.ts +2 -7
- package/package.json +5 -5
- package/pnpm-workspace.yaml +2 -1
- package/src/compiler/array.ts +12 -62
- package/src/compiler/constants.ts +21 -5
- package/src/compiler/index.ts +38 -69
- package/src/compiler/object.ts +7 -24
- package/src/compiler/primitives.ts +51 -49
- package/src/compiler/types.ts +4 -1
- package/src/constants.ts +4 -4
- package/src/reactive/array.ts +33 -17
- package/src/reactive/index.ts +5 -17
- package/src/reactive/object.ts +1 -1
- package/src/system.ts +138 -101
- package/src/types.ts +2 -9
- package/test/async-computed.test.ts +2 -2
- package/test/compiler/compiler.test.ts +38 -22
- package/test/effects.test.ts +37 -1
- package/test/lib/uncaught.ts +26 -0
- package/test/reactive/array.test.ts +169 -99
- package/test/reactive/nested.test.ts +14 -14
- package/test/reactive/objects.test.ts +1 -1
- package/test/reactive/reactive.test.ts +49 -0
- package/test/system.test.ts +130 -22
|
@@ -3,6 +3,7 @@ import { effect, read, signal, write } from '~/system';
|
|
|
3
3
|
import { ReactiveArray } from '~/reactive/array';
|
|
4
4
|
import { ReactiveObject } from '~/reactive/object';
|
|
5
5
|
import reactive from '~/reactive/index';
|
|
6
|
+
import { captureUncaught } from '../lib/uncaught';
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
describe('ReactiveArray', () => {
|
|
@@ -15,7 +16,7 @@ describe('ReactiveArray', () => {
|
|
|
15
16
|
});
|
|
16
17
|
|
|
17
18
|
it('creates array with initial items', () => {
|
|
18
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
19
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
19
20
|
|
|
20
21
|
expect(arr.length).toBe(3);
|
|
21
22
|
expect(arr[0]).toBe(1);
|
|
@@ -24,7 +25,7 @@ describe('ReactiveArray', () => {
|
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
it('$length is reactive', async () => {
|
|
27
|
-
let arr = new ReactiveArray(1, 2, 3),
|
|
28
|
+
let arr = new ReactiveArray([1, 2, 3]),
|
|
28
29
|
lengths: number[] = [];
|
|
29
30
|
|
|
30
31
|
effect(() => {
|
|
@@ -38,12 +39,32 @@ describe('ReactiveArray', () => {
|
|
|
38
39
|
|
|
39
40
|
expect(lengths).toEqual([3, 4]);
|
|
40
41
|
});
|
|
42
|
+
|
|
43
|
+
it('a single numeric item does not create holes', () => {
|
|
44
|
+
let arr = reactive([5]);
|
|
45
|
+
|
|
46
|
+
expect([...arr]).toEqual([5]);
|
|
47
|
+
expect(arr.length).toBe(1);
|
|
48
|
+
expect((arr as unknown as { $length: number }).$length).toBe(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('empty and multi-item reactive arrays are unchanged', () => {
|
|
52
|
+
expect([...reactive([])]).toEqual([]);
|
|
53
|
+
expect([...reactive([1, 2, 3])]).toEqual([1, 2, 3]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('does not overflow the stack for a large array', () => {
|
|
57
|
+
let arr = new ReactiveArray(new Array(500000).fill(0));
|
|
58
|
+
|
|
59
|
+
expect(arr.length).toBe(500000);
|
|
60
|
+
expect(arr.$length).toBe(500000);
|
|
61
|
+
});
|
|
41
62
|
});
|
|
42
63
|
|
|
43
64
|
|
|
44
65
|
describe('$set', () => {
|
|
45
66
|
it('sets value at index', () => {
|
|
46
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
67
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
47
68
|
|
|
48
69
|
arr.$set(1, 20);
|
|
49
70
|
|
|
@@ -51,7 +72,7 @@ describe('ReactiveArray', () => {
|
|
|
51
72
|
});
|
|
52
73
|
|
|
53
74
|
it('skips if same value', () => {
|
|
54
|
-
let arr = new ReactiveArray(1, 2, 3),
|
|
75
|
+
let arr = new ReactiveArray([1, 2, 3]),
|
|
55
76
|
dispatched = false;
|
|
56
77
|
|
|
57
78
|
arr.on('set', () => { dispatched = true; });
|
|
@@ -61,7 +82,7 @@ describe('ReactiveArray', () => {
|
|
|
61
82
|
});
|
|
62
83
|
|
|
63
84
|
it('dispatches set event', () => {
|
|
64
|
-
let arr = new ReactiveArray(1, 2, 3),
|
|
85
|
+
let arr = new ReactiveArray([1, 2, 3]),
|
|
65
86
|
events: { index: number; item: number }[] = [];
|
|
66
87
|
|
|
67
88
|
arr.on('set', (e) => { events.push(e); });
|
|
@@ -71,7 +92,7 @@ describe('ReactiveArray', () => {
|
|
|
71
92
|
});
|
|
72
93
|
|
|
73
94
|
it('updates length when setting beyond current length', async () => {
|
|
74
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
95
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
75
96
|
|
|
76
97
|
arr.$set(5, 99);
|
|
77
98
|
|
|
@@ -79,7 +100,7 @@ describe('ReactiveArray', () => {
|
|
|
79
100
|
});
|
|
80
101
|
|
|
81
102
|
it('$set beyond length updates $length reactively', async () => {
|
|
82
|
-
let arr = new ReactiveArray(1, 2, 3),
|
|
103
|
+
let arr = new ReactiveArray([1, 2, 3]),
|
|
83
104
|
lengths: number[] = [];
|
|
84
105
|
|
|
85
106
|
effect(() => {
|
|
@@ -125,7 +146,7 @@ describe('ReactiveArray', () => {
|
|
|
125
146
|
});
|
|
126
147
|
|
|
127
148
|
it('$set with negative index does not affect length', () => {
|
|
128
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
149
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
129
150
|
|
|
130
151
|
arr.$set(-1 as any, 42);
|
|
131
152
|
|
|
@@ -135,7 +156,7 @@ describe('ReactiveArray', () => {
|
|
|
135
156
|
});
|
|
136
157
|
|
|
137
158
|
it('$set at large index updates $length', async () => {
|
|
138
|
-
let arr = new ReactiveArray(1, 2, 3),
|
|
159
|
+
let arr = new ReactiveArray([1, 2, 3]),
|
|
139
160
|
lengths: number[] = [];
|
|
140
161
|
|
|
141
162
|
effect(() => {
|
|
@@ -155,13 +176,13 @@ describe('ReactiveArray', () => {
|
|
|
155
176
|
|
|
156
177
|
describe('$length', () => {
|
|
157
178
|
it('getter returns reactive length', () => {
|
|
158
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
179
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
159
180
|
|
|
160
181
|
expect(arr.$length).toBe(3);
|
|
161
182
|
});
|
|
162
183
|
|
|
163
184
|
it('setter truncates array via splice', () => {
|
|
164
|
-
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
185
|
+
let arr = new ReactiveArray([1, 2, 3, 4, 5]);
|
|
165
186
|
|
|
166
187
|
arr.$length = 2;
|
|
167
188
|
|
|
@@ -171,7 +192,7 @@ describe('ReactiveArray', () => {
|
|
|
171
192
|
});
|
|
172
193
|
|
|
173
194
|
it('throws when setting length larger than current', () => {
|
|
174
|
-
let arr = new ReactiveArray(1, 2);
|
|
195
|
+
let arr = new ReactiveArray([1, 2]);
|
|
175
196
|
|
|
176
197
|
expect(() => { arr.$length = 5; }).toThrow();
|
|
177
198
|
});
|
|
@@ -189,7 +210,7 @@ describe('ReactiveArray', () => {
|
|
|
189
210
|
});
|
|
190
211
|
|
|
191
212
|
it('returns new length', () => {
|
|
192
|
-
let arr = new ReactiveArray(1);
|
|
213
|
+
let arr = new ReactiveArray([1]);
|
|
193
214
|
|
|
194
215
|
expect(arr.push(2, 3)).toBe(3);
|
|
195
216
|
});
|
|
@@ -205,7 +226,7 @@ describe('ReactiveArray', () => {
|
|
|
205
226
|
});
|
|
206
227
|
|
|
207
228
|
it('no-op for empty push', () => {
|
|
208
|
-
let arr = new ReactiveArray(1),
|
|
229
|
+
let arr = new ReactiveArray([1]),
|
|
209
230
|
dispatched = false;
|
|
210
231
|
|
|
211
232
|
arr.on('push', () => { dispatched = true; });
|
|
@@ -229,7 +250,7 @@ describe('ReactiveArray', () => {
|
|
|
229
250
|
|
|
230
251
|
describe('pop', () => {
|
|
231
252
|
it('removes last item', () => {
|
|
232
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
253
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
233
254
|
|
|
234
255
|
expect(arr.pop()).toBe(3);
|
|
235
256
|
expect(arr.length).toBe(2);
|
|
@@ -242,7 +263,7 @@ describe('ReactiveArray', () => {
|
|
|
242
263
|
});
|
|
243
264
|
|
|
244
265
|
it('dispatches pop event', () => {
|
|
245
|
-
let arr = new ReactiveArray(1, 2),
|
|
266
|
+
let arr = new ReactiveArray([1, 2]),
|
|
246
267
|
events: { item: number }[] = [];
|
|
247
268
|
|
|
248
269
|
arr.on('pop', (e) => { events.push(e); });
|
|
@@ -262,7 +283,7 @@ describe('ReactiveArray', () => {
|
|
|
262
283
|
});
|
|
263
284
|
|
|
264
285
|
it('does not dispatch when popping explicit undefined value', () => {
|
|
265
|
-
let arr = new ReactiveArray<number | undefined>(1, undefined),
|
|
286
|
+
let arr = new ReactiveArray<number | undefined>([1, undefined]),
|
|
266
287
|
dispatched = false;
|
|
267
288
|
|
|
268
289
|
arr.on('pop', () => { dispatched = true; });
|
|
@@ -274,7 +295,7 @@ describe('ReactiveArray', () => {
|
|
|
274
295
|
});
|
|
275
296
|
|
|
276
297
|
it('does not update reactive length when popping explicit undefined value', async () => {
|
|
277
|
-
let arr = new ReactiveArray<number | undefined>(1, undefined),
|
|
298
|
+
let arr = new ReactiveArray<number | undefined>([1, undefined]),
|
|
278
299
|
lengths: number[] = [];
|
|
279
300
|
|
|
280
301
|
effect(() => {
|
|
@@ -293,7 +314,7 @@ describe('ReactiveArray', () => {
|
|
|
293
314
|
|
|
294
315
|
describe('shift', () => {
|
|
295
316
|
it('removes first item', () => {
|
|
296
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
317
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
297
318
|
|
|
298
319
|
expect(arr.shift()).toBe(1);
|
|
299
320
|
expect(arr.length).toBe(2);
|
|
@@ -307,7 +328,7 @@ describe('ReactiveArray', () => {
|
|
|
307
328
|
});
|
|
308
329
|
|
|
309
330
|
it('dispatches shift event', () => {
|
|
310
|
-
let arr = new ReactiveArray(10, 20),
|
|
331
|
+
let arr = new ReactiveArray([10, 20]),
|
|
311
332
|
events: { item: number }[] = [];
|
|
312
333
|
|
|
313
334
|
arr.on('shift', (e) => { events.push(e); });
|
|
@@ -317,7 +338,7 @@ describe('ReactiveArray', () => {
|
|
|
317
338
|
});
|
|
318
339
|
|
|
319
340
|
it('does not dispatch when shifting explicit undefined value', () => {
|
|
320
|
-
let arr = new ReactiveArray<number | undefined>(undefined, 1, 2),
|
|
341
|
+
let arr = new ReactiveArray<number | undefined>([undefined, 1, 2]),
|
|
321
342
|
dispatched = false;
|
|
322
343
|
|
|
323
344
|
arr.on('shift', () => { dispatched = true; });
|
|
@@ -330,7 +351,7 @@ describe('ReactiveArray', () => {
|
|
|
330
351
|
});
|
|
331
352
|
|
|
332
353
|
it('does not update reactive length when shifting explicit undefined value', async () => {
|
|
333
|
-
let arr = new ReactiveArray<number | undefined>(undefined, 1, 2),
|
|
354
|
+
let arr = new ReactiveArray<number | undefined>([undefined, 1, 2]),
|
|
334
355
|
lengths: number[] = [];
|
|
335
356
|
|
|
336
357
|
effect(() => {
|
|
@@ -360,7 +381,7 @@ describe('ReactiveArray', () => {
|
|
|
360
381
|
});
|
|
361
382
|
|
|
362
383
|
it('returns new length', () => {
|
|
363
|
-
let arr = new ReactiveArray(1);
|
|
384
|
+
let arr = new ReactiveArray([1]);
|
|
364
385
|
|
|
365
386
|
expect(arr.unshift(0)).toBe(2);
|
|
366
387
|
});
|
|
@@ -376,7 +397,7 @@ describe('ReactiveArray', () => {
|
|
|
376
397
|
});
|
|
377
398
|
|
|
378
399
|
it('no-op for empty unshift', async () => {
|
|
379
|
-
let arr = new ReactiveArray(1, 2),
|
|
400
|
+
let arr = new ReactiveArray([1, 2]),
|
|
380
401
|
dispatched = false,
|
|
381
402
|
lengths: number[] = [];
|
|
382
403
|
|
|
@@ -399,7 +420,7 @@ describe('ReactiveArray', () => {
|
|
|
399
420
|
|
|
400
421
|
describe('splice', () => {
|
|
401
422
|
it('removes items', () => {
|
|
402
|
-
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
423
|
+
let arr = new ReactiveArray([1, 2, 3, 4, 5]);
|
|
403
424
|
|
|
404
425
|
let removed = arr.splice(1, 2);
|
|
405
426
|
|
|
@@ -408,7 +429,7 @@ describe('ReactiveArray', () => {
|
|
|
408
429
|
});
|
|
409
430
|
|
|
410
431
|
it('inserts items', () => {
|
|
411
|
-
let arr = new ReactiveArray(1, 4, 5);
|
|
432
|
+
let arr = new ReactiveArray([1, 4, 5]);
|
|
412
433
|
|
|
413
434
|
arr.splice(1, 0, 2, 3);
|
|
414
435
|
|
|
@@ -416,7 +437,7 @@ describe('ReactiveArray', () => {
|
|
|
416
437
|
});
|
|
417
438
|
|
|
418
439
|
it('replaces items', () => {
|
|
419
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
440
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
420
441
|
|
|
421
442
|
arr.splice(1, 1, 20);
|
|
422
443
|
|
|
@@ -424,7 +445,7 @@ describe('ReactiveArray', () => {
|
|
|
424
445
|
});
|
|
425
446
|
|
|
426
447
|
it('dispatches splice event', () => {
|
|
427
|
-
let arr = new ReactiveArray(1, 2, 3),
|
|
448
|
+
let arr = new ReactiveArray([1, 2, 3]),
|
|
428
449
|
events: { start: number; deleteCount: number; items: number[] }[] = [];
|
|
429
450
|
|
|
430
451
|
arr.on('splice', (e) => { events.push(e); });
|
|
@@ -434,7 +455,7 @@ describe('ReactiveArray', () => {
|
|
|
434
455
|
});
|
|
435
456
|
|
|
436
457
|
it('no event when nothing changes', () => {
|
|
437
|
-
let arr = new ReactiveArray(1, 2, 3),
|
|
458
|
+
let arr = new ReactiveArray([1, 2, 3]),
|
|
438
459
|
dispatched = false;
|
|
439
460
|
|
|
440
461
|
arr.on('splice', () => { dispatched = true; });
|
|
@@ -444,7 +465,7 @@ describe('ReactiveArray', () => {
|
|
|
444
465
|
});
|
|
445
466
|
|
|
446
467
|
it('splice with start beyond array length removes nothing', () => {
|
|
447
|
-
let arr = new ReactiveArray(1, 2, 3),
|
|
468
|
+
let arr = new ReactiveArray([1, 2, 3]),
|
|
448
469
|
dispatched = false;
|
|
449
470
|
|
|
450
471
|
arr.on('splice', () => { dispatched = true; });
|
|
@@ -457,7 +478,7 @@ describe('ReactiveArray', () => {
|
|
|
457
478
|
});
|
|
458
479
|
|
|
459
480
|
it('splice with negative start removes from end', () => {
|
|
460
|
-
let arr = new ReactiveArray(1, 2, 3, 4, 5),
|
|
481
|
+
let arr = new ReactiveArray([1, 2, 3, 4, 5]),
|
|
461
482
|
events: { start: number; deleteCount: number; items: number[] }[] = [];
|
|
462
483
|
|
|
463
484
|
arr.on('splice', (e) => { events.push(e); });
|
|
@@ -473,7 +494,7 @@ describe('ReactiveArray', () => {
|
|
|
473
494
|
|
|
474
495
|
describe('concat', () => {
|
|
475
496
|
it('appends arrays', () => {
|
|
476
|
-
let arr = new ReactiveArray(1, 2);
|
|
497
|
+
let arr = new ReactiveArray([1, 2]);
|
|
477
498
|
|
|
478
499
|
arr.concat([3, 4], [5]);
|
|
479
500
|
|
|
@@ -490,7 +511,7 @@ describe('ReactiveArray', () => {
|
|
|
490
511
|
});
|
|
491
512
|
|
|
492
513
|
it('returns this (mutating)', () => {
|
|
493
|
-
let arr = new ReactiveArray(1);
|
|
514
|
+
let arr = new ReactiveArray([1]);
|
|
494
515
|
let result = arr.concat([2]);
|
|
495
516
|
|
|
496
517
|
expect(result).toBe(arr);
|
|
@@ -507,7 +528,7 @@ describe('ReactiveArray', () => {
|
|
|
507
528
|
});
|
|
508
529
|
|
|
509
530
|
it('no event when nothing added', () => {
|
|
510
|
-
let arr = new ReactiveArray(1),
|
|
531
|
+
let arr = new ReactiveArray([1]),
|
|
511
532
|
dispatched = false;
|
|
512
533
|
|
|
513
534
|
arr.on('concat', () => { dispatched = true; });
|
|
@@ -517,7 +538,7 @@ describe('ReactiveArray', () => {
|
|
|
517
538
|
});
|
|
518
539
|
|
|
519
540
|
it('concat with mixed arrays and single primitive values', () => {
|
|
520
|
-
let arr = new ReactiveArray(1, 2),
|
|
541
|
+
let arr = new ReactiveArray([1, 2]),
|
|
521
542
|
events: number[][] = [];
|
|
522
543
|
|
|
523
544
|
arr.on('concat', (e) => { events.push(e.items); });
|
|
@@ -531,7 +552,7 @@ describe('ReactiveArray', () => {
|
|
|
531
552
|
|
|
532
553
|
describe('reverse', () => {
|
|
533
554
|
it('reverses in place', () => {
|
|
534
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
555
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
535
556
|
|
|
536
557
|
arr.reverse();
|
|
537
558
|
|
|
@@ -539,13 +560,13 @@ describe('ReactiveArray', () => {
|
|
|
539
560
|
});
|
|
540
561
|
|
|
541
562
|
it('returns this', () => {
|
|
542
|
-
let arr = new ReactiveArray(1, 2);
|
|
563
|
+
let arr = new ReactiveArray([1, 2]);
|
|
543
564
|
|
|
544
565
|
expect(arr.reverse()).toBe(arr);
|
|
545
566
|
});
|
|
546
567
|
|
|
547
568
|
it('dispatches reverse event', () => {
|
|
548
|
-
let arr = new ReactiveArray(1, 2),
|
|
569
|
+
let arr = new ReactiveArray([1, 2]),
|
|
549
570
|
dispatched = false;
|
|
550
571
|
|
|
551
572
|
arr.on('reverse', () => { dispatched = true; });
|
|
@@ -558,7 +579,7 @@ describe('ReactiveArray', () => {
|
|
|
558
579
|
|
|
559
580
|
describe('sort', () => {
|
|
560
581
|
it('sorts in place', () => {
|
|
561
|
-
let arr = new ReactiveArray(3, 1, 2);
|
|
582
|
+
let arr = new ReactiveArray([3, 1, 2]);
|
|
562
583
|
|
|
563
584
|
arr.sort((a, b) => a - b);
|
|
564
585
|
|
|
@@ -566,13 +587,13 @@ describe('ReactiveArray', () => {
|
|
|
566
587
|
});
|
|
567
588
|
|
|
568
589
|
it('returns this', () => {
|
|
569
|
-
let arr = new ReactiveArray(3, 1);
|
|
590
|
+
let arr = new ReactiveArray([3, 1]);
|
|
570
591
|
|
|
571
592
|
expect(arr.sort()).toBe(arr);
|
|
572
593
|
});
|
|
573
594
|
|
|
574
595
|
it('dispatches sort event with order', () => {
|
|
575
|
-
let arr = new ReactiveArray(3, 1, 2),
|
|
596
|
+
let arr = new ReactiveArray([3, 1, 2]),
|
|
576
597
|
order: number[] = [];
|
|
577
598
|
|
|
578
599
|
arr.on('sort', (e) => { order = e.order; });
|
|
@@ -584,7 +605,7 @@ describe('ReactiveArray', () => {
|
|
|
584
605
|
});
|
|
585
606
|
|
|
586
607
|
it('handles duplicates in sort', () => {
|
|
587
|
-
let arr = new ReactiveArray(2, 1, 2),
|
|
608
|
+
let arr = new ReactiveArray([2, 1, 2]),
|
|
588
609
|
order: number[] = [];
|
|
589
610
|
|
|
590
611
|
arr.on('sort', (e) => { order = e.order; });
|
|
@@ -593,11 +614,32 @@ describe('ReactiveArray', () => {
|
|
|
593
614
|
expect([...arr]).toEqual([1, 2, 2]);
|
|
594
615
|
});
|
|
595
616
|
|
|
617
|
+
it('order maps duplicate values stably (first stays first)', () => {
|
|
618
|
+
let arr = new ReactiveArray([3, 1, 3, 2]),
|
|
619
|
+
order: number[] = [];
|
|
620
|
+
|
|
621
|
+
arr.on('sort', (e) => { order = e.order; });
|
|
622
|
+
arr.sort((a, b) => a - b);
|
|
623
|
+
|
|
624
|
+
expect([...arr]).toEqual([1, 2, 3, 3]);
|
|
625
|
+
expect(order).toEqual([1, 3, 0, 2]);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it('all-equal array yields identity order', () => {
|
|
629
|
+
let arr = new ReactiveArray([5, 5, 5]),
|
|
630
|
+
order: number[] = [];
|
|
631
|
+
|
|
632
|
+
arr.on('sort', (e) => { order = e.order; });
|
|
633
|
+
arr.sort((a, b) => a - b);
|
|
634
|
+
|
|
635
|
+
expect(order).toEqual([0, 1, 2]);
|
|
636
|
+
});
|
|
637
|
+
|
|
596
638
|
it('sort preserves object references', () => {
|
|
597
639
|
let a = { id: 3 },
|
|
598
640
|
b = { id: 1 },
|
|
599
641
|
c = { id: 2 },
|
|
600
|
-
arr = new ReactiveArray(a, b, c);
|
|
642
|
+
arr = new ReactiveArray([a, b, c]);
|
|
601
643
|
|
|
602
644
|
arr.sort((x, y) => x.id - y.id);
|
|
603
645
|
|
|
@@ -611,7 +653,7 @@ describe('ReactiveArray', () => {
|
|
|
611
653
|
|
|
612
654
|
describe('clear', () => {
|
|
613
655
|
it('empties array', () => {
|
|
614
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
656
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
615
657
|
|
|
616
658
|
arr.clear();
|
|
617
659
|
|
|
@@ -619,7 +661,7 @@ describe('ReactiveArray', () => {
|
|
|
619
661
|
});
|
|
620
662
|
|
|
621
663
|
it('dispatches clear event', () => {
|
|
622
|
-
let arr = new ReactiveArray(1, 2),
|
|
664
|
+
let arr = new ReactiveArray([1, 2]),
|
|
623
665
|
dispatched = false;
|
|
624
666
|
|
|
625
667
|
arr.on('clear', () => { dispatched = true; });
|
|
@@ -629,7 +671,7 @@ describe('ReactiveArray', () => {
|
|
|
629
671
|
});
|
|
630
672
|
|
|
631
673
|
it('updates reactive length to 0', async () => {
|
|
632
|
-
let arr = new ReactiveArray(1, 2, 3),
|
|
674
|
+
let arr = new ReactiveArray([1, 2, 3]),
|
|
633
675
|
lengths: number[] = [];
|
|
634
676
|
|
|
635
677
|
effect(() => { lengths.push(arr.$length); });
|
|
@@ -643,7 +685,7 @@ describe('ReactiveArray', () => {
|
|
|
643
685
|
|
|
644
686
|
describe('dispose', () => {
|
|
645
687
|
it('empties array', () => {
|
|
646
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
688
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
647
689
|
|
|
648
690
|
arr.dispose();
|
|
649
691
|
|
|
@@ -651,7 +693,7 @@ describe('ReactiveArray', () => {
|
|
|
651
693
|
});
|
|
652
694
|
|
|
653
695
|
it('sets reactive length to 0', async () => {
|
|
654
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
696
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
655
697
|
|
|
656
698
|
arr.dispose();
|
|
657
699
|
|
|
@@ -696,27 +738,33 @@ describe('ReactiveArray', () => {
|
|
|
696
738
|
expect(calls).toBe(1);
|
|
697
739
|
});
|
|
698
740
|
|
|
699
|
-
it('listener errors
|
|
741
|
+
it('listener errors surface asynchronously and the listener is removed', async () => {
|
|
700
742
|
let arr = new ReactiveArray<number>(),
|
|
701
|
-
calls = 0
|
|
743
|
+
calls = 0,
|
|
744
|
+
captured = await captureUncaught(() => {
|
|
745
|
+
arr.on('push', () => { throw new Error('reactivity: test'); });
|
|
746
|
+
arr.on('push', () => { calls++; });
|
|
702
747
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
arr.push(1);
|
|
707
|
-
arr.push(2);
|
|
748
|
+
arr.push(1);
|
|
749
|
+
arr.push(2);
|
|
750
|
+
});
|
|
708
751
|
|
|
709
752
|
expect(calls).toBe(2);
|
|
753
|
+
expect(captured.length).toBe(1);
|
|
754
|
+
expect((captured[0] as Error).message).toBe('reactivity: test');
|
|
710
755
|
});
|
|
711
756
|
|
|
712
|
-
it('null slots reused for new listeners', () => {
|
|
713
|
-
let arr = new ReactiveArray<number>()
|
|
757
|
+
it('null slots reused for new listeners', async () => {
|
|
758
|
+
let arr = new ReactiveArray<number>(),
|
|
759
|
+
fn1 = () => { throw new Error('reactivity: remove me'); },
|
|
760
|
+
fn2 = vi.fn();
|
|
714
761
|
|
|
715
|
-
let
|
|
716
|
-
|
|
762
|
+
let captured = await captureUncaught(() => {
|
|
763
|
+
arr.on('push', fn1);
|
|
764
|
+
arr.push(1); // fn1 throws and gets nulled
|
|
765
|
+
});
|
|
717
766
|
|
|
718
|
-
|
|
719
|
-
arr.push(1); // fn1 throws and gets nulled
|
|
767
|
+
expect(captured.length).toBe(1);
|
|
720
768
|
|
|
721
769
|
arr.on('push', fn2);
|
|
722
770
|
arr.push(2);
|
|
@@ -724,23 +772,26 @@ describe('ReactiveArray', () => {
|
|
|
724
772
|
expect(fn2).toHaveBeenCalledTimes(1);
|
|
725
773
|
});
|
|
726
774
|
|
|
727
|
-
it('multiple listeners removed via errors, new listeners fill holes in order', () => {
|
|
775
|
+
it('multiple listeners removed via errors, new listeners fill holes in order', async () => {
|
|
728
776
|
let arr = new ReactiveArray<number>(),
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
let
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
777
|
+
err1 = () => { throw new Error('reactivity: err1'); },
|
|
778
|
+
err2 = () => { throw new Error('reactivity: err2'); },
|
|
779
|
+
fn3 = vi.fn();
|
|
780
|
+
|
|
781
|
+
let captured = await captureUncaught(() => {
|
|
782
|
+
arr.on('push', err1);
|
|
783
|
+
arr.on('push', err2);
|
|
784
|
+
arr.on('push', fn3);
|
|
785
|
+
arr.push(1); // err1 and err2 throw, slots 0 and 1 nulled
|
|
786
|
+
});
|
|
739
787
|
|
|
740
788
|
expect(fn3).toHaveBeenCalledTimes(1);
|
|
789
|
+
expect(captured.length).toBe(1);
|
|
790
|
+
expect(captured[0]).toBeInstanceOf(AggregateError);
|
|
791
|
+
expect((captured[0] as AggregateError).errors.length).toBe(2);
|
|
741
792
|
|
|
742
|
-
let fn4 = vi.fn()
|
|
743
|
-
|
|
793
|
+
let fn4 = vi.fn(),
|
|
794
|
+
fn5 = vi.fn();
|
|
744
795
|
|
|
745
796
|
arr.on('push', fn4); // fills hole at slot 0
|
|
746
797
|
arr.on('push', fn5); // fills hole at slot 1
|
|
@@ -751,39 +802,44 @@ describe('ReactiveArray', () => {
|
|
|
751
802
|
expect(fn5).toHaveBeenCalledTimes(1);
|
|
752
803
|
});
|
|
753
804
|
|
|
754
|
-
it('trailing null slots cleaned after dispatch', () => {
|
|
755
|
-
let arr = new ReactiveArray<number>()
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
let err2 = () => { throw new Error('remove'); };
|
|
805
|
+
it('trailing null slots cleaned after dispatch', async () => {
|
|
806
|
+
let arr = new ReactiveArray<number>(),
|
|
807
|
+
fn1 = vi.fn(),
|
|
808
|
+
err2 = () => { throw new Error('reactivity: remove'); };
|
|
759
809
|
|
|
760
|
-
|
|
761
|
-
|
|
810
|
+
let captured = await captureUncaught(() => {
|
|
811
|
+
arr.on('push', fn1);
|
|
812
|
+
arr.on('push', err2);
|
|
762
813
|
|
|
763
|
-
|
|
764
|
-
|
|
814
|
+
// Before dispatch: listeners = [fn1, err2] (length 2)
|
|
815
|
+
arr.push(1); // err2 throws → nulled → trailing null cleaned
|
|
816
|
+
});
|
|
765
817
|
|
|
766
818
|
// Trailing null should be cleaned, so internal array length is 1
|
|
767
819
|
expect(arr.listeners['push']!.length).toBe(1);
|
|
768
820
|
expect(fn1).toHaveBeenCalledTimes(1);
|
|
821
|
+
expect(captured.length).toBe(1);
|
|
769
822
|
});
|
|
770
823
|
|
|
771
|
-
it('on() inserts after trailing nulls cleaned from dispatch', () => {
|
|
824
|
+
it('on() inserts after trailing nulls cleaned from dispatch', async () => {
|
|
772
825
|
let arr = new ReactiveArray<number>(),
|
|
773
826
|
fn1 = vi.fn(),
|
|
774
|
-
fn2 = () => { throw new Error('err2'); },
|
|
775
|
-
fn3 = () => { throw new Error('err3'); };
|
|
827
|
+
fn2 = () => { throw new Error('reactivity: err2'); },
|
|
828
|
+
fn3 = () => { throw new Error('reactivity: err3'); };
|
|
776
829
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
830
|
+
let captured = await captureUncaught(() => {
|
|
831
|
+
arr.on('push', fn1);
|
|
832
|
+
arr.on('push', fn2);
|
|
833
|
+
arr.on('push', fn3);
|
|
780
834
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
835
|
+
// Dispatch: fn2 and fn3 throw → nulled → trailing nulls cleaned
|
|
836
|
+
// listeners = [fn1, null, null] → cleanup → [fn1]
|
|
837
|
+
arr.push(1);
|
|
838
|
+
});
|
|
784
839
|
|
|
785
840
|
expect(fn1).toHaveBeenCalledTimes(1);
|
|
786
841
|
expect(arr.listeners['push']!.length).toBe(1);
|
|
842
|
+
expect(captured[0]).toBeInstanceOf(AggregateError);
|
|
787
843
|
|
|
788
844
|
// Register fn4 — should append at index 1 (no holes left)
|
|
789
845
|
let fn4 = vi.fn();
|
|
@@ -942,7 +998,7 @@ describe('ReactiveArray', () => {
|
|
|
942
998
|
spyB = vi.spyOn(b, 'dispose'),
|
|
943
999
|
spyC = vi.spyOn(c, 'dispose');
|
|
944
1000
|
|
|
945
|
-
let arr = new ReactiveArray<ReactiveObject<any>>(a, b, c);
|
|
1001
|
+
let arr = new ReactiveArray<ReactiveObject<any>>([a, b, c]);
|
|
946
1002
|
|
|
947
1003
|
arr.dispose();
|
|
948
1004
|
|
|
@@ -958,7 +1014,7 @@ describe('ReactiveArray', () => {
|
|
|
958
1014
|
spyA = vi.spyOn(a, 'dispose'),
|
|
959
1015
|
spyB = vi.spyOn(b, 'dispose');
|
|
960
1016
|
|
|
961
|
-
let arr = new ReactiveArray<ReactiveObject<any>>(a, b);
|
|
1017
|
+
let arr = new ReactiveArray<ReactiveObject<any>>([a, b]);
|
|
962
1018
|
|
|
963
1019
|
arr.clear();
|
|
964
1020
|
|
|
@@ -973,7 +1029,7 @@ describe('ReactiveArray', () => {
|
|
|
973
1029
|
spyA = vi.spyOn(a, 'dispose'),
|
|
974
1030
|
spyB = vi.spyOn(b, 'dispose');
|
|
975
1031
|
|
|
976
|
-
let arr = new ReactiveArray<ReactiveObject<any>>(a, b);
|
|
1032
|
+
let arr = new ReactiveArray<ReactiveObject<any>>([a, b]);
|
|
977
1033
|
|
|
978
1034
|
arr.pop();
|
|
979
1035
|
|
|
@@ -988,7 +1044,7 @@ describe('ReactiveArray', () => {
|
|
|
988
1044
|
spyA = vi.spyOn(a, 'dispose'),
|
|
989
1045
|
spyB = vi.spyOn(b, 'dispose');
|
|
990
1046
|
|
|
991
|
-
let arr = new ReactiveArray<ReactiveObject<any>>(a, b);
|
|
1047
|
+
let arr = new ReactiveArray<ReactiveObject<any>>([a, b]);
|
|
992
1048
|
|
|
993
1049
|
arr.shift();
|
|
994
1050
|
|
|
@@ -1007,7 +1063,7 @@ describe('ReactiveArray', () => {
|
|
|
1007
1063
|
spyC = vi.spyOn(c, 'dispose'),
|
|
1008
1064
|
spyD = vi.spyOn(d, 'dispose');
|
|
1009
1065
|
|
|
1010
|
-
let arr = new ReactiveArray<ReactiveObject<any>>(a, b, c, d);
|
|
1066
|
+
let arr = new ReactiveArray<ReactiveObject<any>>([a, b, c, d]);
|
|
1011
1067
|
|
|
1012
1068
|
arr.splice(1, 2);
|
|
1013
1069
|
|
|
@@ -1026,7 +1082,7 @@ describe('ReactiveArray', () => {
|
|
|
1026
1082
|
spyB = vi.spyOn(b, 'dispose'),
|
|
1027
1083
|
spyR = vi.spyOn(replacement, 'dispose');
|
|
1028
1084
|
|
|
1029
|
-
let arr = new ReactiveArray<ReactiveObject<any>>(a, b);
|
|
1085
|
+
let arr = new ReactiveArray<ReactiveObject<any>>([a, b]);
|
|
1030
1086
|
|
|
1031
1087
|
arr.splice(0, 1, replacement);
|
|
1032
1088
|
|
|
@@ -1036,10 +1092,24 @@ describe('ReactiveArray', () => {
|
|
|
1036
1092
|
expect(arr.length).toBe(2);
|
|
1037
1093
|
});
|
|
1038
1094
|
|
|
1095
|
+
it('$set() disposes the replaced ReactiveObject', () => {
|
|
1096
|
+
let a = new ReactiveObject({ x: 1 }),
|
|
1097
|
+
b = new ReactiveObject({ y: 2 }),
|
|
1098
|
+
spyA = vi.spyOn(a, 'dispose'),
|
|
1099
|
+
spyB = vi.spyOn(b, 'dispose'),
|
|
1100
|
+
arr = new ReactiveArray<ReactiveObject<any>>([a]);
|
|
1101
|
+
|
|
1102
|
+
arr.$set(0, b);
|
|
1103
|
+
|
|
1104
|
+
expect(spyA).toHaveBeenCalledTimes(1);
|
|
1105
|
+
expect(spyB).not.toHaveBeenCalled();
|
|
1106
|
+
expect(arr[0]).toBe(b);
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1039
1109
|
it('does not dispose non-ReactiveObject elements', () => {
|
|
1040
1110
|
let obj = { dispose: vi.fn() };
|
|
1041
1111
|
|
|
1042
|
-
let arr = new ReactiveArray<any>(1, 'str', obj);
|
|
1112
|
+
let arr = new ReactiveArray<any>([1, 'str', obj]);
|
|
1043
1113
|
|
|
1044
1114
|
arr.dispose();
|
|
1045
1115
|
|