ripple 0.3.72 → 0.3.76

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.
Files changed (172) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/package.json +3 -3
  3. package/src/jsx-runtime.d.ts +4 -10
  4. package/src/runtime/dynamic-client.js +33 -0
  5. package/src/runtime/dynamic-server.js +80 -0
  6. package/src/runtime/index-client.js +5 -13
  7. package/src/runtime/index-server.js +2 -0
  8. package/src/runtime/internal/client/blocks.js +6 -27
  9. package/src/runtime/internal/client/composite.js +11 -6
  10. package/src/runtime/internal/client/for.js +80 -5
  11. package/src/runtime/internal/client/index.js +0 -2
  12. package/src/runtime/internal/client/render.js +5 -2
  13. package/src/runtime/internal/client/types.d.ts +0 -10
  14. package/src/runtime/internal/server/index.js +8 -1
  15. package/tests/client/__snapshots__/computed-properties.test.tsrx.snap +8 -0
  16. package/tests/client/__snapshots__/for.test.tsrx.snap +22 -0
  17. package/tests/client/__snapshots__/html.test.tsrx.snap +4 -0
  18. package/tests/client/array/array.copy-within.test.tsrx +19 -19
  19. package/tests/client/array/array.derived.test.tsrx +97 -109
  20. package/tests/client/array/array.iteration.test.tsrx +28 -28
  21. package/tests/client/array/array.mutations.test.tsrx +68 -68
  22. package/tests/client/array/array.static.test.tsrx +82 -92
  23. package/tests/client/array/array.to-methods.test.tsrx +15 -15
  24. package/tests/client/async-suspend.test.tsrx +180 -179
  25. package/tests/client/basic/__snapshots__/basic.attributes.test.tsrx.snap +2 -0
  26. package/tests/client/basic/__snapshots__/basic.rendering.test.tsrx.snap +4 -0
  27. package/tests/client/basic/basic.attributes.test.tsrx +273 -317
  28. package/tests/client/basic/basic.collections.test.tsrx +55 -61
  29. package/tests/client/basic/basic.components.test.tsrx +198 -220
  30. package/tests/client/basic/basic.errors.test.tsrx +70 -76
  31. package/tests/client/basic/basic.events.test.tsrx +80 -85
  32. package/tests/client/basic/basic.get-set.test.tsrx +54 -64
  33. package/tests/client/basic/basic.hmr.test.tsrx +15 -19
  34. package/tests/client/basic/basic.reactivity.test.tsrx +121 -135
  35. package/tests/client/basic/basic.rendering.test.tsrx +273 -178
  36. package/tests/client/basic/basic.styling.test.tsrx +16 -14
  37. package/tests/client/basic/basic.utilities.test.tsrx +8 -10
  38. package/tests/client/boundaries.test.tsrx +18 -18
  39. package/tests/client/compiler/compiler.assignments.test.tsrx +77 -76
  40. package/tests/client/compiler/compiler.attributes.test.tsrx +18 -14
  41. package/tests/client/compiler/compiler.basic.test.tsrx +357 -288
  42. package/tests/client/compiler/compiler.regex.test.tsrx +40 -44
  43. package/tests/client/compiler/compiler.tracked-access.test.tsrx +57 -38
  44. package/tests/client/compiler/compiler.try-in-function.test.tsrx +16 -16
  45. package/tests/client/compiler/compiler.typescript.test.tsrx +4 -3
  46. package/tests/client/composite/composite.dynamic-components.test.tsrx +62 -47
  47. package/tests/client/composite/composite.generics.test.tsrx +165 -167
  48. package/tests/client/composite/composite.props.test.tsrx +66 -74
  49. package/tests/client/composite/composite.reactivity.test.tsrx +132 -166
  50. package/tests/client/composite/composite.render.test.tsrx +92 -101
  51. package/tests/client/computed-properties.test.tsrx +14 -18
  52. package/tests/client/context.test.tsrx +14 -18
  53. package/tests/client/css/global-additional-cases.test.tsrx +493 -439
  54. package/tests/client/css/global-advanced-selectors.test.tsrx +169 -153
  55. package/tests/client/css/global-at-rules.test.tsrx +71 -66
  56. package/tests/client/css/global-basic.test.tsrx +105 -98
  57. package/tests/client/css/global-classes-ids.test.tsrx +128 -114
  58. package/tests/client/css/global-combinators.test.tsrx +83 -78
  59. package/tests/client/css/global-complex-nesting.test.tsrx +134 -120
  60. package/tests/client/css/global-edge-cases.test.tsrx +138 -120
  61. package/tests/client/css/global-keyframes.test.tsrx +108 -96
  62. package/tests/client/css/global-nested.test.tsrx +88 -78
  63. package/tests/client/css/global-pseudo.test.tsrx +104 -98
  64. package/tests/client/css/global-scoping.test.tsrx +145 -125
  65. package/tests/client/css/style-identifier.test.tsrx +65 -72
  66. package/tests/client/date.test.tsrx +83 -83
  67. package/tests/client/dynamic-elements.test.tsrx +318 -299
  68. package/tests/client/events.test.tsrx +252 -266
  69. package/tests/client/for.test.tsrx +120 -127
  70. package/tests/client/head.test.tsrx +74 -48
  71. package/tests/client/html.test.tsrx +37 -49
  72. package/tests/client/input-value.test.tsrx +1125 -1354
  73. package/tests/client/lazy-array.test.tsrx +10 -16
  74. package/tests/client/lazy-destructuring.test.tsrx +169 -221
  75. package/tests/client/map.test.tsrx +39 -41
  76. package/tests/client/media-query.test.tsrx +15 -19
  77. package/tests/client/object.test.tsrx +46 -56
  78. package/tests/client/portal.test.tsrx +31 -37
  79. package/tests/client/ref.test.tsrx +173 -193
  80. package/tests/client/return.test.tsrx +62 -37
  81. package/tests/client/set.test.tsrx +33 -33
  82. package/tests/client/svg.test.tsrx +197 -216
  83. package/tests/client/switch.test.tsrx +201 -191
  84. package/tests/client/track-async-hydration.test.tsrx +14 -18
  85. package/tests/client/tracked-index-access.test.tsrx +18 -28
  86. package/tests/client/try.test.tsrx +494 -619
  87. package/tests/client/tsx.test.tsrx +286 -292
  88. package/tests/client/typescript-generics.test.tsrx +121 -129
  89. package/tests/client/url/url.derived.test.tsrx +21 -25
  90. package/tests/client/url/url.parsing.test.tsrx +35 -35
  91. package/tests/client/url/url.partial-removal.test.tsrx +32 -32
  92. package/tests/client/url/url.reactivity.test.tsrx +68 -72
  93. package/tests/client/url/url.serialization.test.tsrx +8 -8
  94. package/tests/client/url-search-params/url-search-params.derived.test.tsrx +21 -27
  95. package/tests/client/url-search-params/url-search-params.initialization.test.tsrx +16 -16
  96. package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +37 -37
  97. package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +56 -60
  98. package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +32 -34
  99. package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +9 -9
  100. package/tests/client/url-search-params/url-search-params.tracked-url.test.tsrx +10 -10
  101. package/tests/hydration/compiled/client/basic.js +390 -319
  102. package/tests/hydration/compiled/client/composite.js +52 -44
  103. package/tests/hydration/compiled/client/for.js +734 -604
  104. package/tests/hydration/compiled/client/head.js +183 -103
  105. package/tests/hydration/compiled/client/html.js +93 -86
  106. package/tests/hydration/compiled/client/if-children.js +95 -71
  107. package/tests/hydration/compiled/client/if.js +113 -89
  108. package/tests/hydration/compiled/client/mixed-control-flow.js +225 -209
  109. package/tests/hydration/compiled/client/nested-control-flow.js +94 -98
  110. package/tests/hydration/compiled/client/reactivity.js +26 -24
  111. package/tests/hydration/compiled/client/return.js +8 -42
  112. package/tests/hydration/compiled/client/switch.js +208 -173
  113. package/tests/hydration/compiled/client/track-async-serialization.js +176 -128
  114. package/tests/hydration/compiled/client/try.js +29 -21
  115. package/tests/hydration/compiled/server/basic.js +210 -221
  116. package/tests/hydration/compiled/server/composite.js +13 -14
  117. package/tests/hydration/compiled/server/for.js +427 -444
  118. package/tests/hydration/compiled/server/head.js +199 -189
  119. package/tests/hydration/compiled/server/html.js +33 -41
  120. package/tests/hydration/compiled/server/if-children.js +114 -117
  121. package/tests/hydration/compiled/server/if.js +77 -83
  122. package/tests/hydration/compiled/server/mixed-control-flow.js +145 -150
  123. package/tests/hydration/compiled/server/nested-control-flow.js +10 -0
  124. package/tests/hydration/compiled/server/reactivity.js +24 -22
  125. package/tests/hydration/compiled/server/return.js +6 -18
  126. package/tests/hydration/compiled/server/switch.js +179 -176
  127. package/tests/hydration/compiled/server/track-async-serialization.js +88 -70
  128. package/tests/hydration/compiled/server/try.js +31 -35
  129. package/tests/hydration/components/basic.tsrx +216 -258
  130. package/tests/hydration/components/composite.tsrx +32 -42
  131. package/tests/hydration/components/events.tsrx +81 -101
  132. package/tests/hydration/components/for.tsrx +270 -336
  133. package/tests/hydration/components/head.tsrx +43 -39
  134. package/tests/hydration/components/hmr.tsrx +16 -22
  135. package/tests/hydration/components/html-in-template.tsrx +15 -21
  136. package/tests/hydration/components/html.tsrx +442 -526
  137. package/tests/hydration/components/if-children.tsrx +107 -125
  138. package/tests/hydration/components/if.tsrx +68 -90
  139. package/tests/hydration/components/mixed-control-flow.tsrx +65 -72
  140. package/tests/hydration/components/nested-control-flow.tsrx +202 -216
  141. package/tests/hydration/components/portal.tsrx +33 -41
  142. package/tests/hydration/components/reactivity.tsrx +26 -34
  143. package/tests/hydration/components/return.tsrx +4 -6
  144. package/tests/hydration/components/switch.tsrx +73 -78
  145. package/tests/hydration/components/track-async-serialization.tsrx +83 -93
  146. package/tests/hydration/components/try.tsrx +37 -51
  147. package/tests/hydration/switch.test.js +8 -8
  148. package/tests/server/await.test.tsrx +3 -3
  149. package/tests/server/basic.attributes.test.tsrx +117 -162
  150. package/tests/server/basic.components.test.tsrx +164 -194
  151. package/tests/server/basic.test.tsrx +299 -199
  152. package/tests/server/compiler.test.tsrx +142 -72
  153. package/tests/server/composite.props.test.tsrx +54 -58
  154. package/tests/server/composite.test.tsrx +165 -167
  155. package/tests/server/context.test.tsrx +13 -17
  156. package/tests/server/dynamic-elements.test.tsrx +147 -148
  157. package/tests/server/for.test.tsrx +115 -84
  158. package/tests/server/head.test.tsrx +54 -31
  159. package/tests/server/html-nesting-validation.test.tsrx +16 -8
  160. package/tests/server/if.test.tsrx +49 -59
  161. package/tests/server/lazy-destructuring.test.tsrx +288 -366
  162. package/tests/server/return.test.tsrx +58 -36
  163. package/tests/server/streaming-ssr.test.tsrx +4 -4
  164. package/tests/server/style-identifier.test.tsrx +61 -69
  165. package/tests/server/switch.test.tsrx +89 -97
  166. package/tests/server/track-async-serialization.test.tsrx +85 -103
  167. package/tests/server/try.test.tsrx +275 -360
  168. package/tests/utils/ref-types.test.js +72 -0
  169. package/tests/utils/vite-plugin-config.test.js +41 -74
  170. package/types/index.d.ts +29 -4
  171. package/src/runtime/internal/client/compat.js +0 -40
  172. package/tests/utils/compiler-compat-config.test.js +0 -38
