@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,317 @@
|
|
|
1
|
+
import { bench, describe } from 'vitest';
|
|
2
|
+
import { computed, dispose, effect, onCleanup, read, root, signal, write } from '~/system';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
describe('signal', () => {
|
|
6
|
+
bench('create signal', () => {
|
|
7
|
+
signal(0);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
bench('read signal (no observer)', () => {
|
|
11
|
+
let s = signal(0);
|
|
12
|
+
|
|
13
|
+
read(s);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
bench('write signal (no subscribers)', () => {
|
|
17
|
+
let s = signal(0);
|
|
18
|
+
|
|
19
|
+
write(s, 1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
bench('write signal (1 subscriber)', () => {
|
|
23
|
+
let s = signal(0),
|
|
24
|
+
i = 0;
|
|
25
|
+
|
|
26
|
+
effect(() => {
|
|
27
|
+
read(s);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
write(s, ++i);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
bench('write + read cycle', () => {
|
|
34
|
+
let s = signal(0);
|
|
35
|
+
|
|
36
|
+
write(s, 1);
|
|
37
|
+
|
|
38
|
+
read(s);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
describe('computed', () => {
|
|
44
|
+
bench('create computed', () => {
|
|
45
|
+
let s = signal(0);
|
|
46
|
+
|
|
47
|
+
computed(() => read(s));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
bench('read computed', () => {
|
|
51
|
+
let s = signal(0),
|
|
52
|
+
c = computed(() => read(s));
|
|
53
|
+
|
|
54
|
+
read(c);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
bench('computed chain (depth 5)', () => {
|
|
58
|
+
let s = signal(0),
|
|
59
|
+
c1 = computed(() => read(s) + 1),
|
|
60
|
+
c2 = computed(() => read(c1) + 1),
|
|
61
|
+
c3 = computed(() => read(c2) + 1),
|
|
62
|
+
c4 = computed(() => read(c3) + 1),
|
|
63
|
+
c5 = computed(() => read(c4) + 1);
|
|
64
|
+
|
|
65
|
+
read(c5);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
bench('computed diamond', () => {
|
|
69
|
+
let s = signal(0),
|
|
70
|
+
a = computed(() => read(s) + 1),
|
|
71
|
+
b = computed(() => read(s) * 2),
|
|
72
|
+
c = computed(() => read(a) + read(b));
|
|
73
|
+
|
|
74
|
+
read(c);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
describe('effect', () => {
|
|
80
|
+
bench('create + dispose effect', () => {
|
|
81
|
+
let stop = effect(() => {});
|
|
82
|
+
|
|
83
|
+
stop();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
bench('effect with 1 signal', () => {
|
|
87
|
+
let s = signal(0);
|
|
88
|
+
|
|
89
|
+
let stop = effect(() => {
|
|
90
|
+
read(s);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
stop();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
bench('effect with 10 signals', () => {
|
|
97
|
+
let signals: ReturnType<typeof signal<number>>[] = [];
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < 10; i++) {
|
|
100
|
+
signals.push(signal(i));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let stop = effect(() => {
|
|
104
|
+
for (let i = 0; i < 10; i++) {
|
|
105
|
+
read(signals[i]);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
stop();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
describe('propagation', () => {
|
|
115
|
+
bench('1 signal → 1 effect (sync write)', () => {
|
|
116
|
+
let s = signal(0),
|
|
117
|
+
i = 0;
|
|
118
|
+
|
|
119
|
+
effect(() => {
|
|
120
|
+
read(s);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
write(s, ++i);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
bench('1 signal → 10 effects (sync write)', () => {
|
|
127
|
+
let s = signal(0),
|
|
128
|
+
i = 0;
|
|
129
|
+
|
|
130
|
+
for (let j = 0; j < 10; j++) {
|
|
131
|
+
effect(() => {
|
|
132
|
+
read(s);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
write(s, ++i);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
bench('10 signals → 1 computed → 1 effect', () => {
|
|
140
|
+
let signals: ReturnType<typeof signal<number>>[] = [],
|
|
141
|
+
i = 0;
|
|
142
|
+
|
|
143
|
+
for (let j = 0; j < 10; j++) {
|
|
144
|
+
signals.push(signal(j));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let c = computed(() => {
|
|
148
|
+
let sum = 0;
|
|
149
|
+
|
|
150
|
+
for (let j = 0; j < 10; j++) {
|
|
151
|
+
sum += read(signals[j]);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return sum;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
effect(() => {
|
|
158
|
+
read(c);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
write(signals[0], ++i);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
bench('deep chain (10 computeds)', () => {
|
|
165
|
+
let s = signal(0),
|
|
166
|
+
chain: ReturnType<typeof computed>[] = [],
|
|
167
|
+
i = 0;
|
|
168
|
+
|
|
169
|
+
chain[0] = computed(() => read(s) + 1);
|
|
170
|
+
|
|
171
|
+
for (let j = 1; j < 10; j++) {
|
|
172
|
+
let prev = chain[j - 1];
|
|
173
|
+
|
|
174
|
+
chain[j] = computed(() => read(prev) + 1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
effect(() => {
|
|
178
|
+
read(chain[9]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
write(s, ++i);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
bench('wide fan-out (1 signal → 100 computeds)', () => {
|
|
185
|
+
let s = signal(0),
|
|
186
|
+
i = 0;
|
|
187
|
+
|
|
188
|
+
for (let j = 0; j < 100; j++) {
|
|
189
|
+
computed(() => read(s) + j);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
write(s, ++i);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
describe('memory', () => {
|
|
198
|
+
bench('create + dispose 100 computeds', () => {
|
|
199
|
+
let computeds: ReturnType<typeof computed<number>>[] = [];
|
|
200
|
+
|
|
201
|
+
for (let i = 0; i < 100; i++) {
|
|
202
|
+
computeds.push(computed(() => i));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (let i = 0; i < 100; i++) {
|
|
206
|
+
dispose(computeds[i]);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
bench('link pool (create + dispose cycle)', () => {
|
|
211
|
+
let s = signal(0);
|
|
212
|
+
|
|
213
|
+
for (let i = 0; i < 100; i++) {
|
|
214
|
+
let c = computed(() => read(s));
|
|
215
|
+
|
|
216
|
+
read(c);
|
|
217
|
+
dispose(c);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
bench('root scope create + dispose', () => {
|
|
222
|
+
root((dispose) => {
|
|
223
|
+
let s = signal(0);
|
|
224
|
+
|
|
225
|
+
computed(() => read(s));
|
|
226
|
+
|
|
227
|
+
dispose();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
describe('effect stress', () => {
|
|
234
|
+
bench('create + dispose 1000 effects (pool recycling)', () => {
|
|
235
|
+
for (let i = 0; i < 1000; i++) {
|
|
236
|
+
let stop = effect(() => {});
|
|
237
|
+
|
|
238
|
+
stop();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
describe('deep propagation', () => {
|
|
245
|
+
bench('deep chain (50 computeds)', () => {
|
|
246
|
+
let s = signal(0),
|
|
247
|
+
chain: ReturnType<typeof computed>[] = [],
|
|
248
|
+
i = 0;
|
|
249
|
+
|
|
250
|
+
chain[0] = computed(() => read(s) + 1);
|
|
251
|
+
|
|
252
|
+
for (let j = 1; j < 50; j++) {
|
|
253
|
+
let prev = chain[j - 1];
|
|
254
|
+
|
|
255
|
+
chain[j] = computed(() => read(prev) + 1);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
effect(() => {
|
|
259
|
+
read(chain[49]);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
write(s, ++i);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
describe('stabilization', () => {
|
|
268
|
+
bench('write during stabilization (reschedule path)', () => {
|
|
269
|
+
let a = signal(0),
|
|
270
|
+
b = signal(0),
|
|
271
|
+
i = 0;
|
|
272
|
+
|
|
273
|
+
effect(() => {
|
|
274
|
+
let val = read(a);
|
|
275
|
+
|
|
276
|
+
if (val > 0) {
|
|
277
|
+
write(b, val * 10);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
effect(() => {
|
|
282
|
+
read(b);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
write(a, ++i);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
describe('root', () => {
|
|
291
|
+
bench('root scope creation + disposal', () => {
|
|
292
|
+
root((dispose) => {
|
|
293
|
+
dispose();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
describe('onCleanup', () => {
|
|
300
|
+
bench('register 1 cleanup', () => {
|
|
301
|
+
root(() => {
|
|
302
|
+
effect(() => {
|
|
303
|
+
onCleanup(() => {});
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
bench('register 10 cleanups', () => {
|
|
309
|
+
root(() => {
|
|
310
|
+
effect(() => {
|
|
311
|
+
for (let i = 0; i < 10; i++) {
|
|
312
|
+
onCleanup(() => {});
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
});
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ts } from '@esportsplus/typescript';
|
|
3
|
+
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
4
|
+
import { NAMESPACE } from '~/compiler/constants';
|
|
5
|
+
import type { Bindings } from '~/compiler/types';
|
|
6
|
+
import array from '~/compiler/array';
|
|
7
|
+
import object from '~/compiler/object';
|
|
8
|
+
import primitives from '~/compiler/primitives';
|
|
9
|
+
import pipeline from '~/compiler/index';
|
|
10
|
+
import tscPlugin from '~/compiler/plugins/tsc';
|
|
11
|
+
import vitePlugin from '~/compiler/plugins/vite';
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
function applyIntents(code: string, sourceFile: ts.SourceFile, intents: ReplacementIntent[]): string {
|
|
15
|
+
let sorted = [...intents].sort((a, b) => b.node.getStart(sourceFile) - a.node.getStart(sourceFile));
|
|
16
|
+
|
|
17
|
+
for (let i = 0, n = sorted.length; i < n; i++) {
|
|
18
|
+
let intent = sorted[i],
|
|
19
|
+
end = intent.node.getEnd(),
|
|
20
|
+
start = intent.node.getStart(sourceFile);
|
|
21
|
+
|
|
22
|
+
code = code.slice(0, start) + intent.generate(sourceFile) + code.slice(end);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return code;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isReactiveCall(node: ts.Node): boolean {
|
|
29
|
+
return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'reactive';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parse(code: string): ts.SourceFile {
|
|
33
|
+
return ts.createSourceFile('test.ts', code, ts.ScriptTarget.Latest, true);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function transformPrimitives(code: string): { bindings: Bindings; output: string } {
|
|
37
|
+
let bindings: Bindings = new Map(),
|
|
38
|
+
sourceFile = parse(code),
|
|
39
|
+
intents = primitives(sourceFile, bindings, isReactiveCall);
|
|
40
|
+
|
|
41
|
+
return { bindings, output: applyIntents(code, sourceFile, intents) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function transformArray(code: string, bindings?: Bindings): { bindings: Bindings; output: string } {
|
|
45
|
+
let b: Bindings = bindings ?? new Map(),
|
|
46
|
+
sourceFile = parse(code),
|
|
47
|
+
intents = array(sourceFile, b, undefined);
|
|
48
|
+
|
|
49
|
+
return { bindings: b, output: applyIntents(code, sourceFile, intents) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function transformObject(code: string): { bindings: Bindings; output: string; prepend: string[] } {
|
|
53
|
+
let bindings: Bindings = new Map(),
|
|
54
|
+
sourceFile = parse(code),
|
|
55
|
+
result = object(sourceFile, bindings, undefined);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
bindings,
|
|
59
|
+
output: applyIntents(code, sourceFile, result.replacements),
|
|
60
|
+
prepend: result.prepend
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
describe('primitives transform', () => {
|
|
66
|
+
it('transforms reactive(0) to signal', () => {
|
|
67
|
+
let { output } = transformPrimitives('let x = reactive(0);');
|
|
68
|
+
|
|
69
|
+
expect(output).toContain(`${NAMESPACE}.signal(0)`);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('transforms reactive(() => expr) to computed', () => {
|
|
73
|
+
let { output } = transformPrimitives('let x = reactive(0); let d = reactive(() => x * 2);');
|
|
74
|
+
|
|
75
|
+
expect(output).toContain(`${NAMESPACE}.computed(() =>`);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('transforms reads to namespace read', () => {
|
|
79
|
+
let { output } = transformPrimitives('let x = reactive(0); console.log(x);');
|
|
80
|
+
|
|
81
|
+
expect(output).toContain(`${NAMESPACE}.read(x)`);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('transforms simple assignment to write', () => {
|
|
85
|
+
let { output } = transformPrimitives('let x = reactive(0); x = 5;');
|
|
86
|
+
|
|
87
|
+
expect(output).toContain(`${NAMESPACE}.write(x, 5)`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('transforms compound assignment += to write', () => {
|
|
91
|
+
let { output } = transformPrimitives('let x = reactive(0); x += 5;');
|
|
92
|
+
|
|
93
|
+
expect(output).toContain(`${NAMESPACE}.write(x, x.value + 5)`);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('transforms postfix x++ in statement to write', () => {
|
|
97
|
+
let { output } = transformPrimitives('let x = reactive(0); x++;');
|
|
98
|
+
|
|
99
|
+
expect(output).toContain(`${NAMESPACE}.write(x, x.value + 1)`);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('transforms prefix ++x in expression', () => {
|
|
103
|
+
let { output } = transformPrimitives('let x = reactive(0); let y = ++x;');
|
|
104
|
+
|
|
105
|
+
expect(output).toContain(`(${NAMESPACE}.write(x, x.value + 1), x.value)`);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('transforms postfix x++ in expression with temp variable', () => {
|
|
109
|
+
let { output } = transformPrimitives('let x = reactive(0); let y = x++;');
|
|
110
|
+
|
|
111
|
+
expect(output).toContain(`((_t0) => (${NAMESPACE}.write(x, _t0 + 1), _t0))(x.value)`);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('transforms reads in nested functions within scope', () => {
|
|
115
|
+
let { output } = transformPrimitives('let x = reactive(0); function fn() { return x; }');
|
|
116
|
+
|
|
117
|
+
// The x inside fn IS within the reactive binding scope, so it gets transformed
|
|
118
|
+
expect(output).toContain(`${NAMESPACE}.read(x)`);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('transforms dynamic expression to namespace reactive', () => {
|
|
122
|
+
let { output } = transformPrimitives('let x = reactive(someCall());');
|
|
123
|
+
|
|
124
|
+
expect(output).toContain(`${NAMESPACE}.reactive(someCall())`);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('tracks bindings for signal type', () => {
|
|
128
|
+
let { bindings } = transformPrimitives('let x = reactive(0);');
|
|
129
|
+
|
|
130
|
+
// TYPES.Signal = 3
|
|
131
|
+
expect(bindings.get('x')).toBe(3);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('tracks bindings for computed type', () => {
|
|
135
|
+
let { bindings } = transformPrimitives('let x = reactive(0); let d = reactive(() => x * 2);');
|
|
136
|
+
|
|
137
|
+
// TYPES.Computed = 1
|
|
138
|
+
expect(bindings.get('d')).toBe(1);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('transforms prefix --x in statement', () => {
|
|
142
|
+
let { output } = transformPrimitives('let x = reactive(0); --x;');
|
|
143
|
+
|
|
144
|
+
expect(output).toContain(`${NAMESPACE}.write(x, x.value - 1)`);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('transforms compound assignment -= to write', () => {
|
|
148
|
+
let { output } = transformPrimitives('let x = reactive(0); x -= 3;');
|
|
149
|
+
|
|
150
|
+
expect(output).toContain(`${NAMESPACE}.write(x, x.value - 3)`);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
describe('object transform', () => {
|
|
156
|
+
it('transforms reactive object with signal field', () => {
|
|
157
|
+
let { output, prepend } = transformObject('let obj = reactive({ count: 0 });');
|
|
158
|
+
|
|
159
|
+
expect(prepend.length).toBe(1);
|
|
160
|
+
expect(prepend[0]).toContain(`extends ${NAMESPACE}.ReactiveObject`);
|
|
161
|
+
expect(prepend[0]).toContain(`${NAMESPACE}.read(this.#count)`);
|
|
162
|
+
expect(prepend[0]).toContain(`${NAMESPACE}.write(this.#count`);
|
|
163
|
+
expect(output).toContain('new ');
|
|
164
|
+
expect(output).not.toContain('reactive(');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('transforms reactive object with array field', () => {
|
|
168
|
+
let { prepend } = transformObject('let obj = reactive({ items: [1, 2, 3] });');
|
|
169
|
+
|
|
170
|
+
expect(prepend.length).toBe(1);
|
|
171
|
+
expect(prepend[0]).toContain(`${NAMESPACE}.REACTIVE_ARRAY`);
|
|
172
|
+
expect(prepend[0]).toContain('get items()');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('transforms reactive object with computed field', () => {
|
|
176
|
+
let { prepend } = transformObject('let obj = reactive({ doubled: () => 2 });');
|
|
177
|
+
|
|
178
|
+
expect(prepend.length).toBe(1);
|
|
179
|
+
expect(prepend[0]).toContain(`${NAMESPACE}.COMPUTED`);
|
|
180
|
+
expect(prepend[0]).toContain(`${NAMESPACE}.read(this.#doubled)`);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('transforms reactive object with mixed properties', () => {
|
|
184
|
+
let { output, prepend } = transformObject(
|
|
185
|
+
'let obj = reactive({ count: 0, items: [1], doubled: () => 2 });'
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
expect(prepend.length).toBe(1);
|
|
189
|
+
expect(prepend[0]).toContain(`${NAMESPACE}.SIGNAL`);
|
|
190
|
+
expect(prepend[0]).toContain(`${NAMESPACE}.REACTIVE_ARRAY`);
|
|
191
|
+
expect(prepend[0]).toContain(`${NAMESPACE}.COMPUTED`);
|
|
192
|
+
expect(output).toContain('new ');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('does not transform object with spread assignment', () => {
|
|
196
|
+
let { output, prepend } = transformObject('let obj = reactive({ ...base, count: 0 });');
|
|
197
|
+
|
|
198
|
+
expect(prepend.length).toBe(0);
|
|
199
|
+
expect(output).toContain('reactive(');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('preserves type parameter', () => {
|
|
203
|
+
let { output } = transformObject('let obj = reactive<MyType>({ count: 0 });');
|
|
204
|
+
|
|
205
|
+
expect(output).toContain('<MyType>');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('tracks object binding', () => {
|
|
209
|
+
let { bindings } = transformObject('let obj = reactive({ count: 0 });');
|
|
210
|
+
|
|
211
|
+
// TYPES.Object = 2
|
|
212
|
+
expect(bindings.get('obj')).toBe(2);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('tracks nested array bindings', () => {
|
|
216
|
+
let { bindings } = transformObject('let obj = reactive({ items: [1, 2, 3] });');
|
|
217
|
+
|
|
218
|
+
// TYPES.Array = 0
|
|
219
|
+
expect(bindings.get('obj.items')).toBe(0);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
describe('array transform', () => {
|
|
225
|
+
it('transforms reactive([1,2,3]) to ReactiveArray', () => {
|
|
226
|
+
let { output } = transformArray('let arr = reactive([1, 2, 3]);');
|
|
227
|
+
|
|
228
|
+
expect(output).toContain(`new ${NAMESPACE}.ReactiveArray`);
|
|
229
|
+
expect(output).toContain('...[1, 2, 3]');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('transforms reactive([] as Type[]) to typed ReactiveArray', () => {
|
|
233
|
+
let { output } = transformArray('let arr = reactive([] as number[]);');
|
|
234
|
+
|
|
235
|
+
expect(output).toContain(`new ${NAMESPACE}.ReactiveArray<number>()`);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('transforms arr.length read to arr.$length', () => {
|
|
239
|
+
let bindings: Bindings = new Map();
|
|
240
|
+
|
|
241
|
+
bindings.set('arr', 0); // TYPES.Array = 0
|
|
242
|
+
let { output } = transformArray('let x = arr.length;', bindings);
|
|
243
|
+
|
|
244
|
+
expect(output).toContain('arr.$length');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('transforms arr.length = n to arr.$length = n', () => {
|
|
248
|
+
let bindings: Bindings = new Map();
|
|
249
|
+
|
|
250
|
+
bindings.set('arr', 0);
|
|
251
|
+
let { output } = transformArray('arr.length = 5;', bindings);
|
|
252
|
+
|
|
253
|
+
expect(output).toContain('arr.$length = 5');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('transforms arr.length += n to arr.$length = arr.length + n', () => {
|
|
257
|
+
let bindings: Bindings = new Map();
|
|
258
|
+
|
|
259
|
+
bindings.set('arr', 0);
|
|
260
|
+
let { output } = transformArray('arr.length += 3;', bindings);
|
|
261
|
+
|
|
262
|
+
expect(output).toContain('arr.$length = arr.length + 3');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('transforms arr[i] = value to arr.$set(i, value)', () => {
|
|
266
|
+
let bindings: Bindings = new Map();
|
|
267
|
+
|
|
268
|
+
bindings.set('arr', 0);
|
|
269
|
+
let { output } = transformArray('arr[0] = 42;', bindings);
|
|
270
|
+
|
|
271
|
+
expect(output).toContain('arr.$set(');
|
|
272
|
+
expect(output).toContain('42');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('tracks reactive array binding from reactive call', () => {
|
|
276
|
+
let { bindings } = transformArray('let arr = reactive([1, 2, 3]);');
|
|
277
|
+
|
|
278
|
+
// TYPES.Array = 0
|
|
279
|
+
expect(bindings.get('arr')).toBe(0);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('tracks alias binding from reactive array', () => {
|
|
283
|
+
let bindings: Bindings = new Map();
|
|
284
|
+
|
|
285
|
+
bindings.set('a', 0);
|
|
286
|
+
transformArray('let b = a;', bindings);
|
|
287
|
+
|
|
288
|
+
expect(bindings.get('b')).toBe(0);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('tracks typed parameter as ReactiveArray', () => {
|
|
292
|
+
let bindings: Bindings = new Map();
|
|
293
|
+
|
|
294
|
+
transformArray('function fn(arr: ReactiveArray) { return arr; }', bindings);
|
|
295
|
+
|
|
296
|
+
expect(bindings.get('arr')).toBe(0);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('transforms empty array', () => {
|
|
300
|
+
let { output } = transformArray('let arr = reactive([] as string[]);');
|
|
301
|
+
|
|
302
|
+
expect(output).toContain(`new ${NAMESPACE}.ReactiveArray<string>()`);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
describe('index transform', () => {
|
|
308
|
+
it('exports patterns array', () => {
|
|
309
|
+
expect(pipeline.patterns).toEqual(['reactive(', 'reactive<']);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('has transform function', () => {
|
|
313
|
+
expect(typeof pipeline.transform).toBe('function');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
describe('plugins', () => {
|
|
319
|
+
it('tsc plugin is defined', () => {
|
|
320
|
+
expect(tscPlugin).toBeDefined();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('vite plugin is defined', () => {
|
|
324
|
+
expect(vitePlugin).toBeDefined();
|
|
325
|
+
});
|
|
326
|
+
});
|