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