@esportsplus/reactivity 0.30.2 → 0.31.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/build/constants.d.ts +2 -1
- package/build/constants.js +2 -1
- package/build/reactive/array.js +24 -22
- package/build/reactive/object.js +10 -11
- package/build/system.d.ts +2 -1
- package/build/system.js +35 -23
- package/build/types.d.ts +2 -3
- package/package.json +3 -2
- package/src/constants.ts +3 -1
- package/src/reactive/array.ts +27 -26
- package/src/reactive/object.ts +17 -14
- package/src/system.ts +47 -31
- package/src/types.ts +2 -8
- package/tests/array.ts +906 -0
- package/tests/async-computed.ts +195 -0
- package/tests/bench/array.ts +227 -0
- package/tests/bench/reactive-object.ts +54 -0
- package/tests/bench/system.ts +317 -0
- package/tests/compiler.ts +326 -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 +534 -0
- package/tests/system.ts +1014 -0
- package/tests/tsconfig.json +17 -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,195 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { asyncComputed, effect, read, root, signal, write } from '~/system';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
describe('asyncComputed', () => {
|
|
6
|
+
it('initial value is undefined', () => {
|
|
7
|
+
root(() => {
|
|
8
|
+
let node = asyncComputed(() => Promise.resolve(42));
|
|
9
|
+
|
|
10
|
+
expect(read(node)).toBeUndefined();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('resolves to correct value', async () => {
|
|
15
|
+
let node!: ReturnType<typeof asyncComputed<number>>;
|
|
16
|
+
|
|
17
|
+
root(() => {
|
|
18
|
+
node = asyncComputed(() => Promise.resolve(42));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
22
|
+
|
|
23
|
+
expect(read(node)).toBe(42);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('updates when dependency changes', async () => {
|
|
27
|
+
let node!: ReturnType<typeof asyncComputed<string>>,
|
|
28
|
+
s = signal('hello');
|
|
29
|
+
|
|
30
|
+
root(() => {
|
|
31
|
+
node = asyncComputed(() => Promise.resolve(read(s)));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
35
|
+
|
|
36
|
+
expect(read(node)).toBe('hello');
|
|
37
|
+
|
|
38
|
+
write(s, 'world');
|
|
39
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
40
|
+
|
|
41
|
+
expect(read(node)).toBe('world');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('race condition — rapid changes, only latest promise writes', async () => {
|
|
45
|
+
let node!: ReturnType<typeof asyncComputed<number>>,
|
|
46
|
+
resolvers: ((v: number) => void)[] = [],
|
|
47
|
+
s = signal(1);
|
|
48
|
+
|
|
49
|
+
root(() => {
|
|
50
|
+
node = asyncComputed(() => {
|
|
51
|
+
read(s);
|
|
52
|
+
return new Promise<number>((resolve) => {
|
|
53
|
+
resolvers.push(resolve);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(read(node)).toBeUndefined();
|
|
59
|
+
|
|
60
|
+
write(s, 2);
|
|
61
|
+
await Promise.resolve();
|
|
62
|
+
await Promise.resolve();
|
|
63
|
+
|
|
64
|
+
write(s, 3);
|
|
65
|
+
await Promise.resolve();
|
|
66
|
+
await Promise.resolve();
|
|
67
|
+
|
|
68
|
+
// Resolve first (stale)
|
|
69
|
+
resolvers[0](100);
|
|
70
|
+
await Promise.resolve();
|
|
71
|
+
|
|
72
|
+
expect(read(node)).toBeUndefined();
|
|
73
|
+
|
|
74
|
+
// Resolve second (stale)
|
|
75
|
+
resolvers[1](200);
|
|
76
|
+
await Promise.resolve();
|
|
77
|
+
|
|
78
|
+
expect(read(node)).toBeUndefined();
|
|
79
|
+
|
|
80
|
+
// Resolve latest
|
|
81
|
+
resolvers[2](300);
|
|
82
|
+
await Promise.resolve();
|
|
83
|
+
|
|
84
|
+
expect(read(node)).toBe(300);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('onCleanup works for abort controller', async () => {
|
|
88
|
+
let aborted = false,
|
|
89
|
+
s = signal(1);
|
|
90
|
+
|
|
91
|
+
root(() => {
|
|
92
|
+
asyncComputed((onCleanup) => {
|
|
93
|
+
let controller = new AbortController();
|
|
94
|
+
|
|
95
|
+
controller.signal.addEventListener('abort', () => {
|
|
96
|
+
aborted = true;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
onCleanup(() => controller.abort());
|
|
100
|
+
|
|
101
|
+
return Promise.resolve(read(s));
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
106
|
+
|
|
107
|
+
expect(aborted).toBe(false);
|
|
108
|
+
|
|
109
|
+
write(s, 2);
|
|
110
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
111
|
+
|
|
112
|
+
expect(aborted).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('effect tracks async computed', async () => {
|
|
116
|
+
let node!: ReturnType<typeof asyncComputed<number>>,
|
|
117
|
+
s = signal(10),
|
|
118
|
+
values: (number | undefined)[] = [];
|
|
119
|
+
|
|
120
|
+
root(() => {
|
|
121
|
+
node = asyncComputed(() => Promise.resolve(read(s)));
|
|
122
|
+
|
|
123
|
+
effect(() => {
|
|
124
|
+
values.push(read(node));
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(values).toEqual([undefined]);
|
|
129
|
+
|
|
130
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
131
|
+
|
|
132
|
+
expect(values).toEqual([undefined, 10]);
|
|
133
|
+
|
|
134
|
+
write(s, 20);
|
|
135
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
136
|
+
|
|
137
|
+
expect(values).toEqual([undefined, 10, 20]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('dispose stops updates', async () => {
|
|
141
|
+
let node!: ReturnType<typeof asyncComputed<number>>,
|
|
142
|
+
s = signal(1);
|
|
143
|
+
|
|
144
|
+
let disposeRoot!: VoidFunction;
|
|
145
|
+
|
|
146
|
+
root((dispose) => {
|
|
147
|
+
disposeRoot = dispose;
|
|
148
|
+
node = asyncComputed(() => Promise.resolve(read(s)));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
152
|
+
|
|
153
|
+
expect(read(node)).toBe(1);
|
|
154
|
+
|
|
155
|
+
disposeRoot();
|
|
156
|
+
|
|
157
|
+
write(s, 2);
|
|
158
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
159
|
+
|
|
160
|
+
expect(read(node)).toBe(1);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('rejected promise does not crash and retains previous value', async () => {
|
|
164
|
+
let node!: ReturnType<typeof asyncComputed<number>>,
|
|
165
|
+
s = signal(1);
|
|
166
|
+
|
|
167
|
+
root(() => {
|
|
168
|
+
node = asyncComputed(() => {
|
|
169
|
+
let v = read(s);
|
|
170
|
+
|
|
171
|
+
if (v === 2) {
|
|
172
|
+
return Promise.reject(new Error('fail'));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return Promise.resolve(v);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
180
|
+
|
|
181
|
+
expect(read(node)).toBe(1);
|
|
182
|
+
|
|
183
|
+
write(s, 2);
|
|
184
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
185
|
+
|
|
186
|
+
// Value should remain 1 after rejection
|
|
187
|
+
expect(read(node)).toBe(1);
|
|
188
|
+
|
|
189
|
+
write(s, 3);
|
|
190
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
191
|
+
|
|
192
|
+
// Resumes after non-rejected promise
|
|
193
|
+
expect(read(node)).toBe(3);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { bench, describe } from 'vitest';
|
|
2
|
+
import { ReactiveArray } from '~/reactive/array';
|
|
3
|
+
import { effect } from '~/system';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
describe('ReactiveArray creation', () => {
|
|
7
|
+
bench('create empty', () => {
|
|
8
|
+
new ReactiveArray<number>();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
bench('create with 10 items', () => {
|
|
12
|
+
new ReactiveArray(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
bench('create with 100 items', () => {
|
|
16
|
+
let items = [];
|
|
17
|
+
|
|
18
|
+
for (let i = 0; i < 100; i++) {
|
|
19
|
+
items.push(i);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
new ReactiveArray(...items);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
describe('ReactiveArray push', () => {
|
|
28
|
+
bench('push 1 item', () => {
|
|
29
|
+
let arr = new ReactiveArray<number>();
|
|
30
|
+
|
|
31
|
+
arr.push(1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
bench('push 10 items (single call)', () => {
|
|
35
|
+
let arr = new ReactiveArray<number>();
|
|
36
|
+
|
|
37
|
+
arr.push(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
bench('push 10 items (10 calls)', () => {
|
|
41
|
+
let arr = new ReactiveArray<number>();
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < 10; i++) {
|
|
44
|
+
arr.push(i);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
bench('push with listener', () => {
|
|
49
|
+
let arr = new ReactiveArray<number>();
|
|
50
|
+
|
|
51
|
+
arr.on('push', () => {});
|
|
52
|
+
arr.push(1);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
describe('ReactiveArray pop', () => {
|
|
58
|
+
bench('pop', () => {
|
|
59
|
+
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
60
|
+
|
|
61
|
+
arr.pop();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
describe('ReactiveArray splice', () => {
|
|
67
|
+
bench('splice remove 1', () => {
|
|
68
|
+
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
69
|
+
|
|
70
|
+
arr.splice(2, 1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
bench('splice insert 1', () => {
|
|
74
|
+
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
75
|
+
|
|
76
|
+
arr.splice(2, 0, 99);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
bench('splice replace 1', () => {
|
|
80
|
+
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
81
|
+
|
|
82
|
+
arr.splice(2, 1, 99);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
describe('ReactiveArray sort', () => {
|
|
88
|
+
bench('sort 10 items', () => {
|
|
89
|
+
let arr = new ReactiveArray(5, 3, 8, 1, 9, 2, 7, 4, 6, 10);
|
|
90
|
+
|
|
91
|
+
arr.sort((a, b) => a - b);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
bench('sort 100 items', () => {
|
|
95
|
+
let items = [];
|
|
96
|
+
|
|
97
|
+
for (let i = 100; i > 0; i--) {
|
|
98
|
+
items.push(i);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let arr = new ReactiveArray(...items);
|
|
102
|
+
|
|
103
|
+
arr.sort((a, b) => a - b);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
describe('ReactiveArray $set', () => {
|
|
109
|
+
bench('$set', () => {
|
|
110
|
+
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
111
|
+
|
|
112
|
+
arr.$set(2, 99);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
bench('$set same value (no-op)', () => {
|
|
116
|
+
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
117
|
+
|
|
118
|
+
arr.$set(2, 3);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
describe('ReactiveArray events', () => {
|
|
124
|
+
bench('dispatch to 1 listener', () => {
|
|
125
|
+
let arr = new ReactiveArray<number>();
|
|
126
|
+
|
|
127
|
+
arr.on('push', () => {});
|
|
128
|
+
arr.push(1);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
bench('dispatch to 10 listeners', () => {
|
|
132
|
+
let arr = new ReactiveArray<number>();
|
|
133
|
+
|
|
134
|
+
for (let i = 0; i < 10; i++) {
|
|
135
|
+
arr.on('push', () => {});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
arr.push(1);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
bench('on + once interleaved', () => {
|
|
142
|
+
let arr = new ReactiveArray<number>();
|
|
143
|
+
|
|
144
|
+
arr.on('push', () => {});
|
|
145
|
+
arr.once('push', () => {});
|
|
146
|
+
arr.on('push', () => {});
|
|
147
|
+
arr.push(1);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
describe('ReactiveArray dispose/clear', () => {
|
|
153
|
+
bench('dispose with 100 items', () => {
|
|
154
|
+
let items = [];
|
|
155
|
+
|
|
156
|
+
for (let i = 0; i < 100; i++) {
|
|
157
|
+
items.push(i);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let arr = new ReactiveArray(...items);
|
|
161
|
+
|
|
162
|
+
arr.dispose();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
bench('clear with 100 items', () => {
|
|
166
|
+
let items = [];
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < 100; i++) {
|
|
169
|
+
items.push(i);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let arr = new ReactiveArray(...items);
|
|
173
|
+
|
|
174
|
+
arr.clear();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
describe('ReactiveArray concat/unshift/shift/reverse', () => {
|
|
180
|
+
bench('concat 100 items', () => {
|
|
181
|
+
let arr = new ReactiveArray<number>(),
|
|
182
|
+
items = [];
|
|
183
|
+
|
|
184
|
+
for (let i = 0; i < 100; i++) {
|
|
185
|
+
items.push(i);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
arr.concat(items);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
bench('unshift 10 items', () => {
|
|
192
|
+
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
193
|
+
|
|
194
|
+
arr.unshift(10, 20, 30, 40, 50, 60, 70, 80, 90, 100);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
bench('shift', () => {
|
|
198
|
+
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
199
|
+
|
|
200
|
+
arr.shift();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
bench('reverse 100 items', () => {
|
|
204
|
+
let items = [];
|
|
205
|
+
|
|
206
|
+
for (let i = 0; i < 100; i++) {
|
|
207
|
+
items.push(i);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let arr = new ReactiveArray(...items);
|
|
211
|
+
|
|
212
|
+
arr.reverse();
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
describe('ReactiveArray reactive length', () => {
|
|
218
|
+
bench('read $length in effect', () => {
|
|
219
|
+
let arr = new ReactiveArray(1, 2, 3);
|
|
220
|
+
|
|
221
|
+
let stop = effect(() => {
|
|
222
|
+
arr.$length;
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
stop();
|
|
226
|
+
});
|
|
227
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { bench, describe } from 'vitest';
|
|
2
|
+
import { computed, dispose, effect, read, root, signal, write } from '~/system';
|
|
3
|
+
import { ReactiveArray } from '~/reactive/array';
|
|
4
|
+
import { ReactiveObject } from '~/reactive/object';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
describe('ReactiveObject creation', () => {
|
|
8
|
+
bench('create with 5 signal properties', () => {
|
|
9
|
+
new ReactiveObject({ a: 1, b: 2, c: 3, d: 4, e: 5 });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
bench('create with computed properties', () => {
|
|
13
|
+
new ReactiveObject({
|
|
14
|
+
a: 1,
|
|
15
|
+
b: 2,
|
|
16
|
+
sum: () => 0
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
describe('ReactiveObject read/write', () => {
|
|
23
|
+
bench('read property (signal-backed)', () => {
|
|
24
|
+
let obj = new ReactiveObject({ a: 1, b: 2, c: 3, d: 4, e: 5 });
|
|
25
|
+
|
|
26
|
+
(obj as any).a;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
bench('write property (signal-backed)', () => {
|
|
30
|
+
let obj = new ReactiveObject({ a: 1, b: 2, c: 3, d: 4, e: 5 }),
|
|
31
|
+
i = 0;
|
|
32
|
+
|
|
33
|
+
(obj as any).a = ++i;
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
describe('ReactiveObject dispose', () => {
|
|
39
|
+
bench('dispose with 5 properties', () => {
|
|
40
|
+
let obj = new ReactiveObject({ a: 1, b: 2, c: 3, d: 4, e: 5 });
|
|
41
|
+
|
|
42
|
+
obj.dispose();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
bench('dispose with arrays + computeds', () => {
|
|
46
|
+
let obj = new ReactiveObject({
|
|
47
|
+
items: [1, 2, 3],
|
|
48
|
+
name: 'test',
|
|
49
|
+
total: () => 0
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
obj.dispose();
|
|
53
|
+
});
|
|
54
|
+
});
|