@esportsplus/reactivity 0.31.3 → 0.32.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/.claude/CHANGELOG.md +43 -0
- package/README.md +13 -6
- package/bench/cellx.bench.ts +61 -0
- package/bench/dynamic.bench.ts +103 -0
- package/bench/kairo.bench.ts +368 -0
- package/bench/lib/reactive-adapter.ts +51 -0
- package/bench/molbench.bench.ts +75 -0
- package/{tests/bench/system.ts → bench/system.bench.ts} +3 -3
- package/build/compiler/array.d.ts +1 -1
- package/build/compiler/constants.d.ts +7 -6
- package/build/compiler/constants.js +6 -7
- package/build/compiler/object.d.ts +1 -1
- package/build/compiler/object.js +1 -2
- package/build/compiler/primitives.d.ts +1 -1
- package/build/constants.d.ts +3 -1
- package/build/constants.js +3 -1
- package/build/reactive/array.d.ts +8 -5
- package/build/reactive/array.js +14 -14
- package/build/reactive/index.d.ts +2 -2
- package/build/reactive/index.js +1 -1
- package/build/reactive/object.d.ts +4 -4
- package/build/reactive/object.js +8 -23
- package/build/system.d.ts +15 -6
- package/build/system.js +415 -69
- package/build/types.d.ts +16 -4
- package/package.json +5 -2
- package/src/compiler/array.ts +1 -1
- package/src/compiler/constants.ts +8 -6
- package/src/compiler/index.ts +2 -1
- package/src/compiler/object.ts +2 -2
- package/src/compiler/plugins/vite.ts +1 -0
- package/src/compiler/primitives.ts +1 -1
- package/src/constants.ts +6 -2
- package/src/reactive/array.ts +26 -23
- package/src/reactive/index.ts +6 -6
- package/src/reactive/object.ts +16 -41
- package/src/system.ts +612 -80
- package/src/types.ts +29 -9
- package/test/async-computed.test.ts +323 -0
- package/test/async-errors.test.ts +146 -0
- package/test/async-hardening.test.ts +73 -0
- package/test/async-iterable.test.ts +221 -0
- package/test/async-nested.test.ts +19 -0
- package/test/deep-graphs.test.ts +215 -0
- package/{tests/effects.ts → test/effects.test.ts} +60 -0
- package/test/equals.test.ts +142 -0
- package/test/errors.test.ts +201 -0
- package/test/flush.test.ts +185 -0
- package/test/glitch-freedom.test.ts +115 -0
- package/test/invalidate.test.ts +147 -0
- package/test/lib/wait-for.ts +28 -0
- package/test/pending-writes.test.ts +139 -0
- package/{tests/reactive.ts → test/reactive/reactive.test.ts} +20 -19
- package/test/read-dedup.test.ts +120 -0
- package/test/signal-selector.test.ts +187 -0
- package/{tests/system.ts → test/system.test.ts} +214 -23
- package/test/tsconfig.json +16 -0
- package/test/untrack.test.ts +146 -0
- package/vitest.config.ts +2 -2
- package/tests/async-computed.ts +0 -239
- /package/{tests/bench/array.ts → bench/reactive/array.bench.ts} +0 -0
- /package/{tests/bench/reactive-object.ts → bench/reactive/reactive-object.bench.ts} +0 -0
- /package/{tests → bench}/tsconfig.json +0 -0
- /package/{tests/compiler.ts → test/compiler/compiler.test.ts} +0 -0
- /package/{tests/primitives.ts → test/primitives.test.ts} +0 -0
- /package/{tests/array.ts → test/reactive/array.test.ts} +0 -0
- /package/{tests/nested.ts → test/reactive/nested.test.ts} +0 -0
- /package/{tests/objects.ts → test/reactive/objects.test.ts} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { computed, dispose, effect, isComputed, isSignal, onCleanup, read, root, signal, write } from '~/system';
|
|
3
|
+
import { waitFor } from './lib/wait-for';
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
describe('signal', () => {
|
|
@@ -424,7 +425,7 @@ describe('onCleanup', () => {
|
|
|
424
425
|
expect(fn).not.toHaveBeenCalled();
|
|
425
426
|
});
|
|
426
427
|
|
|
427
|
-
it('throwing cleanup
|
|
428
|
+
it('throwing cleanup still runs subsequent cleanups and rethrows to the dispose caller', () => {
|
|
428
429
|
let called: number[] = [],
|
|
429
430
|
c = computed((onCleanup) => {
|
|
430
431
|
onCleanup(() => { called.push(1); });
|
|
@@ -435,14 +436,89 @@ describe('onCleanup', () => {
|
|
|
435
436
|
|
|
436
437
|
expect(called).toEqual([]);
|
|
437
438
|
|
|
438
|
-
// dispose() calls cleanup() synchronously — no try/catch wraps it
|
|
439
|
-
// cleanup iterates the array: index 0 (push 1), index 1 (push 2, throws)
|
|
440
|
-
// index 2 (push 3) is never reached because no try/catch in cleanup()
|
|
441
439
|
expect(() => dispose(c)).toThrow('cleanup boom');
|
|
442
440
|
|
|
443
|
-
|
|
444
|
-
expect(called).
|
|
445
|
-
expect(
|
|
441
|
+
// Every disposer ran despite the throw
|
|
442
|
+
expect(called).toEqual([1, 2, 3]);
|
|
443
|
+
expect(c.cleanup).toBe(null);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('single throwing cleanup rethrows the exact error', () => {
|
|
447
|
+
let error = new Error('solo boom'),
|
|
448
|
+
c = computed((onCleanup) => {
|
|
449
|
+
onCleanup(() => { throw error; });
|
|
450
|
+
return 1;
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
dispose(c);
|
|
455
|
+
expect.unreachable();
|
|
456
|
+
}
|
|
457
|
+
catch (e) {
|
|
458
|
+
expect(e).toBe(error);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('multiple throwing cleanups aggregate into AggregateError with all captured', () => {
|
|
463
|
+
let called: number[] = [],
|
|
464
|
+
c = computed((onCleanup) => {
|
|
465
|
+
onCleanup(() => { called.push(1); throw new Error('first'); });
|
|
466
|
+
onCleanup(() => { called.push(2); throw new Error('second'); });
|
|
467
|
+
onCleanup(() => { called.push(3); });
|
|
468
|
+
return 1;
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
dispose(c);
|
|
473
|
+
expect.unreachable();
|
|
474
|
+
}
|
|
475
|
+
catch (e) {
|
|
476
|
+
expect(e).toBeInstanceOf(AggregateError);
|
|
477
|
+
expect((e as AggregateError).errors.map((x) => (x as Error).message)).toEqual(['first', 'second']);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
expect(called).toEqual([1, 2, 3]);
|
|
481
|
+
expect(c.cleanup).toBe(null);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('throwing cleanup during recompute does not abort the stabilize pass', async () => {
|
|
485
|
+
let s = signal(0),
|
|
486
|
+
captured: unknown[] = [],
|
|
487
|
+
listeners = process.listeners('uncaughtException'),
|
|
488
|
+
otherValues: number[] = [],
|
|
489
|
+
values: number[] = [];
|
|
490
|
+
|
|
491
|
+
effect(() => {
|
|
492
|
+
values.push(read(s));
|
|
493
|
+
onCleanup(() => { throw new Error('teardown boom'); });
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
effect(() => {
|
|
497
|
+
otherValues.push(read(s));
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
process.removeAllListeners('uncaughtException');
|
|
501
|
+
process.on('uncaughtException', (e) => { captured.push(e); });
|
|
502
|
+
|
|
503
|
+
try {
|
|
504
|
+
write(s, 1);
|
|
505
|
+
await waitFor(() => captured.length === 1, 'teardown error surfaces as uncaught');
|
|
506
|
+
}
|
|
507
|
+
finally {
|
|
508
|
+
process.removeAllListeners('uncaughtException');
|
|
509
|
+
|
|
510
|
+
for (let i = 0, n = listeners.length; i < n; i++) {
|
|
511
|
+
process.on('uncaughtException', listeners[i]);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Both effects re-ran — the pass survived the teardown failure
|
|
516
|
+
expect(values).toEqual([0, 1]);
|
|
517
|
+
expect(otherValues).toEqual([0, 1]);
|
|
518
|
+
|
|
519
|
+
// And the teardown error surfaced as an uncaught microtask exception
|
|
520
|
+
expect(captured.length).toBe(1);
|
|
521
|
+
expect((captured[0] as Error).message).toBe('teardown boom');
|
|
446
522
|
});
|
|
447
523
|
});
|
|
448
524
|
|
|
@@ -610,10 +686,109 @@ describe('computed object size', () => {
|
|
|
610
686
|
expect('type' in c).toBe(false);
|
|
611
687
|
});
|
|
612
688
|
|
|
613
|
-
it('has
|
|
689
|
+
it('has no more own properties than 16 (14 slimmed + gv + rv)', () => {
|
|
614
690
|
let c = computed(() => 42);
|
|
615
691
|
|
|
616
|
-
expect(Object.keys(c).length).
|
|
692
|
+
expect(Object.keys(c).length).toBeLessThanOrEqual(16);
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
describe('globalVersion fast path', () => {
|
|
697
|
+
it('a computed pulled by two tracked readers in one settle runs its fn at most once with correct values', async () => {
|
|
698
|
+
let cRuns = 0,
|
|
699
|
+
s = signal(0),
|
|
700
|
+
seen1: number[] = [],
|
|
701
|
+
seen2: number[] = [];
|
|
702
|
+
|
|
703
|
+
let c = computed(() => {
|
|
704
|
+
cRuns++;
|
|
705
|
+
|
|
706
|
+
return read(s) + 1;
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
effect(() => {
|
|
710
|
+
if (read(s) > 0) {
|
|
711
|
+
seen1.push(read(c));
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
effect(() => {
|
|
716
|
+
if (read(s) > 0) {
|
|
717
|
+
seen2.push(read(c));
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
cRuns = 0;
|
|
722
|
+
|
|
723
|
+
write(s, 1);
|
|
724
|
+
await Promise.resolve();
|
|
725
|
+
await Promise.resolve();
|
|
726
|
+
|
|
727
|
+
expect(cRuns).toBe(1);
|
|
728
|
+
expect(seen1[seen1.length - 1]).toBe(2);
|
|
729
|
+
expect(seen2[seen2.length - 1]).toBe(2);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it('write during recompute leaves the racing node validating normally (no stale skip)', async () => {
|
|
733
|
+
let s = signal(0),
|
|
734
|
+
t = signal(0);
|
|
735
|
+
|
|
736
|
+
let c = computed(() => {
|
|
737
|
+
let v = read(s);
|
|
738
|
+
|
|
739
|
+
if (v > 0) {
|
|
740
|
+
write(t, v * 10);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return v;
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
let d = computed(() => read(t) + read(c));
|
|
747
|
+
|
|
748
|
+
effect(() => {
|
|
749
|
+
read(d);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
write(s, 2);
|
|
753
|
+
await Promise.resolve();
|
|
754
|
+
await Promise.resolve();
|
|
755
|
+
await Promise.resolve();
|
|
756
|
+
|
|
757
|
+
expect(read(t)).toBe(20);
|
|
758
|
+
expect(read(c)).toBe(2);
|
|
759
|
+
expect(read(d)).toBe(22);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it('a fresh signal write invalidates the fast path (no stale skip across a settle boundary)', async () => {
|
|
763
|
+
let cRuns = 0,
|
|
764
|
+
s = signal(1),
|
|
765
|
+
seen: number[] = [];
|
|
766
|
+
|
|
767
|
+
let c = computed(() => {
|
|
768
|
+
cRuns++;
|
|
769
|
+
|
|
770
|
+
return read(s) * 2;
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
effect(() => {
|
|
774
|
+
seen.push(read(c));
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
expect(seen[seen.length - 1]).toBe(2);
|
|
778
|
+
|
|
779
|
+
write(s, 5);
|
|
780
|
+
await Promise.resolve();
|
|
781
|
+
await Promise.resolve();
|
|
782
|
+
|
|
783
|
+
expect(read(c)).toBe(10);
|
|
784
|
+
expect(seen[seen.length - 1]).toBe(10);
|
|
785
|
+
|
|
786
|
+
write(s, 7);
|
|
787
|
+
await Promise.resolve();
|
|
788
|
+
await Promise.resolve();
|
|
789
|
+
|
|
790
|
+
expect(read(c)).toBe(14);
|
|
791
|
+
expect(seen[seen.length - 1]).toBe(14);
|
|
617
792
|
});
|
|
618
793
|
});
|
|
619
794
|
|
|
@@ -842,8 +1017,9 @@ describe('edge cases', () => {
|
|
|
842
1017
|
expect(cValues).toEqual([0, 30]);
|
|
843
1018
|
});
|
|
844
1019
|
|
|
845
|
-
it('computed that throws on update
|
|
1020
|
+
it('computed that throws on update caches the error and rethrows at read', async () => {
|
|
846
1021
|
let s = signal(0),
|
|
1022
|
+
effectErrors: unknown[] = [],
|
|
847
1023
|
effectValues: number[] = [],
|
|
848
1024
|
c = computed(() => {
|
|
849
1025
|
let val = read(s);
|
|
@@ -855,9 +1031,14 @@ describe('edge cases', () => {
|
|
|
855
1031
|
return val * 10;
|
|
856
1032
|
});
|
|
857
1033
|
|
|
858
|
-
effect(
|
|
859
|
-
|
|
860
|
-
|
|
1034
|
+
effect(
|
|
1035
|
+
() => {
|
|
1036
|
+
effectValues.push(read(c));
|
|
1037
|
+
},
|
|
1038
|
+
(e) => {
|
|
1039
|
+
effectErrors.push(e);
|
|
1040
|
+
}
|
|
1041
|
+
);
|
|
861
1042
|
|
|
862
1043
|
expect(effectValues).toEqual([0]);
|
|
863
1044
|
|
|
@@ -870,13 +1051,16 @@ describe('edge cases', () => {
|
|
|
870
1051
|
write(s, 2);
|
|
871
1052
|
await Promise.resolve();
|
|
872
1053
|
|
|
873
|
-
//
|
|
874
|
-
expect(read(c)).
|
|
1054
|
+
// Error propagates to the subscribed effect and rethrows at read
|
|
1055
|
+
expect(() => read(c)).toThrow('boom');
|
|
1056
|
+
expect(effectErrors.length).toBe(1);
|
|
1057
|
+
expect((effectErrors[0] as Error).message).toBe('boom');
|
|
875
1058
|
expect(effectValues).toEqual([0, 10]);
|
|
876
1059
|
});
|
|
877
1060
|
|
|
878
1061
|
it('computed alternates between throwing and succeeding', async () => {
|
|
879
1062
|
let s = signal(0),
|
|
1063
|
+
effectErrors: unknown[] = [],
|
|
880
1064
|
effectValues: number[] = [],
|
|
881
1065
|
c = computed(() => {
|
|
882
1066
|
let val = read(s);
|
|
@@ -888,31 +1072,38 @@ describe('edge cases', () => {
|
|
|
888
1072
|
return val;
|
|
889
1073
|
});
|
|
890
1074
|
|
|
891
|
-
effect(
|
|
892
|
-
|
|
893
|
-
|
|
1075
|
+
effect(
|
|
1076
|
+
() => {
|
|
1077
|
+
effectValues.push(read(c));
|
|
1078
|
+
},
|
|
1079
|
+
(e) => {
|
|
1080
|
+
effectErrors.push(e);
|
|
1081
|
+
}
|
|
1082
|
+
);
|
|
894
1083
|
|
|
895
1084
|
expect(effectValues).toEqual([0]);
|
|
896
1085
|
|
|
897
1086
|
write(s, 1);
|
|
898
1087
|
await Promise.resolve();
|
|
899
1088
|
|
|
900
|
-
// Threw on odd,
|
|
901
|
-
expect(read(c)).
|
|
1089
|
+
// Threw on odd, error cached and rethrown at read
|
|
1090
|
+
expect(() => read(c)).toThrow('odd');
|
|
1091
|
+
expect(effectErrors.length).toBe(1);
|
|
902
1092
|
expect(effectValues).toEqual([0]);
|
|
903
1093
|
|
|
904
1094
|
write(s, 2);
|
|
905
1095
|
await Promise.resolve();
|
|
906
1096
|
|
|
907
|
-
// Succeeds on even, value updates
|
|
1097
|
+
// Succeeds on even, error cleared, value updates
|
|
908
1098
|
expect(read(c)).toBe(2);
|
|
909
1099
|
expect(effectValues).toEqual([0, 2]);
|
|
910
1100
|
|
|
911
1101
|
write(s, 3);
|
|
912
1102
|
await Promise.resolve();
|
|
913
1103
|
|
|
914
|
-
// Threw on odd again
|
|
915
|
-
expect(read(c)).
|
|
1104
|
+
// Threw on odd again — the error caches on the Nth failure too
|
|
1105
|
+
expect(() => read(c)).toThrow('odd');
|
|
1106
|
+
expect(effectErrors.length).toBe(2);
|
|
916
1107
|
expect(effectValues).toEqual([0, 2]);
|
|
917
1108
|
|
|
918
1109
|
write(s, 4);
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { computed, effect, peek, read, signal, untrack, write } from '~/system';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
describe('untrack', () => {
|
|
6
|
+
it('does not re-run an effect for a signal read only inside untrack, and sees current values', async () => {
|
|
7
|
+
let a = signal(1),
|
|
8
|
+
b = signal(10),
|
|
9
|
+
calls = 0,
|
|
10
|
+
seenB: number[] = [];
|
|
11
|
+
|
|
12
|
+
effect(() => {
|
|
13
|
+
read(a);
|
|
14
|
+
seenB.push(untrack(() => read(b)));
|
|
15
|
+
calls++;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(calls).toBe(1);
|
|
19
|
+
expect(seenB).toEqual([10]);
|
|
20
|
+
|
|
21
|
+
write(b, 20);
|
|
22
|
+
await Promise.resolve();
|
|
23
|
+
|
|
24
|
+
// b changed but was only ever read inside untrack — the effect must not re-run
|
|
25
|
+
expect(calls).toBe(1);
|
|
26
|
+
|
|
27
|
+
write(a, 2);
|
|
28
|
+
await Promise.resolve();
|
|
29
|
+
|
|
30
|
+
// a re-runs the effect; the untracked read of b picks up its current value
|
|
31
|
+
expect(calls).toBe(2);
|
|
32
|
+
expect(seenB).toEqual([10, 20]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('restores the observer when fn throws, so a later read in the same effect still tracks', async () => {
|
|
36
|
+
let a = signal(1),
|
|
37
|
+
b = signal(1),
|
|
38
|
+
calls = 0;
|
|
39
|
+
|
|
40
|
+
effect(() => {
|
|
41
|
+
calls++;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
untrack(() => {
|
|
45
|
+
throw new Error('untrack boom');
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// swallow — only the observer-restoration-after-throw behavior is under test
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
read(a);
|
|
53
|
+
read(b);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(calls).toBe(1);
|
|
57
|
+
|
|
58
|
+
write(a, 2);
|
|
59
|
+
await Promise.resolve();
|
|
60
|
+
|
|
61
|
+
expect(calls).toBe(2);
|
|
62
|
+
|
|
63
|
+
write(b, 2);
|
|
64
|
+
await Promise.resolve();
|
|
65
|
+
|
|
66
|
+
expect(calls).toBe(3);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('returns the value fn produces', () => {
|
|
70
|
+
expect(untrack(() => 42)).toBe(42);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
describe('peek', () => {
|
|
76
|
+
it('returns the current value of a signal', () => {
|
|
77
|
+
let s = signal(5);
|
|
78
|
+
|
|
79
|
+
expect(peek(s)).toBe(5);
|
|
80
|
+
|
|
81
|
+
write(s, 6);
|
|
82
|
+
|
|
83
|
+
expect(peek(s)).toBe(6);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('returns the up-to-date value of a dirty computed without subscribing', async () => {
|
|
87
|
+
let s = signal(1),
|
|
88
|
+
c = computed(() => read(s) * 2),
|
|
89
|
+
writerRuns = 0;
|
|
90
|
+
|
|
91
|
+
effect(() => {
|
|
92
|
+
read(c);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
effect(() => {
|
|
96
|
+
read(s);
|
|
97
|
+
writerRuns++;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(writerRuns).toBe(1);
|
|
101
|
+
|
|
102
|
+
write(s, 5);
|
|
103
|
+
|
|
104
|
+
// No await — c is queued/dirty, stabilize() has not run yet
|
|
105
|
+
expect(peek(c)).toBe(10);
|
|
106
|
+
|
|
107
|
+
// peek must not create a subscription: the writer effect's run count is untouched
|
|
108
|
+
expect(writerRuns).toBe(1);
|
|
109
|
+
|
|
110
|
+
await Promise.resolve();
|
|
111
|
+
await Promise.resolve();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('does not add a dependency link when called from inside a tracking scope', async () => {
|
|
115
|
+
let s = signal(1),
|
|
116
|
+
c = computed(() => read(s) * 2),
|
|
117
|
+
caller = computed(() => peek(c));
|
|
118
|
+
|
|
119
|
+
expect(read(caller)).toBe(2);
|
|
120
|
+
expect(caller.deps).toBe(null);
|
|
121
|
+
|
|
122
|
+
write(s, 5);
|
|
123
|
+
await Promise.resolve();
|
|
124
|
+
await Promise.resolve();
|
|
125
|
+
|
|
126
|
+
// caller never subscribed to c, so it does not react to s changing
|
|
127
|
+
expect(read(caller)).toBe(2);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('rethrows the cached error for an errored computed', () => {
|
|
131
|
+
let s = signal(0),
|
|
132
|
+
c = computed(() => {
|
|
133
|
+
if (read(s) > 0) {
|
|
134
|
+
throw new Error('peek boom');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return read(s);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(peek(c)).toBe(0);
|
|
141
|
+
|
|
142
|
+
write(s, 1);
|
|
143
|
+
|
|
144
|
+
expect(() => peek(c)).toThrow('peek boom');
|
|
145
|
+
});
|
|
146
|
+
});
|