ripple 0.3.67 → 0.3.69

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 (182) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/package.json +3 -3
  3. package/src/jsx-runtime.d.ts +2 -2
  4. package/src/runtime/element.js +1 -1
  5. package/src/runtime/index-client.js +11 -11
  6. package/src/runtime/index-server.js +7 -4
  7. package/src/runtime/internal/client/bindings.js +1 -1
  8. package/src/runtime/internal/client/blocks.js +13 -4
  9. package/src/runtime/internal/client/component.js +55 -0
  10. package/src/runtime/internal/client/composite.js +4 -2
  11. package/src/runtime/internal/client/expression.js +65 -7
  12. package/src/runtime/internal/client/hmr.js +54 -43
  13. package/src/runtime/internal/client/index.js +5 -1
  14. package/src/runtime/internal/client/portal.js +70 -69
  15. package/src/runtime/internal/client/render.js +3 -0
  16. package/src/runtime/internal/server/index.js +92 -8
  17. package/tests/client/__snapshots__/html.test.tsrx.snap +3 -3
  18. package/tests/client/array/array.copy-within.test.tsrx +33 -31
  19. package/tests/client/array/array.derived.test.tsrx +186 -169
  20. package/tests/client/array/array.iteration.test.tsrx +40 -37
  21. package/tests/client/array/array.mutations.test.tsrx +113 -101
  22. package/tests/client/array/array.static.test.tsrx +119 -101
  23. package/tests/client/array/array.to-methods.test.tsrx +24 -21
  24. package/tests/client/async-suspend.test.tsrx +247 -246
  25. package/tests/client/basic/__snapshots__/basic.rendering.test.tsrx.snap +0 -1
  26. package/tests/client/basic/basic.attributes.test.tsrx +428 -423
  27. package/tests/client/basic/basic.collections.test.tsrx +109 -102
  28. package/tests/client/basic/basic.components.test.tsrx +323 -205
  29. package/tests/client/basic/basic.errors.test.tsrx +91 -91
  30. package/tests/client/basic/basic.events.test.tsrx +114 -115
  31. package/tests/client/basic/basic.get-set.test.tsrx +97 -87
  32. package/tests/client/basic/basic.hmr.test.tsrx +19 -16
  33. package/tests/client/basic/basic.reactivity.test.tsrx +199 -191
  34. package/tests/client/basic/basic.rendering.test.tsrx +272 -182
  35. package/tests/client/basic/basic.styling.test.tsrx +23 -22
  36. package/tests/client/basic/basic.utilities.test.tsrx +10 -8
  37. package/tests/client/boundaries.test.tsrx +26 -26
  38. package/tests/client/compiler/__snapshots__/compiler.assignments.test.rsrx.snap +5 -5
  39. package/tests/client/compiler/__snapshots__/compiler.assignments.test.tsrx.snap +5 -5
  40. package/tests/client/compiler/compiler.assignments.test.tsrx +77 -81
  41. package/tests/client/compiler/compiler.attributes.test.tsrx +15 -15
  42. package/tests/client/compiler/compiler.basic.test.tsrx +322 -314
  43. package/tests/client/compiler/compiler.regex.test.tsrx +44 -47
  44. package/tests/client/compiler/compiler.tracked-access.test.tsrx +38 -38
  45. package/tests/client/compiler/compiler.try-in-function.test.tsrx +16 -16
  46. package/tests/client/compiler/compiler.typescript.test.tsrx +2 -2
  47. package/tests/client/composite/composite.dynamic-components.test.tsrx +47 -48
  48. package/tests/client/composite/composite.generics.test.tsrx +168 -192
  49. package/tests/client/composite/composite.props.test.tsrx +97 -81
  50. package/tests/client/composite/composite.reactivity.test.tsrx +177 -147
  51. package/tests/client/composite/composite.render.test.tsrx +122 -105
  52. package/tests/client/computed-properties.test.tsrx +28 -28
  53. package/tests/client/context.test.tsrx +21 -21
  54. package/tests/client/css/global-additional-cases.test.tsrx +58 -58
  55. package/tests/client/css/global-advanced-selectors.test.tsrx +16 -16
  56. package/tests/client/css/global-at-rules.test.tsrx +10 -10
  57. package/tests/client/css/global-basic.test.tsrx +14 -14
  58. package/tests/client/css/global-classes-ids.test.tsrx +14 -14
  59. package/tests/client/css/global-combinators.test.tsrx +10 -10
  60. package/tests/client/css/global-complex-nesting.test.tsrx +14 -14
  61. package/tests/client/css/global-edge-cases.test.tsrx +18 -18
  62. package/tests/client/css/global-keyframes.test.tsrx +12 -12
  63. package/tests/client/css/global-nested.test.tsrx +10 -10
  64. package/tests/client/css/global-pseudo.test.tsrx +12 -12
  65. package/tests/client/css/global-scoping.test.tsrx +20 -20
  66. package/tests/client/css/style-identifier.test.tsrx +143 -291
  67. package/tests/client/date.test.tsrx +146 -133
  68. package/tests/client/dynamic-elements.test.tsrx +398 -365
  69. package/tests/client/events.test.tsrx +292 -290
  70. package/tests/client/for.test.tsrx +156 -153
  71. package/tests/client/head.test.tsrx +105 -96
  72. package/tests/client/html.test.tsrx +122 -26
  73. package/tests/client/input-value.test.tsrx +1361 -1314
  74. package/tests/client/lazy-array.test.tsrx +16 -13
  75. package/tests/client/lazy-destructuring.test.tsrx +257 -213
  76. package/tests/client/map.test.tsrx +65 -60
  77. package/tests/client/media-query.test.tsrx +22 -20
  78. package/tests/client/object.test.tsrx +87 -81
  79. package/tests/client/portal.test.tsrx +57 -51
  80. package/tests/client/ref.test.tsrx +233 -202
  81. package/tests/client/return.test.tsrx +71 -2560
  82. package/tests/client/set.test.tsrx +54 -45
  83. package/tests/client/svg.test.tsrx +216 -186
  84. package/tests/client/switch.test.tsrx +194 -193
  85. package/tests/client/track-async-hydration.test.tsrx +18 -14
  86. package/tests/client/tracked-index-access.test.tsrx +28 -18
  87. package/tests/client/try.test.tsrx +675 -548
  88. package/tests/client/tsx.test.tsrx +373 -311
  89. package/tests/client/typescript-generics.test.tsrx +145 -145
  90. package/tests/client/url/url.derived.test.tsrx +33 -28
  91. package/tests/client/url/url.parsing.test.tsrx +61 -51
  92. package/tests/client/url/url.partial-removal.test.tsrx +56 -48
  93. package/tests/client/url/url.reactivity.test.tsrx +142 -125
  94. package/tests/client/url/url.serialization.test.tsrx +13 -11
  95. package/tests/client/url-search-params/url-search-params.derived.test.tsrx +34 -29
  96. package/tests/client/url-search-params/url-search-params.initialization.test.tsrx +25 -21
  97. package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +50 -45
  98. package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +111 -99
  99. package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +49 -43
  100. package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +14 -12
  101. package/tests/client/url-search-params/url-search-params.tracked-url.test.tsrx +16 -14
  102. package/tests/hydration/basic.test.js +3 -3
  103. package/tests/hydration/compiled/client/basic.js +586 -651
  104. package/tests/hydration/compiled/client/composite.js +79 -104
  105. package/tests/hydration/compiled/client/events.js +140 -148
  106. package/tests/hydration/compiled/client/for.js +1005 -1018
  107. package/tests/hydration/compiled/client/head.js +124 -134
  108. package/tests/hydration/compiled/client/hmr.js +41 -48
  109. package/tests/hydration/compiled/client/html-in-template.js +38 -41
  110. package/tests/hydration/compiled/client/html.js +970 -1314
  111. package/tests/hydration/compiled/client/if-children.js +234 -249
  112. package/tests/hydration/compiled/client/if.js +182 -189
  113. package/tests/hydration/compiled/client/mixed-control-flow.js +347 -303
  114. package/tests/hydration/compiled/client/nested-control-flow.js +1084 -832
  115. package/tests/hydration/compiled/client/portal.js +65 -85
  116. package/tests/hydration/compiled/client/reactivity.js +84 -90
  117. package/tests/hydration/compiled/client/return.js +38 -1939
  118. package/tests/hydration/compiled/client/switch.js +218 -224
  119. package/tests/hydration/compiled/client/track-async-serialization.js +250 -259
  120. package/tests/hydration/compiled/client/try.js +123 -132
  121. package/tests/hydration/compiled/server/basic.js +773 -831
  122. package/tests/hydration/compiled/server/composite.js +166 -191
  123. package/tests/hydration/compiled/server/events.js +170 -184
  124. package/tests/hydration/compiled/server/for.js +851 -909
  125. package/tests/hydration/compiled/server/head.js +206 -216
  126. package/tests/hydration/compiled/server/hmr.js +64 -72
  127. package/tests/hydration/compiled/server/html-in-template.js +42 -76
  128. package/tests/hydration/compiled/server/html.js +1362 -1667
  129. package/tests/hydration/compiled/server/if-children.js +419 -445
  130. package/tests/hydration/compiled/server/if.js +194 -208
  131. package/tests/hydration/compiled/server/mixed-control-flow.js +249 -257
  132. package/tests/hydration/compiled/server/nested-control-flow.js +491 -515
  133. package/tests/hydration/compiled/server/portal.js +152 -160
  134. package/tests/hydration/compiled/server/reactivity.js +94 -106
  135. package/tests/hydration/compiled/server/return.js +28 -2172
  136. package/tests/hydration/compiled/server/switch.js +274 -286
  137. package/tests/hydration/compiled/server/track-async-serialization.js +340 -358
  138. package/tests/hydration/compiled/server/try.js +167 -185
  139. package/tests/hydration/components/basic.tsrx +320 -272
  140. package/tests/hydration/components/composite.tsrx +44 -32
  141. package/tests/hydration/components/events.tsrx +101 -91
  142. package/tests/hydration/components/for.tsrx +510 -452
  143. package/tests/hydration/components/head.tsrx +87 -80
  144. package/tests/hydration/components/hmr.tsrx +22 -17
  145. package/tests/hydration/components/html-in-template.tsrx +22 -17
  146. package/tests/hydration/components/html.tsrx +525 -443
  147. package/tests/hydration/components/if-children.tsrx +158 -148
  148. package/tests/hydration/components/if.tsrx +109 -95
  149. package/tests/hydration/components/mixed-control-flow.tsrx +100 -96
  150. package/tests/hydration/components/nested-control-flow.tsrx +215 -203
  151. package/tests/hydration/components/portal.tsrx +41 -34
  152. package/tests/hydration/components/reactivity.tsrx +37 -27
  153. package/tests/hydration/components/return.tsrx +12 -556
  154. package/tests/hydration/components/switch.tsrx +120 -114
  155. package/tests/hydration/components/track-async-serialization.tsrx +107 -91
  156. package/tests/hydration/components/try.tsrx +55 -40
  157. package/tests/hydration/html.test.js +4 -4
  158. package/tests/hydration/return.test.js +13 -532
  159. package/tests/server/await.test.tsrx +3 -3
  160. package/tests/server/basic.attributes.test.tsrx +264 -195
  161. package/tests/server/basic.components.test.tsrx +296 -169
  162. package/tests/server/basic.test.tsrx +300 -198
  163. package/tests/server/compiler.test.tsrx +62 -60
  164. package/tests/server/composite.props.test.tsrx +77 -63
  165. package/tests/server/composite.test.tsrx +168 -192
  166. package/tests/server/context.test.tsrx +18 -12
  167. package/tests/server/dynamic-elements.test.tsrx +197 -180
  168. package/tests/server/for.test.tsrx +85 -78
  169. package/tests/server/head.test.tsrx +50 -43
  170. package/tests/server/html-nesting-validation.test.tsrx +8 -8
  171. package/tests/server/if.test.tsrx +57 -51
  172. package/tests/server/lazy-destructuring.test.tsrx +366 -294
  173. package/tests/server/return.test.tsrx +76 -1355
  174. package/tests/server/streaming-ssr.test.tsrx +4 -75
  175. package/tests/server/style-identifier.test.tsrx +169 -148
  176. package/tests/server/switch.test.tsrx +91 -85
  177. package/tests/server/track-async-serialization.test.tsrx +105 -85
  178. package/tests/server/try.test.tsrx +374 -280
  179. package/tests/utils/compiler-compat-config.test.js +2 -2
  180. package/tests/utils/runtime-imports.test.js +10 -0
  181. package/types/index.d.ts +8 -0
  182. package/tests/client/__snapshots__/html.test.rsrx.snap +0 -40
