@ersbeth/picoflow 1.0.1 → 1.1.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/.cursor/plans/unifier-flowresource-avec-flowderivation-c9506e24.plan.md +372 -0
- package/README.md +17 -1
- package/biome.json +4 -1
- package/dist/picoflow.js +1129 -661
- package/dist/types/flow/base/flowDisposable.d.ts +67 -0
- package/dist/types/flow/base/flowDisposable.d.ts.map +1 -0
- package/dist/types/flow/base/flowEffect.d.ts +127 -0
- package/dist/types/flow/base/flowEffect.d.ts.map +1 -0
- package/dist/types/flow/base/flowGraph.d.ts +97 -0
- package/dist/types/flow/base/flowGraph.d.ts.map +1 -0
- package/dist/types/flow/base/flowSignal.d.ts +134 -0
- package/dist/types/flow/base/flowSignal.d.ts.map +1 -0
- package/dist/types/flow/base/flowTracker.d.ts +15 -0
- package/dist/types/flow/base/flowTracker.d.ts.map +1 -0
- package/dist/types/flow/base/index.d.ts +7 -0
- package/dist/types/flow/base/index.d.ts.map +1 -0
- package/dist/types/flow/base/utils.d.ts +20 -0
- package/dist/types/flow/base/utils.d.ts.map +1 -0
- package/dist/types/{advanced/array.d.ts → flow/collections/flowArray.d.ts} +50 -12
- package/dist/types/flow/collections/flowArray.d.ts.map +1 -0
- package/dist/types/flow/collections/flowMap.d.ts +224 -0
- package/dist/types/flow/collections/flowMap.d.ts.map +1 -0
- package/dist/types/flow/collections/index.d.ts +3 -0
- package/dist/types/flow/collections/index.d.ts.map +1 -0
- package/dist/types/flow/index.d.ts +4 -0
- package/dist/types/flow/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts +137 -0
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts +137 -0
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts +343 -0
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts +81 -0
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts +111 -0
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/index.d.ts +6 -0
- package/dist/types/flow/nodes/async/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/index.d.ts +3 -0
- package/dist/types/flow/nodes/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowConstant.d.ts +108 -0
- package/dist/types/flow/nodes/sync/flowConstant.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts +100 -0
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowNode.d.ts +314 -0
- package/dist/types/flow/nodes/sync/flowNode.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts +57 -0
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowState.d.ts +96 -0
- package/dist/types/flow/nodes/sync/flowState.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/index.d.ts +6 -0
- package/dist/types/flow/nodes/sync/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/solid/converters.d.ts +34 -44
- package/dist/types/solid/converters.d.ts.map +1 -1
- package/dist/types/solid/primitives.d.ts +1 -0
- package/dist/types/solid/primitives.d.ts.map +1 -1
- package/docs/.vitepress/config.mts +1 -1
- package/docs/api/typedoc-sidebar.json +81 -1
- package/package.json +60 -58
- package/src/flow/base/flowDisposable.ts +71 -0
- package/src/flow/base/flowEffect.ts +171 -0
- package/src/flow/base/flowGraph.ts +288 -0
- package/src/flow/base/flowSignal.ts +207 -0
- package/src/flow/base/flowTracker.ts +17 -0
- package/src/flow/base/index.ts +6 -0
- package/src/flow/base/utils.ts +19 -0
- package/src/flow/collections/flowArray.ts +409 -0
- package/src/flow/collections/flowMap.ts +398 -0
- package/src/flow/collections/index.ts +2 -0
- package/src/flow/index.ts +3 -0
- package/src/flow/nodes/async/flowConstantAsync.ts +142 -0
- package/src/flow/nodes/async/flowDerivationAsync.ts +143 -0
- package/src/flow/nodes/async/flowNodeAsync.ts +474 -0
- package/src/flow/nodes/async/flowReadonlyAsync.ts +81 -0
- package/src/flow/nodes/async/flowStateAsync.ts +116 -0
- package/src/flow/nodes/async/index.ts +5 -0
- package/src/flow/nodes/await/advanced/index.ts +5 -0
- package/src/{advanced → flow/nodes/await/advanced}/resource.ts +37 -3
- package/src/{advanced → flow/nodes/await/advanced}/resourceAsync.ts +35 -3
- package/src/{advanced → flow/nodes/await/advanced}/stream.ts +40 -2
- package/src/{advanced → flow/nodes/await/advanced}/streamAsync.ts +38 -3
- package/src/flow/nodes/await/flowConstantAwait.ts +154 -0
- package/src/flow/nodes/await/flowDerivationAwait.ts +154 -0
- package/src/flow/nodes/await/flowNodeAwait.ts +508 -0
- package/src/flow/nodes/await/flowReadonlyAwait.ts +89 -0
- package/src/flow/nodes/await/flowStateAwait.ts +130 -0
- package/src/flow/nodes/await/index.ts +5 -0
- package/src/flow/nodes/index.ts +3 -0
- package/src/flow/nodes/sync/flowConstant.ts +111 -0
- package/src/flow/nodes/sync/flowDerivation.ts +105 -0
- package/src/flow/nodes/sync/flowNode.ts +439 -0
- package/src/flow/nodes/sync/flowReadonly.ts +57 -0
- package/src/flow/nodes/sync/flowState.ts +101 -0
- package/src/flow/nodes/sync/index.ts +5 -0
- package/src/index.ts +1 -47
- package/src/solid/converters.ts +59 -198
- package/src/solid/primitives.ts +4 -0
- package/test/base/flowEffect.test.ts +108 -0
- package/test/base/flowGraph.test.ts +485 -0
- package/test/base/flowSignal.test.ts +372 -0
- package/test/collections/flowArray.asyncStates.test.ts +1553 -0
- package/test/collections/flowArray.scalars.test.ts +1129 -0
- package/test/collections/flowArray.states.test.ts +1365 -0
- package/test/collections/flowMap.asyncStates.test.ts +1105 -0
- package/test/collections/flowMap.scalars.test.ts +877 -0
- package/test/collections/flowMap.states.test.ts +1097 -0
- package/test/nodes/async/flowConstantAsync.test.ts +860 -0
- package/test/nodes/async/flowDerivationAsync.test.ts +1517 -0
- package/test/nodes/async/flowStateAsync.test.ts +1387 -0
- package/test/{resource.test.ts → nodes/await/advanced/resource.test.ts} +21 -19
- package/test/{resourceAsync.test.ts → nodes/await/advanced/resourceAsync.test.ts} +3 -1
- package/test/{stream.test.ts → nodes/await/advanced/stream.test.ts} +30 -28
- package/test/{streamAsync.test.ts → nodes/await/advanced/streamAsync.test.ts} +16 -14
- package/test/nodes/await/flowConstantAwait.test.ts +643 -0
- package/test/nodes/await/flowDerivationAwait.test.ts +1583 -0
- package/test/nodes/await/flowStateAwait.test.ts +999 -0
- package/test/nodes/mixed/derivation.test.ts +1527 -0
- package/test/nodes/sync/flowConstant.test.ts +620 -0
- package/test/nodes/sync/flowDerivation.test.ts +1373 -0
- package/test/nodes/sync/flowState.test.ts +945 -0
- package/test/solid/converters.test.ts +721 -0
- package/test/solid/primitives.test.ts +1031 -0
- package/tsconfig.json +2 -1
- package/vitest.config.ts +7 -1
- package/IMPLEMENTATION_GUIDE.md +0 -1578
- package/dist/types/advanced/array.d.ts.map +0 -1
- package/dist/types/advanced/index.d.ts +0 -9
- package/dist/types/advanced/index.d.ts.map +0 -1
- package/dist/types/advanced/map.d.ts +0 -166
- package/dist/types/advanced/map.d.ts.map +0 -1
- package/dist/types/advanced/resource.d.ts +0 -78
- package/dist/types/advanced/resource.d.ts.map +0 -1
- package/dist/types/advanced/resourceAsync.d.ts +0 -56
- package/dist/types/advanced/resourceAsync.d.ts.map +0 -1
- package/dist/types/advanced/stream.d.ts +0 -117
- package/dist/types/advanced/stream.d.ts.map +0 -1
- package/dist/types/advanced/streamAsync.d.ts +0 -97
- package/dist/types/advanced/streamAsync.d.ts.map +0 -1
- package/dist/types/basic/constant.d.ts +0 -60
- package/dist/types/basic/constant.d.ts.map +0 -1
- package/dist/types/basic/derivation.d.ts +0 -89
- package/dist/types/basic/derivation.d.ts.map +0 -1
- package/dist/types/basic/disposable.d.ts +0 -82
- package/dist/types/basic/disposable.d.ts.map +0 -1
- package/dist/types/basic/effect.d.ts +0 -67
- package/dist/types/basic/effect.d.ts.map +0 -1
- package/dist/types/basic/index.d.ts +0 -10
- package/dist/types/basic/index.d.ts.map +0 -1
- package/dist/types/basic/observable.d.ts +0 -83
- package/dist/types/basic/observable.d.ts.map +0 -1
- package/dist/types/basic/signal.d.ts +0 -69
- package/dist/types/basic/signal.d.ts.map +0 -1
- package/dist/types/basic/state.d.ts +0 -47
- package/dist/types/basic/state.d.ts.map +0 -1
- package/dist/types/basic/trackingContext.d.ts +0 -33
- package/dist/types/basic/trackingContext.d.ts.map +0 -1
- package/dist/types/creators.d.ts +0 -340
- package/dist/types/creators.d.ts.map +0 -1
- package/src/advanced/array.ts +0 -222
- package/src/advanced/index.ts +0 -12
- package/src/advanced/map.ts +0 -193
- package/src/basic/constant.ts +0 -97
- package/src/basic/derivation.ts +0 -147
- package/src/basic/disposable.ts +0 -86
- package/src/basic/effect.ts +0 -104
- package/src/basic/index.ts +0 -9
- package/src/basic/observable.ts +0 -109
- package/src/basic/signal.ts +0 -145
- package/src/basic/state.ts +0 -60
- package/src/basic/trackingContext.ts +0 -45
- package/src/creators.ts +0 -395
- package/test/array.test.ts +0 -600
- package/test/constant.test.ts +0 -44
- package/test/derivation.test.ts +0 -539
- package/test/effect.test.ts +0 -29
- package/test/map.test.ts +0 -240
- package/test/signal.test.ts +0 -72
- package/test/state.test.ts +0 -212
|
@@ -0,0 +1,1583 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { derivationAwait, effect, signal, state, stateAwait } from "#package";
|
|
3
|
+
|
|
4
|
+
describe("flowDerivationAwait", () => {
|
|
5
|
+
describe("unit", () => {
|
|
6
|
+
describe("initialization", () => {
|
|
7
|
+
it("should not call compute function on creation", () => {
|
|
8
|
+
const computeFn = vi.fn(async () => Promise.resolve(42));
|
|
9
|
+
derivationAwait(computeFn);
|
|
10
|
+
|
|
11
|
+
expect(computeFn).not.toHaveBeenCalled();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should call compute function on first access", async () => {
|
|
15
|
+
const computeFn = vi.fn(async () => Promise.resolve(100));
|
|
16
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
17
|
+
const $derivation = derivationAwait(async (t) => {
|
|
18
|
+
computeFn();
|
|
19
|
+
const val = $state.get(t);
|
|
20
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(computeFn).not.toHaveBeenCalled();
|
|
24
|
+
await $derivation.pick();
|
|
25
|
+
expect(computeFn).toHaveBeenCalledTimes(1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should call compute function again when dependency changes", async () => {
|
|
29
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
30
|
+
const computeFn = vi.fn(async (t) => {
|
|
31
|
+
const val = $state.get(t);
|
|
32
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
33
|
+
});
|
|
34
|
+
const $derivation = derivationAwait(computeFn);
|
|
35
|
+
|
|
36
|
+
await $derivation.pick();
|
|
37
|
+
expect(computeFn).toHaveBeenCalledTimes(1);
|
|
38
|
+
|
|
39
|
+
await $state.set(Promise.resolve(2));
|
|
40
|
+
await $derivation.pick();
|
|
41
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should handle number return values", async () => {
|
|
45
|
+
const $state = stateAwait(Promise.resolve(5));
|
|
46
|
+
const $derivation = derivationAwait(async (t) => {
|
|
47
|
+
const val = $state.get(t);
|
|
48
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
49
|
+
});
|
|
50
|
+
expect(await $derivation.pick()).toBe(10);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should handle string return values", async () => {
|
|
54
|
+
const $state = stateAwait(Promise.resolve("hello"));
|
|
55
|
+
const $derivation = derivationAwait(async (t) => {
|
|
56
|
+
const val = $state.get(t);
|
|
57
|
+
return Promise.resolve(val?.toUpperCase() ?? "");
|
|
58
|
+
});
|
|
59
|
+
expect(await $derivation.pick()).toBe("HELLO");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should handle object return values", async () => {
|
|
63
|
+
const $state = stateAwait(Promise.resolve({ a: 1 }));
|
|
64
|
+
const $derivation = derivationAwait(async (t) => {
|
|
65
|
+
const val = $state.get(t);
|
|
66
|
+
return Promise.resolve({
|
|
67
|
+
...(val ?? {}),
|
|
68
|
+
b: 2,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
const value = await $derivation.pick();
|
|
72
|
+
expect(value).toEqual({ a: 1, b: 2 });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should handle array return values", async () => {
|
|
76
|
+
const $state = stateAwait(Promise.resolve([1, 2]));
|
|
77
|
+
const $derivation = derivationAwait(async (t) => {
|
|
78
|
+
const val = $state.get(t);
|
|
79
|
+
return Promise.resolve([...(val ?? []), 3]);
|
|
80
|
+
});
|
|
81
|
+
const value = await $derivation.pick();
|
|
82
|
+
expect(value).toEqual([1, 2, 3]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should handle null return values", async () => {
|
|
86
|
+
const $state = stateAwait(Promise.resolve(true));
|
|
87
|
+
const $derivation = derivationAwait(async (t) => {
|
|
88
|
+
const val = $state.get(t);
|
|
89
|
+
return Promise.resolve(val ? null : "not null");
|
|
90
|
+
});
|
|
91
|
+
expect(await $derivation.pick()).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should handle undefined return values", async () => {
|
|
95
|
+
const $state = stateAwait(Promise.resolve(true));
|
|
96
|
+
const $derivation = derivationAwait(async (t) => {
|
|
97
|
+
const val = $state.get(t);
|
|
98
|
+
return Promise.resolve(val ? undefined : "defined");
|
|
99
|
+
});
|
|
100
|
+
expect(await $derivation.pick()).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("get", () => {
|
|
105
|
+
it("should return computed value with tracking context", async () => {
|
|
106
|
+
const $state = stateAwait(Promise.resolve(5));
|
|
107
|
+
const $derivation = derivationAwait(async (t) => {
|
|
108
|
+
const val = $state.get(t);
|
|
109
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
110
|
+
});
|
|
111
|
+
const $tracker = state(0);
|
|
112
|
+
|
|
113
|
+
// Wait for Promise to resolve first
|
|
114
|
+
await $derivation.pick();
|
|
115
|
+
|
|
116
|
+
const value = $derivation.get($tracker);
|
|
117
|
+
expect(value).toBe(10);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should return undefined if Promise not yet resolved", () => {
|
|
121
|
+
// Create a Promise that resolves after a delay
|
|
122
|
+
const $state = stateAwait(
|
|
123
|
+
new Promise<number>((resolve) => setTimeout(() => resolve(10), 100)),
|
|
124
|
+
);
|
|
125
|
+
const $derivation = derivationAwait(async (t) => {
|
|
126
|
+
const val = $state.get(t);
|
|
127
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
128
|
+
});
|
|
129
|
+
const $tracker = state(0);
|
|
130
|
+
|
|
131
|
+
// Before Promise resolves, get() should return undefined
|
|
132
|
+
const value = $derivation.get($tracker);
|
|
133
|
+
expect(value).toBeUndefined();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should compute lazy value on first get call", async () => {
|
|
137
|
+
const $state = stateAwait(Promise.resolve(10));
|
|
138
|
+
const computeFn = vi.fn(async (t) => {
|
|
139
|
+
const val = $state.get(t);
|
|
140
|
+
return (val ?? 0) * 2;
|
|
141
|
+
});
|
|
142
|
+
const $derivation = derivationAwait(async (t) => {
|
|
143
|
+
return computeFn(t);
|
|
144
|
+
});
|
|
145
|
+
const $tracker = state(0);
|
|
146
|
+
|
|
147
|
+
expect(computeFn).not.toHaveBeenCalled();
|
|
148
|
+
|
|
149
|
+
// First get() may return undefined, but triggers computation
|
|
150
|
+
const value = $derivation.get($tracker);
|
|
151
|
+
await vi.waitFor(() => expect(computeFn).toHaveBeenCalledTimes(1));
|
|
152
|
+
expect(value).toBeUndefined();
|
|
153
|
+
|
|
154
|
+
// Wait for Promise to resolve
|
|
155
|
+
await $derivation.pick();
|
|
156
|
+
const value2 = $derivation.get($tracker);
|
|
157
|
+
await vi.waitFor(() => expect(computeFn).toHaveBeenCalledTimes(2));
|
|
158
|
+
expect(value2).toBe(20);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should recompute when dependency changes", async () => {
|
|
162
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
163
|
+
const $derivation = derivationAwait(async (t) => {
|
|
164
|
+
const val = $state.get(t);
|
|
165
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
166
|
+
});
|
|
167
|
+
const $tracker = state(0);
|
|
168
|
+
|
|
169
|
+
// Wait for initial computation
|
|
170
|
+
await $derivation.pick();
|
|
171
|
+
expect($derivation.get($tracker)).toBe(2);
|
|
172
|
+
|
|
173
|
+
await $state.set(Promise.resolve(2));
|
|
174
|
+
// Wait for recomputation
|
|
175
|
+
await $derivation.pick();
|
|
176
|
+
expect($derivation.get($tracker)).toBe(4);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should throw error when get is called after disposal", async () => {
|
|
180
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
181
|
+
const $derivation = derivationAwait(async (t) => {
|
|
182
|
+
const val = $state.get(t);
|
|
183
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
184
|
+
});
|
|
185
|
+
const $tracker = state(0);
|
|
186
|
+
|
|
187
|
+
await $derivation.pick();
|
|
188
|
+
$derivation.get($tracker);
|
|
189
|
+
$derivation.dispose();
|
|
190
|
+
|
|
191
|
+
expect(() => $derivation.get($tracker)).toThrow(
|
|
192
|
+
"[PicoFlow] Primitive is disposed",
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe("pick", () => {
|
|
198
|
+
it("should return computed value without tracking", async () => {
|
|
199
|
+
const $state = stateAwait(Promise.resolve(15));
|
|
200
|
+
const $derivation = derivationAwait(async (t) => {
|
|
201
|
+
const val = $state.get(t);
|
|
202
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
203
|
+
});
|
|
204
|
+
const value = await $derivation.pick();
|
|
205
|
+
expect(value).toBe(30);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should compute lazy value on first pick call", async () => {
|
|
209
|
+
const computeFn = vi.fn(async (t) => {
|
|
210
|
+
const $s = stateAwait(Promise.resolve(25));
|
|
211
|
+
const val = $s.get(t);
|
|
212
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
213
|
+
});
|
|
214
|
+
const $state = stateAwait(Promise.resolve(25));
|
|
215
|
+
const $derivation = derivationAwait(async (t) => {
|
|
216
|
+
computeFn(t);
|
|
217
|
+
const val = $state.get(t);
|
|
218
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(computeFn).not.toHaveBeenCalled();
|
|
222
|
+
const value = await $derivation.pick();
|
|
223
|
+
expect(value).toBe(50);
|
|
224
|
+
expect(computeFn).toHaveBeenCalledTimes(1);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("should recompute when dependency changes", async () => {
|
|
228
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
229
|
+
const $derivation = derivationAwait(async (t) => {
|
|
230
|
+
const val = $state.get(t);
|
|
231
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(await $derivation.pick()).toBe(2);
|
|
235
|
+
|
|
236
|
+
await $state.set(Promise.resolve(2));
|
|
237
|
+
expect(await $derivation.pick()).toBe(4);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should await Promise to get value", async () => {
|
|
241
|
+
const $state = stateAwait(Promise.resolve(35));
|
|
242
|
+
const $derivation = derivationAwait(async (t) => {
|
|
243
|
+
const val = $state.get(t);
|
|
244
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
245
|
+
});
|
|
246
|
+
const value = await $derivation.pick();
|
|
247
|
+
expect(value).toBe(70);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should throw error when pick is called after disposal", async () => {
|
|
251
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
252
|
+
const $derivation = derivationAwait(async (t) => {
|
|
253
|
+
const val = $state.get(t);
|
|
254
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(await $derivation.pick()).toBe(2);
|
|
258
|
+
|
|
259
|
+
$derivation.dispose();
|
|
260
|
+
|
|
261
|
+
await expect($derivation.pick()).rejects.toThrow(
|
|
262
|
+
"[PicoFlow] Primitive is disposed",
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe("refresh", () => {
|
|
268
|
+
it("should force recomputation", async () => {
|
|
269
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
270
|
+
const computeFn = vi.fn(async (t) => {
|
|
271
|
+
const val = $state.get(t);
|
|
272
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
273
|
+
});
|
|
274
|
+
const $derivation = derivationAwait(computeFn);
|
|
275
|
+
|
|
276
|
+
await $derivation.pick();
|
|
277
|
+
expect(computeFn).toHaveBeenCalledTimes(1);
|
|
278
|
+
|
|
279
|
+
await $derivation.refresh();
|
|
280
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should return Promise<void>", async () => {
|
|
284
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
285
|
+
const $derivation = derivationAwait(async (t) => {
|
|
286
|
+
const val = $state.get(t);
|
|
287
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
288
|
+
});
|
|
289
|
+
const result = await $derivation.refresh();
|
|
290
|
+
expect(result).toBeUndefined();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should not notify when refresh returns same value", async () => {
|
|
294
|
+
const $state = stateAwait(Promise.resolve(5));
|
|
295
|
+
const $derivation = derivationAwait(async (t) => {
|
|
296
|
+
const val = $state.get(t);
|
|
297
|
+
return (val ?? 0) * 2;
|
|
298
|
+
});
|
|
299
|
+
const listener = vi.fn();
|
|
300
|
+
$derivation.subscribe(listener);
|
|
301
|
+
|
|
302
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
|
|
303
|
+
expect(listener).toHaveBeenNthCalledWith(1, undefined);
|
|
304
|
+
expect(listener).toHaveBeenNthCalledWith(2, 10);
|
|
305
|
+
|
|
306
|
+
// Value is 10, refresh will compute same value
|
|
307
|
+
await $derivation.refresh();
|
|
308
|
+
|
|
309
|
+
// Wait to ensure no additional calls
|
|
310
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
311
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("should notify when refresh returns different value", async () => {
|
|
315
|
+
let multiplier = 2;
|
|
316
|
+
const $state = stateAwait(Promise.resolve(5));
|
|
317
|
+
const $derivation = derivationAwait(async (t) => {
|
|
318
|
+
const val = $state.get(t);
|
|
319
|
+
return (val ?? 0) * multiplier;
|
|
320
|
+
});
|
|
321
|
+
const listener = vi.fn();
|
|
322
|
+
$derivation.subscribe(listener);
|
|
323
|
+
|
|
324
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
|
|
325
|
+
expect(listener).toHaveBeenNthCalledWith(1, undefined);
|
|
326
|
+
expect(listener).toHaveBeenNthCalledWith(2, 10);
|
|
327
|
+
|
|
328
|
+
multiplier = 3;
|
|
329
|
+
await $derivation.refresh();
|
|
330
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(3));
|
|
331
|
+
expect(listener).toHaveBeenNthCalledWith(3, 15);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("should throw error when refresh is called after disposal", async () => {
|
|
335
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
336
|
+
const $derivation = derivationAwait(async (t) => {
|
|
337
|
+
const val = $state.get(t);
|
|
338
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
$derivation.dispose();
|
|
342
|
+
|
|
343
|
+
await expect($derivation.refresh()).rejects.toThrow(
|
|
344
|
+
"[PicoFlow] Primitive is disposed",
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("should recompute even if not dirty", async () => {
|
|
349
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
350
|
+
const computeFn = vi.fn(async (t) => {
|
|
351
|
+
const val = $state.get(t);
|
|
352
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
353
|
+
});
|
|
354
|
+
const $derivation = derivationAwait(computeFn);
|
|
355
|
+
|
|
356
|
+
await $derivation.pick();
|
|
357
|
+
expect(computeFn).toHaveBeenCalledTimes(1);
|
|
358
|
+
|
|
359
|
+
// Not dirty, but refresh forces recomputation
|
|
360
|
+
await $derivation.refresh();
|
|
361
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe("watch", () => {
|
|
366
|
+
it("should register dependency when watch is called", () => {
|
|
367
|
+
const $state = stateAwait(Promise.resolve(35));
|
|
368
|
+
const $derivation = derivationAwait(async (t) => {
|
|
369
|
+
const val = $state.get(t);
|
|
370
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
371
|
+
});
|
|
372
|
+
const $tracker = state(0);
|
|
373
|
+
|
|
374
|
+
expect(() => $derivation.watch($tracker)).not.toThrow();
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("should compute lazy value when watch is called", async () => {
|
|
378
|
+
const computeFn = vi.fn(async (t) => {
|
|
379
|
+
const $s = stateAwait(Promise.resolve(40));
|
|
380
|
+
const val = $s.get(t);
|
|
381
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
382
|
+
});
|
|
383
|
+
const $state = stateAwait(Promise.resolve(40));
|
|
384
|
+
const $derivation = derivationAwait(async (t) => {
|
|
385
|
+
computeFn(t);
|
|
386
|
+
const val = $state.get(t);
|
|
387
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
388
|
+
});
|
|
389
|
+
const $tracker = state(0);
|
|
390
|
+
|
|
391
|
+
expect(computeFn).not.toHaveBeenCalled();
|
|
392
|
+
$derivation.watch($tracker);
|
|
393
|
+
// Wait a bit for the computation to complete
|
|
394
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
395
|
+
expect(computeFn).toHaveBeenCalledTimes(1);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("should throw error when watch is called after disposal", () => {
|
|
399
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
400
|
+
const $derivation = derivationAwait(async (t) => {
|
|
401
|
+
const val = $state.get(t);
|
|
402
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
403
|
+
});
|
|
404
|
+
const $tracker = state(0);
|
|
405
|
+
|
|
406
|
+
$derivation.watch($tracker);
|
|
407
|
+
$derivation.dispose();
|
|
408
|
+
|
|
409
|
+
expect(() => $derivation.watch($tracker)).toThrow(
|
|
410
|
+
"[PicoFlow] Primitive is disposed",
|
|
411
|
+
);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe("subscribe", () => {
|
|
416
|
+
it("should return a disposer function", () => {
|
|
417
|
+
const $state = stateAwait(Promise.resolve(50));
|
|
418
|
+
const $derivation = derivationAwait(async (t) => {
|
|
419
|
+
const val = $state.get(t);
|
|
420
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
421
|
+
});
|
|
422
|
+
const listener = vi.fn();
|
|
423
|
+
const disposer = $derivation.subscribe(listener);
|
|
424
|
+
|
|
425
|
+
expect(typeof disposer).toBe("function");
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it("should call listener immediately with computed value (T | undefined)", async () => {
|
|
429
|
+
const $state = stateAwait(Promise.resolve(55));
|
|
430
|
+
const $derivation = derivationAwait(async (t) => {
|
|
431
|
+
const val = $state.get(t);
|
|
432
|
+
return (val ?? 0) * 2;
|
|
433
|
+
});
|
|
434
|
+
const listener = vi.fn();
|
|
435
|
+
|
|
436
|
+
$derivation.subscribe(listener);
|
|
437
|
+
|
|
438
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
|
|
439
|
+
expect(listener).toHaveBeenNthCalledWith(1, undefined);
|
|
440
|
+
expect(listener).toHaveBeenNthCalledWith(2, 110);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it("should call listener when value changes", async () => {
|
|
444
|
+
const $state = stateAwait(Promise.resolve(60));
|
|
445
|
+
const $derivation = derivationAwait(async (t) => {
|
|
446
|
+
const val = $state.get(t);
|
|
447
|
+
return (val ?? 0) * 2;
|
|
448
|
+
});
|
|
449
|
+
const listener = vi.fn();
|
|
450
|
+
|
|
451
|
+
$derivation.subscribe(listener);
|
|
452
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
|
|
453
|
+
expect(listener).toHaveBeenNthCalledWith(1, undefined);
|
|
454
|
+
expect(listener).toHaveBeenNthCalledWith(2, 120);
|
|
455
|
+
|
|
456
|
+
await $state.set(70);
|
|
457
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(4));
|
|
458
|
+
expect(listener).toHaveBeenNthCalledWith(3, 120);
|
|
459
|
+
expect(listener).toHaveBeenNthCalledWith(4, 140);
|
|
460
|
+
|
|
461
|
+
await $state.set(Promise.resolve(80));
|
|
462
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(6));
|
|
463
|
+
expect(listener).toHaveBeenNthCalledWith(5, 140);
|
|
464
|
+
expect(listener).toHaveBeenNthCalledWith(6, 160);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("should not call listener when value does not change", async () => {
|
|
468
|
+
const $state = stateAwait(Promise.resolve(75));
|
|
469
|
+
const $derivation = derivationAwait(async (t) => {
|
|
470
|
+
const val = $state.get(t);
|
|
471
|
+
return (val ?? 0) * 2;
|
|
472
|
+
});
|
|
473
|
+
const listener = vi.fn();
|
|
474
|
+
|
|
475
|
+
$derivation.subscribe(listener);
|
|
476
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
|
|
477
|
+
expect(listener).toHaveBeenNthCalledWith(1, undefined);
|
|
478
|
+
expect(listener).toHaveBeenNthCalledWith(2, 150);
|
|
479
|
+
|
|
480
|
+
await $state.set(Promise.resolve(75));
|
|
481
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
482
|
+
expect(listener).toHaveBeenCalledTimes(2);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it("should support multiple subscriptions", async () => {
|
|
486
|
+
const $state = stateAwait(Promise.resolve(80));
|
|
487
|
+
const $derivation = derivationAwait(async (t) => {
|
|
488
|
+
const val = $state.get(t);
|
|
489
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
490
|
+
});
|
|
491
|
+
const listener1 = vi.fn();
|
|
492
|
+
const listener2 = vi.fn();
|
|
493
|
+
const listener3 = vi.fn();
|
|
494
|
+
|
|
495
|
+
$derivation.subscribe(listener1);
|
|
496
|
+
$derivation.subscribe(listener2);
|
|
497
|
+
$derivation.subscribe(listener3);
|
|
498
|
+
|
|
499
|
+
await vi.waitFor(() => {
|
|
500
|
+
expect(listener1).toHaveBeenCalledTimes(2);
|
|
501
|
+
expect(listener2).toHaveBeenCalledTimes(2);
|
|
502
|
+
expect(listener3).toHaveBeenCalledTimes(2);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
expect(listener1).toHaveBeenNthCalledWith(1, undefined);
|
|
506
|
+
expect(listener1).toHaveBeenNthCalledWith(2, 160);
|
|
507
|
+
expect(listener2).toHaveBeenNthCalledWith(1, undefined);
|
|
508
|
+
expect(listener2).toHaveBeenNthCalledWith(2, 160);
|
|
509
|
+
expect(listener3).toHaveBeenNthCalledWith(1, undefined);
|
|
510
|
+
expect(listener3).toHaveBeenNthCalledWith(2, 160);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it("should throw error when subscribe is called after disposal", () => {
|
|
514
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
515
|
+
const $derivation = derivationAwait(async (t) => {
|
|
516
|
+
const val = $state.get(t);
|
|
517
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
518
|
+
});
|
|
519
|
+
const listener = vi.fn();
|
|
520
|
+
|
|
521
|
+
$derivation.dispose();
|
|
522
|
+
|
|
523
|
+
expect(() => $derivation.subscribe(listener)).toThrow(
|
|
524
|
+
"[PicoFlow] Primitive is disposed",
|
|
525
|
+
);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it("should dispose effect when disposer is called", async () => {
|
|
529
|
+
const $state = stateAwait(Promise.resolve(85));
|
|
530
|
+
const $derivation = derivationAwait(async (t) => {
|
|
531
|
+
const val = $state.get(t);
|
|
532
|
+
return (val ?? 0) * 2;
|
|
533
|
+
});
|
|
534
|
+
const listener = vi.fn();
|
|
535
|
+
|
|
536
|
+
const unsubscribe = $derivation.subscribe(listener);
|
|
537
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
|
|
538
|
+
expect(listener).toHaveBeenNthCalledWith(1, undefined);
|
|
539
|
+
expect(listener).toHaveBeenNthCalledWith(2, 170);
|
|
540
|
+
|
|
541
|
+
await $state.set(Promise.resolve(90));
|
|
542
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(4));
|
|
543
|
+
expect(listener).toHaveBeenNthCalledWith(3, 170);
|
|
544
|
+
expect(listener).toHaveBeenNthCalledWith(4, 180);
|
|
545
|
+
|
|
546
|
+
unsubscribe();
|
|
547
|
+
|
|
548
|
+
await $state.set(Promise.resolve(95));
|
|
549
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
550
|
+
expect(listener).toHaveBeenCalledTimes(4);
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
describe("trigger", () => {
|
|
555
|
+
it("should return a Promise", () => {
|
|
556
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
557
|
+
const $derivation = derivationAwait(async (t) => {
|
|
558
|
+
const val = $state.get(t);
|
|
559
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
560
|
+
});
|
|
561
|
+
const result = $derivation.trigger();
|
|
562
|
+
expect(result).toBeInstanceOf(Promise);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("should throw error when trigger is called after disposal", async () => {
|
|
566
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
567
|
+
const $derivation = derivationAwait(async (t) => {
|
|
568
|
+
const val = $state.get(t);
|
|
569
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
570
|
+
});
|
|
571
|
+
$derivation.dispose();
|
|
572
|
+
await expect($derivation.trigger()).rejects.toThrow(
|
|
573
|
+
"[PicoFlow] Primitive is disposed",
|
|
574
|
+
);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it("should allow multiple triggers", async () => {
|
|
578
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
579
|
+
const $derivation = derivationAwait(async (t) => {
|
|
580
|
+
const val = $state.get(t);
|
|
581
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
582
|
+
});
|
|
583
|
+
const promise1 = $derivation.trigger();
|
|
584
|
+
const promise2 = $derivation.trigger();
|
|
585
|
+
const promise3 = $derivation.trigger();
|
|
586
|
+
|
|
587
|
+
expect(promise1).toBeInstanceOf(Promise);
|
|
588
|
+
expect(promise2).toBeInstanceOf(Promise);
|
|
589
|
+
expect(promise3).toBeInstanceOf(Promise);
|
|
590
|
+
|
|
591
|
+
await Promise.all([promise1, promise2, promise3]);
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
describe("disposal", () => {
|
|
596
|
+
it("should have disposed property set to false initially", () => {
|
|
597
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
598
|
+
const $derivation = derivationAwait(async (t) => {
|
|
599
|
+
const val = $state.get(t);
|
|
600
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
601
|
+
});
|
|
602
|
+
expect($derivation.disposed).toBe(false);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it("should have disposed property set to true after disposal", () => {
|
|
606
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
607
|
+
const $derivation = derivationAwait(async (t) => {
|
|
608
|
+
const val = $state.get(t);
|
|
609
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
610
|
+
});
|
|
611
|
+
$derivation.dispose();
|
|
612
|
+
expect($derivation.disposed).toBe(true);
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
it("should throw error when disposed twice", () => {
|
|
616
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
617
|
+
const $derivation = derivationAwait(async (t) => {
|
|
618
|
+
const val = $state.get(t);
|
|
619
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
620
|
+
});
|
|
621
|
+
$derivation.dispose();
|
|
622
|
+
expect(() => $derivation.dispose()).toThrow(
|
|
623
|
+
"[PicoFlow] Primitive is disposed",
|
|
624
|
+
);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it("should accept self option without throwing", () => {
|
|
628
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
629
|
+
const $derivation = derivationAwait(async (t) => {
|
|
630
|
+
const val = $state.get(t);
|
|
631
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
632
|
+
});
|
|
633
|
+
expect(() => $derivation.dispose({ self: true })).not.toThrow();
|
|
634
|
+
expect($derivation.disposed).toBe(true);
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
describe("special cases", () => {
|
|
639
|
+
it("should handle dynamic dependencies - dependencies change between computations", async () => {
|
|
640
|
+
const $state1 = stateAwait(Promise.resolve(1));
|
|
641
|
+
const $state2 = stateAwait(Promise.resolve(10));
|
|
642
|
+
const $cond = stateAwait(Promise.resolve(true));
|
|
643
|
+
const $derivation = derivationAwait(async (t) => {
|
|
644
|
+
const cond = $cond.get(t);
|
|
645
|
+
if (cond) {
|
|
646
|
+
const val = $state1.get(t);
|
|
647
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
648
|
+
}
|
|
649
|
+
const val = $state2.get(t);
|
|
650
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// Initially depends on state1
|
|
654
|
+
expect(await $derivation.pick()).toBe(2);
|
|
655
|
+
|
|
656
|
+
// Change state1 - should recompute
|
|
657
|
+
await $state1.set(Promise.resolve(2));
|
|
658
|
+
expect(await $derivation.pick()).toBe(4);
|
|
659
|
+
|
|
660
|
+
// Switch to state2 dependency
|
|
661
|
+
await $cond.set(Promise.resolve(false));
|
|
662
|
+
expect(await $derivation.pick()).toBe(20);
|
|
663
|
+
|
|
664
|
+
// Change state2 - should recompute
|
|
665
|
+
await $state2.set(Promise.resolve(20));
|
|
666
|
+
expect(await $derivation.pick()).toBe(40);
|
|
667
|
+
|
|
668
|
+
// Change state1 - should NOT recompute (no longer a dependency)
|
|
669
|
+
await $state1.set(Promise.resolve(100));
|
|
670
|
+
expect(await $derivation.pick()).toBe(40);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it("should unregister dependencies that are no longer used", async () => {
|
|
674
|
+
const $state1 = stateAwait(Promise.resolve(1));
|
|
675
|
+
const $state2 = stateAwait(Promise.resolve(10));
|
|
676
|
+
const $cond = stateAwait(Promise.resolve(true));
|
|
677
|
+
const $derivation = derivationAwait(async (t) => {
|
|
678
|
+
const cond = $cond.get(t);
|
|
679
|
+
if (cond) {
|
|
680
|
+
const val = $state1.get(t);
|
|
681
|
+
return Promise.resolve(val ?? 0);
|
|
682
|
+
}
|
|
683
|
+
const val = $state2.get(t);
|
|
684
|
+
return Promise.resolve(val ?? 0);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
await $derivation.pick();
|
|
688
|
+
// Initially depends on state1
|
|
689
|
+
|
|
690
|
+
await $cond.set(Promise.resolve(false));
|
|
691
|
+
await $derivation.pick();
|
|
692
|
+
// Now depends on state2, state1 should be unregistered
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it("should mark as dirty when dependency changes", async () => {
|
|
696
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
697
|
+
const computeFn = vi.fn(async (t) => {
|
|
698
|
+
const val = $state.get(t);
|
|
699
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
700
|
+
});
|
|
701
|
+
const $derivation = derivationAwait(computeFn);
|
|
702
|
+
|
|
703
|
+
await $derivation.pick();
|
|
704
|
+
expect(computeFn).toHaveBeenCalledTimes(1);
|
|
705
|
+
|
|
706
|
+
// Change dependency - marks as dirty but doesn't recompute yet
|
|
707
|
+
await $state.set(Promise.resolve(2));
|
|
708
|
+
|
|
709
|
+
// Next access triggers recomputation
|
|
710
|
+
await $derivation.pick();
|
|
711
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it("should recompute only on next access when dirty", async () => {
|
|
715
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
716
|
+
const computeFn = vi.fn(async (t) => {
|
|
717
|
+
const val = $state.get(t);
|
|
718
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
719
|
+
});
|
|
720
|
+
const $derivation = derivationAwait(computeFn);
|
|
721
|
+
|
|
722
|
+
await $derivation.pick();
|
|
723
|
+
expect(computeFn).toHaveBeenCalledTimes(1);
|
|
724
|
+
|
|
725
|
+
// Multiple dependency changes
|
|
726
|
+
await $state.set(Promise.resolve(2));
|
|
727
|
+
await $state.set(Promise.resolve(3));
|
|
728
|
+
await $state.set(Promise.resolve(4));
|
|
729
|
+
|
|
730
|
+
// Still only called once more (lazy recomputation)
|
|
731
|
+
await $derivation.pick();
|
|
732
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it("should handle nested derivations", async () => {
|
|
736
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
737
|
+
const $derivation1 = derivationAwait(async (t) => {
|
|
738
|
+
const val = $state.get(t);
|
|
739
|
+
return (val ?? 0) * 2;
|
|
740
|
+
});
|
|
741
|
+
const $derivation2 = derivationAwait(async (t) => {
|
|
742
|
+
const val = $derivation1.get(t);
|
|
743
|
+
return (val ?? 0) * 2;
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
const effectFn = vi.fn();
|
|
747
|
+
effect((t) => {
|
|
748
|
+
effectFn($derivation2.get(t));
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
const value = await $derivation2.pick();
|
|
752
|
+
expect(value).toBe(4);
|
|
753
|
+
|
|
754
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(4));
|
|
755
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, undefined);
|
|
756
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 0);
|
|
757
|
+
expect(effectFn).toHaveBeenNthCalledWith(3, 4);
|
|
758
|
+
expect(effectFn).toHaveBeenNthCalledWith(4, 4);
|
|
759
|
+
|
|
760
|
+
await $state.set(Promise.resolve(2));
|
|
761
|
+
const value2 = await $derivation2.pick();
|
|
762
|
+
expect(value2).toBe(8);
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
it("should handle compute function that returns undefined", async () => {
|
|
766
|
+
const $state = stateAwait(Promise.resolve(true));
|
|
767
|
+
const $derivation = derivationAwait(async (t) => {
|
|
768
|
+
const val = $state.get(t);
|
|
769
|
+
return Promise.resolve(val ? undefined : "defined");
|
|
770
|
+
});
|
|
771
|
+
expect(await $derivation.pick()).toBeUndefined();
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
// TODO: fix this test
|
|
775
|
+
it("should handle compute function that throws error", async () => {
|
|
776
|
+
const error = new Error("Compute error");
|
|
777
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
778
|
+
const $derivation = derivationAwait(async (t) => {
|
|
779
|
+
const val = $state.get(t);
|
|
780
|
+
if (val === 1) {
|
|
781
|
+
throw error;
|
|
782
|
+
}
|
|
783
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
await expect($derivation.pick()).rejects.toThrow("Compute error");
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
describe("integration", () => {
|
|
792
|
+
describe("with effects - get", () => {
|
|
793
|
+
it("should create reactive dependency when get is used in effect", async () => {
|
|
794
|
+
const $state = stateAwait(Promise.resolve(100));
|
|
795
|
+
const $derivation = derivationAwait(async (t) => {
|
|
796
|
+
const val = $state.get(t);
|
|
797
|
+
return (val ?? 0) * 2;
|
|
798
|
+
});
|
|
799
|
+
const effectFn = vi.fn();
|
|
800
|
+
|
|
801
|
+
effect((t) => {
|
|
802
|
+
effectFn($derivation.get(t));
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
806
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, undefined);
|
|
807
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 200);
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
it("should call effect with initial computed value", async () => {
|
|
811
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
812
|
+
const $derivation = derivationAwait(async (t) => {
|
|
813
|
+
const val = $state.get(t);
|
|
814
|
+
return (val ?? 0) * 2;
|
|
815
|
+
});
|
|
816
|
+
const effectFn = vi.fn();
|
|
817
|
+
|
|
818
|
+
effect((t) => {
|
|
819
|
+
effectFn($derivation.get(t));
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
823
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, undefined);
|
|
824
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 2);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it("should call effect when dependency changes", async () => {
|
|
828
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
829
|
+
const $derivation = derivationAwait(async (t) => {
|
|
830
|
+
const val = $state.get(t);
|
|
831
|
+
return (val ?? 0) * 2;
|
|
832
|
+
});
|
|
833
|
+
const effectFn = vi.fn();
|
|
834
|
+
|
|
835
|
+
effect((t) => {
|
|
836
|
+
effectFn($derivation.get(t));
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
840
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, undefined);
|
|
841
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 2);
|
|
842
|
+
|
|
843
|
+
await $state.set(Promise.resolve(2));
|
|
844
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(4));
|
|
845
|
+
expect(effectFn).toHaveBeenNthCalledWith(3, 2);
|
|
846
|
+
expect(effectFn).toHaveBeenNthCalledWith(4, 4);
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
it("should not call effect when value does not change", async () => {
|
|
850
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
851
|
+
const $derivation = derivationAwait(async (t) => {
|
|
852
|
+
const val = $state.get(t);
|
|
853
|
+
return (val ?? 0) * 2;
|
|
854
|
+
});
|
|
855
|
+
const effectFn = vi.fn();
|
|
856
|
+
|
|
857
|
+
effect((t) => {
|
|
858
|
+
effectFn($derivation.get(t));
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
862
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, undefined);
|
|
863
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 2);
|
|
864
|
+
|
|
865
|
+
await $state.set(Promise.resolve(1));
|
|
866
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
867
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
it("should not call effect after effect is disposed", async () => {
|
|
871
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
872
|
+
const $derivation = derivationAwait(async (t) => {
|
|
873
|
+
const val = $state.get(t);
|
|
874
|
+
return (val ?? 0) * 2;
|
|
875
|
+
});
|
|
876
|
+
const effectFn = vi.fn();
|
|
877
|
+
const $effect = effect((t) => {
|
|
878
|
+
effectFn($derivation.get(t));
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
882
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, undefined);
|
|
883
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 2);
|
|
884
|
+
|
|
885
|
+
await $state.set(Promise.resolve(2));
|
|
886
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(4));
|
|
887
|
+
expect(effectFn).toHaveBeenNthCalledWith(3, 2);
|
|
888
|
+
expect(effectFn).toHaveBeenNthCalledWith(4, 4);
|
|
889
|
+
|
|
890
|
+
$effect.dispose();
|
|
891
|
+
|
|
892
|
+
await $state.set(Promise.resolve(3));
|
|
893
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
894
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(4));
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
it("should support multiple effects depending on same derivation", async () => {
|
|
898
|
+
const $state = stateAwait(Promise.resolve(200));
|
|
899
|
+
const $derivation = derivationAwait(async (t) => {
|
|
900
|
+
const val = $state.get(t);
|
|
901
|
+
return (val ?? 0) * 2;
|
|
902
|
+
});
|
|
903
|
+
const effectFn1 = vi.fn();
|
|
904
|
+
const effectFn2 = vi.fn();
|
|
905
|
+
const effectFn3 = vi.fn();
|
|
906
|
+
|
|
907
|
+
effect((t) => {
|
|
908
|
+
effectFn1($derivation.get(t));
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
effect((t) => {
|
|
912
|
+
effectFn2($derivation.get(t));
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
effect((t) => {
|
|
916
|
+
effectFn3($derivation.get(t));
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
await vi.waitFor(() => {
|
|
920
|
+
expect(effectFn1).toHaveBeenCalledTimes(2);
|
|
921
|
+
expect(effectFn2).toHaveBeenCalledTimes(2);
|
|
922
|
+
expect(effectFn3).toHaveBeenCalledTimes(2);
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
expect(effectFn1).toHaveBeenLastCalledWith(400);
|
|
926
|
+
expect(effectFn2).toHaveBeenLastCalledWith(400);
|
|
927
|
+
expect(effectFn3).toHaveBeenLastCalledWith(400);
|
|
928
|
+
|
|
929
|
+
await $state.set(Promise.resolve(300));
|
|
930
|
+
await vi.waitFor(() => {
|
|
931
|
+
expect(effectFn1).toHaveBeenCalledTimes(4);
|
|
932
|
+
expect(effectFn2).toHaveBeenCalledTimes(4);
|
|
933
|
+
expect(effectFn3).toHaveBeenCalledTimes(4);
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
expect(effectFn1).toHaveBeenLastCalledWith(600);
|
|
937
|
+
expect(effectFn2).toHaveBeenLastCalledWith(600);
|
|
938
|
+
expect(effectFn3).toHaveBeenLastCalledWith(600);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
it("should support effect depending on multiple derivations", async () => {
|
|
942
|
+
const $stateA = stateAwait(Promise.resolve(5));
|
|
943
|
+
const $stateB = stateAwait(Promise.resolve(10));
|
|
944
|
+
const $derivationA = derivationAwait(async (t) => {
|
|
945
|
+
const val = $stateA.get(t);
|
|
946
|
+
return (val ?? 0) * 2;
|
|
947
|
+
});
|
|
948
|
+
const $derivationB = derivationAwait(async (t) => {
|
|
949
|
+
const val = $stateB.get(t);
|
|
950
|
+
return (val ?? 0) * 2;
|
|
951
|
+
});
|
|
952
|
+
const effectFn = vi.fn();
|
|
953
|
+
|
|
954
|
+
effect((t) => {
|
|
955
|
+
const a = $derivationA.get(t);
|
|
956
|
+
const b = $derivationB.get(t);
|
|
957
|
+
if (a !== undefined && b !== undefined) {
|
|
958
|
+
effectFn(a, b);
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
963
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, 10, 20);
|
|
964
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 10, 20);
|
|
965
|
+
|
|
966
|
+
await $stateA.set(Promise.resolve(6));
|
|
967
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(4));
|
|
968
|
+
expect(effectFn).toHaveBeenNthCalledWith(3, 10, 20);
|
|
969
|
+
expect(effectFn).toHaveBeenNthCalledWith(4, 12, 20);
|
|
970
|
+
|
|
971
|
+
await $stateB.set(Promise.resolve(11));
|
|
972
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(6));
|
|
973
|
+
expect(effectFn).toHaveBeenNthCalledWith(5, 12, 20);
|
|
974
|
+
expect(effectFn).toHaveBeenNthCalledWith(6, 12, 22);
|
|
975
|
+
});
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
describe("with effects - watch", () => {
|
|
979
|
+
it("should register dependency when watch is used in effect", async () => {
|
|
980
|
+
const $state = stateAwait(Promise.resolve(400));
|
|
981
|
+
const $derivation = derivationAwait(async (t) => {
|
|
982
|
+
const val = $state.get(t);
|
|
983
|
+
return (val ?? 0) * 2;
|
|
984
|
+
});
|
|
985
|
+
const effectFn = vi.fn();
|
|
986
|
+
|
|
987
|
+
effect((t) => {
|
|
988
|
+
$derivation.watch(t);
|
|
989
|
+
effectFn();
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
it("should trigger re-runs when derivation changes after watch", async () => {
|
|
996
|
+
const $state = stateAwait(Promise.resolve(500));
|
|
997
|
+
const $derivation = derivationAwait(async (t) => {
|
|
998
|
+
const val = $state.get(t);
|
|
999
|
+
return (val ?? 0) * 2;
|
|
1000
|
+
});
|
|
1001
|
+
const effectFn = vi.fn();
|
|
1002
|
+
|
|
1003
|
+
effect((t) => {
|
|
1004
|
+
$derivation.watch(t);
|
|
1005
|
+
effectFn();
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
1009
|
+
|
|
1010
|
+
await $state.set(Promise.resolve(600));
|
|
1011
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(4));
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
describe("with effects - subscribe", () => {
|
|
1016
|
+
it("should call listener immediately when subscribe is used", async () => {
|
|
1017
|
+
const $state = stateAwait(Promise.resolve(700));
|
|
1018
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1019
|
+
const val = $state.get(t);
|
|
1020
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
1021
|
+
});
|
|
1022
|
+
const listener = vi.fn();
|
|
1023
|
+
|
|
1024
|
+
$derivation.subscribe(listener);
|
|
1025
|
+
|
|
1026
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
|
|
1027
|
+
expect(listener).toHaveBeenLastCalledWith(1400);
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
it("should support multiple subscriptions with effects", async () => {
|
|
1031
|
+
const $state = stateAwait(Promise.resolve(800));
|
|
1032
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1033
|
+
const val = $state.get(t);
|
|
1034
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
1035
|
+
});
|
|
1036
|
+
const listener1 = vi.fn();
|
|
1037
|
+
const listener2 = vi.fn();
|
|
1038
|
+
|
|
1039
|
+
$derivation.subscribe(listener1);
|
|
1040
|
+
$derivation.subscribe(listener2);
|
|
1041
|
+
|
|
1042
|
+
await vi.waitFor(() => {
|
|
1043
|
+
expect(listener1).toHaveBeenCalledTimes(2);
|
|
1044
|
+
expect(listener2).toHaveBeenCalledTimes(2);
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
await $state.set(Promise.resolve(900));
|
|
1048
|
+
await vi.waitFor(() => {
|
|
1049
|
+
expect(listener1).toHaveBeenCalledTimes(4);
|
|
1050
|
+
expect(listener2).toHaveBeenCalledTimes(4);
|
|
1051
|
+
});
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
it("should dispose effect when disposer is called", async () => {
|
|
1055
|
+
const $state = stateAwait(Promise.resolve(1000));
|
|
1056
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1057
|
+
const val = $state.get(t);
|
|
1058
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
1059
|
+
});
|
|
1060
|
+
const listener = vi.fn();
|
|
1061
|
+
|
|
1062
|
+
const unsubscribe = $derivation.subscribe(listener);
|
|
1063
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(2));
|
|
1064
|
+
|
|
1065
|
+
await $state.set(Promise.resolve(1100));
|
|
1066
|
+
await vi.waitFor(() => expect(listener).toHaveBeenCalledTimes(4));
|
|
1067
|
+
|
|
1068
|
+
unsubscribe();
|
|
1069
|
+
|
|
1070
|
+
await $state.set(Promise.resolve(1200));
|
|
1071
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1072
|
+
expect(listener).toHaveBeenCalledTimes(4);
|
|
1073
|
+
});
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
describe("with effects - disposal", () => {
|
|
1077
|
+
it("should dispose effects when derivation is disposed", async () => {
|
|
1078
|
+
const $state = stateAwait(Promise.resolve(1300));
|
|
1079
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1080
|
+
const val = $state.get(t);
|
|
1081
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
1082
|
+
});
|
|
1083
|
+
const effectFn = vi.fn();
|
|
1084
|
+
const $effect = effect((t) => {
|
|
1085
|
+
$derivation.get(t);
|
|
1086
|
+
effectFn();
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
1090
|
+
expect($effect.disposed).toBe(false);
|
|
1091
|
+
|
|
1092
|
+
$derivation.dispose();
|
|
1093
|
+
|
|
1094
|
+
expect($effect.disposed).toBe(true);
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
it("should not dispose effects when derivation is disposed with self option", async () => {
|
|
1098
|
+
const $state = stateAwait(Promise.resolve(1400));
|
|
1099
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1100
|
+
const val = $state.get(t);
|
|
1101
|
+
return (val ?? 0) * 2;
|
|
1102
|
+
});
|
|
1103
|
+
const effectFn = vi.fn();
|
|
1104
|
+
const $effect = effect((t) => {
|
|
1105
|
+
$derivation.get(t);
|
|
1106
|
+
effectFn();
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
1110
|
+
expect($effect.disposed).toBe(false);
|
|
1111
|
+
|
|
1112
|
+
$derivation.dispose({ self: true });
|
|
1113
|
+
|
|
1114
|
+
expect($effect.disposed).toBe(false);
|
|
1115
|
+
expect($derivation.disposed).toBe(true);
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
it("should unregister effects when derivation is disposed with self option", async () => {
|
|
1119
|
+
const $state = stateAwait(Promise.resolve(1500));
|
|
1120
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1121
|
+
const val = $state.get(t);
|
|
1122
|
+
return (val ?? 0) * 2;
|
|
1123
|
+
});
|
|
1124
|
+
const effectFn = vi.fn();
|
|
1125
|
+
const $effect = effect((t) => {
|
|
1126
|
+
$derivation.get(t);
|
|
1127
|
+
effectFn();
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
1131
|
+
|
|
1132
|
+
$derivation.dispose({ self: true });
|
|
1133
|
+
|
|
1134
|
+
// Effect should still be active but not notified
|
|
1135
|
+
expect($effect.disposed).toBe(false);
|
|
1136
|
+
|
|
1137
|
+
// But derivation is disposed so operations should fail
|
|
1138
|
+
const $tracker = state(0);
|
|
1139
|
+
expect(() => $derivation.get($tracker)).toThrow(
|
|
1140
|
+
"[PicoFlow] Primitive is disposed",
|
|
1141
|
+
);
|
|
1142
|
+
});
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
describe("dependencies", () => {
|
|
1146
|
+
it("should handle chained dependencies", async () => {
|
|
1147
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
1148
|
+
const $derivation1 = derivationAwait(async (t) => {
|
|
1149
|
+
const val = $state.get(t);
|
|
1150
|
+
return (val ?? 0) * 2;
|
|
1151
|
+
});
|
|
1152
|
+
const $derivation2 = derivationAwait(async (t) => {
|
|
1153
|
+
const val = $derivation1.get(t);
|
|
1154
|
+
return (val ?? 0) * 2;
|
|
1155
|
+
});
|
|
1156
|
+
const effectFn = vi.fn();
|
|
1157
|
+
|
|
1158
|
+
effect((t) => {
|
|
1159
|
+
const val = $derivation2.get(t);
|
|
1160
|
+
if (val !== undefined) {
|
|
1161
|
+
effectFn(val);
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
|
|
1166
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, 0);
|
|
1167
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 4);
|
|
1168
|
+
expect(effectFn).toHaveBeenNthCalledWith(3, 4);
|
|
1169
|
+
|
|
1170
|
+
await $state.set(Promise.resolve(2));
|
|
1171
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(6));
|
|
1172
|
+
expect(effectFn).toHaveBeenNthCalledWith(4, 4);
|
|
1173
|
+
expect(effectFn).toHaveBeenNthCalledWith(5, 4);
|
|
1174
|
+
expect(effectFn).toHaveBeenNthCalledWith(6, 8);
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
it("should handle multiple dependencies", async () => {
|
|
1178
|
+
const $state1 = stateAwait(Promise.resolve(1));
|
|
1179
|
+
const $state2 = stateAwait(Promise.resolve(2));
|
|
1180
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1181
|
+
const val1 = $state1.get(t);
|
|
1182
|
+
const val2 = $state2.get(t);
|
|
1183
|
+
return (val1 ?? 0) + (val2 ?? 0);
|
|
1184
|
+
});
|
|
1185
|
+
const effectFn = vi.fn();
|
|
1186
|
+
|
|
1187
|
+
effect((t) => {
|
|
1188
|
+
const val = $derivation.get(t);
|
|
1189
|
+
if (val !== undefined) {
|
|
1190
|
+
effectFn(val);
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
|
|
1195
|
+
expect(effectFn).toHaveBeenLastCalledWith(3);
|
|
1196
|
+
|
|
1197
|
+
await $state1.set(Promise.resolve(2));
|
|
1198
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
|
|
1199
|
+
expect(effectFn).toHaveBeenLastCalledWith(4);
|
|
1200
|
+
|
|
1201
|
+
await $state2.set(Promise.resolve(3));
|
|
1202
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(5));
|
|
1203
|
+
expect(effectFn).toHaveBeenLastCalledWith(5);
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
it("should handle multiple dependants", async () => {
|
|
1207
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
1208
|
+
const $derivation1 = derivationAwait(async (t) => {
|
|
1209
|
+
const val = $state.get(t);
|
|
1210
|
+
return (val ?? 0) * 2;
|
|
1211
|
+
});
|
|
1212
|
+
const $derivation2 = derivationAwait(async (t) => {
|
|
1213
|
+
const val = $state.get(t);
|
|
1214
|
+
return (val ?? 0) * 3;
|
|
1215
|
+
});
|
|
1216
|
+
const effect1Fn = vi.fn();
|
|
1217
|
+
const effect2Fn = vi.fn();
|
|
1218
|
+
|
|
1219
|
+
effect((t) => {
|
|
1220
|
+
const val = $derivation1.get(t);
|
|
1221
|
+
if (val !== undefined) {
|
|
1222
|
+
effect1Fn(val);
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
effect((t) => {
|
|
1227
|
+
const val = $derivation2.get(t);
|
|
1228
|
+
if (val !== undefined) {
|
|
1229
|
+
effect2Fn(val);
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
await vi.waitFor(() => {
|
|
1234
|
+
expect(effect1Fn).toHaveBeenCalledTimes(1);
|
|
1235
|
+
expect(effect2Fn).toHaveBeenCalledTimes(1);
|
|
1236
|
+
});
|
|
1237
|
+
expect(effect1Fn).toHaveBeenNthCalledWith(1, 2);
|
|
1238
|
+
expect(effect2Fn).toHaveBeenNthCalledWith(1, 3);
|
|
1239
|
+
|
|
1240
|
+
await $state.set(Promise.resolve(2));
|
|
1241
|
+
await vi.waitFor(() => {
|
|
1242
|
+
expect(effect1Fn).toHaveBeenCalledTimes(3);
|
|
1243
|
+
expect(effect2Fn).toHaveBeenCalledTimes(3);
|
|
1244
|
+
});
|
|
1245
|
+
expect(effect1Fn).toHaveBeenLastCalledWith(4);
|
|
1246
|
+
expect(effect2Fn).toHaveBeenLastCalledWith(6);
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
it("should handle derivation depending on state and signal", async () => {
|
|
1250
|
+
const $signal = signal();
|
|
1251
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
1252
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1253
|
+
$signal.watch(t);
|
|
1254
|
+
const val = $state.get(t);
|
|
1255
|
+
return (val ?? 0) * 2;
|
|
1256
|
+
});
|
|
1257
|
+
const effectFn = vi.fn();
|
|
1258
|
+
|
|
1259
|
+
effect((t) => {
|
|
1260
|
+
const val = $derivation.get(t);
|
|
1261
|
+
if (val !== undefined) {
|
|
1262
|
+
effectFn(val);
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
|
|
1267
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, 2);
|
|
1268
|
+
|
|
1269
|
+
$signal.trigger();
|
|
1270
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
1271
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 2);
|
|
1272
|
+
|
|
1273
|
+
await $state.set(Promise.resolve(2));
|
|
1274
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(4));
|
|
1275
|
+
expect(effectFn).toHaveBeenNthCalledWith(4, 4);
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
it("should handle derivation depending on multiple states", async () => {
|
|
1279
|
+
const $state1 = stateAwait(Promise.resolve(5));
|
|
1280
|
+
const $state2 = stateAwait(Promise.resolve(10));
|
|
1281
|
+
const $state3 = stateAwait(Promise.resolve(15));
|
|
1282
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1283
|
+
const val1 = $state1.get(t);
|
|
1284
|
+
const val2 = $state2.get(t);
|
|
1285
|
+
const val3 = $state3.get(t);
|
|
1286
|
+
return (val1 ?? 0) + (val2 ?? 0) + (val3 ?? 0);
|
|
1287
|
+
});
|
|
1288
|
+
const effectFn = vi.fn();
|
|
1289
|
+
|
|
1290
|
+
effect((t) => {
|
|
1291
|
+
const val = $derivation.get(t);
|
|
1292
|
+
if (val !== undefined) {
|
|
1293
|
+
effectFn(val);
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
|
|
1298
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, 30);
|
|
1299
|
+
|
|
1300
|
+
await $state1.set(Promise.resolve(6));
|
|
1301
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
|
|
1302
|
+
expect(effectFn).toHaveBeenLastCalledWith(31);
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
it("should handle derivation depending on multiple signals", async () => {
|
|
1306
|
+
const $signal1 = signal();
|
|
1307
|
+
const $signal2 = signal();
|
|
1308
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1309
|
+
$signal1.watch(t);
|
|
1310
|
+
$signal2.watch(t);
|
|
1311
|
+
return Promise.resolve(undefined);
|
|
1312
|
+
});
|
|
1313
|
+
const effectFn = vi.fn();
|
|
1314
|
+
|
|
1315
|
+
effect((t) => {
|
|
1316
|
+
$derivation.watch(t);
|
|
1317
|
+
effectFn();
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
|
|
1321
|
+
|
|
1322
|
+
$signal1.trigger();
|
|
1323
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
1324
|
+
|
|
1325
|
+
$signal2.trigger();
|
|
1326
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
it("should handle derivation depending on state and other derivation", async () => {
|
|
1330
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
1331
|
+
const $derivation1 = derivationAwait(async (t) => {
|
|
1332
|
+
const val = $state.get(t);
|
|
1333
|
+
return (val ?? 0) * 2;
|
|
1334
|
+
});
|
|
1335
|
+
const $derivation2 = derivationAwait(async (t) => {
|
|
1336
|
+
const val1 = $state.get(t);
|
|
1337
|
+
const val2 = $derivation1.get(t);
|
|
1338
|
+
return (val1 ?? 0) + (val2 ?? 0);
|
|
1339
|
+
});
|
|
1340
|
+
const effectFn = vi.fn();
|
|
1341
|
+
|
|
1342
|
+
effect((t) => {
|
|
1343
|
+
const val = $derivation2.get(t);
|
|
1344
|
+
if (val !== undefined) {
|
|
1345
|
+
effectFn(val);
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
|
|
1350
|
+
expect(effectFn).toHaveBeenLastCalledWith(3);
|
|
1351
|
+
|
|
1352
|
+
await $state.set(Promise.resolve(2));
|
|
1353
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(7));
|
|
1354
|
+
expect(effectFn).toHaveBeenLastCalledWith(6);
|
|
1355
|
+
});
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
describe("patterns", () => {
|
|
1359
|
+
it("should handle diamond pattern", async () => {
|
|
1360
|
+
const $stateA = stateAwait(Promise.resolve(1));
|
|
1361
|
+
const $aMult3 = derivationAwait(async (t) => {
|
|
1362
|
+
const val = $stateA.get(t);
|
|
1363
|
+
return (val ?? 0) * 3;
|
|
1364
|
+
});
|
|
1365
|
+
const $aMult2 = derivationAwait(async (t) => {
|
|
1366
|
+
const val = $stateA.get(t);
|
|
1367
|
+
return (val ?? 0) * 2;
|
|
1368
|
+
});
|
|
1369
|
+
const $addAmult3Amult2 = derivationAwait(async (t) => {
|
|
1370
|
+
const val1 = $aMult3.get(t);
|
|
1371
|
+
const val2 = $aMult2.get(t);
|
|
1372
|
+
return (val1 ?? 0) + (val2 ?? 0);
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
const effectFn = vi.fn();
|
|
1376
|
+
|
|
1377
|
+
effect((t) => {
|
|
1378
|
+
const val = $addAmult3Amult2.get(t);
|
|
1379
|
+
if (val !== undefined) {
|
|
1380
|
+
effectFn(val);
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(4));
|
|
1385
|
+
expect(effectFn).toHaveBeenLastCalledWith(5);
|
|
1386
|
+
|
|
1387
|
+
await $stateA.set(Promise.resolve(2));
|
|
1388
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(8));
|
|
1389
|
+
expect(effectFn).toHaveBeenLastCalledWith(10);
|
|
1390
|
+
|
|
1391
|
+
await $stateA.set(Promise.resolve(3));
|
|
1392
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(12));
|
|
1393
|
+
expect(effectFn).toHaveBeenLastCalledWith(15);
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
it("should handle multi diamond pattern", async () => {
|
|
1397
|
+
const $stateA = stateAwait(Promise.resolve(1));
|
|
1398
|
+
const $stateB = stateAwait(Promise.resolve(2));
|
|
1399
|
+
const $addAB = derivationAwait(async (t) => {
|
|
1400
|
+
const a = $stateA.get(t);
|
|
1401
|
+
const b = $stateB.get(t);
|
|
1402
|
+
return (a ?? 0) + (b ?? 0);
|
|
1403
|
+
});
|
|
1404
|
+
|
|
1405
|
+
const $multiplyAB = derivationAwait(async (t) => {
|
|
1406
|
+
const a = $stateA.get(t);
|
|
1407
|
+
const b = $stateB.get(t);
|
|
1408
|
+
return (a ?? 0) * (b ?? 0);
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
const $addAndMultiply = derivationAwait(async (t) => {
|
|
1412
|
+
const add = $addAB.get(t);
|
|
1413
|
+
const multiply = $multiplyAB.get(t);
|
|
1414
|
+
return (add ?? 0) * (multiply ?? 0);
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1417
|
+
const effectFn = vi.fn();
|
|
1418
|
+
|
|
1419
|
+
effect((t) => {
|
|
1420
|
+
const val = $addAndMultiply.get(t);
|
|
1421
|
+
if (val !== undefined) {
|
|
1422
|
+
effectFn(val);
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
|
|
1427
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, 6);
|
|
1428
|
+
|
|
1429
|
+
await $stateA.set(Promise.resolve(2));
|
|
1430
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
1431
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 16);
|
|
1432
|
+
|
|
1433
|
+
await $stateB.set(Promise.resolve(3));
|
|
1434
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
|
|
1435
|
+
expect(effectFn).toHaveBeenNthCalledWith(3, 30);
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
it("should handle derivation with conditional dependencies", async () => {
|
|
1439
|
+
const obj = {
|
|
1440
|
+
cond: stateAwait(Promise.resolve(false)),
|
|
1441
|
+
b: stateAwait(Promise.resolve(2)),
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
const $state = stateAwait(Promise.resolve(obj));
|
|
1445
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1446
|
+
const stateVal = $state.get(t);
|
|
1447
|
+
if (stateVal) {
|
|
1448
|
+
const cond = stateVal.cond.get(t);
|
|
1449
|
+
if (cond) {
|
|
1450
|
+
const b = stateVal.b.get(t);
|
|
1451
|
+
return (b ?? 0) * 2;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
return 0;
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
const effectFn = vi.fn();
|
|
1458
|
+
|
|
1459
|
+
effect((t) => {
|
|
1460
|
+
const val = $derivation.get(t);
|
|
1461
|
+
if (val !== undefined) {
|
|
1462
|
+
effectFn(val);
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
|
|
1466
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
|
|
1467
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, 0);
|
|
1468
|
+
|
|
1469
|
+
const stateVal = await $state.pick();
|
|
1470
|
+
await stateVal.cond.set(Promise.resolve(true));
|
|
1471
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
1472
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 4);
|
|
1473
|
+
|
|
1474
|
+
await $state.set(
|
|
1475
|
+
Promise.resolve({
|
|
1476
|
+
cond: stateAwait(Promise.resolve(false)),
|
|
1477
|
+
b: stateAwait(Promise.resolve(3)),
|
|
1478
|
+
}),
|
|
1479
|
+
);
|
|
1480
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
|
|
1481
|
+
expect(effectFn).toHaveBeenNthCalledWith(3, 0);
|
|
1482
|
+
|
|
1483
|
+
const stateVal2 = await $state.pick();
|
|
1484
|
+
await stateVal2.cond.set(Promise.resolve(true));
|
|
1485
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(4));
|
|
1486
|
+
expect(effectFn).toHaveBeenNthCalledWith(4, 6);
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
it("should handle derivation with dynamic dependencies", async () => {
|
|
1490
|
+
const $state1 = stateAwait(Promise.resolve(1));
|
|
1491
|
+
const $state2 = stateAwait(Promise.resolve(10));
|
|
1492
|
+
const $cond = stateAwait(Promise.resolve(true));
|
|
1493
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1494
|
+
const cond = $cond.get(t);
|
|
1495
|
+
if (cond) {
|
|
1496
|
+
const val = $state1.get(t);
|
|
1497
|
+
return (val ?? 0) * 2;
|
|
1498
|
+
}
|
|
1499
|
+
const val = $state2.get(t);
|
|
1500
|
+
return (val ?? 0) * 2;
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
const effectFn = vi.fn();
|
|
1504
|
+
|
|
1505
|
+
effect((t) => {
|
|
1506
|
+
const val = $derivation.get(t);
|
|
1507
|
+
if (val !== undefined) {
|
|
1508
|
+
effectFn(val);
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
|
|
1513
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, 2);
|
|
1514
|
+
|
|
1515
|
+
await $state1.set(Promise.resolve(2));
|
|
1516
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
1517
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 4);
|
|
1518
|
+
|
|
1519
|
+
await $cond.set(Promise.resolve(false));
|
|
1520
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(3));
|
|
1521
|
+
expect(effectFn).toHaveBeenNthCalledWith(3, 20);
|
|
1522
|
+
|
|
1523
|
+
await $state2.set(Promise.resolve(20));
|
|
1524
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(4));
|
|
1525
|
+
expect(effectFn).toHaveBeenNthCalledWith(4, 40);
|
|
1526
|
+
});
|
|
1527
|
+
});
|
|
1528
|
+
|
|
1529
|
+
describe("with refresh", () => {
|
|
1530
|
+
it("should force recomputation in effect", async () => {
|
|
1531
|
+
let multiplier = 2;
|
|
1532
|
+
const $state = stateAwait(Promise.resolve(5));
|
|
1533
|
+
const $derivation = derivationAwait(async (t) => {
|
|
1534
|
+
const val = $state.get(t);
|
|
1535
|
+
return (val ?? 0) * multiplier;
|
|
1536
|
+
});
|
|
1537
|
+
const effectFn = vi.fn();
|
|
1538
|
+
|
|
1539
|
+
effect((t) => {
|
|
1540
|
+
const val = $derivation.get(t);
|
|
1541
|
+
if (val !== undefined) {
|
|
1542
|
+
effectFn(val);
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
|
|
1547
|
+
expect(effectFn).toHaveBeenNthCalledWith(1, 10);
|
|
1548
|
+
|
|
1549
|
+
multiplier = 3;
|
|
1550
|
+
await $derivation.refresh();
|
|
1551
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(2));
|
|
1552
|
+
expect(effectFn).toHaveBeenNthCalledWith(2, 15);
|
|
1553
|
+
});
|
|
1554
|
+
|
|
1555
|
+
it("shouldn't trigger effects even if dependencies have not changed", async () => {
|
|
1556
|
+
const $state = stateAwait(Promise.resolve(1));
|
|
1557
|
+
const computeFn = vi.fn(async (t) => {
|
|
1558
|
+
const val = $state.get(t);
|
|
1559
|
+
return Promise.resolve((val ?? 0) * 2);
|
|
1560
|
+
});
|
|
1561
|
+
const $derivation = derivationAwait(computeFn);
|
|
1562
|
+
const effectFn = vi.fn();
|
|
1563
|
+
|
|
1564
|
+
effect((t) => {
|
|
1565
|
+
const val = $derivation.get(t);
|
|
1566
|
+
if (val !== undefined) {
|
|
1567
|
+
effectFn(val);
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
await vi.waitFor(() => expect(effectFn).toHaveBeenCalledTimes(1));
|
|
1572
|
+
expect(computeFn).toHaveBeenCalledTimes(1);
|
|
1573
|
+
|
|
1574
|
+
// Refresh forces recomputation even though state hasn't changed
|
|
1575
|
+
await $derivation.refresh();
|
|
1576
|
+
await vi.waitFor(() => expect(computeFn).toHaveBeenCalledTimes(2));
|
|
1577
|
+
// Effect should not be called again because value hasn't changed
|
|
1578
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1579
|
+
expect(effectFn).toHaveBeenCalledTimes(1);
|
|
1580
|
+
});
|
|
1581
|
+
});
|
|
1582
|
+
});
|
|
1583
|
+
});
|