signalium 0.3.7 → 1.0.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/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +21 -0
- package/build/react.js +19 -0
- package/build/transform.js +19 -0
- package/dist/cjs/config.d.ts +8 -3
- package/dist/cjs/config.d.ts.map +1 -1
- package/dist/cjs/config.js +14 -8
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/debug.d.ts +2 -2
- package/dist/cjs/debug.d.ts.map +1 -1
- package/dist/cjs/debug.js +3 -3
- package/dist/cjs/debug.js.map +1 -1
- package/dist/cjs/hooks.d.ts +14 -42
- package/dist/cjs/hooks.d.ts.map +1 -1
- package/dist/cjs/hooks.js +19 -240
- package/dist/cjs/hooks.js.map +1 -1
- package/dist/cjs/index.d.ts +5 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +18 -18
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internals/async.d.ts +50 -0
- package/dist/cjs/internals/async.d.ts.map +1 -0
- package/dist/cjs/internals/async.js +390 -0
- package/dist/cjs/internals/async.js.map +1 -0
- package/dist/cjs/internals/connect.d.ts +4 -0
- package/dist/cjs/internals/connect.d.ts.map +1 -0
- package/dist/cjs/internals/connect.js +37 -0
- package/dist/cjs/internals/connect.js.map +1 -0
- package/dist/cjs/internals/consumer.d.ts +6 -0
- package/dist/cjs/internals/consumer.d.ts.map +1 -0
- package/dist/cjs/internals/consumer.js +13 -0
- package/dist/cjs/internals/consumer.js.map +1 -0
- package/dist/cjs/internals/contexts.d.ts +33 -0
- package/dist/cjs/internals/contexts.d.ts.map +1 -0
- package/dist/cjs/internals/contexts.js +103 -0
- package/dist/cjs/internals/contexts.js.map +1 -0
- package/dist/cjs/internals/derived.d.ts +66 -0
- package/dist/cjs/internals/derived.d.ts.map +1 -0
- package/dist/cjs/internals/derived.js +128 -0
- package/dist/cjs/internals/derived.js.map +1 -0
- package/dist/cjs/internals/dirty.d.ts +5 -0
- package/dist/cjs/internals/dirty.d.ts.map +1 -0
- package/dist/cjs/internals/dirty.js +79 -0
- package/dist/cjs/internals/dirty.js.map +1 -0
- package/dist/cjs/internals/edge.d.ts +32 -0
- package/dist/cjs/internals/edge.d.ts.map +1 -0
- package/dist/cjs/internals/edge.js +59 -0
- package/dist/cjs/internals/edge.js.map +1 -0
- package/dist/cjs/internals/get.d.ts +10 -0
- package/dist/cjs/internals/get.d.ts.map +1 -0
- package/dist/cjs/internals/get.js +255 -0
- package/dist/cjs/internals/get.js.map +1 -0
- package/dist/cjs/internals/scheduling.d.ts +12 -0
- package/dist/cjs/internals/scheduling.d.ts.map +1 -0
- package/dist/cjs/internals/scheduling.js +117 -0
- package/dist/cjs/internals/scheduling.js.map +1 -0
- package/dist/cjs/internals/state.d.ts +18 -0
- package/dist/cjs/internals/state.d.ts.map +1 -0
- package/dist/cjs/internals/state.js +88 -0
- package/dist/cjs/internals/state.js.map +1 -0
- package/dist/cjs/internals/utils/debug-name.d.ts +2 -0
- package/dist/cjs/internals/utils/debug-name.d.ts.map +1 -0
- package/dist/cjs/internals/utils/debug-name.js +14 -0
- package/dist/cjs/internals/utils/debug-name.js.map +1 -0
- package/dist/cjs/internals/utils/equals.d.ts +3 -0
- package/dist/cjs/internals/utils/equals.d.ts.map +1 -0
- package/dist/cjs/internals/utils/equals.js +13 -0
- package/dist/cjs/internals/utils/equals.js.map +1 -0
- package/dist/cjs/internals/utils/hash.d.ts +7 -0
- package/dist/cjs/internals/utils/hash.d.ts.map +1 -0
- package/dist/cjs/internals/utils/hash.js +181 -0
- package/dist/cjs/internals/utils/hash.js.map +1 -0
- package/dist/cjs/internals/utils/stringify.d.ts +3 -0
- package/dist/cjs/internals/utils/stringify.d.ts.map +1 -0
- package/dist/cjs/{utils.js → internals/utils/stringify.js} +5 -27
- package/dist/cjs/internals/utils/stringify.js.map +1 -0
- package/dist/cjs/internals/utils/type-utils.d.ts +6 -0
- package/dist/cjs/internals/utils/type-utils.d.ts.map +1 -0
- package/dist/cjs/internals/utils/type-utils.js +22 -0
- package/dist/cjs/internals/utils/type-utils.js.map +1 -0
- package/dist/cjs/react/context.d.ts +1 -1
- package/dist/cjs/react/context.d.ts.map +1 -1
- package/dist/cjs/react/provider.d.ts +4 -3
- package/dist/cjs/react/provider.d.ts.map +1 -1
- package/dist/cjs/react/provider.js +7 -3
- package/dist/cjs/react/provider.js.map +1 -1
- package/dist/cjs/react/setup.d.ts.map +1 -1
- package/dist/cjs/react/setup.js +2 -1
- package/dist/cjs/react/setup.js.map +1 -1
- package/dist/cjs/react/signal-value.d.ts +5 -1
- package/dist/cjs/react/signal-value.d.ts.map +1 -1
- package/dist/cjs/react/signal-value.js +35 -45
- package/dist/cjs/react/signal-value.js.map +1 -1
- package/dist/cjs/trace.d.ts +32 -28
- package/dist/cjs/trace.d.ts.map +1 -1
- package/dist/cjs/trace.js +14 -16
- package/dist/cjs/trace.js.map +1 -1
- package/dist/cjs/transform.d.ts +6 -0
- package/dist/cjs/transform.d.ts.map +1 -0
- package/dist/cjs/transform.js +92 -0
- package/dist/cjs/transform.js.map +1 -0
- package/dist/cjs/types.d.ts +32 -40
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/esm/config.d.ts +8 -3
- package/dist/esm/config.d.ts.map +1 -1
- package/dist/esm/config.js +12 -7
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/debug.d.ts +2 -2
- package/dist/esm/debug.d.ts.map +1 -1
- package/dist/esm/debug.js +2 -2
- package/dist/esm/debug.js.map +1 -1
- package/dist/esm/hooks.d.ts +14 -42
- package/dist/esm/hooks.d.ts.map +1 -1
- package/dist/esm/hooks.js +17 -226
- package/dist/esm/hooks.js.map +1 -1
- package/dist/esm/index.d.ts +5 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +5 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internals/async.d.ts +50 -0
- package/dist/esm/internals/async.d.ts.map +1 -0
- package/dist/esm/internals/async.js +383 -0
- package/dist/esm/internals/async.js.map +1 -0
- package/dist/esm/internals/connect.d.ts +4 -0
- package/dist/esm/internals/connect.d.ts.map +1 -0
- package/dist/esm/internals/connect.js +33 -0
- package/dist/esm/internals/connect.js.map +1 -0
- package/dist/esm/internals/consumer.d.ts +6 -0
- package/dist/esm/internals/consumer.d.ts.map +1 -0
- package/dist/esm/internals/consumer.js +9 -0
- package/dist/esm/internals/consumer.js.map +1 -0
- package/dist/esm/internals/contexts.d.ts +33 -0
- package/dist/esm/internals/contexts.d.ts.map +1 -0
- package/dist/esm/internals/contexts.js +92 -0
- package/dist/esm/internals/contexts.js.map +1 -0
- package/dist/esm/internals/derived.d.ts +66 -0
- package/dist/esm/internals/derived.d.ts.map +1 -0
- package/dist/esm/internals/derived.js +118 -0
- package/dist/esm/internals/derived.js.map +1 -0
- package/dist/esm/internals/dirty.d.ts +5 -0
- package/dist/esm/internals/dirty.d.ts.map +1 -0
- package/dist/esm/internals/dirty.js +75 -0
- package/dist/esm/internals/dirty.js.map +1 -0
- package/dist/esm/internals/edge.d.ts +32 -0
- package/dist/esm/internals/edge.d.ts.map +1 -0
- package/dist/esm/internals/edge.js +54 -0
- package/dist/esm/internals/edge.js.map +1 -0
- package/dist/esm/internals/get.d.ts +10 -0
- package/dist/esm/internals/get.d.ts.map +1 -0
- package/dist/esm/internals/get.js +247 -0
- package/dist/esm/internals/get.js.map +1 -0
- package/dist/esm/internals/scheduling.d.ts +12 -0
- package/dist/esm/internals/scheduling.d.ts.map +1 -0
- package/dist/esm/internals/scheduling.js +106 -0
- package/dist/esm/internals/scheduling.js.map +1 -0
- package/dist/esm/internals/state.d.ts +18 -0
- package/dist/esm/internals/state.d.ts.map +1 -0
- package/dist/esm/internals/state.js +82 -0
- package/dist/esm/internals/state.js.map +1 -0
- package/dist/esm/internals/utils/debug-name.d.ts +2 -0
- package/dist/esm/internals/utils/debug-name.d.ts.map +1 -0
- package/dist/esm/internals/utils/debug-name.js +11 -0
- package/dist/esm/internals/utils/debug-name.js.map +1 -0
- package/dist/esm/internals/utils/equals.d.ts +3 -0
- package/dist/esm/internals/utils/equals.d.ts.map +1 -0
- package/dist/esm/internals/utils/equals.js +9 -0
- package/dist/esm/internals/utils/equals.js.map +1 -0
- package/dist/esm/internals/utils/hash.d.ts +7 -0
- package/dist/esm/internals/utils/hash.d.ts.map +1 -0
- package/dist/esm/internals/utils/hash.js +174 -0
- package/dist/esm/internals/utils/hash.js.map +1 -0
- package/dist/esm/internals/utils/stringify.d.ts +3 -0
- package/dist/esm/internals/utils/stringify.d.ts.map +1 -0
- package/dist/esm/{utils.js → internals/utils/stringify.js} +4 -25
- package/dist/esm/internals/utils/stringify.js.map +1 -0
- package/dist/esm/internals/utils/type-utils.d.ts +6 -0
- package/dist/esm/internals/utils/type-utils.d.ts.map +1 -0
- package/dist/esm/internals/utils/type-utils.js +15 -0
- package/dist/esm/internals/utils/type-utils.js.map +1 -0
- package/dist/esm/react/context.d.ts +1 -1
- package/dist/esm/react/context.d.ts.map +1 -1
- package/dist/esm/react/provider.d.ts +4 -3
- package/dist/esm/react/provider.d.ts.map +1 -1
- package/dist/esm/react/provider.js +6 -2
- package/dist/esm/react/provider.js.map +1 -1
- package/dist/esm/react/setup.d.ts.map +1 -1
- package/dist/esm/react/setup.js +3 -2
- package/dist/esm/react/setup.js.map +1 -1
- package/dist/esm/react/signal-value.d.ts +5 -1
- package/dist/esm/react/signal-value.d.ts.map +1 -1
- package/dist/esm/react/signal-value.js +34 -45
- package/dist/esm/react/signal-value.js.map +1 -1
- package/dist/esm/trace.d.ts +32 -28
- package/dist/esm/trace.d.ts.map +1 -1
- package/dist/esm/trace.js +13 -15
- package/dist/esm/trace.js.map +1 -1
- package/dist/esm/transform.d.ts +6 -0
- package/dist/esm/transform.d.ts.map +1 -0
- package/dist/esm/transform.js +89 -0
- package/dist/esm/transform.js.map +1 -0
- package/dist/esm/types.d.ts +32 -40
- package/dist/esm/types.d.ts.map +1 -1
- package/package.json +23 -4
- package/src/__tests__/__snapshots__/context.test.ts.snap +2101 -0
- package/src/__tests__/__snapshots__/nesting.test.ts.snap +16201 -0
- package/src/__tests__/__snapshots__/params-and-state.test.ts.snap +1879 -0
- package/src/__tests__/async-task.test.ts +327 -0
- package/src/__tests__/context.test.ts +517 -0
- package/src/__tests__/nesting.test.ts +298 -0
- package/src/__tests__/params-and-state.test.ts +230 -0
- package/src/__tests__/reactive-async.test.ts +548 -0
- package/src/__tests__/reactive-sync.test.ts +130 -0
- package/src/__tests__/subscription.test.ts +510 -0
- package/src/__tests__/utils/async.ts +1 -1
- package/src/__tests__/utils/instrumented-hooks.ts +229 -124
- package/src/__tests__/utils/permute.ts +25 -14
- package/src/config.ts +19 -9
- package/src/debug.ts +2 -2
- package/src/hooks.ts +46 -380
- package/src/index.ts +7 -24
- package/src/internals/async.ts +556 -0
- package/src/internals/connect.ts +41 -0
- package/src/internals/consumer.ts +13 -0
- package/src/internals/contexts.ts +133 -0
- package/src/internals/derived.ts +208 -0
- package/src/internals/dirty.ts +91 -0
- package/src/internals/edge.ts +109 -0
- package/src/internals/get.ts +298 -0
- package/src/internals/scheduling.ts +140 -0
- package/src/internals/state.ts +111 -0
- package/src/internals/utils/debug-name.ts +14 -0
- package/src/internals/utils/equals.ts +12 -0
- package/src/internals/utils/hash.ts +221 -0
- package/src/{utils.ts → internals/utils/stringify.ts} +3 -29
- package/src/internals/utils/type-utils.ts +19 -0
- package/src/react/__tests__/async.test.tsx +704 -0
- package/src/react/__tests__/basic.test.tsx +95 -0
- package/src/react/__tests__/contexts.test.tsx +99 -0
- package/src/react/__tests__/subscriptions.test.tsx +49 -0
- package/src/react/__tests__/utils.tsx +40 -0
- package/src/react/context.ts +1 -1
- package/src/react/provider.tsx +12 -4
- package/src/react/setup.ts +3 -2
- package/src/react/signal-value.ts +47 -67
- package/src/trace.ts +43 -38
- package/src/transform.ts +113 -0
- package/src/types.ts +56 -46
- package/transform.js +19 -0
- package/vitest.workspace.ts +38 -2
- package/dist/cjs/scheduling.d.ts +0 -11
- package/dist/cjs/scheduling.d.ts.map +0 -1
- package/dist/cjs/scheduling.js +0 -108
- package/dist/cjs/scheduling.js.map +0 -1
- package/dist/cjs/signals.d.ts +0 -73
- package/dist/cjs/signals.d.ts.map +0 -1
- package/dist/cjs/signals.js +0 -632
- package/dist/cjs/signals.js.map +0 -1
- package/dist/cjs/utils.d.ts +0 -4
- package/dist/cjs/utils.d.ts.map +0 -1
- package/dist/cjs/utils.js.map +0 -1
- package/dist/esm/scheduling.d.ts +0 -11
- package/dist/esm/scheduling.d.ts.map +0 -1
- package/dist/esm/scheduling.js +0 -97
- package/dist/esm/scheduling.js.map +0 -1
- package/dist/esm/signals.d.ts +0 -73
- package/dist/esm/signals.d.ts.map +0 -1
- package/dist/esm/signals.js +0 -614
- package/dist/esm/signals.js.map +0 -1
- package/dist/esm/utils.d.ts +0 -4
- package/dist/esm/utils.d.ts.map +0 -1
- package/dist/esm/utils.js.map +0 -1
- package/src/__tests__/hooks/async-computed.test.ts +0 -190
- package/src/__tests__/hooks/async-task.test.ts +0 -334
- package/src/__tests__/hooks/computed.test.ts +0 -126
- package/src/__tests__/hooks/context.test.ts +0 -527
- package/src/__tests__/hooks/nesting.test.ts +0 -303
- package/src/__tests__/hooks/params-and-state.test.ts +0 -168
- package/src/__tests__/hooks/subscription.test.ts +0 -97
- package/src/__tests__/signals/async.test.ts +0 -416
- package/src/__tests__/signals/basic.test.ts +0 -399
- package/src/__tests__/signals/subscription.test.ts +0 -632
- package/src/__tests__/signals/watcher.test.ts +0 -253
- package/src/__tests__/utils/builders.ts +0 -22
- package/src/__tests__/utils/instrumented-signals.ts +0 -291
- package/src/react/__tests__/react.test.tsx +0 -227
- package/src/scheduling.ts +0 -130
- package/src/signals.ts +0 -824
@@ -0,0 +1,548 @@
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
2
|
+
import { state } from 'signalium';
|
3
|
+
import { reactive } from './utils/instrumented-hooks.js';
|
4
|
+
import { nextTick } from './utils/async.js';
|
5
|
+
|
6
|
+
describe('async computeds', () => {
|
7
|
+
test('Basic async computed works', async () => {
|
8
|
+
const getC = reactive(async (a: number, b: number) => {
|
9
|
+
return a + b;
|
10
|
+
});
|
11
|
+
|
12
|
+
const result1 = getC(1, 2);
|
13
|
+
expect(result1.isPending).toBe(true);
|
14
|
+
expect(result1.value).toBe(undefined);
|
15
|
+
await nextTick();
|
16
|
+
expect(result1.isResolved).toBe(true);
|
17
|
+
expect(result1.value).toBe(3);
|
18
|
+
|
19
|
+
const result2 = getC(2, 2);
|
20
|
+
expect(result2.isPending).toBe(true);
|
21
|
+
expect(result2.value).toBe(undefined);
|
22
|
+
await nextTick();
|
23
|
+
expect(result2.isResolved).toBe(true);
|
24
|
+
expect(result2.value).toBe(4);
|
25
|
+
});
|
26
|
+
|
27
|
+
test('Async computed is not recomputed when the same arguments are passed', async () => {
|
28
|
+
let computeCount = 0;
|
29
|
+
const getC = reactive(async (a: number, b: number) => {
|
30
|
+
computeCount++;
|
31
|
+
return a + b;
|
32
|
+
});
|
33
|
+
|
34
|
+
const result1 = getC(1, 2);
|
35
|
+
await nextTick();
|
36
|
+
expect(result1.value).toBe(3);
|
37
|
+
expect(computeCount).toBe(1);
|
38
|
+
|
39
|
+
const result2 = getC(1, 2);
|
40
|
+
await nextTick();
|
41
|
+
expect(result2.value).toBe(3);
|
42
|
+
expect(computeCount).toBe(1);
|
43
|
+
});
|
44
|
+
|
45
|
+
test('Async computed is recomputed when the arguments change', async () => {
|
46
|
+
let computeCount = 0;
|
47
|
+
const getC = reactive(async (a: number, b: number) => {
|
48
|
+
computeCount++;
|
49
|
+
return a + b;
|
50
|
+
});
|
51
|
+
|
52
|
+
const result1 = getC(1, 2);
|
53
|
+
await nextTick();
|
54
|
+
expect(result1.value).toBe(3);
|
55
|
+
expect(computeCount).toBe(1);
|
56
|
+
|
57
|
+
const result2 = getC(2, 2);
|
58
|
+
await nextTick();
|
59
|
+
expect(result2.value).toBe(4);
|
60
|
+
expect(computeCount).toBe(2);
|
61
|
+
});
|
62
|
+
|
63
|
+
test('Async computed is recomputed when state changes', async () => {
|
64
|
+
let computeCount = 0;
|
65
|
+
const stateValue = state(1);
|
66
|
+
|
67
|
+
const getC = reactive(async (a: number) => {
|
68
|
+
computeCount++;
|
69
|
+
return a + stateValue.get();
|
70
|
+
});
|
71
|
+
|
72
|
+
const result1 = getC(1);
|
73
|
+
await nextTick();
|
74
|
+
expect(result1.value).toBe(2);
|
75
|
+
expect(computeCount).toBe(1);
|
76
|
+
|
77
|
+
stateValue.set(2);
|
78
|
+
const result2 = getC(1);
|
79
|
+
expect(result2.isPending).toBe(true);
|
80
|
+
await nextTick();
|
81
|
+
expect(result2.value).toBe(3);
|
82
|
+
expect(computeCount).toBe(2);
|
83
|
+
});
|
84
|
+
|
85
|
+
test('Async computed handles errors', async () => {
|
86
|
+
const getC = reactive(async (shouldError: boolean) => {
|
87
|
+
if (shouldError) {
|
88
|
+
throw new Error('Test error');
|
89
|
+
}
|
90
|
+
return 'success';
|
91
|
+
});
|
92
|
+
|
93
|
+
const result1 = getC(false);
|
94
|
+
await nextTick();
|
95
|
+
expect(result1.isResolved).toBe(true);
|
96
|
+
expect(result1.value).toBe('success');
|
97
|
+
|
98
|
+
const result2 = getC(true);
|
99
|
+
await nextTick();
|
100
|
+
expect(result2.isRejected).toBe(true);
|
101
|
+
expect(result2.error as Error).toBeInstanceOf(Error);
|
102
|
+
expect((result2.error as Error).message).toBe('Test error');
|
103
|
+
});
|
104
|
+
|
105
|
+
test('Async computed with init value starts ready', () => {
|
106
|
+
const getC = reactive(async () => 'updated', { initValue: 'initial' });
|
107
|
+
|
108
|
+
const result = getC();
|
109
|
+
expect(result.isReady).toBe(true);
|
110
|
+
expect(result.value).toBe('initial');
|
111
|
+
expect(result.isPending).toBe(true);
|
112
|
+
});
|
113
|
+
|
114
|
+
test('Nested async computeds work correctly', async () => {
|
115
|
+
let innerCount = 0;
|
116
|
+
let outerCount = 0;
|
117
|
+
|
118
|
+
const inner = reactive(async (x: number) => {
|
119
|
+
innerCount++;
|
120
|
+
await nextTick();
|
121
|
+
return x * 2;
|
122
|
+
});
|
123
|
+
|
124
|
+
const outer = reactive(async (x: number) => {
|
125
|
+
outerCount++;
|
126
|
+
const innerResult = inner(x);
|
127
|
+
const result = await innerResult;
|
128
|
+
return result + 1;
|
129
|
+
});
|
130
|
+
|
131
|
+
const result1 = outer(2);
|
132
|
+
expect(result1.value).toBe(undefined);
|
133
|
+
expect(innerCount).toBe(1);
|
134
|
+
expect(outerCount).toBe(1);
|
135
|
+
|
136
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
137
|
+
const result2 = outer(2);
|
138
|
+
expect(result2.value).toBe(5);
|
139
|
+
expect(innerCount).toBe(1);
|
140
|
+
expect(outerCount).toBe(1);
|
141
|
+
});
|
142
|
+
|
143
|
+
test('Nested async computeds handle errors correctly', async () => {
|
144
|
+
const inner = reactive(async (shouldError: boolean) => {
|
145
|
+
if (shouldError) throw new Error('Inner error');
|
146
|
+
await nextTick();
|
147
|
+
return 'inner success';
|
148
|
+
});
|
149
|
+
|
150
|
+
const outer = reactive(async (shouldError: boolean) => {
|
151
|
+
const innerResult = inner(shouldError);
|
152
|
+
await innerResult;
|
153
|
+
return 'outer: ' + innerResult.value;
|
154
|
+
});
|
155
|
+
|
156
|
+
// Test success case
|
157
|
+
const successResult = outer(false);
|
158
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
159
|
+
expect(successResult.isResolved).toBe(true);
|
160
|
+
expect(successResult.value).toBe('outer: inner success');
|
161
|
+
|
162
|
+
// Test error case
|
163
|
+
const errorResult = outer(true);
|
164
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
165
|
+
expect(errorResult.isRejected).toBe(true);
|
166
|
+
expect(errorResult.error).toBeInstanceOf(Error);
|
167
|
+
expect((errorResult.error as Error).message).toBe('Inner error');
|
168
|
+
});
|
169
|
+
|
170
|
+
test('Nested async computeds with init values work correctly', async () => {
|
171
|
+
const inner = reactive(async (x: number) => x * 2, { initValue: 0 });
|
172
|
+
|
173
|
+
const outer = reactive(
|
174
|
+
async (x: number) => {
|
175
|
+
const innerResult = inner(x);
|
176
|
+
await innerResult;
|
177
|
+
return innerResult.value! + 1;
|
178
|
+
},
|
179
|
+
{ initValue: -1 },
|
180
|
+
);
|
181
|
+
|
182
|
+
const result = outer(2);
|
183
|
+
expect(result.isReady).toBe(true);
|
184
|
+
expect(result.value).toBe(-1); // Initial value
|
185
|
+
expect(result.isPending).toBe(true);
|
186
|
+
|
187
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
188
|
+
expect(result.value).toBe(5); // (2 * 2) + 1
|
189
|
+
expect(result.isPending).toBe(false);
|
190
|
+
expect(result.isResolved).toBe(true);
|
191
|
+
});
|
192
|
+
|
193
|
+
test('Nested generator functions with subsequent dependencies track past the first yield', async () => {
|
194
|
+
let inner1Count = 0;
|
195
|
+
let inner2Count = 0;
|
196
|
+
let outerCount = 0;
|
197
|
+
|
198
|
+
const state1Value = state(1);
|
199
|
+
const state2Value = state(2);
|
200
|
+
|
201
|
+
const inner1 = reactive(
|
202
|
+
async (x: number) => {
|
203
|
+
inner1Count++;
|
204
|
+
const state1 = state1Value.get();
|
205
|
+
await nextTick();
|
206
|
+
return x * state1;
|
207
|
+
},
|
208
|
+
{
|
209
|
+
desc: 'inner1',
|
210
|
+
},
|
211
|
+
);
|
212
|
+
|
213
|
+
const inner2 = reactive(
|
214
|
+
async (x: number) => {
|
215
|
+
inner2Count++;
|
216
|
+
const state2 = state2Value.get();
|
217
|
+
await nextTick();
|
218
|
+
return x * state2;
|
219
|
+
},
|
220
|
+
{
|
221
|
+
desc: 'inner2',
|
222
|
+
},
|
223
|
+
);
|
224
|
+
|
225
|
+
const outer = reactive(
|
226
|
+
async (x: number) => {
|
227
|
+
outerCount++;
|
228
|
+
const result1 = await inner1(x);
|
229
|
+
const result2 = await inner2(x);
|
230
|
+
return result1 + result2;
|
231
|
+
},
|
232
|
+
{
|
233
|
+
desc: 'outer',
|
234
|
+
},
|
235
|
+
);
|
236
|
+
|
237
|
+
const result1 = outer(2);
|
238
|
+
expect(result1.value).toBe(undefined);
|
239
|
+
expect(result1.isPending).toBe(true);
|
240
|
+
expect(inner1Count).toBe(1);
|
241
|
+
expect(inner2Count).toBe(0);
|
242
|
+
expect(outerCount).toBe(1);
|
243
|
+
|
244
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
245
|
+
expect(result1.value).toBe(6);
|
246
|
+
expect(result1.isPending).toBe(false);
|
247
|
+
expect(result1.isResolved).toBe(true);
|
248
|
+
expect(inner1Count).toBe(1);
|
249
|
+
expect(inner2Count).toBe(1);
|
250
|
+
expect(outerCount).toBe(1);
|
251
|
+
|
252
|
+
state1Value.set(2);
|
253
|
+
const result2 = outer(2);
|
254
|
+
expect(result2.isPending).toBe(true);
|
255
|
+
expect(result2.value).toBe(6);
|
256
|
+
expect(inner1Count).toBe(2);
|
257
|
+
expect(inner2Count).toBe(1);
|
258
|
+
expect(outerCount).toBe(1);
|
259
|
+
|
260
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
261
|
+
expect(result2.value).toBe(8);
|
262
|
+
expect(result2.isPending).toBe(false);
|
263
|
+
expect(result2.isResolved).toBe(true);
|
264
|
+
expect(inner1Count).toBe(2);
|
265
|
+
expect(inner2Count).toBe(1);
|
266
|
+
expect(outerCount).toBe(2);
|
267
|
+
|
268
|
+
state2Value.set(3);
|
269
|
+
const result3 = outer(2);
|
270
|
+
expect(result3.isPending).toBe(true);
|
271
|
+
expect(result3.value).toBe(8);
|
272
|
+
expect(inner1Count).toBe(2);
|
273
|
+
expect(inner2Count).toBe(2);
|
274
|
+
expect(outerCount).toBe(2);
|
275
|
+
|
276
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
277
|
+
expect(result3.value).toBe(10);
|
278
|
+
expect(result3.isPending).toBe(false);
|
279
|
+
expect(result3.isResolved).toBe(true);
|
280
|
+
expect(inner1Count).toBe(2);
|
281
|
+
expect(inner2Count).toBe(2);
|
282
|
+
expect(outerCount).toBe(3);
|
283
|
+
});
|
284
|
+
|
285
|
+
test('Nested generator functions with subsequent dependencies halt properly when a dependency is pending', async () => {
|
286
|
+
let inner1Count = 0;
|
287
|
+
let inner2Count = 0;
|
288
|
+
let outerCount = 0;
|
289
|
+
|
290
|
+
const state1Value = state(1);
|
291
|
+
const state2Value = state(2);
|
292
|
+
const state3Value = state(3);
|
293
|
+
|
294
|
+
const inner1 = reactive(
|
295
|
+
function* foo(x: number) {
|
296
|
+
inner1Count++;
|
297
|
+
const state = state1Value.get() + state2Value.get();
|
298
|
+
yield nextTick();
|
299
|
+
return x * state;
|
300
|
+
},
|
301
|
+
{
|
302
|
+
desc: 'inner1',
|
303
|
+
},
|
304
|
+
);
|
305
|
+
|
306
|
+
const inner2 = reactive(
|
307
|
+
function* (x: number) {
|
308
|
+
inner2Count++;
|
309
|
+
const state = state2Value.get() + state3Value.get();
|
310
|
+
yield nextTick();
|
311
|
+
return x * state;
|
312
|
+
},
|
313
|
+
{
|
314
|
+
desc: 'inner2',
|
315
|
+
},
|
316
|
+
);
|
317
|
+
|
318
|
+
const outer = reactive(
|
319
|
+
function* (x: number) {
|
320
|
+
outerCount++;
|
321
|
+
const result1 = (yield inner1(x)) as any;
|
322
|
+
const result2 = (yield inner2(x)) as any;
|
323
|
+
return result1 + result2;
|
324
|
+
},
|
325
|
+
{
|
326
|
+
desc: 'outer',
|
327
|
+
},
|
328
|
+
);
|
329
|
+
|
330
|
+
const result1 = outer(2);
|
331
|
+
expect(result1.value).toBe(undefined);
|
332
|
+
expect(result1.isPending).toBe(true);
|
333
|
+
expect(inner1Count).toBe(1);
|
334
|
+
expect(inner2Count).toBe(0);
|
335
|
+
expect(outerCount).toBe(1);
|
336
|
+
|
337
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
338
|
+
expect(result1.value).toBe(16);
|
339
|
+
expect(result1.isPending).toBe(false);
|
340
|
+
expect(result1.isResolved).toBe(true);
|
341
|
+
expect(inner1Count).toBe(1);
|
342
|
+
expect(inner2Count).toBe(1);
|
343
|
+
expect(outerCount).toBe(1);
|
344
|
+
|
345
|
+
state1Value.set(2);
|
346
|
+
state2Value.set(1);
|
347
|
+
const result2 = outer(2);
|
348
|
+
expect(result2.isPending).toBe(true);
|
349
|
+
expect(result2.value).toBe(16);
|
350
|
+
expect(inner1Count).toBe(2);
|
351
|
+
expect(inner2Count).toBe(1);
|
352
|
+
expect(outerCount).toBe(1);
|
353
|
+
|
354
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
355
|
+
expect(result2.value).toBe(14);
|
356
|
+
expect(result2.isPending).toBe(false);
|
357
|
+
expect(result2.isResolved).toBe(true);
|
358
|
+
expect(inner1Count).toBe(2);
|
359
|
+
expect(inner2Count).toBe(2);
|
360
|
+
expect(outerCount).toBe(2);
|
361
|
+
|
362
|
+
state3Value.set(2);
|
363
|
+
const result3 = outer(2);
|
364
|
+
expect(result3.isPending).toBe(true);
|
365
|
+
expect(result3.value).toBe(14);
|
366
|
+
expect(inner1Count).toBe(2);
|
367
|
+
expect(inner2Count).toBe(3);
|
368
|
+
expect(outerCount).toBe(2);
|
369
|
+
|
370
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
371
|
+
expect(result3.value).toBe(12);
|
372
|
+
expect(result3.isPending).toBe(false);
|
373
|
+
expect(result3.isResolved).toBe(true);
|
374
|
+
expect(inner1Count).toBe(2);
|
375
|
+
expect(inner2Count).toBe(3);
|
376
|
+
expect(outerCount).toBe(3);
|
377
|
+
});
|
378
|
+
|
379
|
+
test('it re-dirties pending computeds when a dependency is updated and its ord is BEFORE the current halt', async () => {
|
380
|
+
let inner1Count = 0;
|
381
|
+
let inner2Count = 0;
|
382
|
+
let outerCount = 0;
|
383
|
+
|
384
|
+
const state1Value = state(1);
|
385
|
+
const state2Value = state(2);
|
386
|
+
const state3Value = state(3);
|
387
|
+
|
388
|
+
const inner1 = reactive(
|
389
|
+
function* foo(x: number) {
|
390
|
+
inner1Count++;
|
391
|
+
const state = state1Value.get() + state2Value.get();
|
392
|
+
yield nextTick();
|
393
|
+
return x * state;
|
394
|
+
},
|
395
|
+
{
|
396
|
+
desc: 'inner1',
|
397
|
+
},
|
398
|
+
);
|
399
|
+
|
400
|
+
const inner2 = reactive(
|
401
|
+
function* (x: number) {
|
402
|
+
inner2Count++;
|
403
|
+
const state = state2Value.get() + state3Value.get();
|
404
|
+
yield nextTick();
|
405
|
+
return x * state;
|
406
|
+
},
|
407
|
+
{
|
408
|
+
desc: 'inner2',
|
409
|
+
},
|
410
|
+
);
|
411
|
+
|
412
|
+
const outer = reactive(
|
413
|
+
function* (x: number) {
|
414
|
+
outerCount++;
|
415
|
+
const result1 = (yield inner1(x)) as any;
|
416
|
+
const result2 = (yield inner2(x)) as any;
|
417
|
+
return result1 + result2;
|
418
|
+
},
|
419
|
+
{
|
420
|
+
desc: 'outer',
|
421
|
+
},
|
422
|
+
);
|
423
|
+
|
424
|
+
const result1 = outer(2);
|
425
|
+
expect(result1.value).toBe(undefined);
|
426
|
+
expect(result1.isPending).toBe(true);
|
427
|
+
expect(inner1Count).toBe(1);
|
428
|
+
expect(inner2Count).toBe(0);
|
429
|
+
expect(outerCount).toBe(1);
|
430
|
+
|
431
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
432
|
+
expect(result1.value).toBe(16);
|
433
|
+
expect(result1.isPending).toBe(false);
|
434
|
+
expect(result1.isResolved).toBe(true);
|
435
|
+
expect(inner1Count).toBe(1);
|
436
|
+
expect(inner2Count).toBe(1);
|
437
|
+
expect(outerCount).toBe(1);
|
438
|
+
|
439
|
+
state3Value.set(2);
|
440
|
+
const result2 = outer(2);
|
441
|
+
expect(result2.isPending).toBe(true);
|
442
|
+
expect(result2.value).toBe(16);
|
443
|
+
expect(inner1Count).toBe(1);
|
444
|
+
expect(inner2Count).toBe(2);
|
445
|
+
expect(outerCount).toBe(1);
|
446
|
+
|
447
|
+
state1Value.set(2);
|
448
|
+
const result3 = outer(2);
|
449
|
+
expect(result3.isPending).toBe(true);
|
450
|
+
expect(result3.value).toBe(16);
|
451
|
+
expect(inner1Count).toBe(2);
|
452
|
+
expect(inner2Count).toBe(2);
|
453
|
+
expect(outerCount).toBe(1);
|
454
|
+
|
455
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
456
|
+
expect(result3.value).toBe(16);
|
457
|
+
expect(result3.isPending).toBe(false);
|
458
|
+
expect(result3.isResolved).toBe(true);
|
459
|
+
expect(inner1Count).toBe(2);
|
460
|
+
expect(inner2Count).toBe(2);
|
461
|
+
expect(outerCount).toBe(2);
|
462
|
+
});
|
463
|
+
|
464
|
+
test('it does NOT redirty pending computeds when a dependency is updated and its ord is AFTER the current halt', async () => {
|
465
|
+
let inner1Count = 0;
|
466
|
+
let inner2Count = 0;
|
467
|
+
let outerCount = 0;
|
468
|
+
|
469
|
+
const state1Value = state(1);
|
470
|
+
const state2Value = state(2);
|
471
|
+
const state3Value = state(3);
|
472
|
+
|
473
|
+
const inner1 = reactive(
|
474
|
+
function* foo(x: number) {
|
475
|
+
inner1Count++;
|
476
|
+
const state = state1Value.get() + state2Value.get();
|
477
|
+
yield nextTick();
|
478
|
+
return x * state;
|
479
|
+
},
|
480
|
+
{
|
481
|
+
desc: 'inner1',
|
482
|
+
},
|
483
|
+
);
|
484
|
+
|
485
|
+
const inner2 = reactive(
|
486
|
+
function* (x: number) {
|
487
|
+
inner2Count++;
|
488
|
+
const state = state2Value.get() + state3Value.get();
|
489
|
+
yield nextTick();
|
490
|
+
return x * state;
|
491
|
+
},
|
492
|
+
{
|
493
|
+
desc: 'inner2',
|
494
|
+
},
|
495
|
+
);
|
496
|
+
|
497
|
+
const outer = reactive(
|
498
|
+
function* (x: number) {
|
499
|
+
outerCount++;
|
500
|
+
const result1 = (yield inner1(x)) as any;
|
501
|
+
const result2 = (yield inner2(x)) as any;
|
502
|
+
return result1 + result2;
|
503
|
+
},
|
504
|
+
{
|
505
|
+
desc: 'outer',
|
506
|
+
},
|
507
|
+
);
|
508
|
+
|
509
|
+
const result1 = outer(2);
|
510
|
+
expect(result1.value).toBe(undefined);
|
511
|
+
expect(result1.isPending).toBe(true);
|
512
|
+
expect(inner1Count).toBe(1);
|
513
|
+
expect(inner2Count).toBe(0);
|
514
|
+
expect(outerCount).toBe(1);
|
515
|
+
|
516
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
517
|
+
expect(result1.value).toBe(16);
|
518
|
+
expect(result1.isPending).toBe(false);
|
519
|
+
expect(result1.isResolved).toBe(true);
|
520
|
+
expect(inner1Count).toBe(1);
|
521
|
+
expect(inner2Count).toBe(1);
|
522
|
+
expect(outerCount).toBe(1);
|
523
|
+
|
524
|
+
state1Value.set(2);
|
525
|
+
const result2 = outer(2);
|
526
|
+
expect(result2.isPending).toBe(true);
|
527
|
+
expect(result2.value).toBe(16);
|
528
|
+
expect(inner1Count).toBe(2);
|
529
|
+
expect(inner2Count).toBe(1);
|
530
|
+
expect(outerCount).toBe(1);
|
531
|
+
|
532
|
+
state3Value.set(2);
|
533
|
+
const result3 = outer(2);
|
534
|
+
expect(result3.isPending).toBe(true);
|
535
|
+
expect(result3.value).toBe(16);
|
536
|
+
expect(inner1Count).toBe(2);
|
537
|
+
expect(inner2Count).toBe(1);
|
538
|
+
expect(outerCount).toBe(1);
|
539
|
+
|
540
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
541
|
+
expect(result3.value).toBe(16);
|
542
|
+
expect(result3.isPending).toBe(false);
|
543
|
+
expect(result3.isResolved).toBe(true);
|
544
|
+
expect(inner1Count).toBe(2);
|
545
|
+
expect(inner2Count).toBe(2);
|
546
|
+
expect(outerCount).toBe(2);
|
547
|
+
});
|
548
|
+
});
|
@@ -0,0 +1,130 @@
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
2
|
+
import { state } from '../index.js';
|
3
|
+
import { reactive } from './utils/instrumented-hooks.js';
|
4
|
+
|
5
|
+
describe('reactive (sync)', () => {
|
6
|
+
test('Basic computed works', () => {
|
7
|
+
const getC = reactive((a: number, b: number) => {
|
8
|
+
return a + b;
|
9
|
+
});
|
10
|
+
|
11
|
+
expect(getC.withParams(1, 2)).toHaveValueAndCounts(3, { compute: 1, get: 1 });
|
12
|
+
expect(getC.withParams(1, 2)).toHaveValueAndCounts(3, { compute: 1, get: 2 });
|
13
|
+
expect(getC.withParams(2, 2)).toHaveValueAndCounts(4, { compute: 1, get: 1 });
|
14
|
+
expect(getC.withParams(2, 2)).toHaveValueAndCounts(4, { compute: 1, get: 2 });
|
15
|
+
});
|
16
|
+
|
17
|
+
test('Computed can throw errors', () => {
|
18
|
+
const getC = reactive((a: number) => {
|
19
|
+
if (a < 0) throw new Error('negative number');
|
20
|
+
return a * 2;
|
21
|
+
});
|
22
|
+
|
23
|
+
expect(getC.withParams(2)).toHaveValueAndCounts(4, { compute: 1, get: 1 });
|
24
|
+
expect(() => getC(-1)).toThrow('negative number');
|
25
|
+
});
|
26
|
+
|
27
|
+
describe('nesting behavior', () => {
|
28
|
+
test('Nested computeds work', () => {
|
29
|
+
const getInner = reactive((a: number, b: number) => {
|
30
|
+
return a + b;
|
31
|
+
});
|
32
|
+
|
33
|
+
const getOuter = reactive((x: number) => {
|
34
|
+
return getInner(x, 2) * 2;
|
35
|
+
});
|
36
|
+
|
37
|
+
expect(getOuter.withParams(1)).toHaveValueAndCounts(6, { compute: 1, get: 1 });
|
38
|
+
expect(getOuter.withParams(1)).toHaveValueAndCounts(6, { compute: 1, get: 2 });
|
39
|
+
expect(getOuter.withParams(2)).toHaveValueAndCounts(8, { compute: 1, get: 1 });
|
40
|
+
expect(getOuter.withParams(2)).toHaveValueAndCounts(8, { compute: 1, get: 2 });
|
41
|
+
});
|
42
|
+
|
43
|
+
test('Nested computeds with shared state', () => {
|
44
|
+
const sharedState = state(1);
|
45
|
+
|
46
|
+
const getInner = reactive((a: number) => {
|
47
|
+
return a + sharedState.get();
|
48
|
+
});
|
49
|
+
|
50
|
+
const getOuter = reactive((x: number) => {
|
51
|
+
return getInner(x) * 2;
|
52
|
+
});
|
53
|
+
|
54
|
+
expect(getOuter.withParams(1)).toHaveValueAndCounts(4, { compute: 1, get: 1 });
|
55
|
+
sharedState.set(2);
|
56
|
+
expect(getOuter.withParams(1)).toHaveValueAndCounts(6, { compute: 2, get: 2 });
|
57
|
+
});
|
58
|
+
|
59
|
+
test('Deeply nested computeds maintain independence', () => {
|
60
|
+
const getA = reactive((x: number) => {
|
61
|
+
return x + 1;
|
62
|
+
});
|
63
|
+
|
64
|
+
const getB = reactive((x: number) => {
|
65
|
+
return getA(x) * 2 + getA(x * 2);
|
66
|
+
});
|
67
|
+
|
68
|
+
const getC = reactive((x: number) => {
|
69
|
+
return getB(x) + getA(x);
|
70
|
+
});
|
71
|
+
|
72
|
+
expect(getC.withParams(1)).toHaveValueAndCounts(9, { compute: 1, get: 1 });
|
73
|
+
expect(getC.withParams(1)).toHaveValueAndCounts(9, { compute: 1, get: 2 });
|
74
|
+
expect(getC.withParams(2)).toHaveValueAndCounts(14, { compute: 1, get: 1 });
|
75
|
+
expect(getC.withParams(2)).toHaveValueAndCounts(14, { compute: 1, get: 2 });
|
76
|
+
});
|
77
|
+
|
78
|
+
test('Nested computeds work with state signals', () => {
|
79
|
+
const stateA = state(1);
|
80
|
+
const stateB = state(2);
|
81
|
+
|
82
|
+
const getInner = reactive((x: number) => {
|
83
|
+
return x + stateA.get();
|
84
|
+
});
|
85
|
+
|
86
|
+
const getOuter = reactive((x: number) => {
|
87
|
+
return getInner(x) * stateB.get();
|
88
|
+
});
|
89
|
+
|
90
|
+
expect(getOuter.withParams(3)).toHaveValueAndCounts(8, { compute: 1, get: 1 });
|
91
|
+
|
92
|
+
stateA.set(2);
|
93
|
+
expect(getOuter.withParams(3)).toHaveValueAndCounts(10, { compute: 2, get: 2 });
|
94
|
+
|
95
|
+
stateB.set(3);
|
96
|
+
expect(getOuter.withParams(3)).toHaveValueAndCounts(15, { compute: 3, get: 3 });
|
97
|
+
|
98
|
+
expect(getOuter.withParams(3)).toHaveValueAndCounts(15, { compute: 3, get: 4 });
|
99
|
+
});
|
100
|
+
|
101
|
+
test('Nested computeds work with both state and arguments', () => {
|
102
|
+
const stateA = state(1);
|
103
|
+
const stateB = state(2);
|
104
|
+
|
105
|
+
const getInner = reactive((x: number, y: number) => {
|
106
|
+
return x + y + stateA.get();
|
107
|
+
});
|
108
|
+
|
109
|
+
const getMiddle = reactive((x: number) => {
|
110
|
+
return getInner(x, stateB.get()) * 2;
|
111
|
+
});
|
112
|
+
|
113
|
+
const getOuter = reactive((x: number, y: number) => {
|
114
|
+
return getMiddle(x) + y;
|
115
|
+
});
|
116
|
+
|
117
|
+
expect(getOuter.withParams(1, 3)).toHaveValueAndCounts(11, { compute: 1, get: 1 });
|
118
|
+
|
119
|
+
stateB.set(3);
|
120
|
+
expect(getOuter.withParams(1, 3)).toHaveValueAndCounts(13, { compute: 2, get: 2 });
|
121
|
+
|
122
|
+
stateA.set(2);
|
123
|
+
expect(getOuter.withParams(1, 3)).toHaveValueAndCounts(15, { compute: 3, get: 3 });
|
124
|
+
|
125
|
+
expect(getOuter.withParams(1, 4)).toHaveValueAndCounts(16, { compute: 1, get: 1 });
|
126
|
+
|
127
|
+
expect(getOuter.withParams(1, 4)).toHaveValueAndCounts(16, { compute: 1, get: 2 });
|
128
|
+
});
|
129
|
+
});
|
130
|
+
});
|