@esportsplus/reactivity 0.38.1 → 0.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bench/ownership.bench.ts +212 -0
- package/build/reactive/array.d.ts +3 -2
- package/build/reactive/array.js +28 -10
- package/build/reactive/index.js +1 -1
- package/build/system.d.ts +2 -1
- package/build/system.js +99 -37
- package/build/types.d.ts +5 -0
- package/package.json +1 -1
- package/src/reactive/array.ts +42 -13
- package/src/reactive/index.ts +1 -1
- package/src/system.ts +145 -43
- package/src/types.ts +5 -0
- package/test/ownership.test.ts +586 -0
- package/test/reactive/array.test.ts +68 -0
- package/test/system.test.ts +2 -2
- package/test/untrack.test.ts +225 -1
package/test/untrack.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { computed, effect, peek, read, signal, untrack, write } from '~/system';
|
|
2
|
+
import { computed, effect, hasOwner, onCleanup, peek, read, root, signal, untrack, write } from '~/system';
|
|
3
|
+
import type { Computed } from '~/system';
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
describe('untrack', () => {
|
|
@@ -72,6 +73,229 @@ describe('untrack', () => {
|
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
|
|
76
|
+
describe('untrack ownership', () => {
|
|
77
|
+
it('an effect created inside untrack during a re-run is disposed by the next re-run and by dispose', async () => {
|
|
78
|
+
let inner = signal(0),
|
|
79
|
+
innerRuns: string[] = [],
|
|
80
|
+
outer = signal(0),
|
|
81
|
+
stop = effect(() => {
|
|
82
|
+
let gen = read(outer);
|
|
83
|
+
|
|
84
|
+
untrack(() => {
|
|
85
|
+
effect(() => {
|
|
86
|
+
innerRuns.push(`${gen}:${read(inner)}`);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
write(outer, 1);
|
|
92
|
+
await Promise.resolve();
|
|
93
|
+
|
|
94
|
+
expect(innerRuns).toEqual(['0:0', '1:0']);
|
|
95
|
+
|
|
96
|
+
write(inner, 1);
|
|
97
|
+
await Promise.resolve();
|
|
98
|
+
|
|
99
|
+
// Only generation 1's inner effect is alive; generation 0's was disposed by the re-run
|
|
100
|
+
expect(innerRuns).toEqual(['0:0', '1:0', '1:1']);
|
|
101
|
+
|
|
102
|
+
stop();
|
|
103
|
+
write(inner, 2);
|
|
104
|
+
await Promise.resolve();
|
|
105
|
+
|
|
106
|
+
expect(innerRuns).toEqual(['0:0', '1:0', '1:1']);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('onCleanup inside untrack runs on the next re-run and on dispose', async () => {
|
|
110
|
+
let log: number[] = [],
|
|
111
|
+
s = signal(0),
|
|
112
|
+
stop = effect(() => {
|
|
113
|
+
let v = read(s);
|
|
114
|
+
|
|
115
|
+
untrack(() => onCleanup(() => { log.push(v); }));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
write(s, 1);
|
|
119
|
+
await Promise.resolve();
|
|
120
|
+
|
|
121
|
+
expect(log).toEqual([0]);
|
|
122
|
+
|
|
123
|
+
stop();
|
|
124
|
+
|
|
125
|
+
expect(log).toEqual([0, 1]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('tears down untracked children and cleanups in registration order', async () => {
|
|
129
|
+
let log: string[] = [],
|
|
130
|
+
s = signal(0),
|
|
131
|
+
stop = effect(() => {
|
|
132
|
+
read(s);
|
|
133
|
+
onCleanup(() => { log.push('a'); });
|
|
134
|
+
untrack(() => {
|
|
135
|
+
effect(() => {
|
|
136
|
+
onCleanup(() => { log.push('b'); });
|
|
137
|
+
});
|
|
138
|
+
onCleanup(() => { log.push('c'); });
|
|
139
|
+
});
|
|
140
|
+
root((d) => {
|
|
141
|
+
onCleanup(() => { log.push('d'); });
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
write(s, 1);
|
|
146
|
+
await Promise.resolve();
|
|
147
|
+
|
|
148
|
+
expect(log).toEqual(['a', 'b', 'c', 'd']);
|
|
149
|
+
|
|
150
|
+
stop();
|
|
151
|
+
|
|
152
|
+
// dispose() drains owned children after the owner's own cleanups, exactly as for tracked children
|
|
153
|
+
expect(log.slice(4).sort()).toEqual(['a', 'b', 'c', 'd']);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('reads inside untrack still do not subscribe, nor do reads of untracked children', async () => {
|
|
157
|
+
let a = signal(0),
|
|
158
|
+
b = signal(0),
|
|
159
|
+
runs = 0;
|
|
160
|
+
|
|
161
|
+
effect(() => {
|
|
162
|
+
runs++;
|
|
163
|
+
untrack(() => {
|
|
164
|
+
read(a);
|
|
165
|
+
computed(() => read(b));
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
write(a, 1);
|
|
170
|
+
write(b, 1);
|
|
171
|
+
await Promise.resolve();
|
|
172
|
+
|
|
173
|
+
expect(runs).toBe(1);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('outside any computation creations stay unowned', () => {
|
|
177
|
+
let log: string[] = [],
|
|
178
|
+
stop = untrack(() => {
|
|
179
|
+
onCleanup(() => { log.push('dropped'); });
|
|
180
|
+
|
|
181
|
+
return effect(() => {
|
|
182
|
+
onCleanup(() => { log.push('effect'); });
|
|
183
|
+
});
|
|
184
|
+
}),
|
|
185
|
+
c = untrack(() => computed(() => 1)) as Computed<unknown>;
|
|
186
|
+
|
|
187
|
+
expect(c.owner).toBe(null);
|
|
188
|
+
|
|
189
|
+
stop();
|
|
190
|
+
|
|
191
|
+
expect(log).toEqual(['effect']);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('inside root((d) => ...) creations stay owned by the root', () => {
|
|
195
|
+
let log: string[] = [];
|
|
196
|
+
|
|
197
|
+
root((d) => {
|
|
198
|
+
untrack(() => {
|
|
199
|
+
onCleanup(() => { log.push('cleanup'); });
|
|
200
|
+
effect(() => {
|
|
201
|
+
onCleanup(() => { log.push('effect'); });
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
d();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(log).toEqual(['cleanup', 'effect']);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('nested untrack keeps the running computation as owner', async () => {
|
|
211
|
+
let log: number[] = [],
|
|
212
|
+
s = signal(0),
|
|
213
|
+
stop = effect(() => {
|
|
214
|
+
let v = read(s);
|
|
215
|
+
|
|
216
|
+
untrack(() => untrack(() => onCleanup(() => { log.push(v); })));
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
write(s, 1);
|
|
220
|
+
await Promise.resolve();
|
|
221
|
+
stop();
|
|
222
|
+
|
|
223
|
+
expect(log).toEqual([0, 1]);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('a zero-arg root inside untrack stays detached', async () => {
|
|
227
|
+
let log: number[] = [],
|
|
228
|
+
s = signal(0),
|
|
229
|
+
stop = effect(() => {
|
|
230
|
+
let v = read(s);
|
|
231
|
+
|
|
232
|
+
untrack(() => root(() => {
|
|
233
|
+
onCleanup(() => { log.push(-1); });
|
|
234
|
+
untrack(() => effect(() => {
|
|
235
|
+
onCleanup(() => { log.push(v); });
|
|
236
|
+
}));
|
|
237
|
+
}));
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
write(s, 1);
|
|
241
|
+
await Promise.resolve();
|
|
242
|
+
stop();
|
|
243
|
+
|
|
244
|
+
// Neither the root's cleanup (no owner) nor its effects are torn down by the outer effect
|
|
245
|
+
expect(log).toEqual([]);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('restores owner and observer when fn throws', async () => {
|
|
249
|
+
let a = signal(0),
|
|
250
|
+
runs = 0;
|
|
251
|
+
|
|
252
|
+
effect(() => {
|
|
253
|
+
runs++;
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
untrack(() => {
|
|
257
|
+
throw new Error('boom');
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
// only restoration is under test
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
read(a);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(untrack(() => hasOwner())).toBe(false);
|
|
268
|
+
|
|
269
|
+
write(a, 1);
|
|
270
|
+
await Promise.resolve();
|
|
271
|
+
|
|
272
|
+
expect(runs).toBe(2);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('an async computed created inside untrack is owned by the running computation', async () => {
|
|
276
|
+
let log: string[] = [],
|
|
277
|
+
s = signal(0),
|
|
278
|
+
stop = effect(() => {
|
|
279
|
+
let v = read(s);
|
|
280
|
+
|
|
281
|
+
untrack(() => computed(async () => {
|
|
282
|
+
onCleanup(() => { log.push(`factory ${v}`); });
|
|
283
|
+
return v;
|
|
284
|
+
}));
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
write(s, 1);
|
|
288
|
+
await Promise.resolve();
|
|
289
|
+
|
|
290
|
+
expect(log).toEqual(['factory 0']);
|
|
291
|
+
|
|
292
|
+
stop();
|
|
293
|
+
|
|
294
|
+
expect(log).toEqual(['factory 0', 'factory 1']);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
|
|
75
299
|
describe('peek', () => {
|
|
76
300
|
it('returns the current value of a signal', () => {
|
|
77
301
|
let s = signal(5);
|