@lithent/hmr-parser 0.2.2 → 0.2.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/dist/helper/calculator.d.cts +8 -0
- package/dist/helper/calculator.d.ts +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +5 -5
- package/dist/lithentHmrParser.mjs +4214 -4370
- package/dist/lithentHmrParser.mjs.map +1 -1
- package/dist/lithentHmrParser.umd.js +137 -216
- package/dist/lithentHmrParser.umd.js.map +1 -1
- package/dist/markerParse.d.cts +2 -0
- package/dist/markerParse.d.ts +1 -1
- package/dist/noMarkerParse.d.cts +2 -0
- package/dist/noMarkerParse.d.ts +1 -1
- package/dist/shared.d.cts +4 -0
- package/dist/shared.d.ts +1 -1
- package/dist/strings.d.cts +1 -0
- package/dist/strings.d.ts +1 -1
- package/dist/tests/bootstrap.test.d.cts +1 -0
- package/dist/tests/bootstrap.test.d.ts +1 -0
- package/dist/tests/mark.test.d.cts +1 -0
- package/dist/tests/nomark.test.d.cts +1 -0
- package/dist/transform/componentRegister.d.cts +3 -0
- package/dist/transform/componentRegister.d.ts +2 -2
- package/dist/transform/exportSnapshot.d.cts +3 -0
- package/dist/transform/exportSnapshot.d.ts +3 -0
- package/dist/transform/renderGuard.d.cts +3 -0
- package/dist/transform/renderGuard.d.ts +1 -1
- package/dist/types.d.cts +28 -0
- package/dist/utils/ast/componentCollector.d.cts +9 -0
- package/dist/utils/ast/componentCollector.d.ts +5 -4
- package/package.json +2 -2
- package/src/helper/calculator.ts +27 -7
- package/src/markerParse.ts +30 -14
- package/src/noMarkerParse.ts +16 -4
- package/src/shared.ts +8 -2
- package/src/strings.ts +105 -177
- package/src/tests/bootstrap.test.ts +312 -0
- package/src/tests/mark.test.ts +61 -181
- package/src/tests/nomark.test.tsx +110 -89
- package/src/transform/componentRegister.ts +13 -304
- package/src/transform/exportSnapshot.ts +77 -0
- package/src/transform/renderGuard.ts +59 -49
- package/src/utils/ast/componentCollector.ts +195 -96
package/src/tests/mark.test.ts
CHANGED
|
@@ -1,191 +1,71 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { parse } from '@babel/parser';
|
|
1
3
|
import { transformWithMarker } from '../markerParse';
|
|
2
4
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const transform = (code: string) =>
|
|
6
|
+
transformWithMarker({
|
|
7
|
+
code,
|
|
8
|
+
markerRegex: /\/\*\s*lithent:hmr-boundary(?:\s+([A-Za-z0-9_,\s]+))?\s*\*\//,
|
|
9
|
+
boundaryImportSpecifier: 'lithent/devHelper',
|
|
10
|
+
tagFunctionImportSpecifier: 'lithent',
|
|
11
|
+
});
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
describe('explicit component markers', () => {
|
|
14
|
+
it('wraps local and exported components independently and guards render calls', () => {
|
|
15
|
+
const result = transform(`
|
|
9
16
|
'use client';
|
|
10
|
-
import { render
|
|
11
|
-
|
|
17
|
+
import {mount, render} from 'lithent';
|
|
12
18
|
/* lithent:hmr-boundary default */
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const Secondary = mount((renew, props: { subtitle: string }) => {
|
|
19
|
-
return () => <p>{props.subtitle}</p>;
|
|
20
|
-
});
|
|
21
|
-
|
|
19
|
+
const App = mount(() => {return () => <Secondary/>;});
|
|
20
|
+
const Secondary = () => <span/>;
|
|
21
|
+
render(<App/>, document.body);
|
|
22
22
|
export default App;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
export const Another = mount((renew, props) => {
|
|
38
|
-
return () => <span>{props.value}</span>;
|
|
39
|
-
});
|
|
40
|
-
`;
|
|
41
|
-
|
|
42
|
-
const defaultResult = transformWithMarker({
|
|
43
|
-
code: markerSourceDefault,
|
|
44
|
-
markerRegex: createDefaultMarkerRegex(),
|
|
45
|
-
boundaryImportSpecifier: 'lithent/devHelper',
|
|
46
|
-
tagFunctionImportSpecifier: 'lithent',
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const namedResult = transformWithMarker({
|
|
50
|
-
code: markerSourceNamed,
|
|
51
|
-
markerRegex: createDefaultMarkerRegex(),
|
|
52
|
-
boundaryImportSpecifier: 'lithent/devHelper',
|
|
53
|
-
tagFunctionImportSpecifier: 'lithent',
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
console.log('[marker] transformed default:', defaultResult.transformed);
|
|
57
|
-
console.log('[marker] transformed named:', namedResult.transformed);
|
|
58
|
-
console.log('[marker] code default:', defaultResult.code);
|
|
59
|
-
console.log('[marker] code named:', namedResult.code);
|
|
60
|
-
|
|
61
|
-
if (!import.meta.vitest) {
|
|
62
|
-
const defaultPre = document?.getElementById('marker-default');
|
|
63
|
-
if (defaultPre) {
|
|
64
|
-
defaultPre.textContent = defaultResult.transformed
|
|
65
|
-
? defaultResult.code
|
|
66
|
-
: 'transform skipped';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const namedPre = document?.getElementById('marker-named');
|
|
70
|
-
if (namedPre) {
|
|
71
|
-
namedPre.textContent = namedResult.transformed
|
|
72
|
-
? namedResult.code
|
|
73
|
-
: 'transform skipped';
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (import.meta.vitest) {
|
|
78
|
-
const { describe, it, expect } = import.meta.vitest;
|
|
79
|
-
|
|
80
|
-
describe('transformWithMarker', () => {
|
|
81
|
-
it('replaces marker comment with HMR bootstrap and injects helpers', () => {
|
|
82
|
-
expect(defaultResult.transformed).toBe(true);
|
|
83
|
-
expect(defaultResult.code).not.toMatch(/lithent:hmr-boundary/);
|
|
84
|
-
expect(defaultResult.code).toContain(
|
|
85
|
-
"import { createBoundary } from 'lithent/devHelper';"
|
|
86
|
-
);
|
|
87
|
-
expect(defaultResult.code).toContain('import.meta.hot');
|
|
88
|
-
expect(defaultResult.code).toContain(
|
|
89
|
-
"import { mountCallback, getComponentKey } from 'lithent';"
|
|
90
|
-
);
|
|
91
|
-
expect(defaultResult.code).toContain(
|
|
92
|
-
'const __lithentModuleId = new URL(import.meta.url).pathname;'
|
|
93
|
-
);
|
|
94
|
-
expect(defaultResult.code).toContain(
|
|
95
|
-
'const __lithentHmrTargets = ["App","Secondary"];'
|
|
96
|
-
);
|
|
97
|
-
expect(defaultResult.code).toContain('const knownNames = new Set([');
|
|
98
|
-
expect(defaultResult.code).toContain('...["App","Secondary"]');
|
|
99
|
-
expect(defaultResult.code).toContain('...__lithentHmrTargets');
|
|
100
|
-
expect(defaultResult.code).toContain(
|
|
101
|
-
'const __lithentHotComponent_App = App;'
|
|
102
|
-
);
|
|
103
|
-
expect(defaultResult.code).toContain(
|
|
104
|
-
'__lithentModuleHotStore["App"] = __lithentHotComponent_App;'
|
|
105
|
-
);
|
|
106
|
-
expect(defaultResult.code).toContain(
|
|
107
|
-
'const __lithentHotComponent_Secondary = Secondary;'
|
|
108
|
-
);
|
|
109
|
-
expect(defaultResult.code).toContain(
|
|
110
|
-
'__lithentModuleHotStore["Secondary"] = __lithentHotComponent_Secondary;'
|
|
111
|
-
);
|
|
112
|
-
expect(defaultResult.code).toContain(
|
|
113
|
-
'const __lithentRenderOnce = (factory)'
|
|
114
|
-
);
|
|
115
|
-
const registerMatches =
|
|
116
|
-
defaultResult.code.match(/counterBoundary\.register\(compKey\)/g) ?? [];
|
|
117
|
-
const unregisterMatches =
|
|
118
|
-
defaultResult.code.match(
|
|
119
|
-
/mountCallback\(\(\) => \(\) => unregister\(\)\)/g
|
|
120
|
-
) ?? [];
|
|
121
|
-
expect(registerMatches).toHaveLength(2);
|
|
122
|
-
expect(unregisterMatches).toHaveLength(2);
|
|
123
|
-
});
|
|
23
|
+
`);
|
|
24
|
+
expect(result.transformed).toBe(true);
|
|
25
|
+
expect(result.code).not.toContain('/* lithent:hmr-boundary');
|
|
26
|
+
expect(result.code).toContain('__lithentWrapComponent("App", mount(');
|
|
27
|
+
expect(result.code).toContain('__lithentWrapComponent("Secondary",');
|
|
28
|
+
expect(result.code).toContain('__lithentRenderOnce(0, () => render(');
|
|
29
|
+
expect(() =>
|
|
30
|
+
parse(result.code, {
|
|
31
|
+
sourceType: 'module',
|
|
32
|
+
plugins: ['typescript', 'jsx'],
|
|
33
|
+
})
|
|
34
|
+
).not.toThrow();
|
|
35
|
+
});
|
|
124
36
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
expect(namedResult.code).toContain('const knownNames = new Set([');
|
|
137
|
-
expect(namedResult.code).toContain('...["Counter","Another"]');
|
|
138
|
-
expect(namedResult.code).toContain('...__lithentHmrTargets');
|
|
139
|
-
expect(namedResult.code).toContain(
|
|
140
|
-
'const __lithentHotComponent_Counter = Counter;'
|
|
141
|
-
);
|
|
142
|
-
expect(namedResult.code).toContain(
|
|
143
|
-
'__lithentModuleHotStore["Counter"] = __lithentHotComponent_Counter;'
|
|
144
|
-
);
|
|
145
|
-
expect(namedResult.code).toContain(
|
|
146
|
-
'const __lithentHotComponent_Another = Another;'
|
|
147
|
-
);
|
|
148
|
-
expect(namedResult.code).toContain(
|
|
149
|
-
'__lithentModuleHotStore["Another"] = __lithentHotComponent_Another;'
|
|
150
|
-
);
|
|
151
|
-
const namedRegisters =
|
|
152
|
-
namedResult.code.match(/counterBoundary\.register\(compKey\)/g) ?? [];
|
|
153
|
-
const namedUnregisters =
|
|
154
|
-
namedResult.code.match(
|
|
155
|
-
/mountCallback\(\(\) => \(\) => unregister\(\)\)/g
|
|
156
|
-
) ?? [];
|
|
157
|
-
expect(namedRegisters).toHaveLength(2);
|
|
158
|
-
expect(namedUnregisters).toHaveLength(2);
|
|
37
|
+
it('does not rely on unaliased lifecycle imports or snippet text', () => {
|
|
38
|
+
const result = transform(`
|
|
39
|
+
import {createBoundary} from 'lithent/devHelper';
|
|
40
|
+
import {mount, getComponentKey as key} from 'lithent';
|
|
41
|
+
const snippet = "import {mountCallback} from 'lithent'";
|
|
42
|
+
/* lithent:hmr-boundary Counter */
|
|
43
|
+
export const Counter = mount(() => {return () => <span/>;});
|
|
44
|
+
`);
|
|
45
|
+
const ast = parse(result.code, {
|
|
46
|
+
sourceType: 'module',
|
|
47
|
+
plugins: ['typescript', 'jsx'],
|
|
159
48
|
});
|
|
49
|
+
const boundaryImports = ast.program.body.filter(
|
|
50
|
+
node =>
|
|
51
|
+
node.type === 'ImportDeclaration' &&
|
|
52
|
+
node.source.value === 'lithent/devHelper'
|
|
53
|
+
);
|
|
54
|
+
expect(boundaryImports).toHaveLength(2);
|
|
55
|
+
expect(result.code).toContain('createBoundary as __lithentCreateBoundary');
|
|
56
|
+
expect(result.code).toContain(
|
|
57
|
+
'getComponentKey as __lithentGetComponentKey'
|
|
58
|
+
);
|
|
59
|
+
expect(result.code).toContain('mountCallback as __lithentMountCallback');
|
|
60
|
+
});
|
|
160
61
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
'',
|
|
170
|
-
'const Demo = mount(() => {',
|
|
171
|
-
' return () => <div>demo</div>;',
|
|
172
|
-
'});',
|
|
173
|
-
'',
|
|
174
|
-
'export default Demo;',
|
|
175
|
-
'',
|
|
176
|
-
].join('\n');
|
|
177
|
-
|
|
178
|
-
const snippetResult = transformWithMarker({
|
|
179
|
-
code: sourceWithSnippet,
|
|
180
|
-
markerRegex: createDefaultMarkerRegex(),
|
|
181
|
-
boundaryImportSpecifier: 'lithent/devHelper',
|
|
182
|
-
tagFunctionImportSpecifier: 'lithent',
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
expect(snippetResult.transformed).toBe(true);
|
|
186
|
-
expect(snippetResult.code).toContain(
|
|
187
|
-
"import { mountCallback, getComponentKey } from 'lithent';"
|
|
188
|
-
);
|
|
189
|
-
});
|
|
62
|
+
it('initializes wrappers before declarations even when the marker follows them', () => {
|
|
63
|
+
const result = transform(`
|
|
64
|
+
export const Badge = () => <span/>;
|
|
65
|
+
/* lithent:hmr-boundary Badge */
|
|
66
|
+
`);
|
|
67
|
+
expect(result.code.indexOf('const __lithentWrapComponent')).toBeLessThan(
|
|
68
|
+
result.code.indexOf('export const Badge')
|
|
69
|
+
);
|
|
190
70
|
});
|
|
191
|
-
}
|
|
71
|
+
});
|
|
@@ -1,98 +1,119 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { parse } from '@babel/parser';
|
|
1
3
|
import { transformWithoutMarker } from '../noMarkerParse';
|
|
2
4
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
const Another = mount((_renew, props: { id: number }) => {
|
|
12
|
-
return () => ({ type: 'span', props: {}, children: [String(props.id)] } as any);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const root = document.getElementById('app');
|
|
16
|
-
if (root) {
|
|
17
|
-
render(Demo({ message: 'hello' }), root as any);
|
|
18
|
-
render(Another({ id: 1 }), root as any);
|
|
19
|
-
}
|
|
20
|
-
`;
|
|
5
|
+
const transform = (code: string) =>
|
|
6
|
+
transformWithoutMarker({
|
|
7
|
+
code,
|
|
8
|
+
boundaryImportSpecifier: 'lithent/devHelper',
|
|
9
|
+
tagFunctionImportSpecifier: 'lithent',
|
|
10
|
+
});
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
})
|
|
12
|
+
describe('component discovery without a marker', () => {
|
|
13
|
+
it.each([
|
|
14
|
+
[
|
|
15
|
+
'expression arrow',
|
|
16
|
+
'export const Badge = ({label}) => <span>{label}</span>;',
|
|
17
|
+
],
|
|
18
|
+
[
|
|
19
|
+
'block arrow',
|
|
20
|
+
'export const Badge = ({label}) => { return <span>{label}</span>; };',
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
'function declaration',
|
|
24
|
+
'export function Badge({label}) { return <span>{label}</span>; }',
|
|
25
|
+
],
|
|
26
|
+
[
|
|
27
|
+
'function expression',
|
|
28
|
+
'export const Badge = function ({label}) { return <span>{label}</span>; };',
|
|
29
|
+
],
|
|
30
|
+
[
|
|
31
|
+
'named default',
|
|
32
|
+
'export default function Badge({label}) { return <span>{label}</span>; }',
|
|
33
|
+
],
|
|
34
|
+
[
|
|
35
|
+
'lowercase named default',
|
|
36
|
+
'export default function badge() { return <span/>; }',
|
|
37
|
+
],
|
|
38
|
+
['null output', 'export const Empty = () => null;'],
|
|
39
|
+
[
|
|
40
|
+
'JSX list',
|
|
41
|
+
'export const List = ({items}) => items.map(item => <span>{item}</span>);',
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
'returned local',
|
|
45
|
+
'export const Badge = () => {const output = <span/>; return output;};',
|
|
46
|
+
],
|
|
47
|
+
['anonymous default', 'export default ({label}) => <span>{label}</span>;'],
|
|
48
|
+
[
|
|
49
|
+
'anonymous default function',
|
|
50
|
+
'export default function ({label}) { return <span>{label}</span>; }',
|
|
51
|
+
],
|
|
52
|
+
])('discovers and produces valid syntax for %s', (_name, code) => {
|
|
53
|
+
const result = transform(code);
|
|
54
|
+
expect(result.transformed).toBe(true);
|
|
55
|
+
expect(result.code).toContain('__lithentWrapComponent(');
|
|
56
|
+
expect(() =>
|
|
57
|
+
parse(result.code, {
|
|
58
|
+
sourceType: 'module',
|
|
59
|
+
plugins: ['typescript', 'jsx'],
|
|
60
|
+
})
|
|
61
|
+
).not.toThrow();
|
|
62
|
+
});
|
|
27
63
|
|
|
28
|
-
|
|
29
|
-
|
|
64
|
+
it('discovers automatic JSX runtime calls and leaves ordinary helpers alone', () => {
|
|
65
|
+
const result = transform(`
|
|
66
|
+
import {jsx as _jsx} from 'lithent/jsx-runtime';
|
|
67
|
+
export const Badge = props => _jsx('span', {children: props.label});
|
|
68
|
+
export const Format = value => String(value);
|
|
69
|
+
export const Handle = () => () => _jsx('span', {});
|
|
70
|
+
`);
|
|
71
|
+
expect(result.code).toContain('__lithentWrapComponent("Badge",');
|
|
72
|
+
expect(result.code).not.toContain('__lithentWrapComponent("Format",');
|
|
73
|
+
expect(result.code).not.toContain('__lithentWrapComponent("Handle",');
|
|
74
|
+
});
|
|
30
75
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
76
|
+
it('handles aliased mount, lmount, and multiple declarations', () => {
|
|
77
|
+
const result = transform(`
|
|
78
|
+
import {mount as make, lmount} from 'lithent';
|
|
79
|
+
export const A = make(function () {return () => <span/>;}), B = lmount(() => () => <div/>);
|
|
80
|
+
`);
|
|
81
|
+
expect(result.code).toContain('__lithentWrapComponent("A", make(');
|
|
82
|
+
expect(result.code).toContain('__lithentWrapComponent("B", lmount(');
|
|
83
|
+
expect(() =>
|
|
84
|
+
parse(result.code, {
|
|
85
|
+
sourceType: 'module',
|
|
86
|
+
plugins: ['typescript', 'jsx'],
|
|
87
|
+
})
|
|
88
|
+
).not.toThrow();
|
|
89
|
+
});
|
|
37
90
|
|
|
38
|
-
|
|
39
|
-
|
|
91
|
+
it('recognizes namespace imports and typed expressions', () => {
|
|
92
|
+
const result = transform(`
|
|
93
|
+
import * as core from 'lithent';
|
|
94
|
+
import * as runtime from 'lithent/jsx-runtime';
|
|
95
|
+
export const A = core.mount(() => () => core.h('span', {}));
|
|
96
|
+
export const B = props => runtime.jsx('span', {children: props.label});
|
|
97
|
+
export const C = (() => null) satisfies () => null;
|
|
98
|
+
`);
|
|
99
|
+
for (const name of ['A', 'B', 'C'])
|
|
100
|
+
expect(result.code).toContain(`__lithentWrapComponent("${name}",`);
|
|
101
|
+
});
|
|
40
102
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
103
|
+
it('does not treat imports from other frameworks as Lithent components', () => {
|
|
104
|
+
const result = transform(
|
|
105
|
+
"import {jsx} from 'react/jsx-runtime'; export const Badge = () => jsx('span', {});"
|
|
106
|
+
);
|
|
107
|
+
expect(result.transformed).toBe(false);
|
|
108
|
+
});
|
|
45
109
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
).
|
|
53
|
-
|
|
54
|
-
result.code.includes(
|
|
55
|
-
"import { mountCallback, getComponentKey } from 'lithent'"
|
|
56
|
-
)
|
|
57
|
-
).toBe(true);
|
|
58
|
-
expect(result.code.includes('const compKey = getComponentKey();')).toBe(
|
|
59
|
-
true
|
|
60
|
-
);
|
|
61
|
-
expect(
|
|
62
|
-
result.code.includes('const __lithentHmrTargets = ["Demo","Another"];')
|
|
63
|
-
).toBe(true);
|
|
64
|
-
expect(result.code.includes('const knownNames = new Set([')).toBe(true);
|
|
65
|
-
expect(result.code.includes('...["Demo","Another"]')).toBe(true);
|
|
66
|
-
expect(result.code.includes('...__lithentHmrTargets')).toBe(true);
|
|
67
|
-
expect(
|
|
68
|
-
result.code.includes('const __lithentHotComponent_Demo = Demo;')
|
|
69
|
-
).toBe(true);
|
|
70
|
-
expect(
|
|
71
|
-
result.code.includes(
|
|
72
|
-
'__lithentModuleHotStore["Demo"] = __lithentHotComponent_Demo;'
|
|
73
|
-
)
|
|
74
|
-
).toBe(true);
|
|
75
|
-
expect(
|
|
76
|
-
result.code.includes('const __lithentHotComponent_Another = Another;')
|
|
77
|
-
).toBe(true);
|
|
78
|
-
expect(
|
|
79
|
-
result.code.includes(
|
|
80
|
-
'__lithentModuleHotStore["Another"] = __lithentHotComponent_Another;'
|
|
81
|
-
)
|
|
82
|
-
).toBe(true);
|
|
83
|
-
expect(
|
|
84
|
-
result.code.includes('const __lithentRenderOnce = (factory)')
|
|
85
|
-
).toBe(true);
|
|
86
|
-
expect(/__lithentRenderOnce\(\(\) =>\s*render\(/.test(result.code)).toBe(
|
|
87
|
-
true
|
|
88
|
-
);
|
|
89
|
-
const registerMatches =
|
|
90
|
-
result.code.match(/counterBoundary\.register\(compKey\)/g) ?? [];
|
|
91
|
-
const unregisterMatches =
|
|
92
|
-
result.code.match(/mountCallback\(\(\) => \(\) => unregister\(\)\)/g) ??
|
|
93
|
-
[];
|
|
94
|
-
expect(registerMatches).toHaveLength(2);
|
|
95
|
-
expect(unregisterMatches).toHaveLength(2);
|
|
96
|
-
});
|
|
110
|
+
it('does not discover JSX side effects, async functions, or generators', () => {
|
|
111
|
+
for (const source of [
|
|
112
|
+
'export const Handle = () => {const ignored = <span/>; return 42;};',
|
|
113
|
+
'export const Badge = async () => <span/>;',
|
|
114
|
+
'export function* Badge() {yield <span/>;}',
|
|
115
|
+
]) {
|
|
116
|
+
expect(transform(source).code).not.toContain('__lithentWrapComponent(');
|
|
117
|
+
}
|
|
97
118
|
});
|
|
98
|
-
}
|
|
119
|
+
});
|