@planet-matrix/mobius-model 0.1.4 → 0.3.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/CHANGELOG.md +46 -0
- package/README.md +21 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +12 -6
- package/dist/reactor/index.d.ts +3 -0
- package/dist/reactor/index.d.ts.map +1 -0
- package/dist/reactor/reactor-core/flags.d.ts.map +1 -0
- package/dist/reactor/reactor-core/index.d.ts.map +1 -0
- package/dist/reactor/reactor-core/primitive.d.ts +276 -0
- package/dist/reactor/reactor-core/primitive.d.ts.map +1 -0
- package/dist/{signal/signal-core → reactor/reactor-core}/reactive-system.d.ts +102 -22
- package/dist/reactor/reactor-core/reactive-system.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/branch.d.ts +19 -0
- package/dist/reactor/reactor-operators/branch.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/convert.d.ts +30 -0
- package/dist/reactor/reactor-operators/convert.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/create.d.ts +26 -0
- package/dist/reactor/reactor-operators/create.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/filter.d.ts +269 -0
- package/dist/reactor/reactor-operators/filter.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/index.d.ts +8 -0
- package/dist/reactor/reactor-operators/index.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/join.d.ts +48 -0
- package/dist/reactor/reactor-operators/join.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/map.d.ts +165 -0
- package/dist/reactor/reactor-operators/map.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/utility.d.ts +48 -0
- package/dist/reactor/reactor-operators/utility.d.ts.map +1 -0
- package/package.json +9 -9
- package/src/index.ts +1 -1
- package/src/reactor/README.md +18 -0
- package/src/reactor/index.ts +2 -0
- package/src/reactor/reactor-core/primitive.ts +1046 -0
- package/src/{signal/signal-core → reactor/reactor-core}/reactive-system.ts +392 -93
- package/src/reactor/reactor-operators/branch.ts +66 -0
- package/src/reactor/reactor-operators/convert.ts +70 -0
- package/src/reactor/reactor-operators/create.ts +66 -0
- package/src/reactor/reactor-operators/filter.ts +988 -0
- package/src/reactor/reactor-operators/index.ts +7 -0
- package/src/reactor/reactor-operators/join.ts +174 -0
- package/src/reactor/reactor-operators/map.ts +599 -0
- package/src/reactor/reactor-operators/utility.ts +102 -0
- package/tests/unit/{signal/computed.spec.ts → reactor/alien-signals-computed.spec.ts} +15 -10
- package/tests/unit/reactor/alien-signals-effect-scope.spec.ts +86 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +395 -0
- package/tests/unit/reactor/alien-signals-topology.spec.ts +361 -0
- package/tests/unit/reactor/alien-signals-trigger.spec.ts +75 -0
- package/tests/unit/reactor/alien-signals-untrack.spec.ts +91 -0
- package/tests/unit/reactor/preact-signal.spec.ts +73 -0
- package/tests/unit/reactor/reactor-core.spec.ts +219 -0
- package/tests/unit/reactor/reactor-operators-branch.spec.ts +33 -0
- package/tests/unit/reactor/reactor-operators-convert.spec.ts +31 -0
- package/tests/unit/reactor/reactor-operators-create.spec.ts +47 -0
- package/tests/unit/reactor/reactor-operators-filter.spec.ts +604 -0
- package/tests/unit/reactor/reactor-operators-join.spec.ts +94 -0
- package/tests/unit/reactor/reactor-operators-map.spec.ts +327 -0
- package/tests/unit/reactor/reactor-operators-utility.spec.ts +55 -0
- package/dist/signal/index.d.ts +0 -3
- package/dist/signal/index.d.ts.map +0 -1
- package/dist/signal/signal-core/flags.d.ts.map +0 -1
- package/dist/signal/signal-core/index.d.ts.map +0 -1
- package/dist/signal/signal-core/primitive.d.ts +0 -67
- package/dist/signal/signal-core/primitive.d.ts.map +0 -1
- package/dist/signal/signal-core/reactive-system.d.ts.map +0 -1
- package/dist/signal/signal-operators/index.d.ts +0 -4
- package/dist/signal/signal-operators/index.d.ts.map +0 -1
- package/src/signal/index.ts +0 -2
- package/src/signal/signal-core/README.md +0 -4
- package/src/signal/signal-core/primitive.ts +0 -275
- package/src/signal/signal-operators/index.ts +0 -19
- package/tests/unit/signal/effect.spec.ts +0 -108
- /package/dist/{signal/signal-core → reactor/reactor-core}/flags.d.ts +0 -0
- /package/dist/{signal/signal-core → reactor/reactor-core}/index.d.ts +0 -0
- /package/src/{signal/signal-core → reactor/reactor-core}/flags.ts +0 -0
- /package/src/{signal/signal-core → reactor/reactor-core}/index.ts +0 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { computed, effect, signal } from '#Source/reactor/index.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 这个测试文件应该与 https://github.com/stackblitz/alien-signals/blob/master/tests/topology.spec.ts 保持同步。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
describe("graph updates", () => {
|
|
10
|
+
test('should drop A->B->A updates', () => {
|
|
11
|
+
// A
|
|
12
|
+
// / |
|
|
13
|
+
// B | <- Looks like a flag doesn't it? :D
|
|
14
|
+
// \ |
|
|
15
|
+
// C
|
|
16
|
+
// |
|
|
17
|
+
// D
|
|
18
|
+
const a = signal(() => 2);
|
|
19
|
+
|
|
20
|
+
const b = computed(() => a.get() - 1);
|
|
21
|
+
const c = computed(() => a.get() + b.get());
|
|
22
|
+
|
|
23
|
+
const compute = vi.fn(() => `d: ${c.get()}`);
|
|
24
|
+
const d = computed(compute);
|
|
25
|
+
|
|
26
|
+
// Trigger read
|
|
27
|
+
expect(d.get()).toBe("d: 3");
|
|
28
|
+
expect(compute).toHaveBeenCalledOnce();
|
|
29
|
+
compute.mockClear();
|
|
30
|
+
|
|
31
|
+
a.set(4);
|
|
32
|
+
d.get();
|
|
33
|
+
expect(compute).toHaveBeenCalledOnce();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('should only update every signal once (diamond graph)', () => {
|
|
37
|
+
// In this scenario "D" should only update once when "A" receives
|
|
38
|
+
// an update. This is sometimes referred to as the "diamond" scenario.
|
|
39
|
+
// A
|
|
40
|
+
// / \
|
|
41
|
+
// B C
|
|
42
|
+
// \ /
|
|
43
|
+
// D
|
|
44
|
+
|
|
45
|
+
const a = signal(() => "a");
|
|
46
|
+
const b = computed(() => a.get());
|
|
47
|
+
const c = computed(() => a.get());
|
|
48
|
+
|
|
49
|
+
const spy = vi.fn(() => `${b.get()} ${c.get()}`);
|
|
50
|
+
const d = computed(spy);
|
|
51
|
+
|
|
52
|
+
expect(d.get()).toBe("a a");
|
|
53
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
54
|
+
|
|
55
|
+
a.set("aa");
|
|
56
|
+
expect(d.get()).toBe("aa aa");
|
|
57
|
+
expect(spy).toHaveBeenCalledTimes(2);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('should only update every signal once (diamond graph + tail)', () => {
|
|
61
|
+
// "E" will be likely updated twice if our mark+sweep logic is buggy.
|
|
62
|
+
// A
|
|
63
|
+
// / \
|
|
64
|
+
// B C
|
|
65
|
+
// \ /
|
|
66
|
+
// D
|
|
67
|
+
// |
|
|
68
|
+
// E
|
|
69
|
+
|
|
70
|
+
const a = signal(() => "a");
|
|
71
|
+
const b = computed(() => a.get());
|
|
72
|
+
const c = computed(() => a.get());
|
|
73
|
+
|
|
74
|
+
const d = computed(() => `${b.get()} ${c.get()}`);
|
|
75
|
+
|
|
76
|
+
const spy = vi.fn(() => d.get());
|
|
77
|
+
const e = computed(spy);
|
|
78
|
+
|
|
79
|
+
expect(e.get()).toBe("a a");
|
|
80
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
81
|
+
|
|
82
|
+
a.set("aa");
|
|
83
|
+
expect(e.get()).toBe("aa aa");
|
|
84
|
+
expect(spy).toHaveBeenCalledTimes(2);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('should bail out if result is the same', () => {
|
|
88
|
+
// Bail out if value of "B" never changes
|
|
89
|
+
// A->B->C
|
|
90
|
+
const a = signal(() => "a");
|
|
91
|
+
const b = computed(() => {
|
|
92
|
+
a.get();
|
|
93
|
+
return "foo";
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const spy = vi.fn(() => b.get());
|
|
97
|
+
const c = computed(spy);
|
|
98
|
+
|
|
99
|
+
expect(c.get()).toBe("foo");
|
|
100
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
101
|
+
|
|
102
|
+
a.set("aa");
|
|
103
|
+
expect(c.get()).toBe("foo");
|
|
104
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('should only update every signal once (jagged diamond graph + tails)', () => {
|
|
108
|
+
// "F" and "G" will be likely updated twice if our mark+sweep logic is buggy.
|
|
109
|
+
// A
|
|
110
|
+
// / \
|
|
111
|
+
// B C
|
|
112
|
+
// | |
|
|
113
|
+
// | D
|
|
114
|
+
// \ /
|
|
115
|
+
// E
|
|
116
|
+
// / \
|
|
117
|
+
// F G
|
|
118
|
+
const a = signal(() => "a");
|
|
119
|
+
|
|
120
|
+
const b = computed(() => a.get());
|
|
121
|
+
const c = computed(() => a.get());
|
|
122
|
+
|
|
123
|
+
const d = computed(() => c.get());
|
|
124
|
+
|
|
125
|
+
const eSpy = vi.fn(() => `${b.get()} ${d.get()}`);
|
|
126
|
+
const e = computed(eSpy);
|
|
127
|
+
|
|
128
|
+
const fSpy = vi.fn(() => e.get());
|
|
129
|
+
const f = computed(fSpy);
|
|
130
|
+
const gSpy = vi.fn(() => e.get());
|
|
131
|
+
const g = computed(gSpy);
|
|
132
|
+
|
|
133
|
+
expect(f.get()).toBe("a a");
|
|
134
|
+
expect(fSpy).toHaveBeenCalledTimes(1);
|
|
135
|
+
|
|
136
|
+
expect(g.get()).toBe("a a");
|
|
137
|
+
expect(gSpy).toHaveBeenCalledTimes(1);
|
|
138
|
+
|
|
139
|
+
eSpy.mockClear();
|
|
140
|
+
fSpy.mockClear();
|
|
141
|
+
gSpy.mockClear();
|
|
142
|
+
|
|
143
|
+
a.set("b");
|
|
144
|
+
|
|
145
|
+
expect(e.get()).toBe("b b");
|
|
146
|
+
expect(eSpy).toHaveBeenCalledTimes(1);
|
|
147
|
+
|
|
148
|
+
expect(f.get()).toBe("b b");
|
|
149
|
+
expect(fSpy).toHaveBeenCalledTimes(1);
|
|
150
|
+
|
|
151
|
+
expect(g.get()).toBe("b b");
|
|
152
|
+
expect(gSpy).toHaveBeenCalledTimes(1);
|
|
153
|
+
|
|
154
|
+
eSpy.mockClear();
|
|
155
|
+
fSpy.mockClear();
|
|
156
|
+
gSpy.mockClear();
|
|
157
|
+
|
|
158
|
+
a.set("c");
|
|
159
|
+
|
|
160
|
+
expect(e.get()).toBe("c c");
|
|
161
|
+
expect(eSpy).toHaveBeenCalledTimes(1);
|
|
162
|
+
|
|
163
|
+
expect(f.get()).toBe("c c");
|
|
164
|
+
expect(fSpy).toHaveBeenCalledTimes(1);
|
|
165
|
+
|
|
166
|
+
expect(g.get()).toBe("c c");
|
|
167
|
+
expect(gSpy).toHaveBeenCalledTimes(1);
|
|
168
|
+
|
|
169
|
+
// top to bottom
|
|
170
|
+
expect(eSpy).toHaveBeenCalledBefore(fSpy);
|
|
171
|
+
// left to right
|
|
172
|
+
expect(fSpy).toHaveBeenCalledBefore(gSpy);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('should only subscribe to signals listened to', () => {
|
|
176
|
+
// *A
|
|
177
|
+
// / \
|
|
178
|
+
// *B C <- we don't listen to C
|
|
179
|
+
const a = signal(() => "a");
|
|
180
|
+
|
|
181
|
+
const b = computed(() => a.get());
|
|
182
|
+
const spy = vi.fn(() => a.get());
|
|
183
|
+
computed(spy);
|
|
184
|
+
|
|
185
|
+
expect(b.get()).toBe("a");
|
|
186
|
+
expect(spy).not.toHaveBeenCalled();
|
|
187
|
+
|
|
188
|
+
a.set("aa");
|
|
189
|
+
expect(b.get()).toBe("aa");
|
|
190
|
+
expect(spy).not.toHaveBeenCalled();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('should only subscribe to signals listened to II', () => {
|
|
194
|
+
// Here both "B" and "C" are active in the beginning, but
|
|
195
|
+
// "B" becomes inactive later. At that point it should
|
|
196
|
+
// not receive any updates anymore.
|
|
197
|
+
// *A
|
|
198
|
+
// / \
|
|
199
|
+
// *B D <- we don't listen to C
|
|
200
|
+
// |
|
|
201
|
+
// *C
|
|
202
|
+
const a = signal(() => "a");
|
|
203
|
+
const spyB = vi.fn(() => a.get());
|
|
204
|
+
const b = computed(spyB);
|
|
205
|
+
|
|
206
|
+
const spyC = vi.fn(() => b.get());
|
|
207
|
+
const c = computed(spyC);
|
|
208
|
+
|
|
209
|
+
const d = computed(() => a.get());
|
|
210
|
+
|
|
211
|
+
let result = "";
|
|
212
|
+
const e = effect(() => {
|
|
213
|
+
result = c.get();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(result).toBe("a");
|
|
217
|
+
expect(d.get()).toBe("a");
|
|
218
|
+
|
|
219
|
+
spyB.mockClear();
|
|
220
|
+
spyC.mockClear();
|
|
221
|
+
e.dispose();
|
|
222
|
+
|
|
223
|
+
a.set("aa");
|
|
224
|
+
|
|
225
|
+
expect(spyB).not.toHaveBeenCalled();
|
|
226
|
+
expect(spyC).not.toHaveBeenCalled();
|
|
227
|
+
expect(d.get()).toBe("aa");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('should ensure subs update even if one dep unmarks it', () => {
|
|
231
|
+
// In this scenario "C" always returns the same value. When "A"
|
|
232
|
+
// changes, "B" will update, then "C" at which point its update
|
|
233
|
+
// to "D" will be unmarked. But "D" must still update because
|
|
234
|
+
// "B" marked it. If "D" isn't updated, then we have a bug.
|
|
235
|
+
// A
|
|
236
|
+
// / \
|
|
237
|
+
// B *C <- returns same value every time
|
|
238
|
+
// \ /
|
|
239
|
+
// D
|
|
240
|
+
const a = signal(() => "a");
|
|
241
|
+
const b = computed(() => a.get());
|
|
242
|
+
const c = computed(() => {
|
|
243
|
+
a.get();
|
|
244
|
+
return "c";
|
|
245
|
+
});
|
|
246
|
+
const spy = vi.fn(() => `${b.get()} ${c.get()}`);
|
|
247
|
+
const d = computed(spy);
|
|
248
|
+
|
|
249
|
+
expect(d.get()).toBe("a c");
|
|
250
|
+
spy.mockClear();
|
|
251
|
+
|
|
252
|
+
a.set("aa");
|
|
253
|
+
d.get();
|
|
254
|
+
expect(spy).toHaveReturnedWith("aa c");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('should ensure subs update even if two deps unmark it', () => {
|
|
258
|
+
// In this scenario both "C" and "D" always return the same
|
|
259
|
+
// value. But "E" must still update because "A" marked it.
|
|
260
|
+
// If "E" isn't updated, then we have a bug.
|
|
261
|
+
// A
|
|
262
|
+
// / | \
|
|
263
|
+
// B *C *D
|
|
264
|
+
// \ | /
|
|
265
|
+
// E
|
|
266
|
+
const a = signal(() => "a");
|
|
267
|
+
const b = computed(() => a.get());
|
|
268
|
+
const c = computed(() => {
|
|
269
|
+
a.get();
|
|
270
|
+
return "c";
|
|
271
|
+
});
|
|
272
|
+
const d = computed(() => {
|
|
273
|
+
a.get();
|
|
274
|
+
return "d";
|
|
275
|
+
});
|
|
276
|
+
const spy = vi.fn(() => `${b.get()} ${c.get()} ${d.get()}`);
|
|
277
|
+
const e = computed(spy);
|
|
278
|
+
|
|
279
|
+
expect(e.get()).toBe("a c d");
|
|
280
|
+
spy.mockClear();
|
|
281
|
+
|
|
282
|
+
a.set("aa");
|
|
283
|
+
e.get();
|
|
284
|
+
expect(spy).toHaveReturnedWith("aa c d");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('should support lazy branches', () => {
|
|
288
|
+
const a = signal(() => 0);
|
|
289
|
+
const b = computed(() => a.get());
|
|
290
|
+
const c = computed(() => (a.get() > 0 ? a.get() : b.get()));
|
|
291
|
+
|
|
292
|
+
expect(c.get()).toBe(0);
|
|
293
|
+
a.set(1);
|
|
294
|
+
expect(c.get()).toBe(1);
|
|
295
|
+
|
|
296
|
+
a.set(0);
|
|
297
|
+
expect(c.get()).toBe(0);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test('should not update a sub if all deps unmark it', () => {
|
|
301
|
+
// In this scenario "B" and "C" always return the same value. When "A"
|
|
302
|
+
// changes, "D" should not update.
|
|
303
|
+
// A
|
|
304
|
+
// / \
|
|
305
|
+
// *B *C
|
|
306
|
+
// \ /
|
|
307
|
+
// D
|
|
308
|
+
const a = signal(() => "a");
|
|
309
|
+
const b = computed(() => {
|
|
310
|
+
a.get();
|
|
311
|
+
return "b";
|
|
312
|
+
});
|
|
313
|
+
const c = computed(() => {
|
|
314
|
+
a.get();
|
|
315
|
+
return "c";
|
|
316
|
+
});
|
|
317
|
+
const spy = vi.fn(() => `${b.get()} ${c.get()}`);
|
|
318
|
+
const d = computed(spy);
|
|
319
|
+
|
|
320
|
+
expect(d.get()).toBe("b c");
|
|
321
|
+
spy.mockClear();
|
|
322
|
+
|
|
323
|
+
a.set("aa");
|
|
324
|
+
expect(spy).not.toHaveBeenCalled();
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
describe("error handling", () => {
|
|
330
|
+
test('should keep graph consistent on errors during activation', () => {
|
|
331
|
+
const a = signal(() => 0);
|
|
332
|
+
const b = computed(() => {
|
|
333
|
+
throw new Error("fail");
|
|
334
|
+
});
|
|
335
|
+
const c = computed(() => a.get());
|
|
336
|
+
|
|
337
|
+
expect(() => b.get()).toThrow("fail");
|
|
338
|
+
|
|
339
|
+
a.set(1);
|
|
340
|
+
expect(c.get()).toBe(1);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test('should keep graph consistent on errors in computeds', () => {
|
|
344
|
+
const a = signal(() => 0);
|
|
345
|
+
const b = computed(() => {
|
|
346
|
+
if (a.get() === 1) {
|
|
347
|
+
throw new Error("fail");
|
|
348
|
+
}
|
|
349
|
+
return a.get();
|
|
350
|
+
});
|
|
351
|
+
const c = computed(() => b.get());
|
|
352
|
+
|
|
353
|
+
expect(c.get()).toBe(0);
|
|
354
|
+
|
|
355
|
+
a.set(1);
|
|
356
|
+
expect(() => b.get()).toThrow("fail");
|
|
357
|
+
|
|
358
|
+
a.set(2);
|
|
359
|
+
expect(c.get()).toBe(2);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { computed, effect, signal, trigger } from '#Source/reactor/index.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 这个测试文件应该与 https://github.com/stackblitz/alien-signals/blob/master/tests/trigger.spec.ts 保持同步。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
test('should not throw when triggering with no dependencies', () => {
|
|
10
|
+
trigger(() => {
|
|
11
|
+
// no op
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('should trigger updates for dependent computed signals', () => {
|
|
16
|
+
const arr = signal<number[]>(() => []);
|
|
17
|
+
const length = computed(() => arr.get().length);
|
|
18
|
+
|
|
19
|
+
expect(length.get()).toBe(0);
|
|
20
|
+
arr.get().push(1);
|
|
21
|
+
expect(length.get()).toBe(0);
|
|
22
|
+
trigger(() => {
|
|
23
|
+
arr.get();
|
|
24
|
+
});
|
|
25
|
+
expect(length.get()).toBe(1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('should trigger updates for the second source signal', () => {
|
|
29
|
+
const src1 = signal<number[]>(() => []);
|
|
30
|
+
const src2 = signal<number[]>(() => []);
|
|
31
|
+
const length = computed(() => src2.get().length);
|
|
32
|
+
|
|
33
|
+
expect(length.get()).toBe(0);
|
|
34
|
+
src2.get().push(1);
|
|
35
|
+
expect(length.get()).toBe(0);
|
|
36
|
+
trigger(() => {
|
|
37
|
+
src1.get();
|
|
38
|
+
src2.get();
|
|
39
|
+
});
|
|
40
|
+
expect(length.get()).toBe(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('should trigger effect once', () => {
|
|
44
|
+
const src1 = signal<number[]>(() => []);
|
|
45
|
+
const src2 = signal<number[]>(() => []);
|
|
46
|
+
|
|
47
|
+
let effectRunTimes = 0;
|
|
48
|
+
|
|
49
|
+
effect(() => {
|
|
50
|
+
effectRunTimes = effectRunTimes + 1;
|
|
51
|
+
src1.get();
|
|
52
|
+
src2.get();
|
|
53
|
+
});
|
|
54
|
+
expect(effectRunTimes).toBe(1);
|
|
55
|
+
|
|
56
|
+
trigger(() => {
|
|
57
|
+
src1.get();
|
|
58
|
+
src2.get();
|
|
59
|
+
});
|
|
60
|
+
expect(effectRunTimes).toBe(2);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('should not notify the trigger function sub', () => {
|
|
64
|
+
const src1 = signal<number[]>(() => []);
|
|
65
|
+
const src2 = computed(() => src1.get());
|
|
66
|
+
|
|
67
|
+
effect(() => {
|
|
68
|
+
src1.get();
|
|
69
|
+
src2.get();
|
|
70
|
+
});
|
|
71
|
+
trigger(() => {
|
|
72
|
+
src1.get();
|
|
73
|
+
src2.get();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { computed, effect, effectScope, reactiveSystem, signal } from '#Source/reactor/index.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 这个测试文件应该与 https://github.com/stackblitz/alien-signals/blob/master/tests/untrack.spec.ts 保持同步。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
test('should pause tracking in computed', () => {
|
|
10
|
+
const src = signal(() => 0);
|
|
11
|
+
|
|
12
|
+
let computedRunTimes = 0;
|
|
13
|
+
const c = computed(() => {
|
|
14
|
+
computedRunTimes = computedRunTimes + 1;
|
|
15
|
+
reactiveSystem.setNoActiveNodeAsSub();
|
|
16
|
+
const value = src.get();
|
|
17
|
+
reactiveSystem.resetActiveNodeAsSub();
|
|
18
|
+
return value;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
expect(c.get()).toBe(0);
|
|
22
|
+
expect(computedRunTimes).toBe(1);
|
|
23
|
+
|
|
24
|
+
src.set(1);
|
|
25
|
+
src.set(2);
|
|
26
|
+
src.set(3);
|
|
27
|
+
expect(c.get()).toBe(0);
|
|
28
|
+
expect(computedRunTimes).toBe(1);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should pause tracking in effect', () => {
|
|
32
|
+
const src = signal(() => 0);
|
|
33
|
+
const is = signal(() => 0);
|
|
34
|
+
|
|
35
|
+
let effectRunTimes = 0;
|
|
36
|
+
effect(() => {
|
|
37
|
+
effectRunTimes = effectRunTimes + 1;
|
|
38
|
+
if (is.get() !== 0) {
|
|
39
|
+
reactiveSystem.setNoActiveNodeAsSub();
|
|
40
|
+
src.get();
|
|
41
|
+
reactiveSystem.resetActiveNodeAsSub();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(effectRunTimes).toBe(1);
|
|
46
|
+
|
|
47
|
+
is.set(1);
|
|
48
|
+
expect(effectRunTimes).toBe(2);
|
|
49
|
+
|
|
50
|
+
src.set(1);
|
|
51
|
+
src.set(2);
|
|
52
|
+
src.set(3);
|
|
53
|
+
expect(effectRunTimes).toBe(2);
|
|
54
|
+
|
|
55
|
+
is.set(2);
|
|
56
|
+
expect(effectRunTimes).toBe(3);
|
|
57
|
+
|
|
58
|
+
src.set(4);
|
|
59
|
+
src.set(5);
|
|
60
|
+
src.set(6);
|
|
61
|
+
expect(effectRunTimes).toBe(3);
|
|
62
|
+
|
|
63
|
+
is.set(0);
|
|
64
|
+
expect(effectRunTimes).toBe(4);
|
|
65
|
+
|
|
66
|
+
src.set(7);
|
|
67
|
+
src.set(8);
|
|
68
|
+
src.set(9);
|
|
69
|
+
expect(effectRunTimes).toBe(4);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('should pause tracking in effect scope', () => {
|
|
73
|
+
const src = signal(() => 0);
|
|
74
|
+
|
|
75
|
+
let effectRunTimes = 0;
|
|
76
|
+
effectScope(() => {
|
|
77
|
+
effect(() => {
|
|
78
|
+
effectRunTimes = effectRunTimes + 1;
|
|
79
|
+
reactiveSystem.setNoActiveNodeAsSub();
|
|
80
|
+
src.get();
|
|
81
|
+
reactiveSystem.resetActiveNodeAsSub();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(effectRunTimes).toBe(1);
|
|
86
|
+
|
|
87
|
+
src.set(1);
|
|
88
|
+
src.set(2);
|
|
89
|
+
src.set(3);
|
|
90
|
+
expect(effectRunTimes).toBe(1);
|
|
91
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, it, vi, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import type { Effect } from '#Source/index.ts';
|
|
4
|
+
import { Signal, computed, effect, effectScope, endBatch, reactiveSystem, signal, startBatch } from '#Source/reactor/index.ts';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 这个测试文件应该与 https://github.com/preactjs/signals/blob/main/packages/core/test/signal.test.tsx 保持同步。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
describe("signal", () => {
|
|
11
|
+
it("should return value", () => {
|
|
12
|
+
const v = [1, 2];
|
|
13
|
+
const s = signal(() => v);
|
|
14
|
+
expect(s.get()).to.equal(v);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should inherit from Signal", () => {
|
|
18
|
+
expect(signal(() => 0)).to.be.instanceOf(Signal);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should support .toString()", () => {
|
|
22
|
+
// const s = signal(123);
|
|
23
|
+
// expect(s.toString()).equal("123");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should support .toJSON()", () => {
|
|
27
|
+
// const s = signal(123);
|
|
28
|
+
// expect(s.toJSON()).equal(123);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should support JSON.Stringify()", () => {
|
|
32
|
+
// const s = signal(123);
|
|
33
|
+
// expect(JSON.stringify({ s })).equal(JSON.stringify({ s: 123 }));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should support .valueOf()", () => {
|
|
37
|
+
// const s = signal(123);
|
|
38
|
+
// expect(s).to.have.property("valueOf");
|
|
39
|
+
// expect(s.valueOf).to.be.a("function");
|
|
40
|
+
// expect(s.valueOf()).equal(123);
|
|
41
|
+
// expect(+s).equal(123);
|
|
42
|
+
|
|
43
|
+
// const a = signal(1);
|
|
44
|
+
// const b = signal(2);
|
|
45
|
+
// expect(a + b).to.equal(3);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should notify other listeners of changes after one listener is disposed", () => {
|
|
49
|
+
const s = signal(() => 0);
|
|
50
|
+
const spy1 = vi.fn(() => {
|
|
51
|
+
s.get();
|
|
52
|
+
});
|
|
53
|
+
const spy2 = vi.fn(() => {
|
|
54
|
+
s.get();
|
|
55
|
+
});
|
|
56
|
+
const spy3 = vi.fn(() => {
|
|
57
|
+
s.get();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
effect(spy1);
|
|
61
|
+
const e2 = effect(spy2);
|
|
62
|
+
effect(spy3);
|
|
63
|
+
expect(spy1).toHaveBeenCalledOnce();
|
|
64
|
+
expect(spy2).toHaveBeenCalledOnce();
|
|
65
|
+
expect(spy3).toHaveBeenCalledOnce();
|
|
66
|
+
|
|
67
|
+
e2.dispose();
|
|
68
|
+
s.set(1);
|
|
69
|
+
expect(spy1).toHaveBeenCalledTimes(2);
|
|
70
|
+
expect(spy2).toHaveBeenCalledOnce();
|
|
71
|
+
expect(spy3).toHaveBeenCalledTimes(2);
|
|
72
|
+
});
|
|
73
|
+
});
|