@esportsplus/reactivity 0.30.2 → 0.30.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/reactive/array.js +24 -22
- package/build/reactive/object.js +3 -2
- package/build/system.js +4 -3
- package/package.json +3 -2
- package/src/reactive/array.ts +27 -26
- package/src/reactive/object.ts +5 -2
- package/src/system.ts +5 -3
- package/tests/array.ts +657 -0
- package/tests/bench/array.ts +162 -0
- package/tests/bench/system.ts +230 -0
- package/tests/effects.ts +211 -0
- package/tests/nested.ts +293 -0
- package/tests/objects.ts +185 -0
- package/tests/primitives.ts +280 -0
- package/tests/reactive.ts +324 -0
- package/tests/system.ts +655 -0
- package/vitest.config.ts +18 -0
- package/test/arrays.ts +0 -146
- package/test/debug.ts +0 -7
- package/test/effects.ts +0 -168
- package/test/index.ts +0 -8
- package/test/nested.ts +0 -201
- package/test/objects.ts +0 -106
- package/test/primitives.ts +0 -87
- package/test/range.ts +0 -45
- package/test/vite.config.ts +0 -41
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { computed, effect, read, signal, write } from '~/system';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// These tests validate the runtime behavior of compiler-transformed reactive
|
|
6
|
+
// primitives. The compiler rewrites `reactive(0)` to `signal(0)`, property
|
|
7
|
+
// reads to `read()`, and writes to `write()`.
|
|
8
|
+
|
|
9
|
+
describe('reactive primitives (compiler equivalents)', () => {
|
|
10
|
+
describe('creation and access', () => {
|
|
11
|
+
it('creates signals of various types', () => {
|
|
12
|
+
let count = signal(0),
|
|
13
|
+
flag = signal(true),
|
|
14
|
+
name = signal('test'),
|
|
15
|
+
nullable = signal<string | null>(null);
|
|
16
|
+
|
|
17
|
+
expect(read(count)).toBe(0);
|
|
18
|
+
expect(read(flag)).toBe(true);
|
|
19
|
+
expect(read(name)).toBe('test');
|
|
20
|
+
expect(read(nullable)).toBe(null);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('creates computed derivations', () => {
|
|
24
|
+
let s = signal(2),
|
|
25
|
+
doubled = computed(() => read(s) * 2),
|
|
26
|
+
greeting = signal('test'),
|
|
27
|
+
message = computed(() => `Hello ${read(greeting)}!`);
|
|
28
|
+
|
|
29
|
+
expect(read(doubled)).toBe(4);
|
|
30
|
+
expect(read(message)).toBe('Hello test!');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('conditional computed switches dependency', async () => {
|
|
34
|
+
let flag = signal(true),
|
|
35
|
+
x = signal(1),
|
|
36
|
+
y = signal(2),
|
|
37
|
+
conditional = computed(() => read(flag) ? read(x) + read(y) : 0),
|
|
38
|
+
values: number[] = [];
|
|
39
|
+
|
|
40
|
+
effect(() => {
|
|
41
|
+
values.push(read(conditional));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(values).toEqual([3]);
|
|
45
|
+
|
|
46
|
+
write(flag, false);
|
|
47
|
+
await Promise.resolve();
|
|
48
|
+
|
|
49
|
+
expect(values).toEqual([3, 0]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
describe('compound assignment operators', () => {
|
|
55
|
+
it('+= operator', () => {
|
|
56
|
+
let s = signal(10);
|
|
57
|
+
|
|
58
|
+
write(s, read(s) + 5);
|
|
59
|
+
|
|
60
|
+
expect(read(s)).toBe(15);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('-= operator', () => {
|
|
64
|
+
let s = signal(10);
|
|
65
|
+
|
|
66
|
+
write(s, read(s) - 3);
|
|
67
|
+
|
|
68
|
+
expect(read(s)).toBe(7);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('*= operator', () => {
|
|
72
|
+
let s = signal(4);
|
|
73
|
+
|
|
74
|
+
write(s, read(s) * 3);
|
|
75
|
+
|
|
76
|
+
expect(read(s)).toBe(12);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('/= operator', () => {
|
|
80
|
+
let s = signal(20);
|
|
81
|
+
|
|
82
|
+
write(s, read(s) / 4);
|
|
83
|
+
|
|
84
|
+
expect(read(s)).toBe(5);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('%= operator', () => {
|
|
88
|
+
let s = signal(17);
|
|
89
|
+
|
|
90
|
+
write(s, read(s) % 5);
|
|
91
|
+
|
|
92
|
+
expect(read(s)).toBe(2);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('**= operator', () => {
|
|
96
|
+
let s = signal(3);
|
|
97
|
+
|
|
98
|
+
write(s, read(s) ** 2);
|
|
99
|
+
|
|
100
|
+
expect(read(s)).toBe(9);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('&= operator', () => {
|
|
104
|
+
let s = signal(0xFF);
|
|
105
|
+
|
|
106
|
+
write(s, read(s) & 0x0F);
|
|
107
|
+
|
|
108
|
+
expect(read(s)).toBe(0x0F);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('|= operator', () => {
|
|
112
|
+
let s = signal(0xF0);
|
|
113
|
+
|
|
114
|
+
write(s, read(s) | 0x0F);
|
|
115
|
+
|
|
116
|
+
expect(read(s)).toBe(0xFF);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('^= operator', () => {
|
|
120
|
+
let s = signal(0xFF);
|
|
121
|
+
|
|
122
|
+
write(s, read(s) ^ 0xAA);
|
|
123
|
+
|
|
124
|
+
expect(read(s)).toBe(0x55);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('<<= operator', () => {
|
|
128
|
+
let s = signal(1);
|
|
129
|
+
|
|
130
|
+
write(s, read(s) << 4);
|
|
131
|
+
|
|
132
|
+
expect(read(s)).toBe(16);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('>>= operator', () => {
|
|
136
|
+
let s = signal(16);
|
|
137
|
+
|
|
138
|
+
write(s, read(s) >> 2);
|
|
139
|
+
|
|
140
|
+
expect(read(s)).toBe(4);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('>>>= operator', () => {
|
|
144
|
+
let s = signal(-1);
|
|
145
|
+
|
|
146
|
+
write(s, read(s) >>> 24);
|
|
147
|
+
|
|
148
|
+
expect(read(s)).toBe(255);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('&&= operator', () => {
|
|
152
|
+
let s = signal(1);
|
|
153
|
+
|
|
154
|
+
write(s, read(s) && 42);
|
|
155
|
+
|
|
156
|
+
expect(read(s)).toBe(42);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('||= operator', () => {
|
|
160
|
+
let s = signal(0);
|
|
161
|
+
|
|
162
|
+
write(s, read(s) || 99);
|
|
163
|
+
|
|
164
|
+
expect(read(s)).toBe(99);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('??= operator', () => {
|
|
168
|
+
let s = signal<number | null>(null);
|
|
169
|
+
|
|
170
|
+
write(s, read(s) ?? 42);
|
|
171
|
+
|
|
172
|
+
expect(read(s)).toBe(42);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
describe('increment / decrement', () => {
|
|
178
|
+
it('postfix ++ (statement)', () => {
|
|
179
|
+
let s = signal(5);
|
|
180
|
+
|
|
181
|
+
write(s, read(s) + 1);
|
|
182
|
+
|
|
183
|
+
expect(read(s)).toBe(6);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('postfix -- (statement)', () => {
|
|
187
|
+
let s = signal(5);
|
|
188
|
+
|
|
189
|
+
write(s, read(s) - 1);
|
|
190
|
+
|
|
191
|
+
expect(read(s)).toBe(4);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('prefix ++ in expression context returns new value', () => {
|
|
195
|
+
let s = signal(5),
|
|
196
|
+
newValue = (write(s, read(s) + 1), read(s));
|
|
197
|
+
|
|
198
|
+
expect(newValue).toBe(6);
|
|
199
|
+
expect(read(s)).toBe(6);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('prefix -- in expression context returns new value', () => {
|
|
203
|
+
let s = signal(5),
|
|
204
|
+
newValue = (write(s, read(s) - 1), read(s));
|
|
205
|
+
|
|
206
|
+
expect(newValue).toBe(4);
|
|
207
|
+
expect(read(s)).toBe(4);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('postfix ++ in expression context returns old value', () => {
|
|
211
|
+
let s = signal(5),
|
|
212
|
+
oldValue = read(s);
|
|
213
|
+
|
|
214
|
+
write(s, oldValue + 1);
|
|
215
|
+
|
|
216
|
+
expect(oldValue).toBe(5);
|
|
217
|
+
expect(read(s)).toBe(6);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('postfix -- in expression context returns old value', () => {
|
|
221
|
+
let s = signal(5),
|
|
222
|
+
oldValue = read(s);
|
|
223
|
+
|
|
224
|
+
write(s, oldValue - 1);
|
|
225
|
+
|
|
226
|
+
expect(oldValue).toBe(5);
|
|
227
|
+
expect(read(s)).toBe(4);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
describe('nested derivations', () => {
|
|
233
|
+
it('multiple computed from same signals', () => {
|
|
234
|
+
let x = signal(3),
|
|
235
|
+
y = signal(4),
|
|
236
|
+
sum = computed(() => read(x) + read(y)),
|
|
237
|
+
product = computed(() => read(x) * read(y)),
|
|
238
|
+
nested = computed(() => read(sum) + read(product));
|
|
239
|
+
|
|
240
|
+
expect(read(sum)).toBe(7);
|
|
241
|
+
expect(read(product)).toBe(12);
|
|
242
|
+
expect(read(nested)).toBe(19);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('computed chain updates propagate', async () => {
|
|
246
|
+
let x = signal(3),
|
|
247
|
+
y = signal(4),
|
|
248
|
+
sum = computed(() => read(x) + read(y)),
|
|
249
|
+
product = computed(() => read(x) * read(y)),
|
|
250
|
+
nested = computed(() => read(sum) + read(product)),
|
|
251
|
+
values: number[] = [];
|
|
252
|
+
|
|
253
|
+
effect(() => {
|
|
254
|
+
values.push(read(nested));
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(values).toEqual([19]);
|
|
258
|
+
|
|
259
|
+
write(x, 10);
|
|
260
|
+
await Promise.resolve();
|
|
261
|
+
|
|
262
|
+
// sum = 14, product = 40, nested = 54
|
|
263
|
+
expect(values).toEqual([19, 54]);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
describe('loop accumulation', () => {
|
|
269
|
+
it('accumulates writes in a loop', () => {
|
|
270
|
+
let s = signal(0);
|
|
271
|
+
|
|
272
|
+
for (let i = 0; i < 10; i++) {
|
|
273
|
+
write(s, read(s) + i);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 0+1+2+3+4+5+6+7+8+9 = 45
|
|
277
|
+
expect(read(s)).toBe(45);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
});
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { computed, effect, onCleanup, read, root, signal, write } from '~/system';
|
|
3
|
+
import { ReactiveObject, isReactiveObject } from '~/reactive/object';
|
|
4
|
+
import reactive from '~/reactive/index';
|
|
5
|
+
import { ReactiveArray } from '~/reactive/array';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
describe('ReactiveObject', () => {
|
|
9
|
+
describe('constructor', () => {
|
|
10
|
+
it('creates reactive object from plain object', () => {
|
|
11
|
+
let obj = new ReactiveObject({ a: 1, b: 'hello' });
|
|
12
|
+
|
|
13
|
+
expect((obj as any).a).toBe(1);
|
|
14
|
+
expect((obj as any).b).toBe('hello');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('null constructor does nothing', () => {
|
|
18
|
+
let obj = new ReactiveObject(null);
|
|
19
|
+
|
|
20
|
+
expect(obj).toBeInstanceOf(ReactiveObject);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('makes properties reactive', async () => {
|
|
24
|
+
let obj = new ReactiveObject({ count: 0 }) as any,
|
|
25
|
+
values: number[] = [];
|
|
26
|
+
|
|
27
|
+
effect(() => {
|
|
28
|
+
values.push(obj.count);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(values).toEqual([0]);
|
|
32
|
+
|
|
33
|
+
obj.count = 5;
|
|
34
|
+
await Promise.resolve();
|
|
35
|
+
|
|
36
|
+
expect(values).toEqual([0, 5]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
describe('computed properties', () => {
|
|
42
|
+
it('creates computed from external signal dependency', () => {
|
|
43
|
+
let s = signal(10),
|
|
44
|
+
obj = new ReactiveObject({
|
|
45
|
+
doubled: () => read(s) * 2
|
|
46
|
+
}) as any;
|
|
47
|
+
|
|
48
|
+
expect(obj.doubled).toBe(20);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('updates computed when external dependency changes', async () => {
|
|
52
|
+
let s = signal(1),
|
|
53
|
+
obj = new ReactiveObject({
|
|
54
|
+
doubled: () => read(s) * 2
|
|
55
|
+
}) as any;
|
|
56
|
+
|
|
57
|
+
expect(obj.doubled).toBe(2);
|
|
58
|
+
|
|
59
|
+
write(s, 5);
|
|
60
|
+
await Promise.resolve();
|
|
61
|
+
|
|
62
|
+
expect(obj.doubled).toBe(10);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
describe('array properties', () => {
|
|
68
|
+
it('wraps arrays as ReactiveArray', () => {
|
|
69
|
+
let obj = new ReactiveObject({ items: [1, 2, 3] }) as any;
|
|
70
|
+
|
|
71
|
+
expect(obj.items).toBeInstanceOf(ReactiveArray);
|
|
72
|
+
expect(obj.items.length).toBe(3);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('reactive arrays track length', async () => {
|
|
76
|
+
let obj = new ReactiveObject({ items: [1, 2] }) as any,
|
|
77
|
+
lengths: number[] = [];
|
|
78
|
+
|
|
79
|
+
effect(() => {
|
|
80
|
+
lengths.push(obj.items.$length);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
obj.items.push(3);
|
|
84
|
+
await Promise.resolve();
|
|
85
|
+
|
|
86
|
+
expect(lengths).toEqual([2, 3]);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
describe('dispose', () => {
|
|
92
|
+
it('disposes all nested resources', () => {
|
|
93
|
+
let obj = new ReactiveObject({
|
|
94
|
+
count: 1,
|
|
95
|
+
items: [1, 2, 3]
|
|
96
|
+
}) as any;
|
|
97
|
+
|
|
98
|
+
// Should not throw
|
|
99
|
+
obj.dispose();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('computed stops updating after dispose', async () => {
|
|
103
|
+
let obj = new ReactiveObject({
|
|
104
|
+
count: 1,
|
|
105
|
+
doubled: () => (obj as any).count * 2
|
|
106
|
+
}) as any;
|
|
107
|
+
|
|
108
|
+
obj.dispose();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
describe('isReactiveObject', () => {
|
|
115
|
+
it('returns true for ReactiveObject', () => {
|
|
116
|
+
let obj = new ReactiveObject({ a: 1 });
|
|
117
|
+
|
|
118
|
+
expect(isReactiveObject(obj)).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('returns false for plain objects', () => {
|
|
122
|
+
expect(isReactiveObject({ a: 1 })).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('returns false for null/undefined', () => {
|
|
126
|
+
expect(isReactiveObject(null)).toBe(false);
|
|
127
|
+
expect(isReactiveObject(undefined)).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('returns false for primitives', () => {
|
|
131
|
+
expect(isReactiveObject(42)).toBe(false);
|
|
132
|
+
expect(isReactiveObject('str')).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
describe('reactive()', () => {
|
|
138
|
+
describe('objects', () => {
|
|
139
|
+
it('creates reactive object', () => {
|
|
140
|
+
let obj = reactive({ name: 'John', age: 25 });
|
|
141
|
+
|
|
142
|
+
expect((obj as any).name).toBe('John');
|
|
143
|
+
expect((obj as any).age).toBe(25);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('properties are reactive', async () => {
|
|
147
|
+
let obj = reactive({ count: 0 }) as any,
|
|
148
|
+
values: number[] = [];
|
|
149
|
+
|
|
150
|
+
effect(() => {
|
|
151
|
+
values.push(obj.count);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
obj.count = 10;
|
|
155
|
+
await Promise.resolve();
|
|
156
|
+
|
|
157
|
+
expect(values).toEqual([0, 10]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('has dispose method', () => {
|
|
161
|
+
let obj = reactive({ a: 1 }) as any;
|
|
162
|
+
|
|
163
|
+
expect(typeof obj.dispose).toBe('function');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
describe('arrays', () => {
|
|
169
|
+
it('creates reactive array', () => {
|
|
170
|
+
let arr = reactive([1, 2, 3]);
|
|
171
|
+
|
|
172
|
+
expect(arr.length).toBe(3);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
describe('errors', () => {
|
|
178
|
+
it('throws on invalid input', () => {
|
|
179
|
+
expect(() => reactive(42 as any)).toThrow();
|
|
180
|
+
expect(() => reactive('hello' as any)).toThrow();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
describe('integration', () => {
|
|
187
|
+
it('computed across objects', async () => {
|
|
188
|
+
let a = new ReactiveObject({ value: 10 }) as any,
|
|
189
|
+
b = new ReactiveObject({ value: 20 }) as any,
|
|
190
|
+
results: number[] = [];
|
|
191
|
+
|
|
192
|
+
effect(() => {
|
|
193
|
+
results.push(a.value + b.value);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
expect(results).toEqual([30]);
|
|
197
|
+
|
|
198
|
+
a.value = 100;
|
|
199
|
+
await Promise.resolve();
|
|
200
|
+
|
|
201
|
+
expect(results).toEqual([30, 120]);
|
|
202
|
+
|
|
203
|
+
b.value = 200;
|
|
204
|
+
await Promise.resolve();
|
|
205
|
+
|
|
206
|
+
expect(results).toEqual([30, 120, 300]);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('nested reactive objects', async () => {
|
|
210
|
+
let inner = new ReactiveObject({ x: 1 }) as any;
|
|
211
|
+
let outer = new ReactiveObject({ child: inner }) as any;
|
|
212
|
+
|
|
213
|
+
expect(outer.child).toBe(inner);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('effect with mixed signal and object dependencies', async () => {
|
|
217
|
+
let s = signal(1),
|
|
218
|
+
obj = new ReactiveObject({ count: 10 }) as any,
|
|
219
|
+
results: number[] = [];
|
|
220
|
+
|
|
221
|
+
effect(() => {
|
|
222
|
+
results.push(read(s) + obj.count);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
expect(results).toEqual([11]);
|
|
226
|
+
|
|
227
|
+
write(s, 2);
|
|
228
|
+
await Promise.resolve();
|
|
229
|
+
|
|
230
|
+
expect(results).toEqual([11, 12]);
|
|
231
|
+
|
|
232
|
+
obj.count = 20;
|
|
233
|
+
await Promise.resolve();
|
|
234
|
+
|
|
235
|
+
expect(results).toEqual([11, 12, 22]);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('diamond dependency with objects and computeds', async () => {
|
|
239
|
+
let base = signal(1),
|
|
240
|
+
left = computed(() => read(base) + 1),
|
|
241
|
+
right = computed(() => read(base) * 2),
|
|
242
|
+
results: number[] = [];
|
|
243
|
+
|
|
244
|
+
effect(() => {
|
|
245
|
+
results.push(read(left) + read(right));
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect(results).toEqual([4]);
|
|
249
|
+
|
|
250
|
+
write(base, 5);
|
|
251
|
+
await Promise.resolve();
|
|
252
|
+
|
|
253
|
+
expect(results).toEqual([4, 16]);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('large dependency chain', async () => {
|
|
257
|
+
let s = signal(0),
|
|
258
|
+
chain: ReturnType<typeof computed>[] = [computed(() => read(s))];
|
|
259
|
+
|
|
260
|
+
for (let i = 1; i < 50; i++) {
|
|
261
|
+
let prev = chain[i - 1];
|
|
262
|
+
chain.push(computed(() => read(prev) + 1));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
expect(read(chain[49])).toBe(49);
|
|
266
|
+
|
|
267
|
+
write(s, 10);
|
|
268
|
+
await Promise.resolve();
|
|
269
|
+
|
|
270
|
+
expect(read(chain[49])).toBe(59);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('many signals → one computed', async () => {
|
|
274
|
+
let signals: ReturnType<typeof signal<number>>[] = [];
|
|
275
|
+
|
|
276
|
+
for (let i = 0; i < 100; i++) {
|
|
277
|
+
signals.push(signal(i));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let sum = computed(() => {
|
|
281
|
+
let total = 0;
|
|
282
|
+
|
|
283
|
+
for (let i = 0, n = signals.length; i < n; i++) {
|
|
284
|
+
total += read(signals[i]);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return total;
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
expect(read(sum)).toBe(4950);
|
|
291
|
+
|
|
292
|
+
write(signals[0], 100);
|
|
293
|
+
await Promise.resolve();
|
|
294
|
+
|
|
295
|
+
expect(read(sum)).toBe(5050);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('effect with cleanup and re-subscribe', async () => {
|
|
299
|
+
let a = signal(1),
|
|
300
|
+
b = signal(100),
|
|
301
|
+
toggle = signal(true),
|
|
302
|
+
cleanups = 0,
|
|
303
|
+
values: number[] = [];
|
|
304
|
+
|
|
305
|
+
effect(() => {
|
|
306
|
+
onCleanup(() => { cleanups++; });
|
|
307
|
+
|
|
308
|
+
if (read(toggle)) {
|
|
309
|
+
values.push(read(a));
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
values.push(read(b));
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
expect(values).toEqual([1]);
|
|
317
|
+
|
|
318
|
+
write(toggle, false);
|
|
319
|
+
await Promise.resolve();
|
|
320
|
+
|
|
321
|
+
expect(values).toEqual([1, 100]);
|
|
322
|
+
expect(cleanups).toBe(1);
|
|
323
|
+
});
|
|
324
|
+
});
|