@esportsplus/reactivity 0.37.2 → 0.38.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/build/compiler/array.d.ts +8 -1
- package/build/compiler/array.js +111 -93
- package/build/compiler/bindings.d.ts +18 -12
- package/build/compiler/bindings.js +172 -52
- package/build/compiler/constants.d.ts +1 -0
- package/build/compiler/constants.js +1 -0
- package/build/compiler/index.d.ts +11 -3
- package/build/compiler/index.js +96 -34
- package/build/compiler/object.js +11 -14
- package/build/compiler/plugins/vite.d.ts +14 -0
- package/build/compiler/primitives.d.ts +6 -0
- package/build/compiler/primitives.js +99 -67
- package/build/compiler/types.d.ts +5 -4
- package/build/types.d.ts +1 -1
- package/package.json +2 -2
- package/pnpm-workspace.yaml +1 -1
- package/src/compiler/array.ts +166 -120
- package/src/compiler/bindings.ts +259 -56
- package/src/compiler/constants.ts +1 -0
- package/src/compiler/index.ts +142 -46
- package/src/compiler/object.ts +17 -17
- package/src/compiler/primitives.ts +139 -88
- package/src/compiler/types.ts +7 -4
- package/src/types.ts +2 -1
- package/test/compiler/compiler.test.ts +166 -48
- package/test/compiler/coverage.test.ts +164 -0
|
@@ -27,43 +27,47 @@ function applyIntents(code: string, sourceFile: ts.SourceFile, intents: Replacem
|
|
|
27
27
|
return code;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
// The unit transforms run on real modules: `reactive` is resolved through the import like any consumer's
|
|
31
|
+
const PRELUDE = "import { reactive, ReactiveArray } from '@esportsplus/reactivity';\n";
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
function parse(code: string): { bindings: Bindings; code: string; isReactiveCall: (node: ts.Node) => node is ts.CallExpression; sourceFile: ts.SourceFile } {
|
|
35
|
+
code = PRELUDE + code;
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
let { checker, sourceFile } = languageService.scratch(process.cwd() + '/test.ts', code);
|
|
37
|
+
let { checker, program, sourceFile } = languageService.scratch(process.cwd() + '/test.ts', code),
|
|
38
|
+
bindings = scope.create(checker, program, sourceFile);
|
|
37
39
|
|
|
38
|
-
return { bindings: scope.
|
|
40
|
+
return { bindings, code, isReactiveCall: (node): node is ts.CallExpression => scope.isReactiveCall(bindings, node), sourceFile };
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
// Full pipeline over a module importing reactive(): what a real consumer compiles
|
|
42
44
|
function transformModule(code: string): string {
|
|
43
|
-
|
|
45
|
+
return transformSource(`import { reactive } from '@esportsplus/reactivity';\n` + code);
|
|
46
|
+
}
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
function transformSource(code: string): string {
|
|
49
|
+
let { checker, program, sourceFile } = languageService.scratch(process.cwd() + '/module.ts', code),
|
|
50
|
+
result = pipeline.transform({ checker, code, program, sourceFile } as never);
|
|
47
51
|
|
|
48
52
|
return applyIntents(code, sourceFile, result.replacements ?? []);
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
function transformPrimitives(
|
|
52
|
-
let { bindings, sourceFile } = parse(
|
|
53
|
-
{ replacements } = primitives(sourceFile, bindings, isReactiveCall);
|
|
55
|
+
function transformPrimitives(source: string): { bindings: Bindings; failures: string[]; output: string } {
|
|
56
|
+
let { bindings, code, isReactiveCall, sourceFile } = parse(source),
|
|
57
|
+
{ failures, replacements } = primitives(sourceFile, bindings, isReactiveCall);
|
|
54
58
|
|
|
55
|
-
return { bindings, output: applyIntents(code, sourceFile, replacements) };
|
|
59
|
+
return { bindings, failures: failures.map(f => f.message), output: applyIntents(code, sourceFile, replacements) };
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
function transformArray(
|
|
59
|
-
let { bindings, sourceFile } = parse(
|
|
60
|
-
|
|
62
|
+
function transformArray(source: string): { bindings: Bindings; output: string } {
|
|
63
|
+
let { bindings, code, isReactiveCall, sourceFile } = parse(source),
|
|
64
|
+
{ replacements } = array(sourceFile, bindings, isReactiveCall);
|
|
61
65
|
|
|
62
|
-
return { bindings, output: applyIntents(code, sourceFile,
|
|
66
|
+
return { bindings, output: applyIntents(code, sourceFile, replacements) };
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
function transformObject(
|
|
66
|
-
let { bindings, sourceFile } = parse(
|
|
69
|
+
function transformObject(source: string): { bindings: Bindings; output: string; prepend: string[] } {
|
|
70
|
+
let { bindings, code, isReactiveCall, sourceFile } = parse(source),
|
|
67
71
|
result = object(sourceFile, bindings, isReactiveCall);
|
|
68
72
|
|
|
69
73
|
return {
|
|
@@ -73,24 +77,20 @@ function transformObject(code: string): { bindings: Bindings; output: string; pr
|
|
|
73
77
|
};
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// Kind of the binding the variable `name` declares
|
|
81
|
+
function typeOf(bindings: Bindings, name: string): number | null {
|
|
82
|
+
let found: ts.Identifier | undefined,
|
|
83
|
+
visit = (node: ts.Node): void => {
|
|
84
|
+
if (!found && ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === name) {
|
|
85
|
+
found = node.name;
|
|
86
|
+
}
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
}
|
|
88
|
+
node.forEachChild(visit);
|
|
89
|
+
};
|
|
85
90
|
|
|
86
|
-
|
|
87
|
-
for (let [symbol, paths] of bindings.paths) {
|
|
88
|
-
if (symbol.name === name && paths.has(path)) {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
91
|
+
visit(bindings.sourceFile);
|
|
92
92
|
|
|
93
|
-
return
|
|
93
|
+
return found ? scope.kind(bindings, scope.origin(bindings, found)) : null;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
|
|
@@ -150,12 +150,63 @@ describe('primitives transform', () => {
|
|
|
150
150
|
expect(output).toContain(`${NAMESPACE}.read(x)`);
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
it('
|
|
154
|
-
let { output } = transformPrimitives('
|
|
153
|
+
it('builds a non-literal array or object argument with the runtime reactive()', () => {
|
|
154
|
+
let { output } = transformPrimitives('declare function someCall(): number[];\nlet x = reactive(someCall());');
|
|
155
155
|
|
|
156
156
|
expect(output).toContain(`${NAMESPACE}.reactive(someCall())`);
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
+
it('classifies a non-literal primitive argument as a signal by its type', () => {
|
|
160
|
+
let { output } = transformPrimitives('declare let n: number;\nlet x = reactive(n);\nlet y = x + 1;');
|
|
161
|
+
|
|
162
|
+
expect(output).toContain(`let x = ${NAMESPACE}.signal(n)`);
|
|
163
|
+
expect(output).toContain(`let y = ${NAMESPACE}.read(x) + 1`);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('classifies a function-typed argument as a computed', () => {
|
|
167
|
+
let { output } = transformPrimitives('declare function total(): number;\nlet x = reactive(total);');
|
|
168
|
+
|
|
169
|
+
expect(output).toContain(`let x = ${NAMESPACE}.computed(total)`);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('rejects an argument whose kind is not provable', () => {
|
|
173
|
+
let { failures } = transformPrimitives('declare let value: any;\nlet x = reactive(value);');
|
|
174
|
+
|
|
175
|
+
expect(failures).toHaveLength(1);
|
|
176
|
+
expect(failures[0]).toContain('provable');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('rejects a signal that does not initialize a variable', () => {
|
|
180
|
+
let { failures } = transformPrimitives('declare function take(value: unknown): void;\ntake(reactive(0));\nlet o = { count: reactive(1) };');
|
|
181
|
+
|
|
182
|
+
expect(failures).toHaveLength(2);
|
|
183
|
+
expect(failures[0]).toContain('must initialize a variable');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('rejects a write to a computed', () => {
|
|
187
|
+
let { failures } = transformPrimitives('let d = reactive(() => 1);\nd = 2;');
|
|
188
|
+
|
|
189
|
+
expect(failures).toEqual(['a reactive computed is read-only']);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('rejects a destructuring assignment into a reactive binding', () => {
|
|
193
|
+
let { failures } = transformPrimitives('let x = reactive(0);\n[x] = [1];');
|
|
194
|
+
|
|
195
|
+
expect(failures[0]).toContain('destructuring');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('reads a binding assigned with reactive() after its declaration', () => {
|
|
199
|
+
let { output } = transformPrimitives('let x: number;\nx = reactive(0);\nlet y = x + 1;');
|
|
200
|
+
|
|
201
|
+
expect(output).toContain(`let y = ${NAMESPACE}.read(x) + 1`);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('reads through typeof', () => {
|
|
205
|
+
let { output } = transformPrimitives("let x = reactive(0);\nlet t = typeof x;");
|
|
206
|
+
|
|
207
|
+
expect(output).toContain(`let t = typeof ${NAMESPACE}.read(x)`);
|
|
208
|
+
});
|
|
209
|
+
|
|
159
210
|
it('tracks bindings for signal type', () => {
|
|
160
211
|
let { bindings } = transformPrimitives('let x = reactive(0);');
|
|
161
212
|
|
|
@@ -250,10 +301,16 @@ describe('object transform', () => {
|
|
|
250
301
|
expect(output).toContain('<MyType>');
|
|
251
302
|
});
|
|
252
303
|
|
|
253
|
-
it('
|
|
254
|
-
let {
|
|
304
|
+
it('passes every array property to the constructor, static elements included', () => {
|
|
305
|
+
let { output } = transformObject('let obj = reactive({ n: 1, items: [1, 2] });');
|
|
255
306
|
|
|
256
|
-
expect(
|
|
307
|
+
expect(output).toMatch(/new ReactiveObject_\w+\(\[1, 2\]\)/);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('makes a non-literal array property a reactive array', () => {
|
|
311
|
+
let { prepend } = transformObject('declare let list: number[];\nlet obj = reactive({ items: list });');
|
|
312
|
+
|
|
313
|
+
expect(prepend[0]).toContain(`${NAMESPACE}.REACTIVE_ARRAY`);
|
|
257
314
|
});
|
|
258
315
|
});
|
|
259
316
|
|
|
@@ -290,6 +347,20 @@ describe('array transform', () => {
|
|
|
290
347
|
expect(output).toContain('arr.$length = arr.length + 3');
|
|
291
348
|
});
|
|
292
349
|
|
|
350
|
+
it('transforms arr.length++ and --arr.length to $length writes', () => {
|
|
351
|
+
expect(transformArray('let arr = reactive([1]); arr.length++;').output).toContain('arr.$length = arr.length + 1');
|
|
352
|
+
expect(transformArray('let arr = reactive([1]); --arr.length;').output).toContain('arr.$length = arr.length - 1');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('transforms non-mutating unary operators on arr.length as reads', () => {
|
|
356
|
+
for (let op of ['!', '-', '+', '~']) {
|
|
357
|
+
let { output } = transformArray(`let arr = reactive([1]); let x = ${op}arr.length;`);
|
|
358
|
+
|
|
359
|
+
expect(output).toContain(`${op}arr.$length`);
|
|
360
|
+
expect(output).not.toContain('arr.$length =');
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
293
364
|
it('transforms every compound operator on arr.length with its own token', () => {
|
|
294
365
|
for (let op of ['<<', '>>', '>>>', '??', '||', '&&', '-', '*', '/', '%', '**', '&', '|', '^']) {
|
|
295
366
|
let { output } = transformArray(`let arr = reactive([1]); arr.length ${op}= 2;`);
|
|
@@ -311,16 +382,36 @@ describe('array transform', () => {
|
|
|
311
382
|
expect(typeOf(bindings, 'arr')).toBe(TYPES.Array);
|
|
312
383
|
});
|
|
313
384
|
|
|
314
|
-
it('
|
|
315
|
-
let {
|
|
385
|
+
it('compiles an alias of a reactive array by its type', () => {
|
|
386
|
+
let { output } = transformArray('let a = reactive([1]); let b = a; let n = b.length;');
|
|
316
387
|
|
|
317
|
-
expect(
|
|
388
|
+
expect(output).toContain('let n = b.$length');
|
|
318
389
|
});
|
|
319
390
|
|
|
320
|
-
it('
|
|
321
|
-
let {
|
|
391
|
+
it('compiles a ReactiveArray-typed parameter by its type', () => {
|
|
392
|
+
let { output } = transformArray('function fn(arr: ReactiveArray<number>) { arr[0] = 1; return arr.length; }');
|
|
322
393
|
|
|
323
|
-
expect(
|
|
394
|
+
expect(output).toContain('arr.$set(0, 1)');
|
|
395
|
+
expect(output).toContain('return arr.$length');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('compiles an array property of a reactive object by its type', () => {
|
|
399
|
+
let { output } = transformArray('let obj = reactive({ items: [1] }); let n = obj.items.length;');
|
|
400
|
+
|
|
401
|
+
expect(output).toContain('let n = obj.items.$length');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('routes compound and increment element writes through $set', () => {
|
|
405
|
+
let { output } = transformArray('let arr = reactive([1]); arr[0] += 2; arr[0]++;');
|
|
406
|
+
|
|
407
|
+
expect(output).toContain('((_a, _k) => _a.$set(_k, _a[_k] + (2)))(arr, 0)');
|
|
408
|
+
expect(output).toContain('((_a, _k, _v) => (_a.$set(_k, _v + 1), _v))(arr, 0, arr[0])');
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('leaves a plain array untouched', () => {
|
|
412
|
+
let { output } = transformArray('let arr = [1]; arr[0] = 2; let n = arr.length;');
|
|
413
|
+
|
|
414
|
+
expect(output).toContain('arr[0] = 2; let n = arr.length;');
|
|
324
415
|
});
|
|
325
416
|
|
|
326
417
|
it('transforms empty array', () => {
|
|
@@ -388,12 +479,39 @@ describe('binding resolution (full pipeline)', () => {
|
|
|
388
479
|
expect(output).toContain(`let count = ${NAMESPACE}.signal(0);`);
|
|
389
480
|
expect(output).toContain(`let next = ${NAMESPACE}.read(count) + 1;`);
|
|
390
481
|
});
|
|
482
|
+
|
|
483
|
+
it('transforms calls through an aliased import', () => {
|
|
484
|
+
let output = transformSource("import { reactive as r } from '@esportsplus/reactivity';\nlet count = r(0);\nlet next = count + 1;");
|
|
485
|
+
|
|
486
|
+
expect(output).toContain(`let count = ${NAMESPACE}.signal(0);`);
|
|
487
|
+
expect(output).toContain(`let next = ${NAMESPACE}.read(count) + 1;`);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('transforms a reactive array created through an aliased import', () => {
|
|
491
|
+
let output = transformSource("import { reactive as r } from '@esportsplus/reactivity';\nlet list = r([1, 2]);\nlet size = list.length;");
|
|
492
|
+
|
|
493
|
+
expect(output).not.toContain('r([1, 2])');
|
|
494
|
+
expect(output).toContain('let size = list.$length');
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('leaves a parameter that shadows an alias untouched', () => {
|
|
498
|
+
let code = "import { reactive as r } from '@esportsplus/reactivity';\nfunction f(r: (n: number) => number) { return r(1); }";
|
|
499
|
+
|
|
500
|
+
expect(transformSource(code)).toBe(code);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('leaves another export imported under the name reactive untouched', () => {
|
|
504
|
+
let code = "import { root as reactive } from '@esportsplus/reactivity';\nlet value = reactive(() => 1);";
|
|
505
|
+
|
|
506
|
+
expect(transformSource(code)).toBe(code);
|
|
507
|
+
});
|
|
391
508
|
});
|
|
392
509
|
|
|
393
510
|
|
|
394
511
|
describe('index transform', () => {
|
|
395
|
-
|
|
396
|
-
|
|
512
|
+
// A module can reach reactive() or read a binding under any name, so no text prefilter may skip it
|
|
513
|
+
it('has no text patterns', () => {
|
|
514
|
+
expect('patterns' in pipeline).toBe(false);
|
|
397
515
|
});
|
|
398
516
|
|
|
399
517
|
it('has transform function', () => {
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { NAMESPACE } from '~/compiler/constants';
|
|
5
|
+
import vite from '~/compiler/plugins/vite';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// Every route by which a module reaches `reactive`, or a binding it created, must compile
|
|
9
|
+
const FILES: Record<string, string> = {
|
|
10
|
+
'state.ts': [
|
|
11
|
+
"import { reactive } from '@esportsplus/reactivity';",
|
|
12
|
+
'export let count = reactive(0);',
|
|
13
|
+
'export let list = reactive([1, 2]);',
|
|
14
|
+
'export let store = reactive({ n: 1, items: [1] });',
|
|
15
|
+
'export default reactive(0);'
|
|
16
|
+
].join('\n'),
|
|
17
|
+
'barrel.ts': [
|
|
18
|
+
"export { reactive as make } from '@esportsplus/reactivity';",
|
|
19
|
+
"export * as R from '@esportsplus/reactivity';",
|
|
20
|
+
"export * from './state';"
|
|
21
|
+
].join('\n'),
|
|
22
|
+
'reads.ts': [
|
|
23
|
+
"import { count, list, store } from './state';",
|
|
24
|
+
"import * as S from './state';",
|
|
25
|
+
"import fallback from './state';",
|
|
26
|
+
"import { count as renamed } from './barrel';",
|
|
27
|
+
'export let a = () => count + 1;',
|
|
28
|
+
'export let b = () => S.count + 1;',
|
|
29
|
+
'export let c = () => fallback + 1;',
|
|
30
|
+
'export let d = () => renamed + 1;',
|
|
31
|
+
'export let e = () => list.length;',
|
|
32
|
+
'export let f = () => { list[0] = 5; };',
|
|
33
|
+
'export let g = () => store.items.length;',
|
|
34
|
+
"export let h = () => S['count'] + 1;"
|
|
35
|
+
].join('\n'),
|
|
36
|
+
'entries.ts': [
|
|
37
|
+
"import { make, R } from './barrel';",
|
|
38
|
+
"import * as B from './barrel';",
|
|
39
|
+
'export let a = make(0);',
|
|
40
|
+
'export let b = R.reactive(0);',
|
|
41
|
+
'export let c = B.R.reactive(0);',
|
|
42
|
+
"export let d = R['reactive'](0);",
|
|
43
|
+
'const alias = make;',
|
|
44
|
+
'export let e = alias(0);',
|
|
45
|
+
'const { reactive: destructured } = R;',
|
|
46
|
+
'export let f = destructured(0);',
|
|
47
|
+
'export let g = () => a + b + c + d + e + f;'
|
|
48
|
+
].join('\n')
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
let fixtures: string[] = [];
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
function project(files: Record<string, string>) {
|
|
56
|
+
let directory = mkdtempSync(join(process.cwd(), '.fixture-coverage-')).replace(/\\/g, '/');
|
|
57
|
+
|
|
58
|
+
fixtures.push(directory);
|
|
59
|
+
|
|
60
|
+
for (let [name, code] of Object.entries(files)) {
|
|
61
|
+
writeFileSync(join(directory, name), code);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
writeFileSync(join(directory, 'tsconfig.json'), JSON.stringify({
|
|
65
|
+
compilerOptions: { module: 'esnext', moduleResolution: 'bundler', strict: true, target: 'esnext', types: [] },
|
|
66
|
+
files: Object.keys(files).map(name => './' + name)
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
let plugin = vite({ root: directory });
|
|
70
|
+
|
|
71
|
+
plugin.configResolved({ command: 'build', root: directory });
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
directory,
|
|
75
|
+
plugin,
|
|
76
|
+
transform: (name: string) => plugin.transform(files[name], directory + '/' + name)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
afterEach(() => {
|
|
82
|
+
for (let fixture of fixtures) {
|
|
83
|
+
rmSync(fixture, { force: true, recursive: true });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fixtures = [];
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
describe('compiler coverage', () => {
|
|
91
|
+
it('compiles reads of bindings imported by name, namespace, default, barrel and string key', () => {
|
|
92
|
+
let output = project(FILES).transform('reads.ts')!.code;
|
|
93
|
+
|
|
94
|
+
expect(output).toContain(`export let a = () => ${NAMESPACE}.read(count) + 1;`);
|
|
95
|
+
expect(output).toContain(`export let b = () => ${NAMESPACE}.read(S.count) + 1;`);
|
|
96
|
+
expect(output).toContain(`export let c = () => ${NAMESPACE}.read(fallback) + 1;`);
|
|
97
|
+
expect(output).toContain(`export let d = () => ${NAMESPACE}.read(renamed) + 1;`);
|
|
98
|
+
expect(output).toContain('export let e = () => list.$length;');
|
|
99
|
+
expect(output).toContain('list.$set(0, 5)');
|
|
100
|
+
expect(output).toContain('export let g = () => store.items.$length;');
|
|
101
|
+
expect(output).toContain(`export let h = () => ${NAMESPACE}.read(S['count']) + 1;`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('compiles reactive() reached through renames, namespaces, string keys and const aliases', () => {
|
|
105
|
+
let output = project(FILES).transform('entries.ts')!.code;
|
|
106
|
+
|
|
107
|
+
for (let name of ['a', 'b', 'c', 'd', 'e', 'f']) {
|
|
108
|
+
expect(output).toMatch(new RegExp(`export let ${name} = ${NAMESPACE}\\.signal\\(0\\);`));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
expect(output).toContain(`${NAMESPACE}.read(a) + ${NAMESPACE}.read(b)`);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('passes every array property of a reactive object to its constructor', () => {
|
|
115
|
+
let output = project(FILES).transform('state.ts')!.code;
|
|
116
|
+
|
|
117
|
+
expect(output).toMatch(/export let store = +new ReactiveObject_\w+\(\[1\]\);/);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('fails the build on every use of reactive that cannot be compiled, with its location', () => {
|
|
121
|
+
let files = {
|
|
122
|
+
'escape.ts': [
|
|
123
|
+
"import { reactive } from '@esportsplus/reactivity';",
|
|
124
|
+
'declare function take(value: unknown): void;',
|
|
125
|
+
'take(reactive);',
|
|
126
|
+
'take(reactive(0));'
|
|
127
|
+
].join('\n')
|
|
128
|
+
},
|
|
129
|
+
{ transform } = project(files);
|
|
130
|
+
|
|
131
|
+
expect(() => transform('escape.ts')).toThrow(/escape\.ts:3:6[\s\S]*escape\.ts:4:6/);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('fails the build when a signal is destructured out of a module namespace', () => {
|
|
135
|
+
let files = {
|
|
136
|
+
'state.ts': "import { reactive } from '@esportsplus/reactivity';\nexport let count = reactive(0);",
|
|
137
|
+
'copy.ts': "import * as S from './state';\nconst { count } = S;\nexport let n = count;"
|
|
138
|
+
},
|
|
139
|
+
{ transform } = project(files);
|
|
140
|
+
|
|
141
|
+
expect(() => transform('copy.ts')).toThrow(/copy\.ts:2:9.*destructured/);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('reports the modules a compile relied on so hosts can recompile it when they change', () => {
|
|
145
|
+
let { directory, plugin, transform } = project(FILES),
|
|
146
|
+
dependent = { id: directory + '/reads.ts' };
|
|
147
|
+
|
|
148
|
+
transform('reads.ts');
|
|
149
|
+
|
|
150
|
+
let result = plugin.handleHotUpdate({
|
|
151
|
+
file: directory + '/state.ts',
|
|
152
|
+
modules: [],
|
|
153
|
+
server: {
|
|
154
|
+
moduleGraph: {
|
|
155
|
+
getModulesByFile: (file: string) => file.toLowerCase() === (directory + '/reads.ts').toLowerCase() ? new Set([dependent]) : undefined,
|
|
156
|
+
invalidateModule: () => {}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
timestamp: 1
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(result).toEqual([dependent]);
|
|
163
|
+
});
|
|
164
|
+
});
|