@@ -1,2599 +1,110 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { flushSync, track } from 'ripple';
3
2
  import { compile } from '@tsrx/ripple';
4
3
 
4
+ const TSRX_RETURN_ERROR = 'Return statements are not allowed inside TSRX templates. Move the return before the TSRX return value, or use conditional rendering instead.';
5
+
5
6
  describe('returns in prohibited scopes', () => {
6
7
  it('throws error when return is used in module scope', () => {
7
8
  expect(
8
9
  () => compile(`
9
10
  return;
10
- component App() {
11
+ function App() { return <>
11
12
  <div>{'hello'}</div>
12
- }
13
+ </>; }
13
14
  `, 'test.tsrx', {
14
15
  mode: 'client',
15
16
  }),
16
- ).toThrowError('Return statements are not allowed at the top level of a module.');
17
- });
18
- });
19
-
20
- describe('early return in client components', () => {
21
- it('skips template content after direct return', () => {
22
- component App() {
23
- <div class="before">{'before'}</div>
24
- return;
25
- <div class="after">{'after'}</div>
26
- }
27
-
28
- render(App);
29
- expect(container.querySelector('.before')).toBeTruthy();
30
- expect(container.querySelector('.after')).toBeFalsy();
31
- });
32
-
33
- it('skips rest of body when return condition is true', () => {
34
- component App() {
35
- let condition = true;
36
-
37
- if (condition) {
38
- <div class="guard">{'guard hit'}</div>
39
- return;
40
- }
41
- <div class="rest">{'rest'}</div>
42
- }
43
-
44
- render(App);
45
- expect(container.querySelector('.guard')).toBeTruthy();
46
- expect(container.querySelector('.rest')).toBeFalsy();
47
- });
48
-
49
- it('renders rest of body when return condition is false', () => {
50
- component App() {
51
- let condition = false;
52
-
53
- if (condition) {
54
- <div class="guard">{'guard hit'}</div>
55
- return;
56
- }
57
- <div class="rest">{'rest'}</div>
58
- }
59
-
60
- render(App);
61
- expect(container.querySelector('.guard')).toBeFalsy();
62
- expect(container.querySelector('.rest')).toBeTruthy();
63
- });
64
-
65
- it('reactive: condition changes from false to true hides rest', () => {
66
- component App() {
67
- let &[condition] = track(false);
68
-
69
- <button
70
- onClick={() => {
71
- condition = true;
72
- }}
73
- >
74
- {'toggle'}
75
- </button>
76
- if (condition) {
77
- <div class="guard">{'guard hit'}</div>
78
- return;
79
- }
80
- <div class="rest">{'rest'}</div>
81
- }
82
-
83
- render(App);
84
- expect(container.querySelector('.guard')).toBeFalsy();
85
- expect(container.querySelector('.rest')).toBeTruthy();
86
-
87
- container.querySelector('button').click();
88
- flushSync();
89
-
90
- expect(container.querySelector('.guard')).toBeTruthy();
91
- expect(container.querySelector('.rest')).toBeFalsy();
92
- });
93
-
94
- it('reactive: condition changes from true to false shows rest', () => {
95
- component App() {
96
- let &[condition] = track(true);
97
-
98
- <button
99
- onClick={() => {
100
- condition = false;
101
- }}
102
- >
103
- {'toggle'}
104
- </button>
105
- if (condition) {
106
- <div class="guard">{'guard hit'}</div>
107
- return;
108
- }
109
- <div class="rest">{'rest'}</div>
110
- }
111
-
112
- render(App);
113
- expect(container.querySelector('.guard')).toBeTruthy();
114
- expect(container.querySelector('.rest')).toBeFalsy();
115
-
116
- container.querySelector('button').click();
117
- flushSync();
118
-
119
- expect(container.querySelector('.guard')).toBeFalsy();
120
- expect(container.querySelector('.rest')).toBeTruthy();
121
- });
122
-
123
- it('handles nested ifs with return', () => {
124
- component App() {
125
- let a = true;
126
- let b = true;
127
-
128
- if (a) {
129
- <div class="a">{'a is true'}</div>
130
- if (b) {
131
- <div class="b">{'b is true'}</div>
132
- return;
133
- }
134
- }
135
- <div class="rest">{'rest'}</div>
136
- }
137
-
138
- render(App);
139
- expect(container.querySelector('.a')).toBeTruthy();
140
- expect(container.querySelector('.b')).toBeTruthy();
141
- expect(container.querySelector('.rest')).toBeFalsy();
142
- });
143
-
144
- it('renders rest when nested return condition is not fully met', () => {
145
- component App() {
146
- let a = true;
147
- let b = false;
148
-
149
- if (a) {
150
- <div class="a">{'a is true'}</div>
151
- if (b) {
152
- <div class="b">{'b is true'}</div>
153
- return;
154
- }
155
- }
156
- <div class="rest">{'rest'}</div>
157
- }
158
-
159
- render(App);
160
- expect(container.querySelector('.a')).toBeTruthy();
161
- expect(container.querySelector('.b')).toBeFalsy();
162
- expect(container.querySelector('.rest')).toBeTruthy();
163
- });
164
-
165
- it('renders rest when outer condition is false', () => {
166
- component App() {
167
- let a = false;
168
- let b = true;
169
-
170
- if (a) {
171
- <div class="a">{'a is true'}</div>
172
- if (b) {
173
- <div class="b">{'b is true'}</div>
174
- return;
175
- }
176
- }
177
- <div class="rest">{'rest'}</div>
178
- }
179
-
180
- render(App);
181
- expect(container.querySelector('.a')).toBeFalsy();
182
- expect(container.querySelector('.b')).toBeFalsy();
183
- expect(container.querySelector('.rest')).toBeTruthy();
184
- });
185
-
186
- it('handles content before and after the if-with-return', () => {
187
- component App() {
188
- let shouldReturn = true;
189
-
190
- <div class="before">{'before'}</div>
191
- if (shouldReturn) {
192
- <div class="guard">{'guard'}</div>
193
- return;
194
- }
195
- <div class="after">{'after'}</div>
196
- }
197
-
198
- render(App);
199
- expect(container.querySelector('.before')).toBeTruthy();
200
- expect(container.querySelector('.guard')).toBeTruthy();
201
- expect(container.querySelector('.after')).toBeFalsy();
202
- });
203
-
204
- it('renders multiple elements after guard when condition is false', () => {
205
- component App() {
206
- let shouldReturn = false;
207
-
208
- if (shouldReturn) {
209
- <div class="guard">{'guard'}</div>
210
- return;
211
- }
212
- <div class="first">{'first'}</div>
213
- <div class="second">{'second'}</div>
214
- }
215
-
216
- render(App);
217
- expect(container.querySelector('.guard')).toBeFalsy();
218
- expect(container.querySelector('.first')).toBeTruthy();
219
- expect(container.querySelector('.second')).toBeTruthy();
220
- });
221
-
222
- it('handles multiple sequential returns - first hits', () => {
223
- component App() {
224
- let a = true;
225
- let b = true;
226
-
227
- if (a) {
228
- <div class="first">{'first guard'}</div>
229
- return;
230
- }
231
- if (b) {
232
- <div class="second">{'second guard'}</div>
233
- return;
234
- }
235
- <div class="rest">{'rest'}</div>
236
- }
237
-
238
- render(App);
239
- expect(container.querySelector('.first')).toBeTruthy();
240
- expect(container.querySelector('.second')).toBeFalsy();
241
- expect(container.querySelector('.rest')).toBeFalsy();
242
- });
243
-
244
- it('handles multiple sequential returns - second hits', () => {
245
- component App() {
246
- let a = false;
247
- let b = true;
248
-
249
- if (a) {
250
- <div class="first">{'first guard'}</div>
251
- return;
252
- }
253
- if (b) {
254
- <div class="second">{'second guard'}</div>
255
- return;
256
- }
257
- <div class="rest">{'rest'}</div>
258
- }
259
-
260
- render(App);
261
- expect(container.querySelector('.first')).toBeFalsy();
262
- expect(container.querySelector('.second')).toBeTruthy();
263
- expect(container.querySelector('.rest')).toBeFalsy();
264
- });
265
-
266
- it('handles multiple sequential returns - none hit', () => {
267
- component App() {
268
- let a = false;
269
- let b = false;
270
-
271
- if (a) {
272
- <div class="first">{'first guard'}</div>
273
- return;
274
- }
275
- if (b) {
276
- <div class="second">{'second guard'}</div>
277
- return;
278
- }
279
- <div class="rest">{'rest'}</div>
280
- }
281
-
282
- render(App);
283
- expect(container.querySelector('.first')).toBeFalsy();
284
- expect(container.querySelector('.second')).toBeFalsy();
285
- expect(container.querySelector('.rest')).toBeTruthy();
286
- });
287
-
288
- it('handles deeply nested returns (3 levels)', () => {
289
- component App() {
290
- let a = true;
291
- let b = true;
292
- let c = true;
293
-
294
- if (a) {
295
- <div class="a">{'a'}</div>
296
- if (b) {
297
- <div class="b">{'b'}</div>
298
- if (c) {
299
- <div class="c">{'c'}</div>
300
- return;
301
- }
302
- }
303
- }
304
- <div class="rest">{'rest'}</div>
305
- }
306
-
307
- render(App);
308
- expect(container.querySelector('.a')).toBeTruthy();
309
- expect(container.querySelector('.b')).toBeTruthy();
310
- expect(container.querySelector('.c')).toBeTruthy();
311
- expect(container.querySelector('.rest')).toBeFalsy();
17
+ ).toThrow('Return statements are not allowed at the top level of a module.');
312
18
  });
313
19
 
314
- it('handles deeply nested returns (3 levels) - partial', () => {
315
- component App() {
316
- let a = true;
317
- let b = true;
318
- let c = false;
319
-
320
- if (a) {
321
- <div class="a">{'a'}</div>
322
- if (b) {
323
- <div class="b">{'b'}</div>
324
- if (c) {
325
- <div class="c">{'c'}</div>
326
- return;
327
- }
20
+ it('throws when return is used inside a TSRX fragment', () => {
21
+ expect(
22
+ () => compile(
23
+ `
24
+ function App() {
25
+ return <>
26
+ if (ready) {
27
+ return;
28
+ }
29
+ <div>{'ready'}</div>
30
+ </>;
328
31
  }
329
- }
330
- <div class="rest">{'rest'}</div>
331
- }
332
-
333
- render(App);
334
- expect(container.querySelector('.a')).toBeTruthy();
335
- expect(container.querySelector('.b')).toBeTruthy();
336
- expect(container.querySelector('.c')).toBeFalsy();
337
- expect(container.querySelector('.rest')).toBeTruthy();
338
- });
339
-
340
- it('handles return with else-if chain - first condition', () => {
341
- component App() {
342
- let value = 1;
343
-
344
- if (value === 1) {
345
- <div class="one">{'one'}</div>
346
- return;
347
- } else if (value === 2) {
348
- <div class="two">{'two'}</div>
349
- return;
350
- } else {
351
- <div class="other">{'other'}</div>
352
- return;
353
- }
354
- <div class="never">{'never reached'}</div>
355
- }
356
-
357
- render(App);
358
- expect(container.querySelector('.one')).toBeTruthy();
359
- expect(container.querySelector('.two')).toBeFalsy();
360
- expect(container.querySelector('.other')).toBeFalsy();
361
- expect(container.querySelector('.never')).toBeFalsy();
362
- });
363
-
364
- it('handles return with else-if chain - second condition', () => {
365
- component App() {
366
- let value = 2;
367
-
368
- if (value === 1) {
369
- <div class="one">{'one'}</div>
370
- return;
371
- } else if (value === 2) {
372
- <div class="two">{'two'}</div>
373
- return;
374
- } else {
375
- <div class="other">{'other'}</div>
376
- return;
377
- }
378
- <div class="never">{'never reached'}</div>
379
- }
380
-
381
- render(App);
382
- expect(container.querySelector('.one')).toBeFalsy();
383
- expect(container.querySelector('.two')).toBeTruthy();
384
- expect(container.querySelector('.other')).toBeFalsy();
385
- expect(container.querySelector('.never')).toBeFalsy();
386
- });
387
-
388
- it('handles return with else-if chain - else condition', () => {
389
- component App() {
390
- let value = 3;
391
-
392
- if (value === 1) {
393
- <div class="one">{'one'}</div>
394
- return;
395
- } else if (value === 2) {
396
- <div class="two">{'two'}</div>
397
- return;
398
- } else {
399
- <div class="other">{'other'}</div>
400
- return;
401
- }
402
- <div class="never">{'never reached'}</div>
403
- }
404
-
405
- render(App);
406
- expect(container.querySelector('.one')).toBeFalsy();
407
- expect(container.querySelector('.two')).toBeFalsy();
408
- expect(container.querySelector('.other')).toBeTruthy();
409
- expect(container.querySelector('.never')).toBeFalsy();
410
- });
411
-
412
- it('handles return with complex boolean expression - AND', () => {
413
- component App() {
414
- let a = true;
415
- let b = true;
416
- let c = true;
417
-
418
- if (a && b && c) {
419
- <div class="all">{'all true'}</div>
420
- return;
421
- }
422
- <div class="not-all">{'not all true'}</div>
423
- }
424
-
425
- render(App);
426
- expect(container.querySelector('.all')).toBeTruthy();
427
- expect(container.querySelector('.not-all')).toBeFalsy();
428
- });
429
-
430
- it('handles return with complex boolean expression - OR', () => {
431
- component App() {
432
- let a = false;
433
- let b = true;
434
- let c = false;
435
-
436
- if (a || b || c) {
437
- <div class="any">{'at least one true'}</div>
438
- return;
439
- }
440
- <div class="none">{'all false'}</div>
441
- }
442
-
443
- render(App);
444
- expect(container.querySelector('.any')).toBeTruthy();
445
- expect(container.querySelector('.none')).toBeFalsy();
446
- });
447
-
448
- it('handles return with complex boolean expression - mixed', () => {
449
- component App() {
450
- let a = true;
451
- let b = false;
452
- let c = true;
453
-
454
- if (a && !b || c) {
455
- <div class="complex">{'complex condition met'}</div>
456
- return;
457
- }
458
- <div class="simple">{'complex condition not met'}</div>
459
- }
460
-
461
- render(App);
462
- expect(container.querySelector('.complex')).toBeTruthy();
463
- expect(container.querySelector('.simple')).toBeFalsy();
464
- });
465
-
466
- it('handles return with numeric comparison', () => {
467
- component App() {
468
- let num = 10;
469
-
470
- if (num > 5) {
471
- <div class="greater">{'greater than 5'}</div>
472
- return;
473
- }
474
- <div class="less">{'5 or less'}</div>
475
- }
476
-
477
- render(App);
478
- expect(container.querySelector('.greater')).toBeTruthy();
479
- expect(container.querySelector('.less')).toBeFalsy();
32
+ `,
33
+ 'test.tsrx',
34
+ { mode: 'client' },
35
+ ),
36
+ ).toThrow(TSRX_RETURN_ERROR);
480
37
  });
481
38
 
482
- it('handles return with string comparison', () => {
483
- component App() {
484
- let str = 'hello';
485
-
486
- if (str === 'hello') {
487
- <div class="greeting">{'greeting'}</div>
488
- return;
489
- }
490
- <div class="other">{'not greeting'}</div>
491
- }
492
-
493
- render(App);
494
- expect(container.querySelector('.greeting')).toBeTruthy();
495
- expect(container.querySelector('.other')).toBeFalsy();
496
- });
497
-
498
- it('handles return with negated condition', () => {
499
- component App() {
500
- let flag = false;
501
-
502
- if (!flag) {
503
- <div class="false">{'flag is false'}</div>
504
- return;
505
- }
506
- <div class="true">{'flag is true'}</div>
507
- }
508
-
509
- render(App);
510
- expect(container.querySelector('.false')).toBeTruthy();
511
- expect(container.querySelector('.true')).toBeFalsy();
512
- });
513
-
514
- it('handles return with ternary result', () => {
515
- component App() {
516
- let condition = true;
517
-
518
- if (condition) {
519
- <div class="ternary">{condition ? 'yes' : 'no'}</div>
520
- return;
521
- }
522
- <div class="fallback">{'fallback'}</div>
523
- }
524
-
525
- render(App);
526
- expect(container.querySelector('.ternary').textContent).toBe('yes');
527
- expect(container.querySelector('.fallback')).toBeFalsy();
528
- });
529
-
530
- it('handles return in nested element scope', () => {
531
- component App() {
532
- let show = true;
533
-
534
- <div class="outer">
535
- <span class="label">{'outer'}</span>
536
- if (show) {
537
- <p class="inner">{'inner'}</p>
538
- return;
39
+ it('throws when return is used inside a TSRX element', () => {
40
+ expect(
41
+ () => compile(
42
+ `
43
+ function App() {
44
+ return <section>
45
+ if (ready) {
46
+ return null;
47
+ }
48
+ <div>{'ready'}</div>
49
+ </section>;
539
50
  }
540
- <p class="after">{'after'}</p>
541
- </div>
542
- }
543
-
544
- render(App);
545
- expect(container.querySelector('.outer')).toBeTruthy();
546
- expect(container.querySelector('.label')).toBeTruthy();
547
- expect(container.querySelector('.inner')).toBeTruthy();
548
- expect(container.querySelector('.after')).toBeFalsy();
51
+ `,
52
+ 'test.tsrx',
53
+ { mode: 'client' },
54
+ ),
55
+ ).toThrow(TSRX_RETURN_ERROR);
549
56
  });
550
57
 
551
- it('handles return with multiple elements before and after', () => {
552
- component App() {
553
- let shouldReturn = true;
554
-
555
- <h1 class="title">{'title'}</h1>
556
- <p class="desc">{'description'}</p>
557
- if (shouldReturn) {
558
- <div class="guard">{'guard'}</div>
559
- <span class="guard-span">{'guard span'}</span>
560
- return;
561
- }
562
- <footer class="footer">{'footer'}</footer>
563
- <nav class="nav">{'nav'}</nav>
564
- }
565
-
566
- render(App);
567
- expect(container.querySelector('.title')).toBeTruthy();
568
- expect(container.querySelector('.desc')).toBeTruthy();
569
- expect(container.querySelector('.guard')).toBeTruthy();
570
- expect(container.querySelector('.guard-span')).toBeTruthy();
571
- expect(container.querySelector('.footer')).toBeFalsy();
572
- expect(container.querySelector('.nav')).toBeFalsy();
58
+ it('allows returns inside regular functions declared in TSRX templates', () => {
59
+ expect(
60
+ () => compile(
61
+ `
62
+ function App() {
63
+ return <>
64
+ function label() {
65
+ return 'ready';
66
+ }
67
+ <div>{label()}</div>
68
+ </>;
69
+ }
70
+ `,
71
+ 'test.tsrx',
72
+ { mode: 'client' },
73
+ ),
74
+ ).not.toThrow();
573
75
  });
76
+ });
574
77
 
575
- it('handles return at the beginning of component', () => {
576
- component App() {
577
- if (true) {
578
- <div class="early">{'early exit'}</div>
579
- return;
78
+ describe('function returns in client components', () => {
79
+ it('allows guard returns before TSRX output', () => {
80
+ function App() {
81
+ const ready = true;
82
+ if (!ready) {
83
+ return null;
580
84
  }
581
- <div class="never1">{'never reached'}</div>
582
- <div class="never2">{'also never reached'}</div>
583
- }
584
-
585
- render(App);
586
- expect(container.querySelector('.early')).toBeTruthy();
587
- expect(container.querySelector('.never1')).toBeFalsy();
588
- expect(container.querySelector('.never2')).toBeFalsy();
589
- });
590
85
 
591
- it('handles return at the end of component', () => {
592
- component App() {
593
- <div class="first">{'first'}</div>
594
- <div class="second">{'second'}</div>
595
- if (true) {
596
- <div class="third">{'third'}</div>
597
- return;
598
- }
86
+ return <><div class="ready">{'ready'}</div></>;
599
87
  }
600
88
 
601
89
  render(App);
602
- expect(container.querySelector('.first')).toBeTruthy();
603
- expect(container.querySelector('.second')).toBeTruthy();
604
- expect(container.querySelector('.third')).toBeTruthy();
90
+ expect(container.querySelector('.ready')?.textContent).toBe('ready');
605
91
  });
606
92
 
607
- it('handles return with function call in condition', () => {
608
- component App() {
609
- function check() {
610
- return true;
611
- }
612
-
613
- if (check()) {
614
- <div class="func">{'function returned true'}</div>
615
- return;
616
- }
617
- <div class="no-func">{'function returned false'}</div>
93
+ it('allows components to return null', () => {
94
+ function App() {
95
+ return null;
618
96
  }
619
97
 
620
98
  render(App);
621
- expect(container.querySelector('.func')).toBeTruthy();
622
- expect(container.querySelector('.no-func')).toBeFalsy();
99
+ expect(container.textContent).toBe('');
623
100
  });
624
101
 
625
- it('handles return with arithmetic in condition', () => {
626
- component App() {
627
- let x = 5;
628
- let y = 3;
629
-
630
- if (x + y > 7) {
631
- <div class="greater">{'sum greater than 7'}</div>
632
- return;
633
- }
634
- <div class="less">{'sum 7 or less'}</div>
102
+ it('allows components to return strings', () => {
103
+ function App() {
104
+ return 'hello';
635
105
  }
636
106
 
637
107
  render(App);
638
- expect(container.querySelector('.greater')).toBeTruthy();
639
- expect(container.querySelector('.less')).toBeFalsy();
640
- });
641
-
642
- it('handles multiple sibling returns at same level', () => {
643
- component App() {
644
- let mode = 'b';
645
-
646
- if (mode === 'a') {
647
- <div class="mode-a">{'mode A'}</div>
648
- return;
649
- }
650
-
651
- if (mode === 'b') {
652
- <div class="mode-b">{'mode B'}</div>
653
- return;
654
- }
655
-
656
- if (mode === 'c') {
657
- <div class="mode-c">{'mode C'}</div>
658
- return;
659
- }
660
-
661
- <div class="default">{'default mode'}</div>
662
- }
663
-
664
- render(App);
665
- expect(container.querySelector('.mode-a')).toBeFalsy();
666
- expect(container.querySelector('.mode-b')).toBeTruthy();
667
- expect(container.querySelector('.mode-c')).toBeFalsy();
668
- expect(container.querySelector('.default')).toBeFalsy();
669
- });
670
-
671
- it('handles return with array length check', () => {
672
- component App() {
673
- let items = [1, 2, 3];
674
-
675
- if (items.length > 0) {
676
- <div class="has-items">{'has items'}</div>
677
- return;
678
- }
679
- <div class="empty">{'empty'}</div>
680
- }
681
-
682
- render(App);
683
- expect(container.querySelector('.has-items')).toBeTruthy();
684
- expect(container.querySelector('.empty')).toBeFalsy();
685
- });
686
-
687
- it('handles return with object property check', () => {
688
- component App() {
689
- let obj = { value: 42 };
690
-
691
- if (obj.value === 42) {
692
- <div class="correct">{'correct value'}</div>
693
- return;
694
- }
695
- <div class="wrong">{'wrong value'}</div>
696
- }
697
-
698
- render(App);
699
- expect(container.querySelector('.correct')).toBeTruthy();
700
- expect(container.querySelector('.wrong')).toBeFalsy();
701
- });
702
-
703
- it('handles return with typeof check', () => {
704
- component App() {
705
- let value = 'string';
706
-
707
- if (typeof value === 'string') {
708
- <div class="string">{'is string'}</div>
709
- return;
710
- }
711
- <div class="not-string">{'not string'}</div>
712
- }
713
-
714
- render(App);
715
- expect(container.querySelector('.string')).toBeTruthy();
716
- expect(container.querySelector('.not-string')).toBeFalsy();
717
- });
718
-
719
- it('handles return with null check', () => {
720
- component App() {
721
- let value = null;
722
-
723
- if (value === null) {
724
- <div class="null">{'is null'}</div>
725
- return;
726
- }
727
- <div class="not-null">{'not null'}</div>
728
- }
729
-
730
- render(App);
731
- expect(container.querySelector('.null')).toBeTruthy();
732
- expect(container.querySelector('.not-null')).toBeFalsy();
733
- });
734
-
735
- it('handles return with undefined check', () => {
736
- component App() {
737
- let value = undefined;
738
-
739
- if (value === undefined) {
740
- <div class="undef">{'is undefined'}</div>
741
- return;
742
- }
743
- <div class="not-undef">{'not undefined'}</div>
744
- }
745
-
746
- render(App);
747
- expect(container.querySelector('.undef')).toBeTruthy();
748
- expect(container.querySelector('.not-undef')).toBeFalsy();
749
- });
750
-
751
- it('handles return with truthy/falsy values', () => {
752
- component App() {
753
- let value = 0;
754
-
755
- if (value) {
756
- <div class="truthy">{'truthy'}</div>
757
- return;
758
- }
759
- <div class="falsy">{'falsy'}</div>
760
- }
761
-
762
- render(App);
763
- expect(container.querySelector('.truthy')).toBeFalsy();
764
- expect(container.querySelector('.falsy')).toBeTruthy();
765
- });
766
-
767
- it('handles return with empty string check', () => {
768
- component App() {
769
- let str = '';
770
-
771
- if (str === '') {
772
- <div class="empty">{'empty string'}</div>
773
- return;
774
- }
775
- <div class="non-empty">{'non-empty string'}</div>
776
- }
777
-
778
- render(App);
779
- expect(container.querySelector('.empty')).toBeTruthy();
780
- expect(container.querySelector('.non-empty')).toBeFalsy();
781
- });
782
-
783
- it('handles return with else branch that does not return', () => {
784
- component App() {
785
- let condition = false;
786
-
787
- if (condition) {
788
- <div class="true">{'condition true'}</div>
789
- return;
790
- } else {
791
- <div class="false">{'condition false'}</div>
792
- }
793
- <div class="after">{'after if-else'}</div>
794
- }
795
-
796
- render(App);
797
- expect(container.querySelector('.true')).toBeFalsy();
798
- expect(container.querySelector('.false')).toBeTruthy();
799
- expect(container.querySelector('.after')).toBeTruthy();
800
- });
801
-
802
- it('handles return with else branch that also returns', () => {
803
- component App() {
804
- let condition = false;
805
-
806
- if (condition) {
807
- <div class="true">{'condition true'}</div>
808
- return;
809
- } else {
810
- <div class="false">{'condition false'}</div>
811
- return;
812
- }
813
- <div class="never">{'never reached'}</div>
814
- }
815
-
816
- render(App);
817
- expect(container.querySelector('.true')).toBeFalsy();
818
- expect(container.querySelector('.false')).toBeTruthy();
819
- expect(container.querySelector('.never')).toBeFalsy();
820
- });
821
-
822
- it('handles return with only if branch returning', () => {
823
- component App() {
824
- let condition = false;
825
-
826
- if (condition) {
827
- <div class="true">{'condition true'}</div>
828
- return;
829
- }
830
- <div class="after">{'condition false or after'}</div>
831
- }
832
-
833
- render(App);
834
- expect(container.querySelector('.true')).toBeFalsy();
835
- expect(container.querySelector('.after')).toBeTruthy();
836
- });
837
-
838
- it('handles return with comparison operators', () => {
839
- component App() {
840
- let a = 5;
841
- let b = 10;
842
-
843
- if (a < b) {
844
- <div class="less">{'a less than b'}</div>
845
- return;
846
- }
847
- <div class="not-less">{'a not less than b'}</div>
848
- }
849
-
850
- render(App);
851
- expect(container.querySelector('.less')).toBeTruthy();
852
- expect(container.querySelector('.not-less')).toBeFalsy();
853
- });
854
-
855
- it('handles return with strict equality', () => {
856
- component App() {
857
- let a = 5;
858
- let b = 5;
859
-
860
- if (a === b) {
861
- <div class="equal">{'strict equality'}</div>
862
- return;
863
- }
864
- <div class="not-equal">{'not strictly equal'}</div>
865
- }
866
-
867
- render(App);
868
- expect(container.querySelector('.equal')).toBeTruthy();
869
- expect(container.querySelector('.not-equal')).toBeFalsy();
870
- });
871
-
872
- it('handles return with greater than or equal', () => {
873
- component App() {
874
- let a = 10;
875
- let b = 10;
876
-
877
- if (a >= b) {
878
- <div class="gte">{'a >= b'}</div>
879
- return;
880
- }
881
- <div class="lt">{'a < b'}</div>
882
- }
883
-
884
- render(App);
885
- expect(container.querySelector('.gte')).toBeTruthy();
886
- expect(container.querySelector('.lt')).toBeFalsy();
887
- });
888
-
889
- it('handles return with not equal', () => {
890
- component App() {
891
- let a = 5;
892
- let b = 10;
893
-
894
- if (a != b) {
895
- <div class="neq">{'a != b'}</div>
896
- return;
897
- }
898
- <div class="eq">{'a == b'}</div>
899
- }
900
-
901
- render(App);
902
- expect(container.querySelector('.neq')).toBeTruthy();
903
- expect(container.querySelector('.eq')).toBeFalsy();
904
- });
905
-
906
- it('reactive: nested return condition changes', () => {
907
- component App() {
908
- let &[a] = track(true);
909
- let &[b] = track(true);
910
-
911
- <button
912
- onClick={() => {
913
- b = false;
914
- }}
915
- >
916
- {'toggle b'}
917
- </button>
918
- if (a) {
919
- <div class="a">{'a is true'}</div>
920
- if (b) {
921
- <div class="b">{'b is true'}</div>
922
- return;
923
- }
924
- }
925
- <div class="rest">{'rest'}</div>
926
- }
927
-
928
- render(App);
929
- expect(container.querySelector('.a')).toBeTruthy();
930
- expect(container.querySelector('.b')).toBeTruthy();
931
- expect(container.querySelector('.rest')).toBeFalsy();
932
-
933
- container.querySelector('button').click();
934
- flushSync();
935
-
936
- expect(container.querySelector('.a')).toBeTruthy();
937
- expect(container.querySelector('.b')).toBeFalsy();
938
- expect(container.querySelector('.rest')).toBeTruthy();
939
- });
940
-
941
- it('reactive: return in nested element scope', () => {
942
- component App() {
943
- let &[show] = track(true);
944
-
945
- <button
946
- onClick={() => {
947
- show = false;
948
- }}
949
- >
950
- {'toggle'}
951
- </button>
952
- <div class="outer">
953
- <span class="label">{'outer'}</span>
954
- if (show) {
955
- <p class="inner">{'inner'}</p>
956
- return;
957
- }
958
- <p class="after">{'after'}</p>
959
- </div>
960
- }
961
-
962
- render(App);
963
- expect(container.querySelector('.inner')).toBeTruthy();
964
- expect(container.querySelector('.after')).toBeFalsy();
965
-
966
- container.querySelector('button').click();
967
- flushSync();
968
-
969
- expect(container.querySelector('.inner')).toBeFalsy();
970
- expect(container.querySelector('.after')).toBeTruthy();
971
- });
972
-
973
- describe('nested return scenarios', () => {
974
- it('nested return hides content after inner if inside outer if', () => {
975
- component App() {
976
- let a = true;
977
- let b = true;
978
-
979
- if (a) {
980
- <div class="a">{'a'}</div>
981
- if (b) {
982
- <div class="b">{'b'}</div>
983
- return;
984
- }
985
- <div class="after-inner">{'after inner'}</div>
986
- }
987
- <div class="rest">{'rest'}</div>
988
- }
989
-
990
- render(App);
991
- expect(container.querySelector('.a')).toBeTruthy();
992
- expect(container.querySelector('.b')).toBeTruthy();
993
- expect(container.querySelector('.after-inner')).toBeFalsy();
994
- expect(container.querySelector('.rest')).toBeFalsy();
995
- });
996
-
997
- it('nested return shows content after inner if when inner condition is false', () => {
998
- component App() {
999
- let a = true;
1000
- let b = false;
1001
-
1002
- if (a) {
1003
- <div class="a">{'a'}</div>
1004
- if (b) {
1005
- <div class="b">{'b'}</div>
1006
- return;
1007
- }
1008
- <div class="after-inner">{'after inner'}</div>
1009
- }
1010
- <div class="rest">{'rest'}</div>
1011
- }
1012
-
1013
- render(App);
1014
- expect(container.querySelector('.a')).toBeTruthy();
1015
- expect(container.querySelector('.b')).toBeFalsy();
1016
- expect(container.querySelector('.after-inner')).toBeTruthy();
1017
- expect(container.querySelector('.rest')).toBeTruthy();
1018
- });
1019
-
1020
- it('nested return with sibling returns inside outer if', () => {
1021
- component App() {
1022
- let outer = true;
1023
- let a = false;
1024
- let b = true;
1025
-
1026
- if (outer) {
1027
- <div class="outer">{'outer'}</div>
1028
- if (a) {
1029
- <div class="a">{'a'}</div>
1030
- return;
1031
- }
1032
- <div class="between">{'between'}</div>
1033
- if (b) {
1034
- <div class="b">{'b'}</div>
1035
- return;
1036
- }
1037
- <div class="after-b">{'after b'}</div>
1038
- }
1039
- <div class="rest">{'rest'}</div>
1040
- }
1041
-
1042
- render(App);
1043
- expect(container.querySelector('.outer')).toBeTruthy();
1044
- expect(container.querySelector('.a')).toBeFalsy();
1045
- expect(container.querySelector('.between')).toBeTruthy();
1046
- expect(container.querySelector('.b')).toBeTruthy();
1047
- expect(container.querySelector('.after-b')).toBeFalsy();
1048
- expect(container.querySelector('.rest')).toBeFalsy();
1049
- });
1050
-
1051
- it('nested return inside else branch', () => {
1052
- component App() {
1053
- let a = false;
1054
- let b = true;
1055
-
1056
- if (a) {
1057
- <div class="a">{'a'}</div>
1058
- } else {
1059
- <div class="else">{'else'}</div>
1060
- if (b) {
1061
- <div class="b">{'b'}</div>
1062
- return;
1063
- }
1064
- }
1065
- <div class="rest">{'rest'}</div>
1066
- }
1067
-
1068
- render(App);
1069
- expect(container.querySelector('.a')).toBeFalsy();
1070
- expect(container.querySelector('.else')).toBeTruthy();
1071
- expect(container.querySelector('.b')).toBeTruthy();
1072
- expect(container.querySelector('.rest')).toBeFalsy();
1073
- });
1074
-
1075
- it('deeply nested returns (4 levels) - all true', () => {
1076
- component App() {
1077
- let a = true;
1078
- let b = true;
1079
- let c = true;
1080
- let d = true;
1081
-
1082
- if (a) {
1083
- <div class="a">{'a'}</div>
1084
- if (b) {
1085
- <div class="b">{'b'}</div>
1086
- if (c) {
1087
- <div class="c">{'c'}</div>
1088
- if (d) {
1089
- <div class="d">{'d'}</div>
1090
- return;
1091
- }
1092
- }
1093
- }
1094
- }
1095
- <div class="rest">{'rest'}</div>
1096
- }
1097
-
1098
- render(App);
1099
- expect(container.querySelector('.a')).toBeTruthy();
1100
- expect(container.querySelector('.b')).toBeTruthy();
1101
- expect(container.querySelector('.c')).toBeTruthy();
1102
- expect(container.querySelector('.d')).toBeTruthy();
1103
- expect(container.querySelector('.rest')).toBeFalsy();
1104
- });
1105
-
1106
- it('deeply nested returns (4 levels) - innermost false', () => {
1107
- component App() {
1108
- let a = true;
1109
- let b = true;
1110
- let c = true;
1111
- let d = false;
1112
-
1113
- if (a) {
1114
- <div class="a">{'a'}</div>
1115
- if (b) {
1116
- <div class="b">{'b'}</div>
1117
- if (c) {
1118
- <div class="c">{'c'}</div>
1119
- if (d) {
1120
- <div class="d">{'d'}</div>
1121
- return;
1122
- }
1123
- }
1124
- }
1125
- }
1126
- <div class="rest">{'rest'}</div>
1127
- }
1128
-
1129
- render(App);
1130
- expect(container.querySelector('.a')).toBeTruthy();
1131
- expect(container.querySelector('.b')).toBeTruthy();
1132
- expect(container.querySelector('.c')).toBeTruthy();
1133
- expect(container.querySelector('.d')).toBeFalsy();
1134
- expect(container.querySelector('.rest')).toBeTruthy();
1135
- });
1136
-
1137
- it('nested return with else at outer level', () => {
1138
- component App() {
1139
- let a = true;
1140
- let b = true;
1141
-
1142
- if (a) {
1143
- <div class="a">{'a'}</div>
1144
- if (b) {
1145
- <div class="b">{'b'}</div>
1146
- return;
1147
- }
1148
- } else {
1149
- <div class="else">{'else'}</div>
1150
- }
1151
- <div class="rest">{'rest'}</div>
1152
- }
1153
-
1154
- render(App);
1155
- expect(container.querySelector('.a')).toBeTruthy();
1156
- expect(container.querySelector('.b')).toBeTruthy();
1157
- expect(container.querySelector('.else')).toBeFalsy();
1158
- expect(container.querySelector('.rest')).toBeFalsy();
1159
- });
1160
-
1161
- it('nested return with else at outer level - outer false', () => {
1162
- component App() {
1163
- let a = false;
1164
- let b = true;
1165
-
1166
- if (a) {
1167
- <div class="a">{'a'}</div>
1168
- if (b) {
1169
- <div class="b">{'b'}</div>
1170
- return;
1171
- }
1172
- } else {
1173
- <div class="else">{'else'}</div>
1174
- }
1175
- <div class="rest">{'rest'}</div>
1176
- }
1177
-
1178
- render(App);
1179
- expect(container.querySelector('.a')).toBeFalsy();
1180
- expect(container.querySelector('.b')).toBeFalsy();
1181
- expect(container.querySelector('.else')).toBeTruthy();
1182
- expect(container.querySelector('.rest')).toBeTruthy();
1183
- });
1184
-
1185
- it('nested return hides content at multiple intermediate levels', () => {
1186
- component App() {
1187
- let a = true;
1188
- let b = true;
1189
- let c = true;
1190
-
1191
- if (a) {
1192
- <div class="a">{'a'}</div>
1193
- if (b) {
1194
- <div class="b">{'b'}</div>
1195
- if (c) {
1196
- <div class="c">{'c'}</div>
1197
- return;
1198
- }
1199
- <div class="after-c">{'after c'}</div>
1200
- }
1201
- <div class="after-b">{'after b'}</div>
1202
- }
1203
- <div class="rest">{'rest'}</div>
1204
- }
1205
-
1206
- render(App);
1207
- expect(container.querySelector('.a')).toBeTruthy();
1208
- expect(container.querySelector('.b')).toBeTruthy();
1209
- expect(container.querySelector('.c')).toBeTruthy();
1210
- expect(container.querySelector('.after-c')).toBeFalsy();
1211
- expect(container.querySelector('.after-b')).toBeFalsy();
1212
- expect(container.querySelector('.rest')).toBeFalsy();
1213
- });
1214
-
1215
- it('reactive: nested return - outer condition changes to false shows rest', () => {
1216
- component App() {
1217
- let &[a] = track(true);
1218
- let b = true;
1219
-
1220
- <button
1221
- onClick={() => {
1222
- a = false;
1223
- }}
1224
- >
1225
- {'toggle a'}
1226
- </button>
1227
- if (a) {
1228
- <div class="a">{'a'}</div>
1229
- if (b) {
1230
- <div class="b">{'b'}</div>
1231
- return;
1232
- }
1233
- }
1234
- <div class="rest">{'rest'}</div>
1235
- }
1236
-
1237
- render(App);
1238
- expect(container.querySelector('.a')).toBeTruthy();
1239
- expect(container.querySelector('.b')).toBeTruthy();
1240
- expect(container.querySelector('.rest')).toBeFalsy();
1241
-
1242
- container.querySelector('button').click();
1243
- flushSync();
1244
-
1245
- expect(container.querySelector('.a')).toBeFalsy();
1246
- expect(container.querySelector('.b')).toBeFalsy();
1247
- expect(container.querySelector('.rest')).toBeTruthy();
1248
- });
1249
-
1250
- it('reactive: deeply nested return - innermost condition changes', () => {
1251
- component App() {
1252
- let a = true;
1253
- let b = true;
1254
- let &[c] = track(true);
1255
-
1256
- <button
1257
- onClick={() => {
1258
- c = false;
1259
- }}
1260
- >
1261
- {'toggle c'}
1262
- </button>
1263
- if (a) {
1264
- <div class="a">{'a'}</div>
1265
- if (b) {
1266
- <div class="b">{'b'}</div>
1267
- if (c) {
1268
- <div class="c">{'c'}</div>
1269
- return;
1270
- }
1271
- }
1272
- }
1273
- <div class="rest">{'rest'}</div>
1274
- }
1275
-
1276
- render(App);
1277
- expect(container.querySelector('.a')).toBeTruthy();
1278
- expect(container.querySelector('.b')).toBeTruthy();
1279
- expect(container.querySelector('.c')).toBeTruthy();
1280
- expect(container.querySelector('.rest')).toBeFalsy();
1281
-
1282
- container.querySelector('button').click();
1283
- flushSync();
1284
-
1285
- expect(container.querySelector('.a')).toBeTruthy();
1286
- expect(container.querySelector('.b')).toBeTruthy();
1287
- expect(container.querySelector('.c')).toBeFalsy();
1288
- expect(container.querySelector('.rest')).toBeTruthy();
1289
- });
1290
- });
1291
-
1292
- describe('semantic HTML edge cases', () => {
1293
- it('handles return inside ul element - skips sibling after return', () => {
1294
- component App() {
1295
- let show = true;
1296
-
1297
- <ul>
1298
- <li>{'first'}</li>
1299
- if (show) {
1300
- <li>{'second'}</li>
1301
- return;
1302
- }
1303
- <li>{'third'}</li>
1304
- </ul>
1305
- }
1306
-
1307
- render(App);
1308
- expect(container.querySelector('ul')).toBeTruthy();
1309
- expect(container.querySelectorAll('li').length).toBe(2);
1310
- });
1311
-
1312
- it('handles return inside ol element - skips sibling after return', () => {
1313
- component App() {
1314
- let show = true;
1315
-
1316
- <ol>
1317
- <li>{'first'}</li>
1318
- if (show) {
1319
- <li>{'second'}</li>
1320
- return;
1321
- }
1322
- <li>{'third'}</li>
1323
- </ol>
1324
- }
1325
-
1326
- render(App);
1327
- expect(container.querySelector('ol')).toBeTruthy();
1328
- expect(container.querySelectorAll('li').length).toBe(2);
1329
- });
1330
-
1331
- it('handles return inside select element - skips sibling after return', () => {
1332
- component App() {
1333
- let show = true;
1334
-
1335
- <select>
1336
- <option>{'first'}</option>
1337
- if (show) {
1338
- <option>{'second'}</option>
1339
- return;
1340
- }
1341
- <option>{'third'}</option>
1342
- </select>
1343
- }
1344
-
1345
- render(App);
1346
- expect(container.querySelector('select')).toBeTruthy();
1347
- expect(container.querySelectorAll('option').length).toBe(2);
1348
- });
1349
-
1350
- it('handles return inside tbody element - skips sibling after return', () => {
1351
- component App() {
1352
- let show = true;
1353
-
1354
- <table>
1355
- <tbody>
1356
- <tr>
1357
- <td>{'cell 1'}</td>
1358
- </tr>
1359
- if (show) {
1360
- <tr>
1361
- <td>{'cell 2'}</td>
1362
- </tr>
1363
- return;
1364
- }
1365
- <tr>
1366
- <td>{'cell 3'}</td>
1367
- </tr>
1368
- </tbody>
1369
- </table>
1370
- }
1371
-
1372
- render(App);
1373
- expect(container.querySelector('table')).toBeTruthy();
1374
- expect(container.querySelectorAll('tr').length).toBe(2);
1375
- });
1376
-
1377
- it('handles return inside thead element - skips sibling after return', () => {
1378
- component App() {
1379
- let show = true;
1380
-
1381
- <table>
1382
- <thead>
1383
- <tr>
1384
- <th>{'header 1'}</th>
1385
- </tr>
1386
- if (show) {
1387
- <tr>
1388
- <th>{'header 2'}</th>
1389
- </tr>
1390
- return;
1391
- }
1392
- <tr>
1393
- <th>{'header 3'}</th>
1394
- </tr>
1395
- </thead>
1396
- </table>
1397
- }
1398
-
1399
- render(App);
1400
- expect(container.querySelector('thead')).toBeTruthy();
1401
- expect(container.querySelectorAll('thead tr').length).toBe(2);
1402
- });
1403
-
1404
- it('handles return inside tfoot element - skips sibling after return', () => {
1405
- component App() {
1406
- let show = true;
1407
-
1408
- <table>
1409
- <tfoot>
1410
- <tr>
1411
- <td>{'footer 1'}</td>
1412
- </tr>
1413
- if (show) {
1414
- <tr>
1415
- <td>{'footer 2'}</td>
1416
- </tr>
1417
- return;
1418
- }
1419
- <tr>
1420
- <td>{'footer 3'}</td>
1421
- </tr>
1422
- </tfoot>
1423
- </table>
1424
- }
1425
-
1426
- render(App);
1427
- expect(container.querySelector('tfoot')).toBeTruthy();
1428
- expect(container.querySelectorAll('tfoot tr').length).toBe(2);
1429
- });
1430
-
1431
- it('handles return inside tr element - skips sibling after return', () => {
1432
- component App() {
1433
- let show = true;
1434
-
1435
- <table>
1436
- <tbody>
1437
- <tr>
1438
- <td>{'cell 1'}</td>
1439
- if (show) {
1440
- <td>{'cell 2'}</td>
1441
- return;
1442
- }
1443
- <td>{'cell 3'}</td>
1444
- </tr>
1445
- </tbody>
1446
- </table>
1447
- }
1448
-
1449
- render(App);
1450
- expect(container.querySelector('tr')).toBeTruthy();
1451
- expect(container.querySelectorAll('td').length).toBe(2);
1452
- });
1453
-
1454
- it('handles return inside dl element - skips sibling after return', () => {
1455
- component App() {
1456
- let show = true;
1457
-
1458
- <dl>
1459
- <dt>{'term 1'}</dt>
1460
- <dd>{'definition 1'}</dd>
1461
- if (show) {
1462
- <dt>{'term 2'}</dt>
1463
- <dd>{'definition 2'}</dd>
1464
- return;
1465
- }
1466
- <dt>{'term 3'}</dt>
1467
- <dd>{'definition 3'}</dd>
1468
- </dl>
1469
- }
1470
-
1471
- render(App);
1472
- expect(container.querySelector('dl')).toBeTruthy();
1473
- expect(container.querySelectorAll('dt').length).toBe(2);
1474
- });
1475
-
1476
- it('handles return inside figure element - skips sibling after return', () => {
1477
- component App() {
1478
- let show = true;
1479
-
1480
- <figure>
1481
- <img src="test.jpg" alt="test" />
1482
- if (show) {
1483
- <figcaption>{'caption 1'}</figcaption>
1484
- return;
1485
- }
1486
- <figcaption>{'caption 2'}</figcaption>
1487
- </figure>
1488
- }
1489
-
1490
- render(App);
1491
- expect(container.querySelector('figure')).toBeTruthy();
1492
- expect(container.querySelectorAll('figcaption').length).toBe(1);
1493
- });
1494
-
1495
- it('handles return inside fieldset element - skips sibling after return', () => {
1496
- component App() {
1497
- let show = true;
1498
-
1499
- <fieldset>
1500
- <legend>{'legend'}</legend>
1501
- <input type="text" />
1502
- if (show) {
1503
- <input type="checkbox" />
1504
- return;
1505
- }
1506
- <input type="radio" />
1507
- </fieldset>
1508
- }
1509
-
1510
- render(App);
1511
- expect(container.querySelector('fieldset')).toBeTruthy();
1512
- expect(container.querySelectorAll('input').length).toBe(2);
1513
- });
1514
-
1515
- it('handles return inside optgroup element - skips sibling after return', () => {
1516
- component App() {
1517
- let show = true;
1518
-
1519
- <select>
1520
- <optgroup label="group1">
1521
- <option>{'opt1'}</option>
1522
- if (show) {
1523
- <option>{'opt2'}</option>
1524
- return;
1525
- }
1526
- <option>{'opt3'}</option>
1527
- </optgroup>
1528
- </select>
1529
- }
1530
-
1531
- render(App);
1532
- expect(container.querySelector('optgroup')).toBeTruthy();
1533
- expect(container.querySelectorAll('option').length).toBe(2);
1534
- });
1535
-
1536
- it('handles return inside details element - skips sibling after return', () => {
1537
- component App() {
1538
- let show = true;
1539
-
1540
- <details>
1541
- <summary>{'summary'}</summary>
1542
- <p>{'content 1'}</p>
1543
- if (show) {
1544
- <p>{'content 2'}</p>
1545
- return;
1546
- }
1547
- <p>{'content 3'}</p>
1548
- </details>
1549
- }
1550
-
1551
- render(App);
1552
- expect(container.querySelector('details')).toBeTruthy();
1553
- expect(container.querySelectorAll('details p').length).toBe(2);
1554
- });
1555
-
1556
- it('handles return inside picture element - skips sibling after return', () => {
1557
- component App() {
1558
- let show = true;
1559
-
1560
- <picture>
1561
- <source srcset="large.jpg" media="(min-width: 800px)" />
1562
- if (show) {
1563
- <source srcset="medium.jpg" media="(min-width: 400px)" />
1564
- return;
1565
- }
1566
- <source srcset="small.jpg" />
1567
- <img src="fallback.jpg" alt="fallback" />
1568
- </picture>
1569
- }
1570
-
1571
- render(App);
1572
- expect(container.querySelector('picture')).toBeTruthy();
1573
- expect(container.querySelectorAll('source').length).toBe(2);
1574
- });
1575
-
1576
- it('handles return inside menu element - skips sibling after return', () => {
1577
- component App() {
1578
- let show = true;
1579
-
1580
- <menu>
1581
- <li>{'item 1'}</li>
1582
- if (show) {
1583
- <li>{'item 2'}</li>
1584
- return;
1585
- }
1586
- <li>{'item 3'}</li>
1587
- </menu>
1588
- }
1589
-
1590
- render(App);
1591
- expect(container.querySelector('menu')).toBeTruthy();
1592
- expect(container.querySelectorAll('menu li').length).toBe(2);
1593
- });
1594
-
1595
- it('handles return inside video element - skips sibling after return', () => {
1596
- component App() {
1597
- let show = true;
1598
-
1599
- <video>
1600
- <source src="video.mp4" type="video/mp4" />
1601
- if (show) {
1602
- <track src="captions.vtt" kind="captions" />
1603
- return;
1604
- }
1605
- <track src="chapters.vtt" kind="chapters" />
1606
- </video>
1607
- }
1608
-
1609
- render(App);
1610
- expect(container.querySelector('video')).toBeTruthy();
1611
- expect(container.querySelectorAll('track').length).toBe(1);
1612
- });
1613
-
1614
- it('handles return inside audio element - skips sibling after return', () => {
1615
- component App() {
1616
- let show = true;
1617
-
1618
- <audio>
1619
- <source src="audio.mp3" type="audio/mpeg" />
1620
- if (show) {
1621
- <track src="captions.vtt" kind="captions" />
1622
- return;
1623
- }
1624
- <track src="chapters.vtt" kind="chapters" />
1625
- </audio>
1626
- }
1627
-
1628
- render(App);
1629
- expect(container.querySelector('audio')).toBeTruthy();
1630
- expect(container.querySelectorAll('track').length).toBe(1);
1631
- });
1632
-
1633
- it('handles return inside form element - skips sibling after return', () => {
1634
- component App() {
1635
- let show = true;
1636
-
1637
- <form>
1638
- <input type="text" name="field1" />
1639
- if (show) {
1640
- <input type="text" name="field2" />
1641
- return;
1642
- }
1643
- <input type="text" name="field3" />
1644
- </form>
1645
- }
1646
-
1647
- render(App);
1648
- expect(container.querySelector('form')).toBeTruthy();
1649
- expect(container.querySelectorAll('input').length).toBe(2);
1650
- });
1651
-
1652
- it('handles return inside main element - skips sibling after return', () => {
1653
- component App() {
1654
- let show = true;
1655
-
1656
- <main>
1657
- <h1>{'title'}</h1>
1658
- if (show) {
1659
- <p>{'content'}</p>
1660
- return;
1661
- }
1662
- <p>{'other content'}</p>
1663
- </main>
1664
- }
1665
-
1666
- render(App);
1667
- expect(container.querySelector('main')).toBeTruthy();
1668
- expect(container.querySelectorAll('main p').length).toBe(1);
1669
- });
1670
-
1671
- it('handles return inside article element - skips sibling after return', () => {
1672
- component App() {
1673
- let show = true;
1674
-
1675
- <article>
1676
- <h2>{'article title'}</h2>
1677
- if (show) {
1678
- <p>{'article content'}</p>
1679
- return;
1680
- }
1681
- <p>{'more content'}</p>
1682
- </article>
1683
- }
1684
-
1685
- render(App);
1686
- expect(container.querySelector('article')).toBeTruthy();
1687
- expect(container.querySelectorAll('article p').length).toBe(1);
1688
- });
1689
-
1690
- it('handles return inside section element - skips sibling after return', () => {
1691
- component App() {
1692
- let show = true;
1693
-
1694
- <section>
1695
- <h2>{'section title'}</h2>
1696
- if (show) {
1697
- <p>{'section content'}</p>
1698
- return;
1699
- }
1700
- <p>{'more content'}</p>
1701
- </section>
1702
- }
1703
-
1704
- render(App);
1705
- expect(container.querySelector('section')).toBeTruthy();
1706
- expect(container.querySelectorAll('section p').length).toBe(1);
1707
- });
1708
-
1709
- it('handles return inside aside element - skips sibling after return', () => {
1710
- component App() {
1711
- let show = true;
1712
-
1713
- <aside>
1714
- <h3>{'sidebar title'}</h3>
1715
- if (show) {
1716
- <p>{'sidebar content'}</p>
1717
- return;
1718
- }
1719
- <p>{'more sidebar'}</p>
1720
- </aside>
1721
- }
1722
-
1723
- render(App);
1724
- expect(container.querySelector('aside')).toBeTruthy();
1725
- expect(container.querySelectorAll('aside p').length).toBe(1);
1726
- });
1727
-
1728
- it('handles return inside nav element - skips sibling after return', () => {
1729
- component App() {
1730
- let show = true;
1731
-
1732
- <nav>
1733
- <a href="/">{'home'}</a>
1734
- if (show) {
1735
- <a href="/about">{'about'}</a>
1736
- return;
1737
- }
1738
- <a href="/contact">{'contact'}</a>
1739
- </nav>
1740
- }
1741
-
1742
- render(App);
1743
- expect(container.querySelector('nav')).toBeTruthy();
1744
- expect(container.querySelectorAll('nav a').length).toBe(2);
1745
- });
1746
-
1747
- it('handles return inside header element - skips sibling after return', () => {
1748
- component App() {
1749
- let show = true;
1750
-
1751
- <header>
1752
- <h1>{'site title'}</h1>
1753
- if (show) {
1754
- <nav>{'nav'}</nav>
1755
- return;
1756
- }
1757
- <p>{'subtitle'}</p>
1758
- </header>
1759
- }
1760
-
1761
- render(App);
1762
- expect(container.querySelector('header')).toBeTruthy();
1763
- expect(container.querySelector('header nav')).toBeTruthy();
1764
- });
1765
-
1766
- it('handles return inside footer element - skips sibling after return', () => {
1767
- component App() {
1768
- let show = true;
1769
-
1770
- <footer>
1771
- <p>{'copyright'}</p>
1772
- if (show) {
1773
- <a href="/privacy">{'privacy'}</a>
1774
- return;
1775
- }
1776
- <a href="/terms">{'terms'}</a>
1777
- </footer>
1778
- }
1779
-
1780
- render(App);
1781
- expect(container.querySelector('footer')).toBeTruthy();
1782
- expect(container.querySelectorAll('footer a').length).toBe(1);
1783
- });
1784
-
1785
- it('handles return inside address element - skips sibling after return', () => {
1786
- component App() {
1787
- let show = true;
1788
-
1789
- <address>
1790
- <span>{'street'}</span>
1791
- if (show) {
1792
- <span>{'city'}</span>
1793
- return;
1794
- }
1795
- <span>{'country'}</span>
1796
- </address>
1797
- }
1798
-
1799
- render(App);
1800
- expect(container.querySelector('address')).toBeTruthy();
1801
- expect(container.querySelectorAll('address span').length).toBe(2);
1802
- });
1803
-
1804
- it('handles return inside blockquote element - skips sibling after return', () => {
1805
- component App() {
1806
- let show = true;
1807
-
1808
- <blockquote>
1809
- <p>{'quote part 1'}</p>
1810
- if (show) {
1811
- <p>{'quote part 2'}</p>
1812
- return;
1813
- }
1814
- <p>{'quote part 3'}</p>
1815
- </blockquote>
1816
- }
1817
-
1818
- render(App);
1819
- expect(container.querySelector('blockquote')).toBeTruthy();
1820
- expect(container.querySelectorAll('blockquote p').length).toBe(2);
1821
- });
1822
-
1823
- it('handles return inside pre element - skips sibling after return', () => {
1824
- component App() {
1825
- let show = true;
1826
-
1827
- <pre>
1828
- <code>{'line 1\n'}</code>
1829
- if (show) {
1830
- <code>{'line 2\n'}</code>
1831
- return;
1832
- }
1833
- <code>{'line 3'}</code>
1834
- </pre>
1835
- }
1836
-
1837
- render(App);
1838
- expect(container.querySelector('pre')).toBeTruthy();
1839
- expect(container.querySelectorAll('pre code').length).toBe(2);
1840
- });
1841
-
1842
- it('handles deeply nested semantic structure with return', () => {
1843
- component App() {
1844
- let show = true;
1845
-
1846
- <table>
1847
- <thead>
1848
- <tr>
1849
- <th>{'header'}</th>
1850
- if (show) {
1851
- <th>{'extra header'}</th>
1852
- return;
1853
- }
1854
- <th>{'another header'}</th>
1855
- </tr>
1856
- </thead>
1857
- </table>
1858
- }
1859
-
1860
- render(App);
1861
- expect(container.querySelector('thead')).toBeTruthy();
1862
- expect(container.querySelectorAll('th').length).toBe(2);
1863
- });
1864
-
1865
- it('handles return in nested ul > li structure', () => {
1866
- component App() {
1867
- let show = true;
1868
-
1869
- <ul>
1870
- <li>
1871
- <span>{'item 1'}</span>
1872
- if (show) {
1873
- <ul>
1874
- <li>{'nested 1'}</li>
1875
- </ul>
1876
- return;
1877
- }
1878
- <ul>
1879
- <li>{'nested 2'}</li>
1880
- </ul>
1881
- </li>
1882
- </ul>
1883
- }
1884
-
1885
- render(App);
1886
- expect(container.querySelector('ul')).toBeTruthy();
1887
- expect(container.querySelectorAll('ul li ul li').length).toBe(1);
1888
- });
1889
-
1890
- it('handles return in select > optgroup structure', () => {
1891
- component App() {
1892
- let show = true;
1893
-
1894
- <select>
1895
- <optgroup label="group1">
1896
- <option>{'opt1'}</option>
1897
- </optgroup>
1898
- if (show) {
1899
- <optgroup label="group2">
1900
- <option>{'opt2'}</option>
1901
- </optgroup>
1902
- return;
1903
- }
1904
- <optgroup label="group3">
1905
- <option>{'opt3'}</option>
1906
- </optgroup>
1907
- </select>
1908
- }
1909
-
1910
- render(App);
1911
- expect(container.querySelector('select')).toBeTruthy();
1912
- expect(container.querySelectorAll('optgroup').length).toBe(2);
1913
- });
1914
-
1915
- it('handles return when condition is false in semantic element', () => {
1916
- component App() {
1917
- let show = false;
1918
-
1919
- <ul>
1920
- <li>{'first'}</li>
1921
- if (show) {
1922
- <li>{'second'}</li>
1923
- return;
1924
- }
1925
- <li>{'third'}</li>
1926
- </ul>
1927
- }
1928
-
1929
- render(App);
1930
- expect(container.querySelector('ul')).toBeTruthy();
1931
- expect(container.querySelectorAll('li').length).toBe(2);
1932
- expect(container.querySelector('li:last-child').textContent).toBe('third');
1933
- });
1934
-
1935
- it('handles multiple returns in table structure', () => {
1936
- component App() {
1937
- let mode = 1;
1938
-
1939
- <table>
1940
- <tbody>
1941
- <tr>
1942
- <td>{'row 1'}</td>
1943
- </tr>
1944
- if (mode === 1) {
1945
- <tr>
1946
- <td>{'row 2'}</td>
1947
- </tr>
1948
- return;
1949
- }
1950
- if (mode === 2) {
1951
- <tr>
1952
- <td>{'row 3'}</td>
1953
- </tr>
1954
- return;
1955
- }
1956
- <tr>
1957
- <td>{'row 4'}</td>
1958
- </tr>
1959
- </tbody>
1960
- </table>
1961
- }
1962
-
1963
- render(App);
1964
- expect(container.querySelector('table')).toBeTruthy();
1965
- expect(container.querySelectorAll('tr').length).toBe(2);
1966
- });
1967
- });
1968
-
1969
- describe('return value validation', () => {
1970
- it('throws error for return with string value', () => {
1971
- expect(() => {
1972
- compile(`component App() {
1973
- if (true) {
1974
- <div>{'test'}</div>
1975
- return 'hello';
1976
- }
1977
- }`, 'test.tsrx', {
1978
- mode: 'client',
1979
- });
1980
- }).toThrow('Return statements inside components cannot have a return value.');
1981
- });
1982
-
1983
- it('throws error for return with number value', () => {
1984
- expect(() => {
1985
- compile(`component App() {
1986
- if (true) {
1987
- <div>{'test'}</div>
1988
- return 42;
1989
- }
1990
- }`, 'test.tsrx', {
1991
- mode: 'client',
1992
- });
1993
- }).toThrow('Return statements inside components cannot have a return value.');
1994
- });
1995
-
1996
- it('throws error for return with null value', () => {
1997
- expect(() => {
1998
- compile(`component App() {
1999
- if (true) {
2000
- <div>{'test'}</div>
2001
- return null;
2002
- }
2003
- }`, 'test.tsrx', {
2004
- mode: 'client',
2005
- });
2006
- }).toThrow('Return statements inside components cannot have a return value.');
2007
- });
2008
-
2009
- it('throws error for return with undefined value', () => {
2010
- expect(() => {
2011
- compile(`component App() {
2012
- if (true) {
2013
- <div>{'test'}</div>
2014
- return undefined;
2015
- }
2016
- }`, 'test.tsrx', {
2017
- mode: 'client',
2018
- });
2019
- }).toThrow('Return statements inside components cannot have a return value.');
2020
- });
2021
-
2022
- it('throws error for return with object value', () => {
2023
- expect(() => {
2024
- compile(
2025
- `component App() {
2026
- if (true) {
2027
- <div>{'test'}</div>
2028
- return { foo: 'bar' };
2029
- }
2030
- }`,
2031
- 'test.tsrx',
2032
- { mode: 'client' },
2033
- );
2034
- }).toThrow('Return statements inside components cannot have a return value.');
2035
- });
2036
-
2037
- it('throws error for return with variable', () => {
2038
- expect(() => {
2039
- compile(
2040
- `component App() {
2041
- let x = 5;
2042
- if (true) {
2043
- <div>{'test'}</div>
2044
- return x;
2045
- }
2046
- }`,
2047
- 'test.tsrx',
2048
- { mode: 'client' },
2049
- );
2050
- }).toThrow('Return statements inside components cannot have a return value.');
2051
- });
2052
-
2053
- it('accepts void return', () => {
2054
- component App() {
2055
- let show = true;
2056
-
2057
- if (show) {
2058
- <div>{'test'}</div>
2059
- return;
2060
- }
2061
- <div>{'fallback'}</div>
2062
- }
2063
-
2064
- render(App);
2065
- expect(container.querySelector('div')).toBeTruthy();
2066
- });
2067
- });
2068
-
2069
- describe('deeply nested conditions with returns', () => {
2070
- it('handles return inside nested div > if > div > if chain', () => {
2071
- component App() {
2072
- let a = false;
2073
- let b = false;
2074
- let c = false;
2075
- let d = false;
2076
-
2077
- <div class="outer">
2078
- if (a) {
2079
- <span class="a">{'branch a'}</span>
2080
- }
2081
- <div class="inner">
2082
- if (b) {
2083
- <span class="b">{'branch b'}</span>
2084
- }
2085
- if (c) {
2086
- return;
2087
- }
2088
- if (d) {
2089
- <span class="d">{'branch d'}</span>
2090
- return;
2091
- }
2092
- </div>
2093
- </div>
2094
- <div class="after">{'after'}</div>
2095
- }
2096
-
2097
- render(App);
2098
- expect(container.querySelector('.outer')).toBeTruthy();
2099
- expect(container.querySelector('.inner')).toBeTruthy();
2100
- expect(container.querySelector('.after')).toBeTruthy();
2101
- expect(container.querySelector('.a')).toBeFalsy();
2102
- expect(container.querySelector('.b')).toBeFalsy();
2103
- expect(container.querySelector('.d')).toBeFalsy();
2104
- });
2105
-
2106
- it('nested: first return triggers, hides content after outer div', () => {
2107
- component App() {
2108
- let a = false;
2109
- let b = false;
2110
- let c = true;
2111
- let d = false;
2112
-
2113
- <div class="outer">
2114
- if (a) {
2115
- <span class="a">{'branch a'}</span>
2116
- }
2117
- <div class="inner">
2118
- if (b) {
2119
- <span class="b">{'branch b'}</span>
2120
- }
2121
- if (c) {
2122
- return;
2123
- }
2124
- if (d) {
2125
- <span class="d">{'branch d'}</span>
2126
- return;
2127
- }
2128
- </div>
2129
- </div>
2130
- <div class="after">{'after'}</div>
2131
- }
2132
-
2133
- render(App);
2134
- expect(container.querySelector('.outer')).toBeTruthy();
2135
- expect(container.querySelector('.inner')).toBeTruthy();
2136
- expect(container.querySelector('.after')).toBeFalsy();
2137
- });
2138
-
2139
- it('nested: second return triggers with template, hides content after', () => {
2140
- component App() {
2141
- let a = true;
2142
- let b = true;
2143
- let c = false;
2144
- let d = true;
2145
-
2146
- <div class="outer">
2147
- if (a) {
2148
- <span class="a">{'branch a'}</span>
2149
- }
2150
- <div class="inner">
2151
- if (b) {
2152
- <span class="b">{'branch b'}</span>
2153
- }
2154
- if (c) {
2155
- return;
2156
- }
2157
- if (d) {
2158
- <span class="d">{'branch d'}</span>
2159
- return;
2160
- }
2161
- </div>
2162
- </div>
2163
- <div class="after">{'after'}</div>
2164
- }
2165
-
2166
- render(App);
2167
- expect(container.querySelector('.a')).toBeTruthy();
2168
- expect(container.querySelector('.b')).toBeTruthy();
2169
- expect(container.querySelector('.d')).toBeTruthy();
2170
- expect(container.querySelector('.after')).toBeFalsy();
2171
- });
2172
-
2173
- it('nested: both returns active, first wins', () => {
2174
- component App() {
2175
- let a = false;
2176
- let b = false;
2177
- let c = true;
2178
- let d = true;
2179
-
2180
- <div class="outer">
2181
- if (a) {
2182
- <span class="a">{'branch a'}</span>
2183
- }
2184
- <div class="inner">
2185
- if (b) {
2186
- <span class="b">{'branch b'}</span>
2187
- }
2188
- if (c) {
2189
- return;
2190
- }
2191
- if (d) {
2192
- <span class="d">{'branch d'}</span>
2193
- return;
2194
- }
2195
- </div>
2196
- </div>
2197
- <div class="after">{'after'}</div>
2198
- }
2199
-
2200
- render(App);
2201
- expect(container.querySelector('.outer')).toBeTruthy();
2202
- expect(container.querySelector('.inner')).toBeTruthy();
2203
- expect(container.querySelector('.d')).toBeFalsy();
2204
- expect(container.querySelector('.after')).toBeFalsy();
2205
- });
2206
-
2207
- it('nested reactive: toggling conditions updates DOM', () => {
2208
- component App() {
2209
- let &[a] = track(false);
2210
- let &[b] = track(false);
2211
- let &[c] = track(false);
2212
- let &[d] = track(false);
2213
-
2214
- <button
2215
- class="toggle-c"
2216
- onClick={() => {
2217
- c = !c;
2218
- }}
2219
- >
2220
- {'toggle c'}
2221
- </button>
2222
- <button
2223
- class="toggle-d"
2224
- onClick={() => {
2225
- d = !d;
2226
- }}
2227
- >
2228
- {'toggle d'}
2229
- </button>
2230
-
2231
- <div class="outer">
2232
- if (a) {
2233
- <span class="a">{'branch a'}</span>
2234
- }
2235
- <div class="inner">
2236
- if (b) {
2237
- <span class="b">{'branch b'}</span>
2238
- }
2239
- if (c) {
2240
- return;
2241
- }
2242
- if (d) {
2243
- <span class="d">{'branch d'}</span>
2244
- return;
2245
- }
2246
- </div>
2247
- </div>
2248
- <div class="after">{'after'}</div>
2249
- }
2250
-
2251
- render(App);
2252
- expect(container.querySelector('.after')).toBeTruthy();
2253
- expect(container.querySelector('.d')).toBeFalsy();
2254
-
2255
- // trigger c return
2256
- container.querySelector('.toggle-c').click();
2257
- flushSync();
2258
- expect(container.querySelector('.after')).toBeFalsy();
2259
- expect(container.querySelector('.d')).toBeFalsy();
2260
-
2261
- // untrigger c, trigger d
2262
- container.querySelector('.toggle-c').click();
2263
- flushSync();
2264
- container.querySelector('.toggle-d').click();
2265
- flushSync();
2266
- expect(container.querySelector('.d')).toBeTruthy();
2267
- expect(container.querySelector('.after')).toBeFalsy();
2268
-
2269
- // untrigger d, everything visible again
2270
- container.querySelector('.toggle-d').click();
2271
- flushSync();
2272
- expect(container.querySelector('.after')).toBeTruthy();
2273
- });
2274
-
2275
- it(
2276
- 'nested return with conditional parent nodes should be rendered when the would be parent is false',
2277
- () => {
2278
- component App() {
2279
- let a = true;
2280
- let &[b] = track(true);
2281
-
2282
- <button
2283
- class="toggle"
2284
- onClick={() => {
2285
- b = !b;
2286
- }}
2287
- >
2288
- {'Toggle'}
2289
- </button>
2290
- if (a) {
2291
- <div class="a">{'a'}</div>
2292
- if (b) {
2293
- <div class="b">{'b'}</div>
2294
- return;
2295
- }
2296
- }
2297
- <div class="rest">{'rest'}</div>
2298
- }
2299
-
2300
- render(App);
2301
-
2302
- // Initially a=true and b=true - shows a, b, hides rest
2303
- expect(container.querySelector('.a')?.textContent).toBe('a');
2304
- expect(container.querySelector('.b')?.textContent).toBe('b');
2305
- expect(container.querySelector('.rest')).toBeNull();
2306
-
2307
- // Toggle b to false - rest should appear
2308
- container.querySelector('.toggle')?.click();
2309
- flushSync();
2310
-
2311
- expect(container.querySelector('.a')?.textContent).toBe('a');
2312
- expect(container.querySelector('.b')).toBeNull();
2313
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
2314
-
2315
- // Toggle b back to true
2316
- container.querySelector('.toggle')?.click();
2317
- flushSync();
2318
-
2319
- expect(container.querySelector('.a')?.textContent).toBe('a');
2320
- expect(container.querySelector('.b')?.textContent).toBe('b');
2321
- expect(container.querySelector('.rest')).toBeNull();
2322
- },
2323
- );
2324
-
2325
- it('reactive sibling returns cycle through first, second, and fallback branches', () => {
2326
- component App() {
2327
- let &[mode] = track('first');
2328
-
2329
- <button
2330
- class="toggle"
2331
- onClick={() => {
2332
- if (mode === 'first') {
2333
- mode = 'second';
2334
- } else if (mode === 'second') {
2335
- mode = 'none';
2336
- } else {
2337
- mode = 'first';
2338
- }
2339
- }}
2340
- >
2341
- {'toggle'}
2342
- </button>
2343
-
2344
- if (mode === 'first') {
2345
- <div class="first">{'first guard'}</div>
2346
- return;
2347
- }
2348
-
2349
- if (mode === 'second') {
2350
- <div class="second">{'second guard'}</div>
2351
- return;
2352
- }
2353
-
2354
- <div class="rest">{'rest'}</div>
2355
- }
2356
-
2357
- render(App);
2358
- expect(container.querySelector('.first')?.textContent).toBe('first guard');
2359
- expect(container.querySelector('.second')).toBeNull();
2360
- expect(container.querySelector('.rest')).toBeNull();
2361
-
2362
- container.querySelector('.toggle')?.click();
2363
- flushSync();
2364
- expect(container.querySelector('.first')).toBeNull();
2365
- expect(container.querySelector('.second')?.textContent).toBe('second guard');
2366
- expect(container.querySelector('.rest')).toBeNull();
2367
-
2368
- container.querySelector('.toggle')?.click();
2369
- flushSync();
2370
- expect(container.querySelector('.first')).toBeNull();
2371
- expect(container.querySelector('.second')).toBeNull();
2372
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
2373
-
2374
- container.querySelector('.toggle')?.click();
2375
- flushSync();
2376
- expect(container.querySelector('.first')?.textContent).toBe('first guard');
2377
- expect(container.querySelector('.second')).toBeNull();
2378
- expect(container.querySelector('.rest')).toBeNull();
2379
- });
2380
-
2381
- it('reactive nested returns with tracked outer and inner flags transition correctly', () => {
2382
- component App() {
2383
- let &[a] = track(true);
2384
- let &[b] = track(true);
2385
-
2386
- <button
2387
- class="toggle-a"
2388
- onClick={() => {
2389
- a = !a;
2390
- }}
2391
- >
2392
- {'toggle a'}
2393
- </button>
2394
-
2395
- <button
2396
- class="toggle-b"
2397
- onClick={() => {
2398
- b = !b;
2399
- }}
2400
- >
2401
- {'toggle b'}
2402
- </button>
2403
-
2404
- if (a) {
2405
- <div class="a">{'a'}</div>
2406
- if (b) {
2407
- <div class="b">{'b'}</div>
2408
- return;
2409
- }
2410
- }
2411
-
2412
- <div class="rest">{a ? 'a-on rest' : 'a-off rest'}</div>
2413
- }
2414
-
2415
- render(App);
2416
- expect(container.querySelector('.a')?.textContent).toBe('a');
2417
- expect(container.querySelector('.b')?.textContent).toBe('b');
2418
- expect(container.querySelector('.rest')).toBeNull();
2419
-
2420
- container.querySelector('.toggle-b')?.click();
2421
- flushSync();
2422
- expect(container.querySelector('.a')?.textContent).toBe('a');
2423
- expect(container.querySelector('.b')).toBeNull();
2424
- expect(container.querySelector('.rest')?.textContent).toBe('a-on rest');
2425
-
2426
- container.querySelector('.toggle-a')?.click();
2427
- flushSync();
2428
- expect(container.querySelector('.a')).toBeNull();
2429
- expect(container.querySelector('.b')).toBeNull();
2430
- expect(container.querySelector('.rest')?.textContent).toBe('a-off rest');
2431
-
2432
- container.querySelector('.toggle-a')?.click();
2433
- flushSync();
2434
- expect(container.querySelector('.a')?.textContent).toBe('a');
2435
- expect(container.querySelector('.b')).toBeNull();
2436
- expect(container.querySelector('.rest')?.textContent).toBe('a-on rest');
2437
-
2438
- container.querySelector('.toggle-b')?.click();
2439
- flushSync();
2440
- expect(container.querySelector('.a')?.textContent).toBe('a');
2441
- expect(container.querySelector('.b')?.textContent).toBe('b');
2442
- expect(container.querySelector('.rest')).toBeNull();
2443
- });
2444
-
2445
- it('reactive else-if return chain transitions between return and non-return states', () => {
2446
- component App() {
2447
- let &[status] = track(0);
2448
-
2449
- <button
2450
- class="toggle"
2451
- onClick={() => {
2452
- status = (status + 1) % 3;
2453
- }}
2454
- >
2455
- {'toggle'}
2456
- </button>
2457
-
2458
- if (status === 0) {
2459
- <div class="zero">{'zero'}</div>
2460
- return;
2461
- } else if (status === 1) {
2462
- <div class="one">{'one'}</div>
2463
- return;
2464
- }
2465
-
2466
- <div class="rest">{'rest'}</div>
2467
- <div class="tail">{'tail'}</div>
2468
- }
2469
-
2470
- render(App);
2471
- expect(container.querySelector('.zero')?.textContent).toBe('zero');
2472
- expect(container.querySelector('.one')).toBeNull();
2473
- expect(container.querySelector('.rest')).toBeNull();
2474
- expect(container.querySelector('.tail')).toBeNull();
2475
-
2476
- container.querySelector('.toggle')?.click();
2477
- flushSync();
2478
- expect(container.querySelector('.zero')).toBeNull();
2479
- expect(container.querySelector('.one')?.textContent).toBe('one');
2480
- expect(container.querySelector('.rest')).toBeNull();
2481
- expect(container.querySelector('.tail')).toBeNull();
2482
-
2483
- container.querySelector('.toggle')?.click();
2484
- flushSync();
2485
- expect(container.querySelector('.zero')).toBeNull();
2486
- expect(container.querySelector('.one')).toBeNull();
2487
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
2488
- expect(container.querySelector('.tail')?.textContent).toBe('tail');
2489
-
2490
- container.querySelector('.toggle')?.click();
2491
- flushSync();
2492
- expect(container.querySelector('.zero')?.textContent).toBe('zero');
2493
- expect(container.querySelector('.one')).toBeNull();
2494
- expect(container.querySelector('.rest')).toBeNull();
2495
- expect(container.querySelector('.tail')).toBeNull();
2496
- });
2497
- });
2498
- });
2499
-
2500
- describe('throw statements in if blocks', () => {
2501
- it('allows if statement with throw in then body', () => {
2502
- const code = `
2503
- export default component App() {
2504
- let error = true;
2505
- if (error) {
2506
- throw new Error('Test error');
2507
- }
2508
- <div>{'no error'}</div>
2509
- }
2510
- `;
2511
- expect(() => {
2512
- compile(code, 'test.tsrx');
2513
- }).not.toThrow();
2514
- });
2515
-
2516
- it('allows if statement with throw in else body', () => {
2517
- const code = `
2518
- export default component App() {
2519
- let error = false;
2520
- if (error) {
2521
- <div>{'no error'}</div>
2522
- } else {
2523
- throw new Error('Test error');
2524
- }
2525
- }
2526
- `;
2527
- expect(() => {
2528
- compile(code, 'test.tsrx');
2529
- }).not.toThrow();
2530
- });
2531
-
2532
- it('allows if statement with throw in both bodies', () => {
2533
- const code = `
2534
- export default component App() {
2535
- let mode = 'error';
2536
- if (mode === 'a') {
2537
- <div>{'a'}</div>
2538
- } else if (mode === 'b') {
2539
- <div>{'b'}</div>
2540
- } else {
2541
- throw new Error('Unknown mode');
2542
- }
2543
- }
2544
- `;
2545
- expect(() => {
2546
- compile(code, 'test.tsrx');
2547
- }).not.toThrow();
2548
- });
2549
-
2550
- it('allows nested if with throw', () => {
2551
- const code = `
2552
- export default component App() {
2553
- let a = true;
2554
- let b = true;
2555
- if (a) {
2556
- if (b) {
2557
- throw new Error('Both true');
2558
- }
2559
- <div>{'a only'}</div>
2560
- }
2561
- <div>{'rest'}</div>
2562
- }
2563
- `;
2564
- expect(() => {
2565
- compile(code, 'test.tsrx');
2566
- }).not.toThrow();
2567
- });
2568
-
2569
- it('allows throw with reactive condition', () => {
2570
- const code = `
2571
- export default component App() {
2572
- let &[error] = track(false);
2573
- if (error) {
2574
- throw new Error('Error occurred');
2575
- }
2576
- <div>{'success'}</div>
2577
- }
2578
- `;
2579
- expect(() => {
2580
- compile(code, 'test.tsrx');
2581
- }).not.toThrow();
2582
- });
2583
-
2584
- it('allows throw with template in then body and throw in else body', () => {
2585
- const code = `
2586
- export default component App() {
2587
- let error = true;
2588
- if (error) {
2589
- <div>{'error case'}</div>
2590
- } else {
2591
- throw new Error('No error');
2592
- }
2593
- }
2594
- `;
2595
- expect(() => {
2596
- compile(code, 'test.tsrx');
2597
- }).not.toThrow();
108
+ expect(container.textContent).toBe('hello');
2598
109
  });
2599
110
  });