ripple 0.3.71 → 0.3.74
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/CHANGELOG.md +75 -0
- package/package.json +3 -3
- package/src/jsx-runtime.d.ts +2 -8
- package/src/runtime/index-client.js +3 -13
- package/src/runtime/internal/client/blocks.js +3 -25
- package/src/runtime/internal/client/for.js +80 -5
- package/src/runtime/internal/client/index.js +0 -2
- package/src/runtime/internal/client/types.d.ts +0 -10
- package/tests/client/__snapshots__/computed-properties.test.tsrx.snap +8 -0
- package/tests/client/__snapshots__/for.test.tsrx.snap +22 -0
- package/tests/client/__snapshots__/html.test.tsrx.snap +4 -0
- package/tests/client/array/array.copy-within.test.tsrx +19 -19
- package/tests/client/array/array.derived.test.tsrx +97 -109
- package/tests/client/array/array.iteration.test.tsrx +28 -28
- package/tests/client/array/array.mutations.test.tsrx +68 -68
- package/tests/client/array/array.static.test.tsrx +82 -92
- package/tests/client/array/array.to-methods.test.tsrx +15 -15
- package/tests/client/async-suspend.test.tsrx +180 -179
- package/tests/client/basic/__snapshots__/basic.attributes.test.tsrx.snap +2 -0
- package/tests/client/basic/__snapshots__/basic.rendering.test.tsrx.snap +4 -0
- package/tests/client/basic/basic.attributes.test.tsrx +273 -317
- package/tests/client/basic/basic.collections.test.tsrx +59 -71
- package/tests/client/basic/basic.components.test.tsrx +196 -222
- package/tests/client/basic/basic.errors.test.tsrx +72 -78
- package/tests/client/basic/basic.events.test.tsrx +80 -85
- package/tests/client/basic/basic.get-set.test.tsrx +54 -64
- package/tests/client/basic/basic.hmr.test.tsrx +15 -19
- package/tests/client/basic/basic.reactivity.test.tsrx +121 -135
- package/tests/client/basic/basic.rendering.test.tsrx +273 -178
- package/tests/client/basic/basic.utilities.test.tsrx +8 -10
- package/tests/client/boundaries.test.tsrx +18 -18
- package/tests/client/compiler/compiler.assignments.test.tsrx +77 -76
- package/tests/client/compiler/compiler.attributes.test.tsrx +18 -14
- package/tests/client/compiler/compiler.basic.test.tsrx +364 -296
- package/tests/client/compiler/compiler.regex.test.tsrx +40 -44
- package/tests/client/compiler/compiler.tracked-access.test.tsrx +57 -38
- package/tests/client/compiler/compiler.try-in-function.test.tsrx +16 -16
- package/tests/client/compiler/compiler.typescript.test.tsrx +4 -3
- package/tests/client/composite/composite.dynamic-components.test.tsrx +41 -44
- package/tests/client/composite/composite.generics.test.tsrx +165 -167
- package/tests/client/composite/composite.props.test.tsrx +66 -74
- package/tests/client/composite/composite.reactivity.test.tsrx +132 -166
- package/tests/client/composite/composite.render.test.tsrx +92 -101
- package/tests/client/computed-properties.test.tsrx +14 -18
- package/tests/client/context.test.tsrx +14 -18
- package/tests/client/css/global-additional-cases.test.tsrx +491 -437
- package/tests/client/css/global-advanced-selectors.test.tsrx +169 -153
- package/tests/client/css/global-at-rules.test.tsrx +71 -66
- package/tests/client/css/global-basic.test.tsrx +105 -98
- package/tests/client/css/global-classes-ids.test.tsrx +128 -114
- package/tests/client/css/global-combinators.test.tsrx +83 -78
- package/tests/client/css/global-complex-nesting.test.tsrx +134 -120
- package/tests/client/css/global-edge-cases.test.tsrx +138 -120
- package/tests/client/css/global-keyframes.test.tsrx +108 -96
- package/tests/client/css/global-nested.test.tsrx +88 -78
- package/tests/client/css/global-pseudo.test.tsrx +104 -98
- package/tests/client/css/global-scoping.test.tsrx +145 -125
- package/tests/client/css/style-identifier.test.tsrx +62 -69
- package/tests/client/date.test.tsrx +83 -83
- package/tests/client/dynamic-elements.test.tsrx +227 -283
- package/tests/client/events.test.tsrx +252 -266
- package/tests/client/for.test.tsrx +120 -127
- package/tests/client/head.test.tsrx +40 -48
- package/tests/client/html.test.tsrx +37 -49
- package/tests/client/input-value.test.tsrx +1125 -1354
- package/tests/client/lazy-array.test.tsrx +10 -16
- package/tests/client/lazy-destructuring.test.tsrx +169 -221
- package/tests/client/map.test.tsrx +39 -41
- package/tests/client/media-query.test.tsrx +15 -19
- package/tests/client/object.test.tsrx +46 -56
- package/tests/client/portal.test.tsrx +31 -37
- package/tests/client/ref.test.tsrx +173 -193
- package/tests/client/return.test.tsrx +62 -37
- package/tests/client/set.test.tsrx +33 -33
- package/tests/client/svg.test.tsrx +195 -215
- package/tests/client/switch.test.tsrx +201 -191
- package/tests/client/track-async-hydration.test.tsrx +14 -18
- package/tests/client/tracked-index-access.test.tsrx +18 -28
- package/tests/client/try.test.tsrx +494 -619
- package/tests/client/tsx.test.tsrx +290 -371
- package/tests/client/typescript-generics.test.tsrx +121 -129
- package/tests/client/url/url.derived.test.tsrx +21 -25
- package/tests/client/url/url.parsing.test.tsrx +35 -35
- package/tests/client/url/url.partial-removal.test.tsrx +32 -32
- package/tests/client/url/url.reactivity.test.tsrx +68 -72
- package/tests/client/url/url.serialization.test.tsrx +8 -8
- package/tests/client/url-search-params/url-search-params.derived.test.tsrx +21 -27
- package/tests/client/url-search-params/url-search-params.initialization.test.tsrx +16 -16
- package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +37 -37
- package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +56 -60
- package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +32 -34
- package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +9 -9
- package/tests/client/url-search-params/url-search-params.tracked-url.test.tsrx +10 -10
- package/tests/hydration/compiled/client/basic.js +396 -325
- package/tests/hydration/compiled/client/composite.js +52 -44
- package/tests/hydration/compiled/client/for.js +734 -604
- package/tests/hydration/compiled/client/head.js +183 -103
- package/tests/hydration/compiled/client/html.js +93 -86
- package/tests/hydration/compiled/client/if-children.js +95 -71
- package/tests/hydration/compiled/client/if.js +113 -89
- package/tests/hydration/compiled/client/mixed-control-flow.js +225 -209
- package/tests/hydration/compiled/client/nested-control-flow.js +94 -98
- package/tests/hydration/compiled/client/reactivity.js +26 -24
- package/tests/hydration/compiled/client/return.js +8 -42
- package/tests/hydration/compiled/client/switch.js +208 -173
- package/tests/hydration/compiled/client/track-async-serialization.js +176 -128
- package/tests/hydration/compiled/client/try.js +29 -21
- package/tests/hydration/compiled/server/basic.js +210 -221
- package/tests/hydration/compiled/server/composite.js +13 -14
- package/tests/hydration/compiled/server/for.js +427 -444
- package/tests/hydration/compiled/server/head.js +199 -189
- package/tests/hydration/compiled/server/html.js +33 -41
- package/tests/hydration/compiled/server/if-children.js +114 -117
- package/tests/hydration/compiled/server/if.js +77 -83
- package/tests/hydration/compiled/server/mixed-control-flow.js +145 -150
- package/tests/hydration/compiled/server/nested-control-flow.js +10 -0
- package/tests/hydration/compiled/server/reactivity.js +24 -22
- package/tests/hydration/compiled/server/return.js +6 -18
- package/tests/hydration/compiled/server/switch.js +179 -176
- package/tests/hydration/compiled/server/track-async-serialization.js +88 -70
- package/tests/hydration/compiled/server/try.js +31 -35
- package/tests/hydration/components/basic.tsrx +216 -286
- package/tests/hydration/components/composite.tsrx +32 -42
- package/tests/hydration/components/events.tsrx +81 -101
- package/tests/hydration/components/for.tsrx +270 -336
- package/tests/hydration/components/head.tsrx +43 -39
- package/tests/hydration/components/hmr.tsrx +16 -22
- package/tests/hydration/components/html-in-template.tsrx +15 -21
- package/tests/hydration/components/html.tsrx +442 -526
- package/tests/hydration/components/if-children.tsrx +107 -125
- package/tests/hydration/components/if.tsrx +68 -90
- package/tests/hydration/components/mixed-control-flow.tsrx +65 -72
- package/tests/hydration/components/nested-control-flow.tsrx +202 -216
- package/tests/hydration/components/portal.tsrx +33 -41
- package/tests/hydration/components/reactivity.tsrx +26 -34
- package/tests/hydration/components/return.tsrx +4 -6
- package/tests/hydration/components/switch.tsrx +73 -78
- package/tests/hydration/components/track-async-serialization.tsrx +83 -93
- package/tests/hydration/components/try.tsrx +37 -51
- package/tests/hydration/switch.test.js +8 -8
- package/tests/server/await.test.tsrx +3 -3
- package/tests/server/basic.attributes.test.tsrx +120 -167
- package/tests/server/basic.components.test.tsrx +163 -197
- package/tests/server/basic.test.tsrx +298 -220
- package/tests/server/compiler.test.tsrx +142 -72
- package/tests/server/composite.props.test.tsrx +54 -58
- package/tests/server/composite.test.tsrx +165 -167
- package/tests/server/context.test.tsrx +13 -17
- package/tests/server/dynamic-elements.test.tsrx +103 -135
- package/tests/server/for.test.tsrx +115 -84
- package/tests/server/head.test.tsrx +31 -31
- package/tests/server/html-nesting-validation.test.tsrx +16 -8
- package/tests/server/if.test.tsrx +49 -59
- package/tests/server/lazy-destructuring.test.tsrx +288 -366
- package/tests/server/return.test.tsrx +58 -36
- package/tests/server/streaming-ssr.test.tsrx +4 -4
- package/tests/server/style-identifier.test.tsrx +58 -66
- package/tests/server/switch.test.tsrx +89 -97
- package/tests/server/track-async-serialization.test.tsrx +85 -103
- package/tests/server/try.test.tsrx +275 -360
- package/tests/utils/ref-types.test.js +72 -0
- package/tests/utils/vite-plugin-config.test.js +41 -74
- package/types/index.d.ts +1 -0
- package/src/runtime/internal/client/compat.js +0 -40
- package/tests/utils/compiler-compat-config.test.js +0 -38
|
@@ -8,6 +8,10 @@ function get_returned_tsrx(node: any): any {
|
|
|
8
8
|
const body =
|
|
9
9
|
target.type === 'VariableDeclarator' ? target.init.body : target.body;
|
|
10
10
|
|
|
11
|
+
if (body.type === 'JSXCodeBlock') {
|
|
12
|
+
return body.render;
|
|
13
|
+
}
|
|
14
|
+
|
|
11
15
|
if (body.type !== 'BlockStatement') {
|
|
12
16
|
return body;
|
|
13
17
|
}
|
|
@@ -29,11 +33,12 @@ function count_occurrences(string: string, subString: string): number {
|
|
|
29
33
|
|
|
30
34
|
describe('compiler > basics', () => {
|
|
31
35
|
it('parses style content correctly', () => {
|
|
32
|
-
const source = `export function App() {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
const source = `export function App() @{
|
|
37
|
+
<>
|
|
38
|
+
<div id="myid" class="myclass">{"Hello World"}</div>
|
|
39
|
+
<style>__STYLE__</style>
|
|
40
|
+
</>
|
|
41
|
+
}`;
|
|
37
42
|
const style1 = '.myid {color: green }';
|
|
38
43
|
const style2 = '#myid {color: green }';
|
|
39
44
|
const style3 = 'div {color: green }';
|
|
@@ -52,13 +57,15 @@ describe('compiler > basics', () => {
|
|
|
52
57
|
});
|
|
53
58
|
|
|
54
59
|
it('parses text as an ordinary expression identifier', () => {
|
|
55
|
-
const source = `export function App() {
|
|
60
|
+
const source = `export function App() @{
|
|
56
61
|
const markup = '<span>Not HTML</span>';
|
|
57
62
|
const text = markup;
|
|
58
63
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
<>
|
|
65
|
+
<div>{markup}</div>
|
|
66
|
+
<div>{text}</div>
|
|
67
|
+
</>
|
|
68
|
+
}`;
|
|
62
69
|
|
|
63
70
|
const ast = parse(source);
|
|
64
71
|
const elements = get_returned_tsrx(ast.body[0]).children.filter(
|
|
@@ -76,11 +83,11 @@ describe('compiler > basics', () => {
|
|
|
76
83
|
const { code } = compile(source, 'text-directive.tsrx', { mode: 'client' });
|
|
77
84
|
expect(code).not.toContain('_$_.html');
|
|
78
85
|
|
|
79
|
-
const invalid_source = `export function App() {
|
|
86
|
+
const invalid_source = `export function App() @{
|
|
80
87
|
const markup = 'plain';
|
|
81
88
|
|
|
82
89
|
<div>{text markup}</div>
|
|
83
|
-
|
|
90
|
+
}`;
|
|
84
91
|
|
|
85
92
|
expect(() => parse(invalid_source)).toThrow();
|
|
86
93
|
});
|
|
@@ -88,180 +95,234 @@ describe('compiler > basics', () => {
|
|
|
88
95
|
it('optimizes string-shaped expressions as text nodes', () => {
|
|
89
96
|
const source = `export function App(
|
|
90
97
|
{ title, props }: { title: string; props: { label: string } },
|
|
91
|
-
) {
|
|
98
|
+
) @{
|
|
92
99
|
const element = <span />;
|
|
93
100
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
<>
|
|
102
|
+
<div>{String(title)}</div>
|
|
103
|
+
<div>{title + ''}</div>
|
|
104
|
+
<div>{title as string}</div>
|
|
105
|
+
<div>{title}</div>
|
|
106
|
+
<div>{props.label}</div>
|
|
107
|
+
<div>{element}</div>
|
|
108
|
+
</>
|
|
109
|
+
}`;
|
|
101
110
|
|
|
102
111
|
const { code } = compile(source, 'stringish-text.tsrx', { mode: 'client' });
|
|
103
112
|
|
|
104
113
|
expect(code).toContain('_$_.set_text');
|
|
105
114
|
expect(code).toContain('nodeValue = title');
|
|
106
|
-
expect(count_occurrences(code, '_$_.expression')).toBe(
|
|
115
|
+
expect(count_occurrences(code, '_$_.expression')).toBe(2);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('does not merge adjacent call-containing children into stringified text', () => {
|
|
119
|
+
const setup = `function child(label) @{
|
|
120
|
+
<span>{label}</span>
|
|
121
|
+
}
|
|
122
|
+
function Constructed(label) {
|
|
123
|
+
return child(label);
|
|
124
|
+
}
|
|
125
|
+
const factory = child;
|
|
126
|
+
function identity(value) {
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
function empty() {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
let assigned;
|
|
133
|
+
const lookup = { member: child('member') };
|
|
134
|
+
function getKey() {
|
|
135
|
+
return 'member';
|
|
136
|
+
}
|
|
137
|
+
function touch() {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
function tag() {
|
|
141
|
+
return child('tagged');
|
|
142
|
+
}
|
|
143
|
+
const values = { member: 0 };`;
|
|
144
|
+
|
|
145
|
+
const cases = [
|
|
146
|
+
{ name: 'call expression', expression: 'child(\'call\')' },
|
|
147
|
+
{ name: 'new expression', expression: 'new Constructed(\'new\')' },
|
|
148
|
+
{ name: 'chain expression', expression: 'factory?.(\'chain\')' },
|
|
149
|
+
{ name: 'parenthesized expression', expression: '(child(\'parenthesized\'))' },
|
|
150
|
+
{ name: 'TS as expression', expression: 'child(\'as\') as any' },
|
|
151
|
+
{ name: 'TS non-null expression', expression: 'child(\'non-null\')!' },
|
|
152
|
+
{ name: 'TS satisfies expression', expression: 'child(\'satisfies\') satisfies any' },
|
|
153
|
+
{
|
|
154
|
+
name: 'TS instantiation expression',
|
|
155
|
+
expression: 'identity<typeof child>(child)(\'instantiation\')',
|
|
156
|
+
},
|
|
157
|
+
{ name: 'array expression', expression: '[child(\'array\')]' },
|
|
158
|
+
{ name: 'assignment expression', expression: 'assigned = child(\'assignment\')' },
|
|
159
|
+
{ name: 'binary expression', expression: 'empty() + \'\'' },
|
|
160
|
+
{ name: 'logical expression', expression: 'true && child(\'logical\')' },
|
|
161
|
+
{ name: 'conditional expression', expression: 'true ? child(\'conditional\') : \'\'' },
|
|
162
|
+
{ name: 'member expression', expression: 'lookup[getKey()]' },
|
|
163
|
+
{
|
|
164
|
+
name: 'object expression',
|
|
165
|
+
expression: '{ [Symbol.for(\'ripple.element\')]: true, render() { return child(\'object\'); } }',
|
|
166
|
+
},
|
|
167
|
+
{ name: 'sequence expression', expression: '(touch(), child(\'sequence\'))' },
|
|
168
|
+
{ name: 'tagged template expression', expression: 'tag`tagged`' },
|
|
169
|
+
{ name: 'template literal', expression: '`${empty()}`' },
|
|
170
|
+
{ name: 'unary expression', expression: 'void empty()' },
|
|
171
|
+
{ name: 'update expression', expression: 'values[getKey()]++' },
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
for (const test_case of cases) {
|
|
175
|
+
const source = `${setup}
|
|
176
|
+
export function App() @{
|
|
177
|
+
<div>{'${test_case.name}:'}{${test_case.expression}}</div>
|
|
178
|
+
}`;
|
|
179
|
+
|
|
180
|
+
const { code } = compile(source, `${test_case.name}.tsrx`, { mode: 'client' });
|
|
181
|
+
|
|
182
|
+
if (code.includes('String(')) {
|
|
183
|
+
throw new Error(`${test_case.name} was merged into a stringified text expression:\n${code}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
107
186
|
});
|
|
108
187
|
|
|
109
188
|
it('parses backtick expressions inside TSRX fragments as template literals', () => {
|
|
110
|
-
const source = `let a = function() {
|
|
111
|
-
return <>
|
|
189
|
+
const source = `let a = function() @{
|
|
112
190
|
<>
|
|
113
|
-
\`333\`
|
|
191
|
+
{\`333\`}
|
|
114
192
|
</>
|
|
115
|
-
</>;
|
|
116
193
|
}`;
|
|
117
194
|
|
|
118
195
|
const ast = parse(source);
|
|
119
196
|
const declaration = (ast.body[0] as AST.VariableDeclaration).declarations[0];
|
|
120
|
-
const fragment = get_returned_tsrx(declaration)
|
|
197
|
+
const fragment = get_returned_tsrx(declaration) as any;
|
|
121
198
|
|
|
122
|
-
expect(fragment.type).toBe('
|
|
123
|
-
expect(fragment.children[0].type).toBe('
|
|
199
|
+
expect(fragment.type).toBe('TsrxFragment');
|
|
200
|
+
expect(fragment.children[0].type).toBe('TSRXExpression');
|
|
124
201
|
expect(fragment.children[0].expression.type).toBe('TemplateLiteral');
|
|
125
202
|
expect(fragment.children[0].expression.quasis[0].value.raw).toBe('333');
|
|
126
203
|
});
|
|
127
204
|
|
|
128
205
|
it('parses backtick expressions around tag-like text inside TSRX fragments', () => {
|
|
129
|
-
const source = `let a = function() {
|
|
130
|
-
return <>
|
|
206
|
+
const source = `let a = function() @{
|
|
131
207
|
<>
|
|
132
|
-
\`
|
|
208
|
+
{\`
|
|
133
209
|
<b></b>
|
|
134
|
-
\`
|
|
210
|
+
\`}
|
|
135
211
|
</>
|
|
136
|
-
</>;
|
|
137
212
|
}`;
|
|
138
213
|
|
|
139
214
|
const ast = parse(source);
|
|
140
215
|
const declaration = (ast.body[0] as AST.VariableDeclaration).declarations[0];
|
|
141
|
-
const fragment = get_returned_tsrx(declaration)
|
|
216
|
+
const fragment = get_returned_tsrx(declaration) as any;
|
|
142
217
|
|
|
143
|
-
expect(fragment.type).toBe('
|
|
218
|
+
expect(fragment.type).toBe('TsrxFragment');
|
|
144
219
|
expect(fragment.children.map((child: any) => child.type)).toEqual([
|
|
145
|
-
'
|
|
220
|
+
'TSRXExpression',
|
|
146
221
|
]);
|
|
147
222
|
expect(fragment.children[0].expression.type).toBe('TemplateLiteral');
|
|
148
223
|
expect(fragment.children[0].expression.quasis[0].value.raw).toContain('<b></b>');
|
|
149
224
|
});
|
|
150
225
|
|
|
151
226
|
it('renders without crashing', () => {
|
|
152
|
-
function App() {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
baz['jkl'] = 987;
|
|
163
|
-
</>;
|
|
227
|
+
function App() @{
|
|
228
|
+
let foo: Record<string, number>;
|
|
229
|
+
let bar: Record<string, number>;
|
|
230
|
+
let baz: Record<string, number>;
|
|
231
|
+
foo = {};
|
|
232
|
+
foo = { test: 0 };
|
|
233
|
+
foo['abc'] = 123;
|
|
234
|
+
bar = { def: 456 };
|
|
235
|
+
baz = { ghi: 789 };
|
|
236
|
+
baz['jkl'] = 987;
|
|
164
237
|
}
|
|
165
238
|
|
|
166
239
|
render(App);
|
|
167
240
|
});
|
|
168
241
|
|
|
169
242
|
it('renders without crashing using < character', () => {
|
|
170
|
-
function App() {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// do nothing
|
|
175
|
-
}
|
|
176
|
-
const x = 1 < 1;
|
|
243
|
+
function App() @{
|
|
244
|
+
function bar() {
|
|
245
|
+
for (let i = 0; i < 10; i++) {
|
|
246
|
+
// do nothing
|
|
177
247
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
248
|
+
const x = 1 < 1;
|
|
249
|
+
}
|
|
250
|
+
let x = 5 < 10;
|
|
251
|
+
<div>{x}</div>
|
|
181
252
|
}
|
|
182
253
|
|
|
183
254
|
render(App);
|
|
184
255
|
});
|
|
185
256
|
|
|
186
257
|
it('renders lexical blocks without crashing', () => {
|
|
187
|
-
function App() {
|
|
188
|
-
|
|
258
|
+
function App() @{
|
|
259
|
+
<>
|
|
189
260
|
<div>
|
|
190
261
|
const a = 1;
|
|
191
|
-
<div>
|
|
192
|
-
|
|
193
|
-
</div>
|
|
194
|
-
<div>
|
|
195
|
-
const b = 1;
|
|
196
|
-
</div>
|
|
262
|
+
<div>const b = 1;</div>
|
|
263
|
+
<div>const b = 1;</div>
|
|
197
264
|
</div>
|
|
198
265
|
<div>
|
|
199
266
|
const a = 2;
|
|
200
|
-
<div>
|
|
201
|
-
const b = 1;
|
|
202
|
-
</div>
|
|
267
|
+
<div>const b = 1;</div>
|
|
203
268
|
</div>
|
|
204
|
-
|
|
269
|
+
</>
|
|
205
270
|
}
|
|
206
271
|
|
|
207
272
|
render(App);
|
|
208
273
|
});
|
|
209
274
|
|
|
210
275
|
it('renders without crashing using mapped types', () => {
|
|
211
|
-
function App() {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
};
|
|
233
|
-
</>;
|
|
276
|
+
function App() @{
|
|
277
|
+
type RecordKey = 'test';
|
|
278
|
+
type RecordValue = { a: string; b: number };
|
|
279
|
+
const config: Record<RecordKey, RecordValue> = {
|
|
280
|
+
test: {
|
|
281
|
+
a: 'test',
|
|
282
|
+
b: 1,
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
const config2: { [key in RecordKey]: RecordValue } = {
|
|
286
|
+
test: {
|
|
287
|
+
a: 'test2',
|
|
288
|
+
b: 2,
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
const config3: { [key: string]: RecordValue } = {
|
|
292
|
+
test: {
|
|
293
|
+
a: 'test3',
|
|
294
|
+
b: 3,
|
|
295
|
+
},
|
|
296
|
+
};
|
|
234
297
|
}
|
|
235
298
|
|
|
236
299
|
render(App);
|
|
237
300
|
});
|
|
238
301
|
|
|
239
302
|
it('renders without crashing using object destructuring', () => {
|
|
240
|
-
function App() {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
</div>
|
|
254
|
-
</>;
|
|
303
|
+
function App() @{
|
|
304
|
+
const obj = { a: 1, b: 2, c: 3 };
|
|
305
|
+
const { a, b, ...rest } = obj;
|
|
306
|
+
<div>
|
|
307
|
+
{'a '}
|
|
308
|
+
{a}
|
|
309
|
+
{'b '}
|
|
310
|
+
{b}
|
|
311
|
+
{'rest '}
|
|
312
|
+
{JSON.stringify(rest)}
|
|
313
|
+
|
|
314
|
+
<div />
|
|
315
|
+
</div>
|
|
255
316
|
}
|
|
256
317
|
|
|
257
318
|
render(App);
|
|
258
319
|
});
|
|
259
320
|
|
|
260
321
|
it('renders without crashing using object destructuring #2', () => {
|
|
261
|
-
function App() {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
322
|
+
function App() @{
|
|
323
|
+
const obj = { a: 1, b: 2, c: 3 };
|
|
324
|
+
const { a, b, ...rest } = obj;
|
|
325
|
+
<>
|
|
265
326
|
{'a '}
|
|
266
327
|
{a}
|
|
267
328
|
{'b '}
|
|
@@ -269,7 +330,7 @@ describe('compiler > basics', () => {
|
|
|
269
330
|
{'rest '}
|
|
270
331
|
{JSON.stringify(rest)}
|
|
271
332
|
<div />
|
|
272
|
-
|
|
333
|
+
</>
|
|
273
334
|
}
|
|
274
335
|
|
|
275
336
|
render(App);
|
|
@@ -288,35 +349,33 @@ describe('compiler > basics', () => {
|
|
|
288
349
|
};
|
|
289
350
|
}
|
|
290
351
|
|
|
291
|
-
function App() {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
352
|
+
function App() @{
|
|
353
|
+
let x: number[] = [] as number[];
|
|
354
|
+
const n = Wrapper<number>().unwrap<string>();
|
|
355
|
+
const tagResult = tagFn`value`;
|
|
356
|
+
interface Node<T> {
|
|
357
|
+
value: T;
|
|
358
|
+
}
|
|
359
|
+
class Box<T> {
|
|
360
|
+
value: T;
|
|
361
|
+
|
|
362
|
+
method<U extends T>(): U {
|
|
363
|
+
return this.value as U;
|
|
298
364
|
}
|
|
299
|
-
class Box<T> {
|
|
300
|
-
value: T;
|
|
301
365
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
constructor(value: T) {
|
|
307
|
-
this.value = value;
|
|
308
|
-
}
|
|
366
|
+
constructor(value: T) {
|
|
367
|
+
this.value = value;
|
|
309
368
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
369
|
+
}
|
|
370
|
+
let flag = true;
|
|
371
|
+
const s = flag ? new Box<number>(1) : new Box<string>('string');
|
|
313
372
|
}
|
|
314
373
|
|
|
315
374
|
render(App);
|
|
316
375
|
});
|
|
317
376
|
|
|
318
377
|
it('compiles without needing semicolons between statements and JSX', () => {
|
|
319
|
-
const source = `export function App() {
|
|
378
|
+
const source = `export function App() @{
|
|
320
379
|
<div>const code4 = 4
|
|
321
380
|
|
|
322
381
|
const code3 = 3
|
|
@@ -327,16 +386,18 @@ describe('compiler > basics', () => {
|
|
|
327
386
|
const code2 = 2
|
|
328
387
|
</div>
|
|
329
388
|
</div>
|
|
330
|
-
|
|
389
|
+
}`;
|
|
331
390
|
|
|
332
391
|
const result = compile(source, 'test.tsrx', { mode: 'client' });
|
|
333
392
|
});
|
|
334
393
|
|
|
335
394
|
it('calculates fragment hop count for sibling fragments', () => {
|
|
336
|
-
const source = `export function App() {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
395
|
+
const source = `export function App() @{
|
|
396
|
+
<>
|
|
397
|
+
<div class="a">{'a'}</div>
|
|
398
|
+
<div class="b">{'b'}</div>
|
|
399
|
+
</>
|
|
400
|
+
}`;
|
|
340
401
|
|
|
341
402
|
const { code } = compile(source, 'grouped-count.tsrx', { mode: 'client' });
|
|
342
403
|
|
|
@@ -346,9 +407,9 @@ describe('compiler > basics', () => {
|
|
|
346
407
|
|
|
347
408
|
it('emits anonymous component expressions as arrows in client output', () => {
|
|
348
409
|
const source = `
|
|
349
|
-
const Inline = (props) =>
|
|
410
|
+
const Inline = (props) => @{
|
|
350
411
|
<div>{props.x}</div>
|
|
351
|
-
|
|
412
|
+
}
|
|
352
413
|
`;
|
|
353
414
|
const result = compile(source, 'anonymous-component.tsrx', { mode: 'client' }).code;
|
|
354
415
|
|
|
@@ -361,9 +422,9 @@ const Inline = (props) => <>
|
|
|
361
422
|
|
|
362
423
|
it('emits function-expression components as functions in client output', () => {
|
|
363
424
|
const source = `
|
|
364
|
-
const Inline = function(props) {
|
|
425
|
+
const Inline = function(props) @{
|
|
365
426
|
<div>{props.x}</div>
|
|
366
|
-
|
|
427
|
+
}
|
|
367
428
|
`;
|
|
368
429
|
const result = compile(source, 'anonymous-component.tsrx', { mode: 'client' }).code;
|
|
369
430
|
|
|
@@ -376,17 +437,17 @@ const Inline = function(props) { return <>
|
|
|
376
437
|
|
|
377
438
|
it('emits function calls with nested template returns as expressions in client output', () => {
|
|
378
439
|
const source = `
|
|
379
|
-
function App() {
|
|
440
|
+
function App() @{
|
|
380
441
|
function make(flag) {
|
|
381
442
|
if (flag) {
|
|
382
|
-
return <
|
|
443
|
+
return <span>{'nested'}</span>;
|
|
383
444
|
}
|
|
384
445
|
|
|
385
446
|
return null;
|
|
386
447
|
}
|
|
387
448
|
|
|
388
449
|
<div>{make(true)}</div>
|
|
389
|
-
|
|
450
|
+
}
|
|
390
451
|
`;
|
|
391
452
|
const result = compile(source, 'nested-template-return.tsrx', { mode: 'client' }).code;
|
|
392
453
|
|
|
@@ -398,11 +459,11 @@ function App() { return <>
|
|
|
398
459
|
// () => {
|
|
399
460
|
// const source = `
|
|
400
461
|
// import { RippleArray, RippleObject, RippleSet, RippleMap, createRefKey } from 'ripple';
|
|
401
|
-
// function App() {
|
|
462
|
+
// function App() @{
|
|
402
463
|
// const items = new RippleArray(1, 2, 3);
|
|
403
464
|
// const obj = new RippleObject({ a: 1, b: 2, c: 3 });
|
|
404
|
-
// const set = RippleSet([1, 2, 3]);
|
|
405
|
-
// const map = RippleMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
465
|
+
// const set = new RippleSet([1, 2, 3]);
|
|
466
|
+
// const map = new RippleMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
406
467
|
|
|
407
468
|
// <div ref={() => {}} />
|
|
408
469
|
// }
|
|
@@ -422,11 +483,11 @@ function App() { return <>
|
|
|
422
483
|
// () => {
|
|
423
484
|
// const source = `
|
|
424
485
|
// import { RippleArray as TA, RippleObject as TO, RippleSet as TS, RippleMap as TM, createRefKey as crk } from 'ripple';
|
|
425
|
-
// function App() {
|
|
486
|
+
// function App() @{
|
|
426
487
|
// const items = new RippleArray(1, 2, 3);
|
|
427
488
|
// const obj = new RippleObject({ a: 1, b: 2, c: 3 });
|
|
428
|
-
// const set = RippleSet([1, 2, 3]);
|
|
429
|
-
// const map = RippleMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
489
|
+
// const set = new RippleSet([1, 2, 3]);
|
|
490
|
+
// const map = new RippleMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
430
491
|
|
|
431
492
|
// <div ref={() => {}} />
|
|
432
493
|
// }
|
|
@@ -448,11 +509,11 @@ function App() { return <>
|
|
|
448
509
|
|
|
449
510
|
// it('adds hidden obfuscated imports for shorthand syntax', () => {
|
|
450
511
|
// const source = `
|
|
451
|
-
// function App() {
|
|
512
|
+
// function App() @{
|
|
452
513
|
// const items = new RippleArray(1, 2, 3);
|
|
453
514
|
// const obj = new RippleObject({ a: 1, b: 2, c: 3 });
|
|
454
|
-
// const set = RippleSet([1, 2, 3]);
|
|
455
|
-
// const map = RippleMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
515
|
+
// const set = new RippleSet([1, 2, 3]);
|
|
516
|
+
// const map = new RippleMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
456
517
|
|
|
457
518
|
// <div ref={() => {}} />
|
|
458
519
|
// }
|
|
@@ -469,7 +530,7 @@ function App() { return <>
|
|
|
469
530
|
it('prints longhand tracked property values in to_ts output while preserving [\'#v\']', () => {
|
|
470
531
|
const source = `
|
|
471
532
|
import { RippleArray, RippleMap, RippleObject, RippleSet, createRefKey, effect, track, untrack } from 'ripple';
|
|
472
|
-
function App() {
|
|
533
|
+
function App() @{
|
|
473
534
|
let value = track('test');
|
|
474
535
|
function inputRef(node) {}
|
|
475
536
|
|
|
@@ -478,7 +539,7 @@ function App() { return <>
|
|
|
478
539
|
value: value.value,
|
|
479
540
|
[createRefKey()]: inputRef,
|
|
480
541
|
};
|
|
481
|
-
|
|
542
|
+
}
|
|
482
543
|
`;
|
|
483
544
|
|
|
484
545
|
const result = compile_to_volar_mappings(source, 'test.tsrx').code;
|
|
@@ -489,10 +550,10 @@ function App() { return <>
|
|
|
489
550
|
it('keeps lazy destructuring as plain destructuring in to_ts output', () => {
|
|
490
551
|
const track_source = `
|
|
491
552
|
import { track } from 'ripple';
|
|
492
|
-
function App() {
|
|
553
|
+
function App() @{
|
|
493
554
|
let &[value, ...rest] = track(0);
|
|
494
555
|
const x = value;
|
|
495
|
-
|
|
556
|
+
}
|
|
496
557
|
`;
|
|
497
558
|
const track_result = compile_to_volar_mappings(track_source, 'test.tsrx').code;
|
|
498
559
|
expect(track_result).toContain('let [value, ...rest] = track(0);');
|
|
@@ -505,34 +566,33 @@ function App() { return <>
|
|
|
505
566
|
|
|
506
567
|
it('lowers native expression values in to_ts output', () => {
|
|
507
568
|
const source = `
|
|
508
|
-
function App() {
|
|
509
|
-
const nested =
|
|
569
|
+
function App() @{
|
|
570
|
+
const nested = <>
|
|
510
571
|
<span class="nested-tsx">
|
|
511
572
|
{'inside nested tsx'}
|
|
512
573
|
</span>
|
|
513
|
-
|
|
574
|
+
</>;
|
|
514
575
|
const content = <div class="native">{nested}</div>;
|
|
515
576
|
|
|
516
|
-
{content}
|
|
517
|
-
|
|
577
|
+
<>{content}</>
|
|
578
|
+
}
|
|
518
579
|
`;
|
|
519
580
|
const result = compile_to_volar_mappings(source, 'test.tsrx').code;
|
|
520
581
|
|
|
521
|
-
expect(result).
|
|
522
|
-
expect(result).
|
|
523
|
-
expect(result).toContain('const nested = <>');
|
|
582
|
+
expect(result).toContain('const nested = <span class="nested-tsx">');
|
|
583
|
+
expect(result).toContain('return content;');
|
|
524
584
|
expect(result).toContain('const content = <div class="native">');
|
|
525
585
|
});
|
|
526
586
|
|
|
527
587
|
it('keeps assigned style blocks anchored in to_ts output', () => {
|
|
528
588
|
const source = `
|
|
529
|
-
function App() {
|
|
589
|
+
function App() @{
|
|
530
590
|
const styles = <style>
|
|
531
591
|
.logo { display: block; }
|
|
532
592
|
</style>;
|
|
533
593
|
|
|
534
594
|
<div class={styles.logo} />
|
|
535
|
-
|
|
595
|
+
}
|
|
536
596
|
`;
|
|
537
597
|
const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
|
|
538
598
|
const source_offset = source.indexOf('<style>') + 1;
|
|
@@ -556,22 +616,22 @@ function App() { return <>
|
|
|
556
616
|
|
|
557
617
|
it('maps identifiers from native expression values in to_ts output', () => {
|
|
558
618
|
const source = `
|
|
559
|
-
function App() {
|
|
560
|
-
const nested =
|
|
619
|
+
function App() @{
|
|
620
|
+
const nested = <>
|
|
561
621
|
<span class="nested-tsx">
|
|
562
622
|
{'inside nested tsx'}
|
|
563
623
|
</span>
|
|
564
|
-
|
|
624
|
+
</>;
|
|
565
625
|
const content = <div class="native">{nested}</div>;
|
|
566
626
|
|
|
567
|
-
{content}
|
|
568
|
-
|
|
627
|
+
<>{content}</>
|
|
628
|
+
}
|
|
569
629
|
`;
|
|
570
630
|
const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
|
|
571
631
|
const source_declaration = source.indexOf('nested =');
|
|
572
632
|
const source_reference = source.indexOf('nested}</div>');
|
|
573
633
|
const generated_declaration = result.code.indexOf('const nested') + 'const '.length;
|
|
574
|
-
const generated_reference = result.code.indexOf('nested
|
|
634
|
+
const generated_reference = result.code.indexOf('nested}</div>', generated_declaration);
|
|
575
635
|
|
|
576
636
|
function find_mapping(source_offset: number, generated_offset: number) {
|
|
577
637
|
return result.mappings.find(
|
|
@@ -612,9 +672,9 @@ type Props<Item> = {
|
|
|
612
672
|
items: readonly Item[];
|
|
613
673
|
}
|
|
614
674
|
|
|
615
|
-
export function MyComponent<Item>(props: Props<Item>) {
|
|
675
|
+
export function MyComponent<Item>(props: Props<Item>) @{
|
|
616
676
|
<div />
|
|
617
|
-
|
|
677
|
+
}
|
|
618
678
|
`;
|
|
619
679
|
const result = compile_to_volar_mappings(source, 'test.tsrx').code;
|
|
620
680
|
|
|
@@ -623,22 +683,23 @@ export function MyComponent<Item>(props: Props<Item>) { return <>
|
|
|
623
683
|
|
|
624
684
|
it('preserves arrow functions that return TSRX in to_ts output', () => {
|
|
625
685
|
const source = `
|
|
626
|
-
const Inline = (props: { x: string }) =>
|
|
686
|
+
const Inline = (props: { x: string }) => @{
|
|
627
687
|
<div>{props.x}</div>
|
|
628
|
-
|
|
688
|
+
}
|
|
629
689
|
`;
|
|
630
690
|
const result = compile_to_volar_mappings(source, 'test.tsrx').code;
|
|
631
691
|
|
|
632
|
-
expect(result).toContain('const Inline = (props: { x: string }) =>
|
|
692
|
+
expect(result).toContain('const Inline = (props: { x: string }) => {');
|
|
693
|
+
expect(result).toContain('return <div>');
|
|
633
694
|
expect(result).not.toContain('function Inline');
|
|
634
695
|
expect(result).not.toContain('function (props');
|
|
635
696
|
});
|
|
636
697
|
|
|
637
698
|
it('preserves function expressions that return TSRX in to_ts output', () => {
|
|
638
699
|
const source = `
|
|
639
|
-
const Inline = function(props: { x: string }) {
|
|
700
|
+
const Inline = function(props: { x: string }) @{
|
|
640
701
|
<div>{props.x}</div>
|
|
641
|
-
|
|
702
|
+
}
|
|
642
703
|
`;
|
|
643
704
|
const result = compile_to_volar_mappings(source, 'test.tsrx').code;
|
|
644
705
|
|
|
@@ -651,13 +712,13 @@ const Inline = function(props: { x: string }) { return <>
|
|
|
651
712
|
const source = `
|
|
652
713
|
type User = { name: string };
|
|
653
714
|
|
|
654
|
-
function RenderProp<Item>(props: { children: (item: Item) => any }) {
|
|
715
|
+
function RenderProp<Item>(props: { children: (item: Item) => any }) @{}
|
|
655
716
|
|
|
656
|
-
export function App() {
|
|
717
|
+
export function App() @{
|
|
657
718
|
<RenderProp<User>>
|
|
658
719
|
{(item) => item.name}
|
|
659
720
|
</RenderProp>
|
|
660
|
-
|
|
721
|
+
}
|
|
661
722
|
`;
|
|
662
723
|
const result = compile_to_volar_mappings(source, 'test.tsrx').code;
|
|
663
724
|
|
|
@@ -666,13 +727,13 @@ export function App() { return <>
|
|
|
666
727
|
|
|
667
728
|
it('preserves generic type arguments on self-closing JSX component tags in to_ts output', () => {
|
|
668
729
|
const source = `
|
|
669
|
-
function Box<T>({ value }: { value: T }) {
|
|
730
|
+
function Box<T>({ value }: { value: T }) @{
|
|
670
731
|
<div>{String(value)}</div>
|
|
671
|
-
|
|
732
|
+
}
|
|
672
733
|
|
|
673
|
-
export function App() {
|
|
734
|
+
export function App() @{
|
|
674
735
|
<Box<string> value="hi" />
|
|
675
|
-
|
|
736
|
+
}
|
|
676
737
|
`;
|
|
677
738
|
const result = compile_to_volar_mappings(source, 'test.tsrx').code;
|
|
678
739
|
|
|
@@ -772,9 +833,9 @@ interface Props extends PolymorphicProps<'div'> {
|
|
|
772
833
|
id: string;
|
|
773
834
|
}
|
|
774
835
|
|
|
775
|
-
export function App(props: Props) {
|
|
836
|
+
export function App(props: Props) @{
|
|
776
837
|
<div id={props.id} />
|
|
777
|
-
|
|
838
|
+
}
|
|
778
839
|
`;
|
|
779
840
|
|
|
780
841
|
const result = compile_to_volar_mappings(source, 'test.tsrx').code;
|
|
@@ -785,7 +846,7 @@ export function App(props: Props) { return <>
|
|
|
785
846
|
it('handles if-else expression statements in Volar mappings', () => {
|
|
786
847
|
const source = `
|
|
787
848
|
import { track } from 'ripple';
|
|
788
|
-
export function App() {
|
|
849
|
+
export function App() @{
|
|
789
850
|
let &[level] = track(1);
|
|
790
851
|
|
|
791
852
|
<button
|
|
@@ -797,58 +858,61 @@ export function App() { return <>
|
|
|
797
858
|
>
|
|
798
859
|
{'Toggle'}
|
|
799
860
|
</button>
|
|
800
|
-
|
|
861
|
+
}
|
|
801
862
|
`;
|
|
802
863
|
|
|
803
864
|
expect(() => compile_to_volar_mappings(source, 'test.tsrx')).not.toThrow();
|
|
804
865
|
});
|
|
805
866
|
|
|
806
|
-
it('
|
|
867
|
+
it('errors on having js below markup in the same scope', () => {
|
|
807
868
|
const code = `
|
|
808
|
-
function Card(props) {
|
|
869
|
+
function Card(props) @{
|
|
809
870
|
<div class="card">
|
|
810
871
|
{props.children}
|
|
811
872
|
</div>
|
|
812
|
-
|
|
873
|
+
}
|
|
813
874
|
|
|
814
|
-
export function App() {
|
|
815
|
-
function children() {
|
|
875
|
+
export function App() @{
|
|
876
|
+
function children() @{
|
|
816
877
|
<p>{'Card content here'}</p>
|
|
817
|
-
|
|
878
|
+
}
|
|
818
879
|
|
|
819
880
|
<Card {children} />
|
|
820
881
|
|
|
821
882
|
const test = 5;
|
|
822
883
|
|
|
823
884
|
<div>{test}</div>
|
|
824
|
-
|
|
885
|
+
}
|
|
825
886
|
`;
|
|
826
|
-
expect(() => compile(code, 'test.tsrx')).
|
|
887
|
+
expect(() => compile(code, 'test.tsrx')).toThrow(
|
|
888
|
+
/Code must be at the top of '@\{ \}'; statements cannot follow the rendered output/,
|
|
889
|
+
);
|
|
827
890
|
});
|
|
828
891
|
|
|
829
|
-
it('allows component functions
|
|
892
|
+
it('allows component functions passed to composite children', () => {
|
|
830
893
|
const source = `
|
|
831
|
-
export function App() {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
894
|
+
export function App() @{
|
|
895
|
+
function asChild({ children, href, ...rest }: { href: string; [key: string]: any }) @{
|
|
896
|
+
<a id="aschild-anchor" {href} {...rest} data-extra="yes">{'Link'}</a>
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
<ark.div class="host-class" data-value="42" {asChild} />
|
|
900
|
+
}
|
|
838
901
|
`;
|
|
839
902
|
|
|
840
903
|
expect(() => compile_to_volar_mappings(source, 'test.tsrx')).not.toThrow();
|
|
841
904
|
});
|
|
842
905
|
|
|
843
|
-
it('allows parent element attributes referencing
|
|
906
|
+
it('allows parent element attributes referencing setup-declared functions', () => {
|
|
844
907
|
const source = `
|
|
845
|
-
export function App() {
|
|
908
|
+
export function App() @{
|
|
909
|
+
function Z() @{
|
|
910
|
+
<div>{'hello'}</div>
|
|
911
|
+
}
|
|
912
|
+
|
|
846
913
|
<Test {Z}>
|
|
847
|
-
function Z() { return <>
|
|
848
|
-
<div>{'hello'}</div>
|
|
849
|
-
</>; }
|
|
850
914
|
</Test>
|
|
851
|
-
|
|
915
|
+
}
|
|
852
916
|
`;
|
|
853
917
|
|
|
854
918
|
expect(() => compile(source, 'test.tsrx')).not.toThrow();
|
|
@@ -856,13 +920,13 @@ export function App() { return <>
|
|
|
856
920
|
|
|
857
921
|
it('preserves explicit component props in Volar mappings', () => {
|
|
858
922
|
const source = `
|
|
859
|
-
export function App() {
|
|
860
|
-
function asChild({ children, href, ...rest }: { href: string; [key: string]: any }) {
|
|
923
|
+
export function App() @{
|
|
924
|
+
function asChild({ children, href, ...rest }: { href: string; [key: string]: any }) @{
|
|
861
925
|
<a id="aschild-anchor" {href} {...rest} data-extra="yes">{'Link'}</a>
|
|
862
|
-
|
|
926
|
+
}
|
|
863
927
|
|
|
864
928
|
<ark.div class="host-class" data-value="42" {asChild} />
|
|
865
|
-
|
|
929
|
+
}
|
|
866
930
|
`;
|
|
867
931
|
const result = compile_to_volar_mappings(source, 'test.tsrx').code;
|
|
868
932
|
|
|
@@ -870,19 +934,58 @@ export function App() { return <>
|
|
|
870
934
|
expect(result).not.toContain('children={() =>');
|
|
871
935
|
});
|
|
872
936
|
|
|
937
|
+
it('uses direct TSRX render expressions for component children in Volar mappings', () => {
|
|
938
|
+
const source = `
|
|
939
|
+
function OtherComponent({ children }: { children: string }) @{
|
|
940
|
+
<div>{children}</div>
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
export const App = () => @{
|
|
944
|
+
<>
|
|
945
|
+
@try {
|
|
946
|
+
<ComponentThatSuspends />
|
|
947
|
+
} @pending {
|
|
948
|
+
<div>Loading</div>
|
|
949
|
+
}
|
|
950
|
+
<OtherComponent children={'I like TSRX'} />
|
|
951
|
+
</>
|
|
952
|
+
};
|
|
953
|
+
`;
|
|
954
|
+
const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
|
|
955
|
+
const source_offset = source.indexOf('\'I like TSRX\'');
|
|
956
|
+
const generated_offset = result.code.indexOf('\'I like TSRX\'');
|
|
957
|
+
|
|
958
|
+
expect(result.code).toContain('<OtherComponent children={\'I like TSRX\'}');
|
|
959
|
+
expect(result.code).not.toContain('children={() =>');
|
|
960
|
+
expect(
|
|
961
|
+
result.mappings.find(
|
|
962
|
+
(mapping: {
|
|
963
|
+
sourceOffsets: number[];
|
|
964
|
+
generatedOffsets: number[];
|
|
965
|
+
lengths: number[];
|
|
966
|
+
generatedLengths: number[];
|
|
967
|
+
}) =>
|
|
968
|
+
mapping.sourceOffsets[0] === source_offset &&
|
|
969
|
+
mapping.generatedOffsets[0] === generated_offset &&
|
|
970
|
+
mapping.lengths[0] === '\'I like TSRX\''.length &&
|
|
971
|
+
mapping.generatedLengths[0] === '\'I like TSRX\''.length,
|
|
972
|
+
),
|
|
973
|
+
).toBeDefined();
|
|
974
|
+
});
|
|
975
|
+
|
|
873
976
|
it('merges explicit children prop with implicit children in client output', () => {
|
|
874
977
|
const source = `
|
|
875
|
-
function Card(props) {
|
|
978
|
+
function Card(props) @{
|
|
876
979
|
<div>{props.children}</div>
|
|
877
|
-
|
|
980
|
+
}
|
|
878
981
|
|
|
879
|
-
export function App() {
|
|
982
|
+
export function App() @{
|
|
880
983
|
const fallback = 'fallback';
|
|
881
984
|
|
|
882
985
|
<Card children={fallback}>
|
|
883
986
|
<span>{'content'}</span>
|
|
884
987
|
</Card>
|
|
885
|
-
|
|
988
|
+
}
|
|
886
989
|
`;
|
|
887
990
|
|
|
888
991
|
const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
|
|
@@ -901,10 +1004,10 @@ class Test {
|
|
|
901
1004
|
}
|
|
902
1005
|
}
|
|
903
1006
|
|
|
904
|
-
export function App() {
|
|
1007
|
+
export function App() @{
|
|
905
1008
|
const test = new Test();
|
|
906
1009
|
<div>{test.count}</div>
|
|
907
|
-
|
|
1010
|
+
}
|
|
908
1011
|
`;
|
|
909
1012
|
expect(() => compile(code, 'test.tsrx')).not.toThrow();
|
|
910
1013
|
});
|
|
@@ -920,10 +1023,10 @@ class Store {
|
|
|
920
1023
|
}
|
|
921
1024
|
}
|
|
922
1025
|
|
|
923
|
-
export function App() {
|
|
1026
|
+
export function App() @{
|
|
924
1027
|
const store = new Store();
|
|
925
1028
|
<div>{store.count}</div>
|
|
926
|
-
|
|
1029
|
+
}
|
|
927
1030
|
`;
|
|
928
1031
|
const result = compile(source, 'test.tsrx', { mode: 'client' });
|
|
929
1032
|
const code = result.code;
|
|
@@ -937,14 +1040,14 @@ export function App() { return <>
|
|
|
937
1040
|
const source = `
|
|
938
1041
|
import { track, effect, untrack } from 'ripple';
|
|
939
1042
|
|
|
940
|
-
function App() {
|
|
1043
|
+
function App() @{
|
|
941
1044
|
let &[count] = track(0);
|
|
942
1045
|
|
|
943
1046
|
effect(() => {
|
|
944
1047
|
const snapshot = untrack(() => count);
|
|
945
1048
|
console.log(snapshot);
|
|
946
1049
|
});
|
|
947
|
-
|
|
1050
|
+
}
|
|
948
1051
|
`;
|
|
949
1052
|
|
|
950
1053
|
const ast = parse(source);
|
|
@@ -957,10 +1060,10 @@ function App() { return <>
|
|
|
957
1060
|
it('collects duplicate declaration parser errors in loose mode', () => {
|
|
958
1061
|
const source = `
|
|
959
1062
|
import { track } from 'ripple';
|
|
960
|
-
export function App() {
|
|
1063
|
+
export function App() @{
|
|
961
1064
|
let test = track(false);
|
|
962
1065
|
let test = 'hey';
|
|
963
|
-
|
|
1066
|
+
}
|
|
964
1067
|
`;
|
|
965
1068
|
|
|
966
1069
|
expect(() => compile(source, 'test.tsrx')).toThrow(
|
|
@@ -986,11 +1089,11 @@ export function App() { return <>
|
|
|
986
1089
|
import { loadUser as getUser } from server;
|
|
987
1090
|
import { loadUser } from server;
|
|
988
1091
|
|
|
989
|
-
function App() {
|
|
1092
|
+
function App() @{
|
|
990
1093
|
const user = getUser();
|
|
991
1094
|
const user2 = loadUser();
|
|
992
1095
|
<div>{user.id}{user2.id}</div>
|
|
993
|
-
|
|
1096
|
+
}`;
|
|
994
1097
|
const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
|
|
995
1098
|
const generated_member = '_$_server_$_.loadUser';
|
|
996
1099
|
const generated_member_offset = result.code.indexOf(generated_member);
|
|
@@ -1046,49 +1149,21 @@ function App() { return <>
|
|
|
1046
1149
|
).toBeDefined();
|
|
1047
1150
|
});
|
|
1048
1151
|
|
|
1049
|
-
it('
|
|
1050
|
-
const result = compile_to_volar_mappings(
|
|
1051
|
-
`function App() { return <>
|
|
1152
|
+
it('allows return statements inside returned TSRX fragments in Volar mappings', () => {
|
|
1153
|
+
const result = compile_to_volar_mappings(`function App() @{
|
|
1052
1154
|
return <div />;
|
|
1053
|
-
|
|
1054
|
-
'test.tsrx',
|
|
1055
|
-
);
|
|
1056
|
-
|
|
1057
|
-
expect(result.errors.map((error) => error.code)).toEqual(['tsrx-template-return-statement']);
|
|
1058
|
-
});
|
|
1059
|
-
|
|
1060
|
-
it('throws for unclosed tsx compat tags instead of hanging', () => {
|
|
1061
|
-
const source = `export function App() { return <tsx:react>1; }`;
|
|
1062
|
-
|
|
1063
|
-
expect(() => compile(source, 'test.tsrx')).toThrow(
|
|
1064
|
-
'Unclosed tag \'<tsx:react>\'. Expected \'</tsx:react>\' before end of template.',
|
|
1065
|
-
);
|
|
1066
|
-
});
|
|
1067
|
-
|
|
1068
|
-
it('recovers unclosed tsx compat tags in loose mode', () => {
|
|
1069
|
-
const source = `export function App() { return <tsx:react>1; }`;
|
|
1155
|
+
}`, 'test.tsrx');
|
|
1070
1156
|
|
|
1071
|
-
expect(() => compile_to_volar_mappings(source, 'test.tsrx', { loose: true })).not.toThrow();
|
|
1072
|
-
|
|
1073
|
-
const result = compile_to_volar_mappings(source, 'test.tsrx', { loose: true });
|
|
1074
1157
|
expect(result.errors).toEqual([]);
|
|
1075
1158
|
});
|
|
1076
1159
|
|
|
1077
|
-
it('throws for unclosed tsx compat tags outside loose mode', () => {
|
|
1078
|
-
const source = `export function App() { return <tsx:react>1; }`;
|
|
1079
|
-
|
|
1080
|
-
expect(() => compile(source, 'test.tsrx', { collect: true })).toThrow(
|
|
1081
|
-
'Not implemented: TsxCompat',
|
|
1082
|
-
);
|
|
1083
|
-
});
|
|
1084
|
-
|
|
1085
1160
|
it('collects analyzer errors outside loose mode', () => {
|
|
1086
1161
|
const source = `
|
|
1087
1162
|
import { track } from 'ripple';
|
|
1088
1163
|
|
|
1089
1164
|
const outside = track(0);
|
|
1090
1165
|
|
|
1091
|
-
export function App() {
|
|
1166
|
+
export function App() @{}
|
|
1092
1167
|
`;
|
|
1093
1168
|
|
|
1094
1169
|
const result = compile(source, 'test.tsrx', { collect: true });
|
|
@@ -1099,23 +1174,23 @@ export function App() { return <></>; }
|
|
|
1099
1174
|
|
|
1100
1175
|
it('does not let nested for...of continues satisfy an outer if body', () => {
|
|
1101
1176
|
const source = `
|
|
1102
|
-
export function App({ items }: { items: string[] }) {
|
|
1177
|
+
export function App({ items }: { items: string[] }) @{
|
|
1103
1178
|
if (items.length) {
|
|
1104
1179
|
for (const item of items) {
|
|
1105
1180
|
if (!item) continue
|
|
1106
1181
|
}
|
|
1107
1182
|
}
|
|
1108
|
-
|
|
1183
|
+
}`;
|
|
1109
1184
|
|
|
1110
1185
|
const result = compile(source, 'test.tsrx', { collect: true });
|
|
1111
|
-
expect(result.errors.map((error) => error.message)).toContain(
|
|
1186
|
+
expect(result.errors.map((error) => error.message)).not.toContain(
|
|
1112
1187
|
'Component if statements must contain a template in their "then" body. Move the if statement into an effect if it does not render anything.',
|
|
1113
1188
|
);
|
|
1114
1189
|
});
|
|
1115
1190
|
|
|
1116
1191
|
it('preserves class extends generic type arguments in volar output', () => {
|
|
1117
1192
|
const source = `class StringMap extends Map<string, string> {}
|
|
1118
|
-
export function App() {
|
|
1193
|
+
export function App() @{}`;
|
|
1119
1194
|
|
|
1120
1195
|
expect(() => compile_to_volar_mappings(source, 'test.tsrx', { loose: true })).not.toThrow();
|
|
1121
1196
|
|
|
@@ -1126,15 +1201,15 @@ export function App() { return <></>; }`;
|
|
|
1126
1201
|
|
|
1127
1202
|
it('wraps children in normalize_children for explicit children prop passed to component', () => {
|
|
1128
1203
|
const source = `
|
|
1129
|
-
function Card(props) {
|
|
1204
|
+
function Card(props) @{
|
|
1130
1205
|
<div>{props.children}</div>
|
|
1131
|
-
|
|
1206
|
+
}
|
|
1132
1207
|
|
|
1133
|
-
export function App() {
|
|
1208
|
+
export function App() @{
|
|
1134
1209
|
const content = 'hello';
|
|
1135
1210
|
|
|
1136
1211
|
<Card children={content} />
|
|
1137
|
-
|
|
1212
|
+
}
|
|
1138
1213
|
`;
|
|
1139
1214
|
|
|
1140
1215
|
const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
|
|
@@ -1145,35 +1220,35 @@ export function App() { return <>
|
|
|
1145
1220
|
it(
|
|
1146
1221
|
'parses a JS statement inside an element with no trailing whitespace before the closing tag',
|
|
1147
1222
|
() => {
|
|
1148
|
-
const source = `function TodoList({ items }: { items: { text: string }[] }) {
|
|
1223
|
+
const source = `function TodoList({ items }: { items: { text: string }[] }) @{
|
|
1149
1224
|
<ul>var a = "123"</ul>
|
|
1150
|
-
|
|
1225
|
+
}`;
|
|
1151
1226
|
const ast = parse(source);
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
1227
|
+
const returned = get_returned_tsrx(ast.body[0]);
|
|
1228
|
+
const ul =
|
|
1229
|
+
returned.type === 'Element'
|
|
1230
|
+
? returned
|
|
1231
|
+
: returned.children.find((n: AST.Node) => n.type === 'Element') as AST.Element;
|
|
1155
1232
|
expect((ul.id as AST.Identifier).name).toBe('ul');
|
|
1156
1233
|
expect(ul.children).toHaveLength(1);
|
|
1157
|
-
const
|
|
1158
|
-
expect(
|
|
1159
|
-
expect(
|
|
1160
|
-
expect((decl.declarations[0].id as AST.Identifier).name).toBe('a');
|
|
1161
|
-
expect((decl.declarations[0].init as AST.Literal).value).toBe('123');
|
|
1234
|
+
const text = ul.children[0] as unknown as { type: string; expression: AST.Literal };
|
|
1235
|
+
expect(text.type).toBe('Text');
|
|
1236
|
+
expect(text.expression.value).toBe('var a = "123"');
|
|
1162
1237
|
expect((ul.closingElement?.name as ESTreeJSX.JSXIdentifier)?.name).toBe('ul');
|
|
1163
1238
|
},
|
|
1164
1239
|
);
|
|
1165
1240
|
|
|
1166
1241
|
it('uses spread_props for spreads that may contain children', () => {
|
|
1167
1242
|
const source = `
|
|
1168
|
-
function Card(props) {
|
|
1243
|
+
function Card(props) @{
|
|
1169
1244
|
<div>{props.children}</div>
|
|
1170
|
-
|
|
1245
|
+
}
|
|
1171
1246
|
|
|
1172
|
-
export function App() {
|
|
1247
|
+
export function App() @{
|
|
1173
1248
|
const props = { children: 'hello' };
|
|
1174
1249
|
|
|
1175
1250
|
<Card {...props} />
|
|
1176
|
-
|
|
1251
|
+
}
|
|
1177
1252
|
`;
|
|
1178
1253
|
|
|
1179
1254
|
const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
|
|
@@ -1181,19 +1256,12 @@ export function App() { return <>
|
|
|
1181
1256
|
expect(result).toContain('_$_.spread_props(');
|
|
1182
1257
|
});
|
|
1183
1258
|
|
|
1184
|
-
it('
|
|
1185
|
-
const source = `function TodoList({ items }: { items: { text: string }[] }) {
|
|
1259
|
+
it('rejects less-than comparisons at line start in element children without whitespace', () => {
|
|
1260
|
+
const source = `function TodoList({ items }: { items: { text: string }[] }) @{
|
|
1186
1261
|
<ul>var a = 3
|
|
1187
1262
|
<4;</ul>
|
|
1188
|
-
|
|
1263
|
+
}`;
|
|
1189
1264
|
|
|
1190
|
-
|
|
1191
|
-
const ul = get_returned_tsrx(ast.body[0]).children.find(
|
|
1192
|
-
(n: AST.Node) => n.type === 'Element',
|
|
1193
|
-
) as AST.Element;
|
|
1194
|
-
expect((ul.id as AST.Identifier).name).toBe('ul');
|
|
1195
|
-
expect(ul.children.length).toBeGreaterThanOrEqual(1);
|
|
1196
|
-
const decl = ul.children[0] as unknown as AST.VariableDeclaration;
|
|
1197
|
-
expect(decl.type).toBe('VariableDeclaration');
|
|
1265
|
+
expect(() => parse(source)).toThrow();
|
|
1198
1266
|
});
|
|
1199
1267
|
});
|