@@ -3,8 +3,8 @@ import { flushSync, track } from 'ripple';
3
3
 
4
4
  describe('basic client > rendering & text', () => {
5
5
  it('renders static text', () => {
6
- function Basic() {
7
- return <><div>{'Hello World'}</div></>;
6
+ function Basic() @{
7
+ <div>{'Hello World'}</div>
8
8
  }
9
9
 
10
10
  expect(container).toBeDefined();
@@ -14,11 +14,9 @@ describe('basic client > rendering & text', () => {
14
14
  });
15
15
 
16
16
  it('renders semi-dynamic text', () => {
17
- function Basic() {
18
- return <>
19
- let message = 'Hello World';
20
- <div>{message}</div>
21
- </>;
17
+ function Basic() @{
18
+ let message = 'Hello World';
19
+ <div>{message}</div>
22
20
  }
23
21
 
24
22
  render(Basic);
@@ -27,11 +25,9 @@ describe('basic client > rendering & text', () => {
27
25
  });
28
26
 
29
27
  it('renders string interpolation without creating HTML', () => {
30
- function Basic() {
31
- return <>
32
- let markup = '<span>Not HTML</span>';
33
- <div>{markup}</div>
34
- </>;
28
+ function Basic() @{
29
+ let markup = '<span>Not HTML</span>';
30
+ <div>{markup}</div>
35
31
  }
36
32
 
37
33
  render(Basic);
@@ -42,32 +38,35 @@ describe('basic client > rendering & text', () => {
42
38
  expect(div.querySelector('span')).toBeNull();
43
39
  });
44
40
 
45
- it('renders direct double-quoted text children as text', () => {
46
- function Basic() {
47
- return <>
48
- <div class="entities">"Rock &amp; &quot;Roll&quot;"</div>
49
- <div class="backslash">"line\nbreak"</div>
50
- <pre class="multiline">"first
51
- second"</pre>
52
- </>;
41
+ it('renders direct JSX text children as text', () => {
42
+ function Basic() @{
43
+ <>
44
+ <div class="entities">Rock &amp; &quot;Roll&quot;</div>
45
+ <div class="backslash">line break</div>
46
+ <pre class="multiline">
47
+ first
48
+ second
49
+ </pre>
50
+ </>
53
51
  }
54
52
 
55
53
  render(Basic);
56
54
 
57
55
  expect(container.querySelector('.entities').textContent).toBe('Rock & "Roll"');
58
- expect(container.querySelector('.backslash').textContent).toBe('line\\nbreak');
56
+ expect(container.querySelector('.backslash').textContent).toBe('line break');
59
57
  expect(container.querySelector('.multiline').textContent).toBe('first\nsecond');
60
58
  });
61
59
 
62
- it('does not render TSRX statements outside returned component templates', () => {
60
+ it('does not render JavaScript statements outside returned component templates', () => {
63
61
  function Basic() {
64
62
  const ready = true;
65
63
 
66
64
  if (ready) {
67
- <div class="leaked">"should not render"</div>
65
+ const leaked = 'should not render';
66
+ void leaked;
68
67
  }
69
68
 
70
- return <>"Hello world"</>;
69
+ return <>Hello world</>;
71
70
  }
72
71
 
73
72
  render(Basic);
@@ -77,14 +76,12 @@ second"</pre>
77
76
  });
78
77
 
79
78
  it('renders primitive component return branches', () => {
80
- function Basic() {
79
+ function Basic() @{
81
80
  const ready = false;
82
-
83
81
  if (!ready) {
84
82
  return 'Waiting';
85
83
  }
86
-
87
- return <>"Ready"</>;
84
+ <>Ready</>
88
85
  }
89
86
 
90
87
  render(Basic);
@@ -122,10 +119,127 @@ second"</pre>
122
119
  expect(container.textContent).toBe('');
123
120
  });
124
121
 
125
- it('does not render elements after an ASI return', () => {
122
+ it('does not stringify adjacent call-containing expression children', () => {
123
+ function child(label: string) @{
124
+ <span>{label}</span>
125
+ }
126
+
127
+ function empty() {
128
+ return null;
129
+ }
130
+
131
+ const Constructed = function Constructed(label: string) {
132
+ return child(label);
133
+ } as unknown as {
134
+ new (label: string): ReturnType<typeof child>;
135
+ };
136
+
137
+ const factory = child;
138
+
139
+ function tag(_strings: TemplateStringsArray) {
140
+ return child('tagged');
141
+ }
142
+
143
+ const lookup = {
144
+ member: child('member'),
145
+ };
146
+
147
+ function getKey(): keyof typeof lookup {
148
+ return 'member';
149
+ }
150
+
151
+ function touch() {
152
+ return null;
153
+ }
154
+
155
+ let assigned;
156
+
157
+ function Basic() @{
158
+ <>
159
+ <div>
160
+ {'call:'}
161
+ {child('call')}
162
+ </div>
163
+ <div>
164
+ {'new:'}
165
+ {new Constructed('new')}
166
+ </div>
167
+ <div>
168
+ {'chain:'}
169
+ {factory?.('chain')}
170
+ </div>
171
+ <div>
172
+ {'ts-wrapper:'}
173
+ {child('ts-wrapper')!}
174
+ </div>
175
+ <div>
176
+ {'array:'}
177
+ {[child('array')]}
178
+ </div>
179
+ <div>
180
+ {'assignment:'}
181
+ {assigned = child('assignment')}
182
+ </div>
183
+ <div>
184
+ {'logical:'}
185
+ {true && child('logical')}
186
+ </div>
187
+ <div>
188
+ {'conditional:'}
189
+ {true ? child('conditional') : ''}
190
+ </div>
191
+ <div>
192
+ {'member:'}
193
+ {lookup[getKey()]}
194
+ </div>
195
+ <div>
196
+ {'object:'}
197
+ {{
198
+ [Symbol.for('ripple.element')]: true,
199
+ render() {
200
+ return child('object');
201
+ },
202
+ }}
203
+ </div>
204
+ <div>
205
+ {'sequence:'}
206
+ {(touch(), child('sequence'))}
207
+ </div>
208
+ <div>
209
+ {'tagged:'}
210
+ {tag`tagged`}
211
+ </div>
212
+ <div>
213
+ {'unary:'}
214
+ {void empty()}
215
+ </div>
216
+ </>
217
+ }
218
+
219
+ render(Basic);
220
+
221
+ expect([...container.querySelectorAll('div')].map((div) => div.textContent)).toEqual([
222
+ 'call:call',
223
+ 'new:new',
224
+ 'chain:chain',
225
+ 'ts-wrapper:ts-wrapper',
226
+ 'array:array',
227
+ 'assignment:assignment',
228
+ 'logical:logical',
229
+ 'conditional:conditional',
230
+ 'member:member',
231
+ 'object:object',
232
+ 'sequence:sequence',
233
+ 'tagged:tagged',
234
+ 'unary:',
235
+ ]);
236
+ });
237
+
238
+ it('does not render unreachable statements after an ASI return', () => {
126
239
  function Basic() {
127
240
  return;
128
- <div>"should not render"</div>
241
+ const leaked = 'should not render';
242
+ void leaked;
129
243
  }
130
244
 
131
245
  render(Basic);
@@ -133,14 +247,14 @@ second"</pre>
133
247
  expect(container.textContent).toBe('');
134
248
  });
135
249
 
136
- it('renders bare double-quoted text in if-else branches', () => {
137
- function App() {
138
- return <>
139
- let condition = false;
140
- if (condition) {
141
- "Hello Ripple"
142
- } else "Hello React"
143
- </>;
250
+ it('renders bare JSX text in if-else branches', () => {
251
+ function App() @{
252
+ let condition = false;
253
+ @if (condition) {
254
+ <>Hello Ripple</>
255
+ } @else {
256
+ <>Hello React</>
257
+ }
144
258
  }
145
259
 
146
260
  render(App);
@@ -149,18 +263,16 @@ second"</pre>
149
263
  });
150
264
 
151
265
  it('renders dynamic text', () => {
152
- function Basic() {
153
- return <>
154
- let &[message] = track('Hello World');
266
+ function Basic() @{
267
+ let &[message] = track('Hello World');
268
+ <>
155
269
  <button
156
270
  onClick={() => {
157
271
  message = 'Hello Ripple';
158
272
  }}
159
- >
160
- {'Change Text'}
161
- </button>
273
+ >{'Change Text'}</button>
162
274
  <div>{message}</div>
163
- </>;
275
+ </>
164
276
  }
165
277
 
166
278
  render(Basic);
@@ -174,8 +286,8 @@ second"</pre>
174
286
  });
175
287
 
176
288
  it('renders empty string literal', () => {
177
- function Basic() {
178
- return <><div>{''}</div></>;
289
+ function Basic() @{
290
+ <div>{''}</div>
179
291
  }
180
292
 
181
293
  render(Basic);
@@ -183,8 +295,8 @@ second"</pre>
183
295
  });
184
296
 
185
297
  it('renders empty template literal', () => {
186
- function Basic() {
187
- return <><div>{``}</div></>;
298
+ function Basic() @{
299
+ <div>{``}</div>
188
300
  }
189
301
 
190
302
  render(Basic);
@@ -192,22 +304,22 @@ second"</pre>
192
304
  });
193
305
 
194
306
  it('renders tick template literal for nested children', () => {
195
- function Child({ level, children }: { level: number; children: any }) {
196
- return <>
197
- if (level == 1) {
307
+ function Child({ level, children }: { level: number; children: any }) @{
308
+ <>
309
+ @if (level == 1) {
198
310
  <h1>{children}</h1>
199
311
  }
200
- if (level == 2) {
312
+ @if (level == 2) {
201
313
  <h2>{children}</h2>
202
314
  }
203
- if (level == 3) {
315
+ @if (level == 3) {
204
316
  <h3>{children}</h3>
205
317
  }
206
- </>;
318
+ </>
207
319
  }
208
320
 
209
- function App() {
210
- return <><Child level={1}>{`Heading 1`}</Child></>;
321
+ function App() @{
322
+ <Child level={1}>{`Heading 1`}</Child>
211
323
  }
212
324
 
213
325
  render(App);
@@ -215,14 +327,14 @@ second"</pre>
215
327
  });
216
328
 
217
329
  it('renders simple JS expression logic correctly', () => {
218
- function Example() {
219
- return <>
220
- let test: Record<number, string> = {};
221
- let counter = 0;
222
- test[counter++] = 'Test';
330
+ function Example() @{
331
+ let test: Record<number, string> = {};
332
+ let counter = 0;
333
+ test[counter++] = 'Test';
334
+ <>
223
335
  <div>{JSON.stringify(test)}</div>
224
336
  <div>{JSON.stringify(counter)}</div>
225
- </>;
337
+ </>
226
338
  }
227
339
  render(Example);
228
340
 
@@ -230,31 +342,29 @@ second"</pre>
230
342
  });
231
343
 
232
344
  it('renders with mixed static and dynamic content', () => {
233
- function Basic() {
234
- return <>
235
- let &[name] = track('World');
236
- let &[count] = track(0);
237
- const staticMessage = 'Welcome to Ripple!';
238
- <div class="mixed-content">
239
- <h1>{staticMessage}</h1>
240
- <p class="greeting">{'Hello, ' + name + '!'}</p>
241
- <p class="notifications">{'You have ' + count + ' notifications'}</p>
242
- <button
243
- onClick={() => {
244
- count++;
245
- }}
246
- >
247
- {'Add Notification'}
248
- </button>
249
- <button
250
- onClick={() => {
251
- name = name === 'World' ? 'User' : 'World';
252
- }}
253
- >
254
- {'Toggle Name'}
255
- </button>
256
- </div>
257
- </>;
345
+ function Basic() @{
346
+ let &[name] = track('World');
347
+ let &[count] = track(0);
348
+ const staticMessage = 'Welcome to Ripple!';
349
+ <div class="mixed-content">
350
+ <h1>{staticMessage}</h1>
351
+ <p class="greeting">
352
+ {'Hello, ' + name + '!'}
353
+ </p>
354
+ <p class="notifications">
355
+ {'You have ' + count + ' notifications'}
356
+ </p>
357
+ <button
358
+ onClick={() => {
359
+ count++;
360
+ }}
361
+ >{'Add Notification'}</button>
362
+ <button
363
+ onClick={() => {
364
+ name = name === 'World' ? 'User' : 'World';
365
+ }}
366
+ >{'Toggle Name'}</button>
367
+ </div>
258
368
  }
259
369
 
260
370
  render(Basic);
@@ -278,16 +388,16 @@ second"</pre>
278
388
  });
279
389
 
280
390
  it('basic operations', () => {
281
- function App() {
282
- return <>
283
- let &[count] = track(0);
284
- const a = count++;
285
- const b = ++count;
391
+ function App() @{
392
+ let &[count] = track(0);
393
+ const a = count++;
394
+ const b = ++count;
395
+ <>
286
396
  <div>{a}</div>
287
397
  <div>{b}</div>
288
398
  <div>{5}</div>
289
399
  <div>{count}</div>
290
- </>;
400
+ </>
291
401
  }
292
402
 
293
403
  render(App);
@@ -295,36 +405,32 @@ second"</pre>
295
405
  });
296
406
 
297
407
  it('renders with conditional rendering using if statements', () => {
298
- function Basic() {
299
- return <>
300
- let &[showContent] = track(false);
301
- let &[userRole] = track('guest');
408
+ function Basic() @{
409
+ let &[showContent] = track(false);
410
+ let &[userRole] = track('guest');
411
+ <>
302
412
  <button
303
413
  onClick={() => {
304
414
  showContent = !showContent;
305
415
  }}
306
- >
307
- {'Toggle Content'}
308
- </button>
416
+ >{'Toggle Content'}</button>
309
417
  <button
310
418
  onClick={() => {
311
419
  userRole = userRole === 'guest' ? 'admin' : 'guest';
312
420
  }}
313
- >
314
- {'Toggle Role'}
315
- </button>
421
+ >{'Toggle Role'}</button>
316
422
  <div class="content">
317
- if (showContent) {
318
- if (userRole === 'admin') {
423
+ @if (showContent) {
424
+ @if (userRole === 'admin') {
319
425
  <div class="admin-content">{'Admin content'}</div>
320
- } else {
426
+ } @else {
321
427
  <div class="user-content">{'User content'}</div>
322
428
  }
323
- } else {
429
+ } @else {
324
430
  <div class="no-content">{'No content'}</div>
325
431
  }
326
432
  </div>
327
- </>;
433
+ </>
328
434
  }
329
435
 
330
436
  render(Basic);
@@ -352,14 +458,9 @@ second"</pre>
352
458
  });
353
459
 
354
460
  it('should handle lexical scopes correctly', () => {
355
- function App() {
356
- return <>
357
- <section>
358
- let sectionData = 'Nested scope variable';
359
-
360
- {sectionData}
361
- </section>
362
- </>;
461
+ function App() @{
462
+ let sectionData = 'Nested scope variable';
463
+ <section>{sectionData}</section>
363
464
  }
364
465
 
365
466
  render(App);
@@ -367,48 +468,48 @@ second"</pre>
367
468
  });
368
469
 
369
470
  it('runs nested JavaScript blocks inside component-local callables', () => {
370
- function App() {
371
- return <>
372
- function readFunction() {
373
- const label = 'function outer';
374
- let result = '';
471
+ function App() @{
472
+ function readFunction() {
473
+ const label = 'function outer';
474
+ let result = '';
475
+
476
+ {
477
+ const label = 'function inner';
478
+ result = label;
479
+ }
375
480
 
376
- {
377
- const label = 'function inner';
378
- result = label;
379
- }
481
+ return `${result} / ${label}`;
482
+ }
483
+ const readArrow = () => {
484
+ const offset = 5;
485
+ let value = offset;
380
486
 
381
- return `${result} / ${label}`;
487
+ {
488
+ const offset = 17;
489
+ value += offset;
382
490
  }
383
- const readArrow = () => {
384
- const offset = 5;
385
- let value = offset;
491
+
492
+ return value;
493
+ };
494
+ class Reader {
495
+ read() {
496
+ const label = 'method outer';
497
+ let result = '';
386
498
 
387
499
  {
388
- const offset = 17;
389
- value += offset;
500
+ const label = 'method inner';
501
+ result = label;
390
502
  }
391
503
 
392
- return value;
393
- };
394
- class Reader {
395
- read() {
396
- const label = 'method outer';
397
- let result = '';
398
-
399
- {
400
- const label = 'method inner';
401
- result = label;
402
- }
403
-
404
- return `${result} / ${label}`;
405
- }
504
+ return `${result} / ${label}`;
406
505
  }
407
- const reader = new Reader();
506
+ }
507
+ const reader = new Reader();
508
+ <>
408
509
  <div class="block-function">{readFunction()}</div>
409
510
  <div class="block-arrow">{readArrow()}</div>
410
511
  <div class="block-method">{reader.read()}</div>
411
- </>;
512
+ </>
412
513
  }
413
514
 
414
515
  render(App);
@@ -423,27 +524,23 @@ second"</pre>
423
524
  });
424
525
 
425
526
  it('should handle consecutive text nodes without duplication', () => {
426
- function App() {
427
- return <>
428
- const Something = conditional('a');
429
- <Something />
430
- function conditional(item: 'a') {
431
- let hello = 'Hello';
432
- const obj = {
433
- a: function A() {
434
- return <>
435
- <div>
436
- {'a'}
437
- {' '}
438
- {hello}
439
- </div>
440
- </>;
441
- },
442
- };
527
+ function App() @{
528
+ const Something = conditional('a');
529
+ function conditional(item: 'a') {
530
+ let hello = 'Hello';
531
+ const obj = {
532
+ a: function A() @{
533
+ <div>
534
+ {'a'}
535
+ {' '}
536
+ {hello}
537
+ </div>
538
+ },
539
+ };
443
540
 
444
- return obj[item];
445
- }
446
- </>;
541
+ return obj[item];
542
+ }
543
+ <Something />
447
544
  }
448
545
 
449
546
  render(App);
@@ -452,16 +549,14 @@ second"</pre>
452
549
  });
453
550
 
454
551
  it('should handle multiple consecutive text expressions', () => {
455
- function App() {
456
- return <>
457
- let name = 'World';
458
- <div>
459
- {'Hello'}
460
- {' '}
461
- {name}
462
- {'!'}
463
- </div>
464
- </>;
552
+ function App() @{
553
+ let name = 'World';
554
+ <div>
555
+ {'Hello'}
556
+ {' '}
557
+ {name}
558
+ {'!'}
559
+ </div>
465
560
  }
466
561
 
467
562
  render(App);
@@ -39,23 +39,25 @@ describe('basic client > styling', () => {
39
39
  });
40
40
 
41
41
  it('renders with keyframes in styling scoped to component', () => {
42
- const source = `export function Basic() { return <>
43
- <div>
44
- <p>{'Styled paragraph'}</p>
45
- </div>
42
+ const source = `export function Basic() @{
43
+ <>
44
+ <div>
45
+ <p>{'Styled paragraph'}</p>
46
+ </div>
46
47
 
47
- <style>
48
- div {
49
- animation-name: anim;
50
- }
48
+ <style>
49
+ div {
50
+ animation-name: anim;
51
+ }
51
52
 
52
- @keyframes anim {}
53
+ @keyframes anim {}
53
54
 
54
- p {
55
- animation-name: anim;
56
- }
57
- </style>
58
- </>; }`;
55
+ p {
56
+ animation-name: anim;
57
+ }
58
+ </style>
59
+ </>
60
+ }`;
59
61
 
60
62
  const { css } = compile(source, 'test.tsrx');
61
63
  const name = css.match(/@keyframes\s+([a-zA-Z0-9_-]+)\s*\{/)[1];