@esportsplus/reactivity 0.30.3 → 0.31.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 → README.md} +14 -11
- package/build/constants.d.ts +2 -1
- package/build/constants.js +2 -1
- package/build/reactive/array.js +3 -0
- package/build/reactive/object.js +7 -9
- package/build/system.d.ts +2 -1
- package/build/system.js +31 -20
- package/build/types.d.ts +2 -3
- package/package.json +1 -1
- package/src/constants.ts +3 -1
- package/src/reactive/array.ts +4 -0
- package/src/reactive/object.ts +12 -12
- package/src/system.ts +42 -28
- package/src/types.ts +2 -8
- package/tests/array.ts +369 -0
- package/tests/async-computed.ts +239 -0
- package/tests/bench/array.ts +77 -0
- package/tests/bench/reactive-object.ts +59 -0
- package/tests/bench/system.ts +142 -1
- package/tests/compiler.ts +326 -0
- package/tests/objects.ts +84 -0
- package/tests/reactive.ts +210 -0
- package/tests/system.ts +509 -0
- package/tests/tsconfig.json +17 -0
package/tests/system.ts
CHANGED
|
@@ -423,6 +423,27 @@ describe('onCleanup', () => {
|
|
|
423
423
|
expect(returned).toBe(fn);
|
|
424
424
|
expect(fn).not.toHaveBeenCalled();
|
|
425
425
|
});
|
|
426
|
+
|
|
427
|
+
it('throwing cleanup skips subsequent cleanups in same array', () => {
|
|
428
|
+
let called: number[] = [],
|
|
429
|
+
c = computed((onCleanup) => {
|
|
430
|
+
onCleanup(() => { called.push(1); });
|
|
431
|
+
onCleanup(() => { called.push(2); throw new Error('cleanup boom'); });
|
|
432
|
+
onCleanup(() => { called.push(3); });
|
|
433
|
+
return 42;
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
expect(called).toEqual([]);
|
|
437
|
+
|
|
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
|
+
expect(() => dispose(c)).toThrow('cleanup boom');
|
|
442
|
+
|
|
443
|
+
expect(called).toContain(1);
|
|
444
|
+
expect(called).toContain(2);
|
|
445
|
+
expect(called).not.toContain(3);
|
|
446
|
+
});
|
|
426
447
|
});
|
|
427
448
|
|
|
428
449
|
|
|
@@ -478,6 +499,33 @@ describe('root', () => {
|
|
|
478
499
|
expect(outer).toBe(1);
|
|
479
500
|
expect(inner).toBe(1);
|
|
480
501
|
});
|
|
502
|
+
|
|
503
|
+
it('tracks disposables counter for unowned computeds', () => {
|
|
504
|
+
let before = root.disposables;
|
|
505
|
+
|
|
506
|
+
root(() => {
|
|
507
|
+
computed(() => 1);
|
|
508
|
+
computed(() => 2);
|
|
509
|
+
computed(() => 3);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// root restores disposables to outer value after execution
|
|
513
|
+
expect(root.disposables).toBe(before);
|
|
514
|
+
|
|
515
|
+
// Nested: inner root creates computeds, outer root creates computeds
|
|
516
|
+
root(() => {
|
|
517
|
+
computed(() => 10);
|
|
518
|
+
|
|
519
|
+
root(() => {
|
|
520
|
+
computed(() => 20);
|
|
521
|
+
computed(() => 30);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
computed(() => 40);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
expect(root.disposables).toBe(before);
|
|
528
|
+
});
|
|
481
529
|
});
|
|
482
530
|
|
|
483
531
|
|
|
@@ -548,10 +596,93 @@ describe('isComputed', () => {
|
|
|
548
596
|
expect(isComputed(1)).toBe(false);
|
|
549
597
|
expect(isComputed(null)).toBe(false);
|
|
550
598
|
});
|
|
599
|
+
|
|
600
|
+
it('returns false for objects with state field but no STATE_COMPUTED bit', () => {
|
|
601
|
+
expect(isComputed({ state: 0 })).toBe(false);
|
|
602
|
+
expect(isComputed({ state: 1 })).toBe(false);
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
describe('computed object size', () => {
|
|
607
|
+
it('does not have a type field', () => {
|
|
608
|
+
let c = computed(() => 42);
|
|
609
|
+
|
|
610
|
+
expect('type' in c).toBe(false);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it('has fewer own properties than 14 (old size)', () => {
|
|
614
|
+
let c = computed(() => 42);
|
|
615
|
+
|
|
616
|
+
expect(Object.keys(c).length).toBeLessThan(14);
|
|
617
|
+
});
|
|
551
618
|
});
|
|
552
619
|
|
|
553
620
|
|
|
554
621
|
describe('edge cases', () => {
|
|
622
|
+
it('diamond graph dedup — notify state mask prevents redundant recomputation', async () => {
|
|
623
|
+
let s = signal(1),
|
|
624
|
+
calls = 0,
|
|
625
|
+
left = computed(() => read(s) + 1),
|
|
626
|
+
right = computed(() => read(s) * 2),
|
|
627
|
+
join = computed(() => {
|
|
628
|
+
calls++;
|
|
629
|
+
return read(left) + read(right);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
effect(() => {
|
|
633
|
+
read(join);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
expect(read(join)).toBe(4);
|
|
637
|
+
calls = 0;
|
|
638
|
+
|
|
639
|
+
write(s, 2);
|
|
640
|
+
await Promise.resolve();
|
|
641
|
+
|
|
642
|
+
expect(read(join)).toBe(7);
|
|
643
|
+
expect(calls).toBe(1);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it('dynamic height adjustment — correct ordering after switching deps', async () => {
|
|
647
|
+
let s = signal(1),
|
|
648
|
+
toggle = signal(true),
|
|
649
|
+
a = computed(() => read(s) + 1),
|
|
650
|
+
b = computed(() => read(a) + 1),
|
|
651
|
+
c = computed(() => read(b) + 1),
|
|
652
|
+
order: string[] = [],
|
|
653
|
+
d = computed(() => {
|
|
654
|
+
order.push('d');
|
|
655
|
+
|
|
656
|
+
if (read(toggle)) {
|
|
657
|
+
return read(a);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return read(c);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
effect(() => {
|
|
664
|
+
read(d);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
expect(read(d)).toBe(2);
|
|
668
|
+
order.length = 0;
|
|
669
|
+
|
|
670
|
+
// Switch to reading `c` (height 3) instead of `a` (height 1)
|
|
671
|
+
write(toggle, false);
|
|
672
|
+
await Promise.resolve();
|
|
673
|
+
|
|
674
|
+
expect(read(d)).toBe(4);
|
|
675
|
+
|
|
676
|
+
order.length = 0;
|
|
677
|
+
|
|
678
|
+
// Write to source — d should recompute after c due to height adjustment
|
|
679
|
+
write(s, 10);
|
|
680
|
+
await Promise.resolve();
|
|
681
|
+
|
|
682
|
+
expect(read(d)).toBe(13);
|
|
683
|
+
expect(order).toEqual(['d']);
|
|
684
|
+
});
|
|
685
|
+
|
|
555
686
|
it('handles circular computed reads without infinite loop', () => {
|
|
556
687
|
let s = signal(0),
|
|
557
688
|
c1 = computed(() => read(s)),
|
|
@@ -652,4 +783,382 @@ describe('edge cases', () => {
|
|
|
652
783
|
|
|
653
784
|
expect(innerDisposed).toBe(true);
|
|
654
785
|
});
|
|
786
|
+
|
|
787
|
+
it('stabilizer re-schedules when effect writes to signal during stabilization', async () => {
|
|
788
|
+
let a = signal(0),
|
|
789
|
+
b = signal(0),
|
|
790
|
+
bValues: number[] = [];
|
|
791
|
+
|
|
792
|
+
effect(() => {
|
|
793
|
+
let val = read(a);
|
|
794
|
+
|
|
795
|
+
if (val > 0) {
|
|
796
|
+
write(b, val * 100);
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
effect(() => {
|
|
801
|
+
bValues.push(read(b));
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
write(a, 3);
|
|
805
|
+
await Promise.resolve();
|
|
806
|
+
await Promise.resolve();
|
|
807
|
+
|
|
808
|
+
expect(bValues).toEqual([0, 300]);
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('stabilizer re-schedules with nested write chain A → B → C', async () => {
|
|
812
|
+
let a = signal(0),
|
|
813
|
+
b = signal(0),
|
|
814
|
+
c = signal(0),
|
|
815
|
+
cValues: number[] = [];
|
|
816
|
+
|
|
817
|
+
effect(() => {
|
|
818
|
+
let val = read(a);
|
|
819
|
+
|
|
820
|
+
if (val > 0) {
|
|
821
|
+
write(b, val * 2);
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
effect(() => {
|
|
826
|
+
let val = read(b);
|
|
827
|
+
|
|
828
|
+
if (val > 0) {
|
|
829
|
+
write(c, val * 3);
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
effect(() => {
|
|
834
|
+
cValues.push(read(c));
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
write(a, 5);
|
|
838
|
+
await Promise.resolve();
|
|
839
|
+
await Promise.resolve();
|
|
840
|
+
await Promise.resolve();
|
|
841
|
+
|
|
842
|
+
expect(cValues).toEqual([0, 30]);
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
it('computed that throws on update retains previous value', async () => {
|
|
846
|
+
let s = signal(0),
|
|
847
|
+
effectValues: number[] = [],
|
|
848
|
+
c = computed(() => {
|
|
849
|
+
let val = read(s);
|
|
850
|
+
|
|
851
|
+
if (val === 2) {
|
|
852
|
+
throw new Error('boom');
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
return val * 10;
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
effect(() => {
|
|
859
|
+
effectValues.push(read(c));
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
expect(effectValues).toEqual([0]);
|
|
863
|
+
|
|
864
|
+
write(s, 1);
|
|
865
|
+
await Promise.resolve();
|
|
866
|
+
|
|
867
|
+
expect(effectValues).toEqual([0, 10]);
|
|
868
|
+
expect(read(c)).toBe(10);
|
|
869
|
+
|
|
870
|
+
write(s, 2);
|
|
871
|
+
await Promise.resolve();
|
|
872
|
+
|
|
873
|
+
// Value should remain 10 since throw prevented update
|
|
874
|
+
expect(read(c)).toBe(10);
|
|
875
|
+
expect(effectValues).toEqual([0, 10]);
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
it('computed alternates between throwing and succeeding', async () => {
|
|
879
|
+
let s = signal(0),
|
|
880
|
+
effectValues: number[] = [],
|
|
881
|
+
c = computed(() => {
|
|
882
|
+
let val = read(s);
|
|
883
|
+
|
|
884
|
+
if (val % 2 !== 0) {
|
|
885
|
+
throw new Error('odd');
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
return val;
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
effect(() => {
|
|
892
|
+
effectValues.push(read(c));
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
expect(effectValues).toEqual([0]);
|
|
896
|
+
|
|
897
|
+
write(s, 1);
|
|
898
|
+
await Promise.resolve();
|
|
899
|
+
|
|
900
|
+
// Threw on odd, value stays 0
|
|
901
|
+
expect(read(c)).toBe(0);
|
|
902
|
+
expect(effectValues).toEqual([0]);
|
|
903
|
+
|
|
904
|
+
write(s, 2);
|
|
905
|
+
await Promise.resolve();
|
|
906
|
+
|
|
907
|
+
// Succeeds on even, value updates
|
|
908
|
+
expect(read(c)).toBe(2);
|
|
909
|
+
expect(effectValues).toEqual([0, 2]);
|
|
910
|
+
|
|
911
|
+
write(s, 3);
|
|
912
|
+
await Promise.resolve();
|
|
913
|
+
|
|
914
|
+
// Threw on odd again, value stays 2
|
|
915
|
+
expect(read(c)).toBe(2);
|
|
916
|
+
expect(effectValues).toEqual([0, 2]);
|
|
917
|
+
|
|
918
|
+
write(s, 4);
|
|
919
|
+
await Promise.resolve();
|
|
920
|
+
|
|
921
|
+
// Succeeds again
|
|
922
|
+
expect(read(c)).toBe(4);
|
|
923
|
+
expect(effectValues).toEqual([0, 2, 4]);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
it('heap auto-resizes for computed chain deeper than 64', async () => {
|
|
927
|
+
let s = signal(0),
|
|
928
|
+
chain: ReturnType<typeof computed>[] = [computed(() => read(s) + 1)];
|
|
929
|
+
|
|
930
|
+
for (let i = 1; i < 80; i++) {
|
|
931
|
+
let prev = chain[i - 1];
|
|
932
|
+
|
|
933
|
+
chain.push(computed(() => read(prev) + 1));
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
let tail = chain[chain.length - 1],
|
|
937
|
+
result = -1;
|
|
938
|
+
|
|
939
|
+
effect(() => {
|
|
940
|
+
result = read(tail);
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
expect(result).toBe(80);
|
|
944
|
+
|
|
945
|
+
write(s, 10);
|
|
946
|
+
await Promise.resolve();
|
|
947
|
+
|
|
948
|
+
expect(result).toBe(90);
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
it('system remains functional under high effect churn', async () => {
|
|
952
|
+
let s = signal(0),
|
|
953
|
+
stops: (() => void)[] = [];
|
|
954
|
+
|
|
955
|
+
for (let i = 0; i < 200; i++) {
|
|
956
|
+
stops.push(effect(() => { read(s); }));
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
for (let i = 0, n = stops.length; i < n; i++) {
|
|
960
|
+
stops[i]();
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
stops.length = 0;
|
|
964
|
+
|
|
965
|
+
let result = -1;
|
|
966
|
+
|
|
967
|
+
effect(() => {
|
|
968
|
+
result = read(s);
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
write(s, 42);
|
|
972
|
+
await Promise.resolve();
|
|
973
|
+
|
|
974
|
+
expect(result).toBe(42);
|
|
975
|
+
|
|
976
|
+
for (let i = 0; i < 200; i++) {
|
|
977
|
+
stops.push(effect(() => { read(s); }));
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
for (let i = 0, n = stops.length; i < n; i++) {
|
|
981
|
+
stops[i]();
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
write(s, 99);
|
|
985
|
+
await Promise.resolve();
|
|
986
|
+
|
|
987
|
+
expect(result).toBe(99);
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
it('write during stabilization triggers reschedule and computed sees updated value', async () => {
|
|
991
|
+
let s1 = signal(1),
|
|
992
|
+
s2 = signal(0),
|
|
993
|
+
c = computed(() => read(s2) * 10),
|
|
994
|
+
cValues: number[] = [];
|
|
995
|
+
|
|
996
|
+
// Effect 1: writes to s2 when s1 changes — triggers during stabilization
|
|
997
|
+
effect(() => {
|
|
998
|
+
let val = read(s1);
|
|
999
|
+
|
|
1000
|
+
if (val > 1) {
|
|
1001
|
+
write(s2, val);
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
// Effect 2: reads computed c which depends on s2
|
|
1006
|
+
effect(() => {
|
|
1007
|
+
cValues.push(read(c));
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
expect(cValues).toEqual([0]);
|
|
1011
|
+
|
|
1012
|
+
// Write s1 → effect1 runs during stabilization → writes s2=5
|
|
1013
|
+
// → reschedule → c recomputes with s2=5 → effect2 sees 50
|
|
1014
|
+
write(s1, 5);
|
|
1015
|
+
await Promise.resolve();
|
|
1016
|
+
await Promise.resolve();
|
|
1017
|
+
|
|
1018
|
+
expect(cValues).toEqual([0, 50]);
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
it('diamond with write during stabilization propagates through both branches', async () => {
|
|
1022
|
+
let source = signal(1),
|
|
1023
|
+
trigger = signal(0),
|
|
1024
|
+
left = computed(() => read(source) + 1),
|
|
1025
|
+
right = computed(() => read(source) * 2),
|
|
1026
|
+
join = computed(() => read(left) + read(right)),
|
|
1027
|
+
joinValues: number[] = [];
|
|
1028
|
+
|
|
1029
|
+
// Effect that writes source during stabilization
|
|
1030
|
+
effect(() => {
|
|
1031
|
+
let val = read(trigger);
|
|
1032
|
+
|
|
1033
|
+
if (val > 0) {
|
|
1034
|
+
write(source, val);
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
// Effect that reads the diamond join
|
|
1039
|
+
effect(() => {
|
|
1040
|
+
joinValues.push(read(join));
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
// Initial: source=1, left=2, right=2, join=4
|
|
1044
|
+
expect(joinValues).toEqual([4]);
|
|
1045
|
+
|
|
1046
|
+
// trigger=10 → effect writes source=10 → reschedule
|
|
1047
|
+
// left=11, right=20, join=31
|
|
1048
|
+
write(trigger, 10);
|
|
1049
|
+
await Promise.resolve();
|
|
1050
|
+
await Promise.resolve();
|
|
1051
|
+
|
|
1052
|
+
expect(joinValues).toEqual([4, 31]);
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
it('disposed computed does not prevent new computed from reading same signal', async () => {
|
|
1056
|
+
let s = signal(10),
|
|
1057
|
+
c1 = computed(() => read(s) * 2),
|
|
1058
|
+
c2Values: number[] = [];
|
|
1059
|
+
|
|
1060
|
+
expect(read(c1)).toBe(20);
|
|
1061
|
+
|
|
1062
|
+
dispose(c1);
|
|
1063
|
+
|
|
1064
|
+
// After dispose, c1 retains its last computed value
|
|
1065
|
+
expect(c1.value).toBe(20);
|
|
1066
|
+
|
|
1067
|
+
// New computed can read the same signal
|
|
1068
|
+
let c2 = computed(() => read(s) + 5);
|
|
1069
|
+
|
|
1070
|
+
expect(read(c2)).toBe(15);
|
|
1071
|
+
|
|
1072
|
+
// Subscribe with an effect so c2 reacts to changes
|
|
1073
|
+
effect(() => {
|
|
1074
|
+
c2Values.push(read(c2));
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
expect(c2Values).toEqual([15]);
|
|
1078
|
+
|
|
1079
|
+
// Signal still propagates to new computed
|
|
1080
|
+
write(s, 20);
|
|
1081
|
+
await Promise.resolve();
|
|
1082
|
+
|
|
1083
|
+
expect(c2Values).toEqual([15, 25]);
|
|
1084
|
+
|
|
1085
|
+
// Disposed computed value is stale — not updated
|
|
1086
|
+
expect(c1.value).toBe(20);
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
it('disposed computed retains last value and does not recompute', async () => {
|
|
1090
|
+
let s = signal(1),
|
|
1091
|
+
calls = 0,
|
|
1092
|
+
c = computed(() => {
|
|
1093
|
+
calls++;
|
|
1094
|
+
return read(s) * 3;
|
|
1095
|
+
}),
|
|
1096
|
+
result = -1;
|
|
1097
|
+
|
|
1098
|
+
// Subscribe so it's in the heap
|
|
1099
|
+
effect(() => {
|
|
1100
|
+
result = read(c);
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
expect(result).toBe(3);
|
|
1104
|
+
expect(calls).toBe(1);
|
|
1105
|
+
|
|
1106
|
+
dispose(c);
|
|
1107
|
+
|
|
1108
|
+
// After disposal, value is retained
|
|
1109
|
+
expect(c.value).toBe(3);
|
|
1110
|
+
|
|
1111
|
+
write(s, 10);
|
|
1112
|
+
await Promise.resolve();
|
|
1113
|
+
|
|
1114
|
+
// Not recomputed after dispose
|
|
1115
|
+
expect(calls).toBe(1);
|
|
1116
|
+
expect(c.value).toBe(3);
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
it('link pool handles >1000 dependencies with disposal and reuse', async () => {
|
|
1120
|
+
let signals: ReturnType<typeof signal>[] = [],
|
|
1121
|
+
stops: (() => void)[] = [];
|
|
1122
|
+
|
|
1123
|
+
for (let i = 0; i < 1100; i++) {
|
|
1124
|
+
signals.push(signal(i));
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// Create effect reading all 1100 signals
|
|
1128
|
+
stops.push(effect(() => {
|
|
1129
|
+
for (let i = 0, n = signals.length; i < n; i++) {
|
|
1130
|
+
read(signals[i]);
|
|
1131
|
+
}
|
|
1132
|
+
}));
|
|
1133
|
+
|
|
1134
|
+
// Dispose to return links to pool
|
|
1135
|
+
stops[0]();
|
|
1136
|
+
stops.length = 0;
|
|
1137
|
+
|
|
1138
|
+
// Create new effects reusing pooled links
|
|
1139
|
+
let sum = -1;
|
|
1140
|
+
|
|
1141
|
+
stops.push(effect(() => {
|
|
1142
|
+
let total = 0;
|
|
1143
|
+
|
|
1144
|
+
for (let i = 0; i < 50; i++) {
|
|
1145
|
+
total += read(signals[i]);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
sum = total;
|
|
1149
|
+
}));
|
|
1150
|
+
|
|
1151
|
+
// sum of 0..49 = 1225
|
|
1152
|
+
expect(sum).toBe(1225);
|
|
1153
|
+
|
|
1154
|
+
write(signals[0], 100);
|
|
1155
|
+
await Promise.resolve();
|
|
1156
|
+
|
|
1157
|
+
// 1225 - 0 + 100 = 1325
|
|
1158
|
+
expect(sum).toBe(1325);
|
|
1159
|
+
|
|
1160
|
+
for (let i = 0, n = stops.length; i < n; i++) {
|
|
1161
|
+
stops[i]();
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
655
1164
|
});
|