@ersbeth/picoflow 2.0.2 → 2.0.3
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 +10 -0
- package/SKILL.md +106 -0
- package/package.json +5 -1
- package/.gitlab-ci.yml +0 -24
- package/.vscode/settings.json +0 -5
- package/CHANGELOG.md +0 -94
- package/biome.json +0 -47
- package/docs/.vitepress/config.mts +0 -145
- package/docs/api/functions/array.md +0 -35
- package/docs/api/functions/constant.md +0 -33
- package/docs/api/functions/constantAsync.md +0 -69
- package/docs/api/functions/derivation.md +0 -34
- package/docs/api/functions/derivationAsync.md +0 -34
- package/docs/api/functions/from.md +0 -129
- package/docs/api/functions/isDisposable.md +0 -27
- package/docs/api/functions/map.md +0 -36
- package/docs/api/functions/signal.md +0 -21
- package/docs/api/functions/state.md +0 -67
- package/docs/api/functions/stateAsync.md +0 -69
- package/docs/api/functions/subscribe.md +0 -40
- package/docs/api/functions/writableDerivation.md +0 -33
- package/docs/api/functions/writableDerivationAsync.md +0 -34
- package/docs/api/index.md +0 -61
- package/docs/api/interfaces/FlowArray.md +0 -439
- package/docs/api/interfaces/FlowConstant.md +0 -220
- package/docs/api/interfaces/FlowConstantAsync.md +0 -221
- package/docs/api/interfaces/FlowDerivation.md +0 -241
- package/docs/api/interfaces/FlowDerivationAsync.md +0 -242
- package/docs/api/interfaces/FlowDisposable.md +0 -59
- package/docs/api/interfaces/FlowEffect.md +0 -64
- package/docs/api/interfaces/FlowMap.md +0 -374
- package/docs/api/interfaces/FlowObservable.md +0 -155
- package/docs/api/interfaces/FlowSignal.md +0 -156
- package/docs/api/interfaces/FlowState.md +0 -269
- package/docs/api/interfaces/FlowStateAsync.md +0 -268
- package/docs/api/interfaces/FlowSubscribable.md +0 -55
- package/docs/api/interfaces/FlowTracker.md +0 -61
- package/docs/api/interfaces/FlowValue.md +0 -222
- package/docs/api/interfaces/FlowWritableDerivation.md +0 -292
- package/docs/api/interfaces/FlowWritableDerivationAsync.md +0 -293
- package/docs/api/type-aliases/DerivationFunction.md +0 -28
- package/docs/api/type-aliases/DerivationFunctionAsync.md +0 -28
- package/docs/api/type-aliases/FlowArrayAction.md +0 -60
- package/docs/api/type-aliases/FlowDataTracker.md +0 -33
- package/docs/api/type-aliases/FlowMapAction.md +0 -48
- package/docs/api/type-aliases/FlowOnDataListener.md +0 -33
- package/docs/api/type-aliases/FlowOnErrorListener.md +0 -27
- package/docs/api/type-aliases/FlowOnPendingListener.md +0 -21
- package/docs/api/type-aliases/FlowReadonly.md +0 -22
- package/docs/api/type-aliases/InitFunction.md +0 -21
- package/docs/api/type-aliases/InitFunctionAsync.md +0 -21
- package/docs/api/type-aliases/NotPromise.md +0 -21
- package/docs/api/type-aliases/UpdateFunction.md +0 -27
- package/docs/api/type-aliases/UpdateFunctionAsync.md +0 -27
- package/docs/api/typedoc-sidebar.json +0 -65
- package/docs/examples/examples.md +0 -2311
- package/docs/examples/patterns.md +0 -649
- package/docs/guide/advanced/architecture.md +0 -1234
- package/docs/guide/advanced/disposal.md +0 -426
- package/docs/guide/advanced/migration-v1.md +0 -464
- package/docs/guide/advanced/migration-v2.md +0 -204
- package/docs/guide/advanced/solidjs.md +0 -135
- package/docs/guide/introduction/concepts.md +0 -57
- package/docs/guide/introduction/conventions.md +0 -30
- package/docs/guide/introduction/getting-started.md +0 -139
- package/docs/guide/introduction/lifecycle.md +0 -368
- package/docs/guide/primitives/array.md +0 -286
- package/docs/guide/primitives/constant.md +0 -207
- package/docs/guide/primitives/derivations.md +0 -281
- package/docs/guide/primitives/effects.md +0 -372
- package/docs/guide/primitives/map.md +0 -265
- package/docs/guide/primitives/overview.md +0 -92
- package/docs/guide/primitives/signal.md +0 -222
- package/docs/guide/primitives/state.md +0 -272
- package/docs/index.md +0 -47
- package/docs/public/logo.svg +0 -1
- package/src/api/base/flowDisposable.ts +0 -44
- package/src/api/base/flowObservable.ts +0 -28
- package/src/api/base/flowSubscribable.ts +0 -87
- package/src/api/base/flowTracker.ts +0 -7
- package/src/api/base/index.ts +0 -4
- package/src/api/index.ts +0 -2
- package/src/api/nodes/async/flowConstantAsync.ts +0 -36
- package/src/api/nodes/async/flowDerivationAsync.ts +0 -42
- package/src/api/nodes/async/flowStateAsync.ts +0 -47
- package/src/api/nodes/async/flowWritableDerivationAsync.ts +0 -33
- package/src/api/nodes/async/index.ts +0 -4
- package/src/api/nodes/collections/flowArray.ts +0 -155
- package/src/api/nodes/collections/flowMap.ts +0 -115
- package/src/api/nodes/collections/index.ts +0 -2
- package/src/api/nodes/flowEffect.ts +0 -42
- package/src/api/nodes/flowSignal.ts +0 -28
- package/src/api/nodes/flowValue.ts +0 -37
- package/src/api/nodes/index.ts +0 -7
- package/src/api/nodes/sync/flowConstant.ts +0 -33
- package/src/api/nodes/sync/flowDerivation.ts +0 -41
- package/src/api/nodes/sync/flowState.ts +0 -45
- package/src/api/nodes/sync/flowWritableDerivation.ts +0 -31
- package/src/api/nodes/sync/index.ts +0 -4
- package/src/api/nodes/utils.ts +0 -24
- package/src/base/disposable.ts +0 -18
- package/src/base/executionStack.ts +0 -42
- package/src/base/index.ts +0 -5
- package/src/base/node.ts +0 -98
- package/src/base/observable.ts +0 -92
- package/src/base/observer.ts +0 -51
- package/src/converters/index.ts +0 -1
- package/src/converters/solid.ts +0 -109
- package/src/index.ts +0 -2
- package/src/nodes/arrayNode.ts +0 -180
- package/src/nodes/effectNode.ts +0 -58
- package/src/nodes/index.ts +0 -7
- package/src/nodes/mapNode.ts +0 -125
- package/src/nodes/signalNode.ts +0 -19
- package/src/nodes/valueAsyncNode.ts +0 -85
- package/src/nodes/valueNode.ts +0 -148
- package/src/nodes/valueSyncNode.ts +0 -125
- package/src/schedulers/asyncResolver.ts +0 -78
- package/src/schedulers/asyncScheduler.ts +0 -66
- package/src/schedulers/index.ts +0 -4
- package/src/schedulers/pendingError.ts +0 -13
- package/src/schedulers/scheduler.ts +0 -9
- package/src/schedulers/syncResolver.ts +0 -69
- package/src/schedulers/syncScheduler.ts +0 -55
- package/test/base/pendingError.test.ts +0 -67
- package/test/converters/solid.derivation.browser.test.tsx +0 -69
- package/test/converters/solid.node.test.ts +0 -654
- package/test/converters/solid.state.browser.test.tsx +0 -1592
- package/test/reactivity/flowSignal.test.ts +0 -226
- package/test/reactivity/nodes/async/asyncScheduler/asyncResolver.test.ts +0 -593
- package/test/reactivity/nodes/async/asyncScheduler/asyncScheduler.test.ts +0 -317
- package/test/reactivity/nodes/async/flowConstantAsync.test.ts +0 -652
- package/test/reactivity/nodes/async/flowDerivation.test.ts +0 -898
- package/test/reactivity/nodes/async/flowDerivationAsync.test.ts +0 -1716
- package/test/reactivity/nodes/async/flowStateAsync.test.ts +0 -708
- package/test/reactivity/nodes/async/flowWritableDerivationAsync.test.ts +0 -614
- package/test/reactivity/nodes/collections/flowArray.asyncStates.test.ts +0 -1289
- package/test/reactivity/nodes/collections/flowArray.scalars.test.ts +0 -961
- package/test/reactivity/nodes/collections/flowArray.states.test.ts +0 -1035
- package/test/reactivity/nodes/collections/flowMap.asyncStates.test.ts +0 -960
- package/test/reactivity/nodes/collections/flowMap.scalars.test.ts +0 -775
- package/test/reactivity/nodes/collections/flowMap.states.test.ts +0 -958
- package/test/reactivity/nodes/sync/flowConstant.test.ts +0 -377
- package/test/reactivity/nodes/sync/flowDerivation.test.ts +0 -896
- package/test/reactivity/nodes/sync/flowState.test.ts +0 -341
- package/test/reactivity/nodes/sync/flowWritableDerivation.test.ts +0 -603
- package/test/vitest.d.ts +0 -10
- package/tsconfig.json +0 -37
- package/typedoc.json +0 -37
- package/vite.config.ts +0 -31
- package/vitest.browser.config.ts +0 -21
- package/vitest.config.ts +0 -17
|
@@ -1,898 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import type { FlowTracker } from "~";
|
|
3
|
-
import { derivation, signal, stateAsync, subscribe } from "~";
|
|
4
|
-
|
|
5
|
-
describe("flowDerivation", () => {
|
|
6
|
-
describe("unit", () => {
|
|
7
|
-
describe("initialization", () => {
|
|
8
|
-
it("should support self-derivation function without previous value", async () => {
|
|
9
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
10
|
-
const compute = (t: FlowTracker) => {
|
|
11
|
-
return 2 * $stateAsync.get(t);
|
|
12
|
-
};
|
|
13
|
-
const $derivation = derivation(compute);
|
|
14
|
-
const onData = vi.fn();
|
|
15
|
-
$derivation.subscribe(onData);
|
|
16
|
-
|
|
17
|
-
expect(await $derivation.pick()).toBe(2);
|
|
18
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
19
|
-
expect(onData).toHaveBeenLastCalledWith(2);
|
|
20
|
-
|
|
21
|
-
$stateAsync.set(Promise.resolve(2));
|
|
22
|
-
expect(await $derivation.pick()).toBe(4);
|
|
23
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
24
|
-
expect(onData).toHaveBeenLastCalledWith(4);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("should support self-derivation function with previous value", async () => {
|
|
28
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
29
|
-
const compute = (t: FlowTracker, previous?: number) => {
|
|
30
|
-
const current = $stateAsync.get(t);
|
|
31
|
-
return previous !== undefined ? previous + current : current;
|
|
32
|
-
};
|
|
33
|
-
const $derivation = derivation(compute);
|
|
34
|
-
const onData = vi.fn();
|
|
35
|
-
$derivation.subscribe(onData);
|
|
36
|
-
|
|
37
|
-
expect(await $derivation.pick()).toBe(1);
|
|
38
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
39
|
-
expect(onData).toHaveBeenLastCalledWith(1);
|
|
40
|
-
|
|
41
|
-
$stateAsync.set(Promise.resolve(2));
|
|
42
|
-
expect(await $derivation.pick()).toBe(3);
|
|
43
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
44
|
-
expect(onData).toHaveBeenLastCalledWith(3);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe("reactive dependencies", () => {
|
|
49
|
-
it("should recompute when state dependency changes", async () => {
|
|
50
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
51
|
-
const computeFn = vi.fn((t) => $stateAsync.get(t) * 2);
|
|
52
|
-
const $derivation = derivation(computeFn);
|
|
53
|
-
|
|
54
|
-
expect(await $derivation.pick()).toBe(2);
|
|
55
|
-
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
56
|
-
|
|
57
|
-
$stateAsync.set(Promise.resolve(2));
|
|
58
|
-
expect(await $derivation.pick()).toBe(4);
|
|
59
|
-
expect(computeFn).toHaveBeenCalledTimes(4);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("should recompute when signal dependency is triggered", async () => {
|
|
63
|
-
const $signal = signal();
|
|
64
|
-
const $stateAsync = stateAsync(Promise.resolve(0));
|
|
65
|
-
const computeFn = vi.fn((t) => {
|
|
66
|
-
$signal.watch(t);
|
|
67
|
-
return $stateAsync.get(t);
|
|
68
|
-
});
|
|
69
|
-
const $derivation = derivation(computeFn);
|
|
70
|
-
|
|
71
|
-
expect(await $derivation.pick()).toBe(0);
|
|
72
|
-
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
73
|
-
|
|
74
|
-
$stateAsync.set(Promise.resolve(1));
|
|
75
|
-
$signal.trigger();
|
|
76
|
-
expect(await $derivation.pick()).toBe(1);
|
|
77
|
-
expect(computeFn).toHaveBeenCalledTimes(4);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("should recompute when another derivation dependency changes", async () => {
|
|
81
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
82
|
-
const $derivation1 = derivation((t) => $stateAsync.get(t) * 2);
|
|
83
|
-
derivation((t) => $derivation1.get(t) * 2);
|
|
84
|
-
const computeFn = vi.fn((t) => $derivation1.get(t) * 2);
|
|
85
|
-
const $derivation2Spy = derivation(computeFn);
|
|
86
|
-
|
|
87
|
-
expect(await $derivation2Spy.pick()).toBe(4);
|
|
88
|
-
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
89
|
-
|
|
90
|
-
$stateAsync.set(Promise.resolve(2));
|
|
91
|
-
expect(await $derivation2Spy.pick()).toBe(8);
|
|
92
|
-
expect(computeFn).toHaveBeenCalledTimes(4);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("should mark as dirty when dependency changes", async () => {
|
|
96
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
97
|
-
const computeFn = vi.fn((t) => $stateAsync.get(t) * 2);
|
|
98
|
-
const $derivation = derivation(computeFn);
|
|
99
|
-
|
|
100
|
-
await $derivation.pick();
|
|
101
|
-
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
102
|
-
|
|
103
|
-
$stateAsync.set(Promise.resolve(2));
|
|
104
|
-
// Should be dirty but not recomputed yet
|
|
105
|
-
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
106
|
-
|
|
107
|
-
// Next access triggers recomputation
|
|
108
|
-
await $derivation.pick();
|
|
109
|
-
expect(computeFn).toHaveBeenCalledTimes(4);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it("should recompute if value is identical after dependency change", async () => {
|
|
113
|
-
const $stateAsync = stateAsync(Promise.resolve(2));
|
|
114
|
-
const computeFn = vi.fn((t) => $stateAsync.get(t) * 0);
|
|
115
|
-
const $derivation = derivation(computeFn);
|
|
116
|
-
const onData = vi.fn();
|
|
117
|
-
|
|
118
|
-
$derivation.subscribe(onData);
|
|
119
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
120
|
-
expect(onData).toHaveBeenLastCalledWith(0);
|
|
121
|
-
|
|
122
|
-
$stateAsync.set(Promise.resolve(5));
|
|
123
|
-
// Value is still 0 (2*0 = 0, 5*0 = 0), but derivation is marked dirty
|
|
124
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
125
|
-
expect(onData).toHaveBeenLastCalledWith(0);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe("get", () => {
|
|
130
|
-
it("should recompute when dependency changes with tracking via subscribe", async () => {
|
|
131
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
132
|
-
const computeFn = vi.fn((t) => $stateAsync.get(t) * 2);
|
|
133
|
-
const $derivation = derivation(computeFn);
|
|
134
|
-
const onData = vi.fn();
|
|
135
|
-
|
|
136
|
-
$derivation.subscribe((value) => {
|
|
137
|
-
onData(value);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
141
|
-
expect(onData).toHaveBeenLastCalledWith(2);
|
|
142
|
-
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
143
|
-
|
|
144
|
-
$stateAsync.set(Promise.resolve(2));
|
|
145
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
146
|
-
expect(onData).toHaveBeenLastCalledWith(4);
|
|
147
|
-
expect(computeFn).toHaveBeenCalledTimes(3);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("should register dependencies automatically via tracker in subscribe", async () => {
|
|
151
|
-
const $state1Async = stateAsync(Promise.resolve(1));
|
|
152
|
-
const $state2Async = stateAsync(Promise.resolve(2));
|
|
153
|
-
const computeFn = vi.fn((t) => {
|
|
154
|
-
return $state1Async.get(t) + $state2Async.get(t);
|
|
155
|
-
});
|
|
156
|
-
const $derivation = derivation(computeFn);
|
|
157
|
-
const onData = vi.fn();
|
|
158
|
-
|
|
159
|
-
$derivation.subscribe((value) => {
|
|
160
|
-
onData(value);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
164
|
-
expect(onData).toHaveBeenLastCalledWith(3);
|
|
165
|
-
|
|
166
|
-
$state1Async.set(Promise.resolve(3));
|
|
167
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
168
|
-
expect(onData).toHaveBeenLastCalledWith(5);
|
|
169
|
-
|
|
170
|
-
$state2Async.set(Promise.resolve(4));
|
|
171
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
172
|
-
expect(onData).toHaveBeenLastCalledWith(7);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it("should propagate errors in compute function with dependencies", async () => {
|
|
176
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
177
|
-
const error = new Error("Compute error");
|
|
178
|
-
const $derivation = derivation((t) => {
|
|
179
|
-
const value = $stateAsync.get(t);
|
|
180
|
-
if (value > 1) {
|
|
181
|
-
throw error;
|
|
182
|
-
}
|
|
183
|
-
return value;
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
await $derivation.pick();
|
|
187
|
-
|
|
188
|
-
$stateAsync.set(Promise.resolve(2));
|
|
189
|
-
await expect($derivation.pick()).rejects.toThrow(error);
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
describe("watch", () => {
|
|
194
|
-
it("should register dependency when watch is called in effect context", async () => {
|
|
195
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
196
|
-
const $derivation = derivation((t) => $stateAsync.get(t) * 2);
|
|
197
|
-
const onData = vi.fn();
|
|
198
|
-
|
|
199
|
-
subscribe((t) => $derivation.watch(t), onData);
|
|
200
|
-
|
|
201
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it("should recompute when dependency changes after watch", async () => {
|
|
205
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
206
|
-
const computeFn = vi.fn((t) => $stateAsync.get(t) * 2);
|
|
207
|
-
const $derivation = derivation(computeFn);
|
|
208
|
-
const onData = vi.fn();
|
|
209
|
-
|
|
210
|
-
subscribe((t) => $derivation.watch(t), onData);
|
|
211
|
-
|
|
212
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
213
|
-
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
214
|
-
|
|
215
|
-
$stateAsync.set(Promise.resolve(2));
|
|
216
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
217
|
-
expect(computeFn).toHaveBeenCalledTimes(3);
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
describe("pick", () => {
|
|
222
|
-
it("should recompute when dependency changes without tracking", async () => {
|
|
223
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
224
|
-
const computeFn = vi.fn((t) => $stateAsync.get(t) * 2);
|
|
225
|
-
const $derivation = derivation(computeFn);
|
|
226
|
-
|
|
227
|
-
expect(await $derivation.pick()).toBe(2);
|
|
228
|
-
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
229
|
-
|
|
230
|
-
$stateAsync.set(Promise.resolve(2));
|
|
231
|
-
expect(await $derivation.pick()).toBe(4);
|
|
232
|
-
expect(computeFn).toHaveBeenCalledTimes(4);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
describe("refresh", () => {
|
|
237
|
-
it("should force recomputation even if not dirty", async () => {
|
|
238
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
239
|
-
const computeFn = vi.fn((t) => $stateAsync.get(t) * 2);
|
|
240
|
-
const $derivation = derivation(computeFn);
|
|
241
|
-
|
|
242
|
-
await $derivation.pick();
|
|
243
|
-
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
244
|
-
|
|
245
|
-
$derivation.refresh();
|
|
246
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
247
|
-
expect(computeFn).toHaveBeenCalledTimes(3);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it("should notify only if value changes after refresh", async () => {
|
|
251
|
-
let multiplier = 2;
|
|
252
|
-
const $stateAsync = stateAsync(Promise.resolve(5));
|
|
253
|
-
const $derivation = derivation((t) => $stateAsync.get(t) * multiplier);
|
|
254
|
-
const onData = vi.fn();
|
|
255
|
-
|
|
256
|
-
$derivation.subscribe(onData);
|
|
257
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
258
|
-
expect(onData).toHaveBeenLastCalledWith(10);
|
|
259
|
-
|
|
260
|
-
multiplier = 3;
|
|
261
|
-
$derivation.refresh();
|
|
262
|
-
// Refresh triggers notification asynchronously
|
|
263
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2), {
|
|
264
|
-
timeout: 2000,
|
|
265
|
-
});
|
|
266
|
-
expect(onData).toHaveBeenLastCalledWith(15);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it("should not notify if value is identical after refresh", async () => {
|
|
270
|
-
const $stateAsync = stateAsync(Promise.resolve(5));
|
|
271
|
-
const $derivation = derivation((t) => $stateAsync.get(t) * 2);
|
|
272
|
-
const onData = vi.fn();
|
|
273
|
-
|
|
274
|
-
$derivation.subscribe(onData);
|
|
275
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
276
|
-
|
|
277
|
-
$derivation.refresh();
|
|
278
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
279
|
-
expect(onData).toHaveBeenCalledTimes(1);
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it("should force recomputation even if dependencies have not changed", async () => {
|
|
283
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
284
|
-
const computeFn = vi.fn((t) => $stateAsync.get(t) * 2);
|
|
285
|
-
const $derivation = derivation(computeFn);
|
|
286
|
-
|
|
287
|
-
await $derivation.pick();
|
|
288
|
-
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
289
|
-
|
|
290
|
-
$derivation.refresh();
|
|
291
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
292
|
-
expect(computeFn).toHaveBeenCalledTimes(3);
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
describe("dynamic dependencies", () => {
|
|
297
|
-
it("should handle dependencies that change between computations", async () => {
|
|
298
|
-
const $state1Async = stateAsync(Promise.resolve(1));
|
|
299
|
-
const $state2Async = stateAsync(Promise.resolve(10));
|
|
300
|
-
const $condAsync = stateAsync(Promise.resolve(true));
|
|
301
|
-
const computeFn = vi.fn((t) => {
|
|
302
|
-
if ($condAsync.get(t)) {
|
|
303
|
-
return $state1Async.get(t) * 2;
|
|
304
|
-
}
|
|
305
|
-
return $state2Async.get(t) * 2;
|
|
306
|
-
});
|
|
307
|
-
const $derivation = derivation(computeFn);
|
|
308
|
-
|
|
309
|
-
expect(await $derivation.pick()).toBe(2);
|
|
310
|
-
expect(computeFn).toHaveBeenCalledTimes(3);
|
|
311
|
-
|
|
312
|
-
$state1Async.set(Promise.resolve(2));
|
|
313
|
-
expect(await $derivation.pick()).toBe(4);
|
|
314
|
-
expect(computeFn).toHaveBeenCalledTimes(5);
|
|
315
|
-
|
|
316
|
-
$condAsync.set(Promise.resolve(false));
|
|
317
|
-
expect(await $derivation.pick()).toBe(20);
|
|
318
|
-
expect(computeFn).toHaveBeenCalledTimes(8);
|
|
319
|
-
|
|
320
|
-
$state2Async.set(Promise.resolve(20));
|
|
321
|
-
expect(await $derivation.pick()).toBe(40);
|
|
322
|
-
expect(computeFn).toHaveBeenCalledTimes(10);
|
|
323
|
-
|
|
324
|
-
$state1Async.set(Promise.resolve(100));
|
|
325
|
-
expect(await $derivation.pick()).toBe(40);
|
|
326
|
-
expect(computeFn).toHaveBeenCalledTimes(10);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it("should unregister dependencies that are no longer used", async () => {
|
|
330
|
-
const $state1Async = stateAsync(Promise.resolve(1));
|
|
331
|
-
const $state2Async = stateAsync(Promise.resolve(10));
|
|
332
|
-
const $condAsync = stateAsync(Promise.resolve(true));
|
|
333
|
-
const $derivation = derivation((t) => {
|
|
334
|
-
if ($condAsync.get(t)) {
|
|
335
|
-
return $state1Async.get(t);
|
|
336
|
-
}
|
|
337
|
-
return $state2Async.get(t);
|
|
338
|
-
});
|
|
339
|
-
const onData = vi.fn();
|
|
340
|
-
|
|
341
|
-
$derivation.subscribe(onData);
|
|
342
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
343
|
-
|
|
344
|
-
$condAsync.set(Promise.resolve(false));
|
|
345
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
346
|
-
|
|
347
|
-
$state1Async.set(Promise.resolve(100));
|
|
348
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
349
|
-
expect(onData).toHaveBeenCalledTimes(2);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it("should handle dependencies that appear and disappear", async () => {
|
|
353
|
-
const $state1Async = stateAsync(Promise.resolve(1));
|
|
354
|
-
const $state2Async = stateAsync(Promise.resolve(2));
|
|
355
|
-
const $useState1Async = stateAsync(Promise.resolve(true));
|
|
356
|
-
const $derivation = derivation((t) => {
|
|
357
|
-
if ($useState1Async.get(t)) {
|
|
358
|
-
return $state1Async.get(t);
|
|
359
|
-
}
|
|
360
|
-
return $state2Async.get(t);
|
|
361
|
-
});
|
|
362
|
-
const onData = vi.fn();
|
|
363
|
-
|
|
364
|
-
$derivation.subscribe(onData);
|
|
365
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
366
|
-
expect(onData).toHaveBeenLastCalledWith(1);
|
|
367
|
-
|
|
368
|
-
$useState1Async.set(Promise.resolve(false));
|
|
369
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
370
|
-
expect(onData).toHaveBeenLastCalledWith(2);
|
|
371
|
-
|
|
372
|
-
$useState1Async.set(Promise.resolve(true));
|
|
373
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
374
|
-
expect(onData).toHaveBeenLastCalledWith(1);
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
describe("nested derivations", () => {
|
|
379
|
-
it("should handle derivation depending on another derivation", async () => {
|
|
380
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
381
|
-
const $derivation1 = derivation((t) => $stateAsync.get(t) * 2);
|
|
382
|
-
const $derivation2 = derivation((t) => $derivation1.get(t) * 2);
|
|
383
|
-
|
|
384
|
-
expect(await $derivation2.pick()).toBe(4);
|
|
385
|
-
|
|
386
|
-
$stateAsync.set(Promise.resolve(2));
|
|
387
|
-
expect(await $derivation2.pick()).toBe(8);
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
it("should handle chain of derivations", async () => {
|
|
391
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
392
|
-
const $derivationA = derivation((t) => $stateAsync.get(t) * 2);
|
|
393
|
-
const $derivationB = derivation((t) => $derivationA.get(t) * 2);
|
|
394
|
-
const $derivationC = derivation((t) => $derivationB.get(t) * 2);
|
|
395
|
-
|
|
396
|
-
expect(await $derivationC.pick()).toBe(8);
|
|
397
|
-
|
|
398
|
-
$stateAsync.set(Promise.resolve(2));
|
|
399
|
-
expect(await $derivationC.pick()).toBe(16);
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
it("should recompute in cascade when dependency changes", async () => {
|
|
403
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
404
|
-
const computeFnA = vi.fn((t) => $stateAsync.get(t) * 2);
|
|
405
|
-
const computeFnB = vi.fn((t) => {
|
|
406
|
-
const $a = derivation(computeFnA);
|
|
407
|
-
return $a.get(t) * 2;
|
|
408
|
-
});
|
|
409
|
-
derivation(computeFnA);
|
|
410
|
-
const $derivationB = derivation(computeFnB);
|
|
411
|
-
|
|
412
|
-
const result1 = await $derivationB.pick();
|
|
413
|
-
expect(result1).toBe(4);
|
|
414
|
-
expect(computeFnA).toHaveBeenCalled();
|
|
415
|
-
expect(computeFnB).toHaveBeenCalled();
|
|
416
|
-
|
|
417
|
-
$stateAsync.set(Promise.resolve(2));
|
|
418
|
-
const result2 = await $derivationB.pick();
|
|
419
|
-
expect(result2).toBe(8);
|
|
420
|
-
expect(computeFnA).toHaveBeenCalledTimes(6);
|
|
421
|
-
expect(computeFnB).toHaveBeenCalledTimes(4);
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
describe("error handling", () => {
|
|
426
|
-
it("should propagate errors in compute function with dependencies", async () => {
|
|
427
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
428
|
-
const error = new Error("Compute error");
|
|
429
|
-
const $derivation = derivation((t) => {
|
|
430
|
-
const value = $stateAsync.get(t);
|
|
431
|
-
if (value > 1) {
|
|
432
|
-
throw error;
|
|
433
|
-
}
|
|
434
|
-
return value;
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
expect(await $derivation.pick()).toBe(1);
|
|
438
|
-
|
|
439
|
-
$stateAsync.set(Promise.resolve(2));
|
|
440
|
-
await expect($derivation.pick()).rejects.toThrow("Compute error");
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
describe("with effect", () => {
|
|
446
|
-
describe("reactive dependencies", () => {
|
|
447
|
-
it("should re-execute effect when derivation dependency changes", async () => {
|
|
448
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
449
|
-
const $derivation = derivation((t) => $stateAsync.get(t) * 2);
|
|
450
|
-
const onData = vi.fn();
|
|
451
|
-
|
|
452
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
453
|
-
|
|
454
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
455
|
-
|
|
456
|
-
$stateAsync.set(Promise.resolve(2));
|
|
457
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
it("should re-execute effect if derivation value is identical", async () => {
|
|
461
|
-
const $stateAsync = stateAsync(Promise.resolve(2));
|
|
462
|
-
const $derivation = derivation((t) => $stateAsync.get(t) * 0);
|
|
463
|
-
const onData = vi.fn();
|
|
464
|
-
|
|
465
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
466
|
-
|
|
467
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
468
|
-
|
|
469
|
-
$stateAsync.set(Promise.resolve(5));
|
|
470
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
it("should work with derivation depending on state", async () => {
|
|
474
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
475
|
-
const $derivation = derivation((t) => $stateAsync.get(t) * 2);
|
|
476
|
-
const onData = vi.fn();
|
|
477
|
-
|
|
478
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
479
|
-
|
|
480
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
481
|
-
expect(onData).toHaveBeenLastCalledWith(2);
|
|
482
|
-
|
|
483
|
-
$stateAsync.set(Promise.resolve(2));
|
|
484
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
485
|
-
expect(onData).toHaveBeenLastCalledWith(4);
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
it("should work with derivation depending on signal", async () => {
|
|
489
|
-
const $signal = signal();
|
|
490
|
-
const $stateAsync = stateAsync(Promise.resolve(0));
|
|
491
|
-
const $derivation = derivation((t) => {
|
|
492
|
-
$signal.watch(t);
|
|
493
|
-
return $stateAsync.get(t);
|
|
494
|
-
});
|
|
495
|
-
const onData = vi.fn();
|
|
496
|
-
|
|
497
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
498
|
-
|
|
499
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
500
|
-
expect(onData).toHaveBeenLastCalledWith(0);
|
|
501
|
-
|
|
502
|
-
$stateAsync.set(Promise.resolve(1));
|
|
503
|
-
$signal.trigger();
|
|
504
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
505
|
-
expect(onData).toHaveBeenLastCalledWith(1);
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
it("should work with derivation depending on another derivation", async () => {
|
|
509
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
510
|
-
const $derivation1 = derivation((t) => $stateAsync.get(t) * 2);
|
|
511
|
-
const $derivation2 = derivation((t) => $derivation1.get(t) * 2);
|
|
512
|
-
const onData = vi.fn();
|
|
513
|
-
|
|
514
|
-
subscribe((t) => $derivation2.get(t), onData);
|
|
515
|
-
|
|
516
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
517
|
-
expect(onData).toHaveBeenLastCalledWith(4);
|
|
518
|
-
|
|
519
|
-
$stateAsync.set(Promise.resolve(2));
|
|
520
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
521
|
-
expect(onData).toHaveBeenLastCalledWith(8);
|
|
522
|
-
});
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
describe("multiple dependencies", () => {
|
|
526
|
-
it("should work with derivation depending on multiple states", async () => {
|
|
527
|
-
const $stateAAsync = stateAsync(Promise.resolve(5));
|
|
528
|
-
const $stateBAsync = stateAsync(Promise.resolve(10));
|
|
529
|
-
const $derivation = derivation((t) => $stateAAsync.get(t) + $stateBAsync.get(t));
|
|
530
|
-
const onData = vi.fn();
|
|
531
|
-
|
|
532
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
533
|
-
|
|
534
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
535
|
-
expect(onData).toHaveBeenLastCalledWith(15);
|
|
536
|
-
|
|
537
|
-
$stateAAsync.set(Promise.resolve(6));
|
|
538
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
539
|
-
expect(onData).toHaveBeenLastCalledWith(16);
|
|
540
|
-
|
|
541
|
-
$stateBAsync.set(Promise.resolve(11));
|
|
542
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
543
|
-
expect(onData).toHaveBeenLastCalledWith(17);
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
it("should work with derivation depending on multiple signals", async () => {
|
|
547
|
-
const $signal1 = signal();
|
|
548
|
-
const $signal2 = signal();
|
|
549
|
-
const $derivation = derivation((t) => {
|
|
550
|
-
$signal1.watch(t);
|
|
551
|
-
$signal2.watch(t);
|
|
552
|
-
return 42;
|
|
553
|
-
});
|
|
554
|
-
const onData = vi.fn();
|
|
555
|
-
|
|
556
|
-
subscribe((t) => $derivation.watch(t), onData);
|
|
557
|
-
|
|
558
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
559
|
-
|
|
560
|
-
$signal1.trigger();
|
|
561
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
562
|
-
|
|
563
|
-
$signal2.trigger();
|
|
564
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
it("should work with derivation depending on state and signal", async () => {
|
|
568
|
-
const $signal = signal();
|
|
569
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
570
|
-
const $derivation = derivation((t) => {
|
|
571
|
-
$signal.watch(t);
|
|
572
|
-
return $stateAsync.get(t) * 2;
|
|
573
|
-
});
|
|
574
|
-
const onData = vi.fn();
|
|
575
|
-
|
|
576
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
577
|
-
|
|
578
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
579
|
-
expect(onData).toHaveBeenLastCalledWith(2);
|
|
580
|
-
|
|
581
|
-
$signal.trigger();
|
|
582
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
583
|
-
expect(onData).toHaveBeenLastCalledWith(2);
|
|
584
|
-
|
|
585
|
-
$stateAsync.set(Promise.resolve(2));
|
|
586
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
587
|
-
expect(onData).toHaveBeenLastCalledWith(4);
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
it("should work with derivation depending on state and another derivation", async () => {
|
|
591
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
592
|
-
const $derivation1 = derivation((t) => $stateAsync.get(t) * 2);
|
|
593
|
-
const $derivation2 = derivation((t) => $stateAsync.get(t) + $derivation1.get(t));
|
|
594
|
-
const onData = vi.fn();
|
|
595
|
-
|
|
596
|
-
subscribe((t) => $derivation2.get(t), onData);
|
|
597
|
-
|
|
598
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
599
|
-
expect(onData).toHaveBeenLastCalledWith(3);
|
|
600
|
-
|
|
601
|
-
$stateAsync.set(Promise.resolve(2));
|
|
602
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
603
|
-
expect(onData).toHaveBeenLastCalledWith(6);
|
|
604
|
-
});
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
describe("nested derivations", () => {
|
|
608
|
-
it("should work with chained derivations", async () => {
|
|
609
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
610
|
-
const $derivation1 = derivation((t) => $stateAsync.get(t) * 2);
|
|
611
|
-
const $derivation2 = derivation((t) => $derivation1.get(t) * 2);
|
|
612
|
-
const $derivation3 = derivation((t) => $derivation2.get(t) * 2);
|
|
613
|
-
const onData = vi.fn();
|
|
614
|
-
|
|
615
|
-
subscribe((t) => $derivation3.get(t), onData);
|
|
616
|
-
|
|
617
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
618
|
-
expect(onData).toHaveBeenLastCalledWith(8);
|
|
619
|
-
|
|
620
|
-
$stateAsync.set(Promise.resolve(2));
|
|
621
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
622
|
-
expect(onData).toHaveBeenLastCalledWith(16);
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
it("should re-execute effect when distant dependency changes", async () => {
|
|
626
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
627
|
-
const $derivation1 = derivation((t) => $stateAsync.get(t) * 2);
|
|
628
|
-
const $derivation2 = derivation((t) => $derivation1.get(t) * 2);
|
|
629
|
-
const onData = vi.fn();
|
|
630
|
-
|
|
631
|
-
subscribe((t) => $derivation2.get(t), onData);
|
|
632
|
-
|
|
633
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
634
|
-
expect(onData).toHaveBeenLastCalledWith(4);
|
|
635
|
-
|
|
636
|
-
$stateAsync.set(Promise.resolve(2));
|
|
637
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
638
|
-
expect(onData).toHaveBeenLastCalledWith(8);
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
it("should propagate changes correctly through the chain", async () => {
|
|
642
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
643
|
-
const $derivation1 = derivation((t) => $stateAsync.get(t) * 2);
|
|
644
|
-
const $derivation2 = derivation((t) => $derivation1.get(t) * 2);
|
|
645
|
-
const onData1 = vi.fn();
|
|
646
|
-
const onData2 = vi.fn();
|
|
647
|
-
|
|
648
|
-
subscribe((t) => $derivation1.get(t), onData1);
|
|
649
|
-
subscribe((t) => $derivation2.get(t), onData2);
|
|
650
|
-
|
|
651
|
-
await vi.waitFor(() => {
|
|
652
|
-
expect(onData1).toHaveBeenCalledTimes(1);
|
|
653
|
-
expect(onData2).toHaveBeenCalledTimes(1);
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
$stateAsync.set(Promise.resolve(2));
|
|
657
|
-
await vi.waitFor(() => {
|
|
658
|
-
expect(onData1).toHaveBeenCalledTimes(2);
|
|
659
|
-
expect(onData2).toHaveBeenCalledTimes(2);
|
|
660
|
-
});
|
|
661
|
-
});
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
describe("dynamic dependencies", () => {
|
|
665
|
-
it("should work with derivation with conditional dependencies", async () => {
|
|
666
|
-
const $state1Async = stateAsync(Promise.resolve(1));
|
|
667
|
-
const $state2Async = stateAsync(Promise.resolve(10));
|
|
668
|
-
const $condAsync = stateAsync(Promise.resolve(true));
|
|
669
|
-
const $derivation = derivation((t) => {
|
|
670
|
-
if ($condAsync.get(t)) {
|
|
671
|
-
return $state1Async.get(t) * 2;
|
|
672
|
-
}
|
|
673
|
-
return $state2Async.get(t) * 2;
|
|
674
|
-
});
|
|
675
|
-
const onData = vi.fn();
|
|
676
|
-
|
|
677
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
678
|
-
|
|
679
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
680
|
-
expect(onData).toHaveBeenLastCalledWith(2);
|
|
681
|
-
|
|
682
|
-
$state1Async.set(Promise.resolve(2));
|
|
683
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
684
|
-
expect(onData).toHaveBeenLastCalledWith(4);
|
|
685
|
-
|
|
686
|
-
$condAsync.set(Promise.resolve(false));
|
|
687
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
688
|
-
expect(onData).toHaveBeenLastCalledWith(20);
|
|
689
|
-
|
|
690
|
-
$state2Async.set(Promise.resolve(20));
|
|
691
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(4));
|
|
692
|
-
expect(onData).toHaveBeenLastCalledWith(40);
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
it("should re-execute effect when dependencies change dynamically", async () => {
|
|
696
|
-
const $state1Async = stateAsync(Promise.resolve(1));
|
|
697
|
-
const $state2Async = stateAsync(Promise.resolve(2));
|
|
698
|
-
const $useState1Async = stateAsync(Promise.resolve(true));
|
|
699
|
-
const $derivation = derivation((t) => {
|
|
700
|
-
if ($useState1Async.get(t)) {
|
|
701
|
-
return $state1Async.get(t);
|
|
702
|
-
}
|
|
703
|
-
return $state2Async.get(t);
|
|
704
|
-
});
|
|
705
|
-
const onData = vi.fn();
|
|
706
|
-
|
|
707
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
708
|
-
|
|
709
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
710
|
-
expect(onData).toHaveBeenLastCalledWith(1);
|
|
711
|
-
|
|
712
|
-
$useState1Async.set(Promise.resolve(false));
|
|
713
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
714
|
-
expect(onData).toHaveBeenLastCalledWith(2);
|
|
715
|
-
|
|
716
|
-
$state1Async.set(Promise.resolve(10));
|
|
717
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
718
|
-
expect(onData).toHaveBeenCalledTimes(2);
|
|
719
|
-
|
|
720
|
-
$state2Async.set(Promise.resolve(20));
|
|
721
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
722
|
-
expect(onData).toHaveBeenLastCalledWith(20);
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
it("should unregister old dependencies correctly", async () => {
|
|
726
|
-
const $state1Async = stateAsync(Promise.resolve(1));
|
|
727
|
-
const $state2Async = stateAsync(Promise.resolve(10));
|
|
728
|
-
const $condAsync = stateAsync(Promise.resolve(true));
|
|
729
|
-
const $derivation = derivation((t) => {
|
|
730
|
-
if ($condAsync.get(t)) {
|
|
731
|
-
return $state1Async.get(t);
|
|
732
|
-
}
|
|
733
|
-
return $state2Async.get(t);
|
|
734
|
-
});
|
|
735
|
-
const onData = vi.fn();
|
|
736
|
-
|
|
737
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
738
|
-
|
|
739
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
740
|
-
|
|
741
|
-
$condAsync.set(Promise.resolve(false));
|
|
742
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
743
|
-
|
|
744
|
-
$state1Async.set(Promise.resolve(100));
|
|
745
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
746
|
-
expect(onData).toHaveBeenCalledTimes(2);
|
|
747
|
-
});
|
|
748
|
-
});
|
|
749
|
-
|
|
750
|
-
describe("complex patterns", () => {
|
|
751
|
-
it("should handle diamond pattern", async () => {
|
|
752
|
-
const $stateAAsync = stateAsync(Promise.resolve(1));
|
|
753
|
-
const $aMult3 = derivation((t) => $stateAAsync.get(t) * 3);
|
|
754
|
-
const $aMult2 = derivation((t) => $stateAAsync.get(t) * 2);
|
|
755
|
-
const $addAmult3Amult2 = derivation((t) => $aMult3.get(t) + $aMult2.get(t));
|
|
756
|
-
const onData = vi.fn();
|
|
757
|
-
|
|
758
|
-
subscribe((t) => $addAmult3Amult2.get(t), onData);
|
|
759
|
-
|
|
760
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
761
|
-
expect(onData).toHaveBeenLastCalledWith(5);
|
|
762
|
-
|
|
763
|
-
$stateAAsync.set(Promise.resolve(2));
|
|
764
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
765
|
-
expect(onData).toHaveBeenLastCalledWith(10);
|
|
766
|
-
|
|
767
|
-
$stateAAsync.set(Promise.resolve(3));
|
|
768
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
769
|
-
expect(onData).toHaveBeenLastCalledWith(15);
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
it("should handle multi-diamond pattern", async () => {
|
|
773
|
-
const $stateAAsync = stateAsync(Promise.resolve(1));
|
|
774
|
-
const $stateBAsync = stateAsync(Promise.resolve(2));
|
|
775
|
-
const $addAB = derivation((t) => {
|
|
776
|
-
const a = $stateAAsync.get(t);
|
|
777
|
-
const b = $stateBAsync.get(t);
|
|
778
|
-
return a + b;
|
|
779
|
-
});
|
|
780
|
-
const $multiplyAB = derivation((t) => {
|
|
781
|
-
const a = $stateAAsync.get(t);
|
|
782
|
-
const b = $stateBAsync.get(t);
|
|
783
|
-
return a * b;
|
|
784
|
-
});
|
|
785
|
-
const $addAndMultiply = derivation((t) => {
|
|
786
|
-
const add = $addAB.get(t);
|
|
787
|
-
const multiply = $multiplyAB.get(t);
|
|
788
|
-
return add * multiply;
|
|
789
|
-
});
|
|
790
|
-
const onData = vi.fn();
|
|
791
|
-
|
|
792
|
-
subscribe((t) => $addAndMultiply.get(t), onData);
|
|
793
|
-
|
|
794
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
795
|
-
expect(onData).toHaveBeenLastCalledWith(6);
|
|
796
|
-
|
|
797
|
-
$stateAAsync.set(Promise.resolve(2));
|
|
798
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
799
|
-
expect(onData).toHaveBeenLastCalledWith(16);
|
|
800
|
-
|
|
801
|
-
$stateBAsync.set(Promise.resolve(3));
|
|
802
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
803
|
-
expect(onData).toHaveBeenLastCalledWith(30);
|
|
804
|
-
});
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
describe("with refresh", () => {
|
|
808
|
-
it("should re-execute effect when refresh forces recomputation", async () => {
|
|
809
|
-
let multiplier = 2;
|
|
810
|
-
const $stateAsync = stateAsync(Promise.resolve(5));
|
|
811
|
-
const $derivation = derivation((t) => $stateAsync.get(t) * multiplier);
|
|
812
|
-
const onData = vi.fn();
|
|
813
|
-
|
|
814
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
815
|
-
|
|
816
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
817
|
-
expect(onData).toHaveBeenLastCalledWith(10);
|
|
818
|
-
|
|
819
|
-
multiplier = 3;
|
|
820
|
-
$derivation.refresh();
|
|
821
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
822
|
-
expect(onData).toHaveBeenLastCalledWith(15);
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
it("should not re-execute effect if refresh returns same value", async () => {
|
|
826
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
827
|
-
const computeFn = vi.fn((t) => $stateAsync.get(t) * 2);
|
|
828
|
-
const $derivation = derivation(computeFn);
|
|
829
|
-
const onData = vi.fn();
|
|
830
|
-
|
|
831
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
832
|
-
|
|
833
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
834
|
-
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
835
|
-
|
|
836
|
-
$derivation.refresh();
|
|
837
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
838
|
-
expect(onData).toHaveBeenCalledTimes(1);
|
|
839
|
-
expect(computeFn).toHaveBeenCalledTimes(3);
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
it("should force recomputation even if dependencies have not changed", async () => {
|
|
843
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
844
|
-
const computeFn = vi.fn((t) => $stateAsync.get(t) * 2);
|
|
845
|
-
const $derivation = derivation(computeFn);
|
|
846
|
-
const onData = vi.fn();
|
|
847
|
-
|
|
848
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
849
|
-
|
|
850
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
851
|
-
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
852
|
-
|
|
853
|
-
$derivation.refresh();
|
|
854
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
855
|
-
expect(computeFn).toHaveBeenCalledTimes(3);
|
|
856
|
-
});
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
describe("error handling", () => {
|
|
860
|
-
it("should propagate errors in compute function with dependencies in effect", async () => {
|
|
861
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
862
|
-
const error = new Error("Effect compute error");
|
|
863
|
-
const $derivation = derivation((t) => {
|
|
864
|
-
const value = $stateAsync.get(t);
|
|
865
|
-
if (value > 1) {
|
|
866
|
-
throw error;
|
|
867
|
-
}
|
|
868
|
-
return value;
|
|
869
|
-
});
|
|
870
|
-
|
|
871
|
-
const onData = vi.fn();
|
|
872
|
-
const onError = vi.fn();
|
|
873
|
-
subscribe((t) => $derivation.get(t), onData, onError);
|
|
874
|
-
|
|
875
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
876
|
-
|
|
877
|
-
$stateAsync.set(Promise.resolve(2));
|
|
878
|
-
await vi.waitFor(() => expect(onError).toHaveBeenCalledTimes(1));
|
|
879
|
-
expect(onError).toHaveBeenLastCalledWith(error);
|
|
880
|
-
});
|
|
881
|
-
|
|
882
|
-
it("should handle errors when dependency is disposed during computation", async () => {
|
|
883
|
-
const $stateAsync = stateAsync(Promise.resolve(1));
|
|
884
|
-
const $derivation = derivation((t) => $stateAsync.get(t) * 2);
|
|
885
|
-
const onData = vi.fn();
|
|
886
|
-
|
|
887
|
-
subscribe((t) => $derivation.get(t), onData);
|
|
888
|
-
|
|
889
|
-
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
890
|
-
|
|
891
|
-
$stateAsync.dispose();
|
|
892
|
-
|
|
893
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
894
|
-
expect(onData).toHaveBeenCalledTimes(1);
|
|
895
|
-
});
|
|
896
|
-
});
|
|
897
|
-
});
|
|
898
|
-
});
|