ripple 0.2.208 → 0.2.210

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 (108) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +2 -1
  3. package/package.json +2 -6
  4. package/shims/rollup-estree-types.d.ts +1 -1
  5. package/src/compiler/index.d.ts +1 -0
  6. package/src/compiler/index.js +7 -1
  7. package/src/compiler/phases/1-parse/index.js +15 -6
  8. package/src/compiler/phases/2-analyze/css-analyze.js +100 -104
  9. package/src/compiler/phases/2-analyze/index.js +215 -2
  10. package/src/compiler/phases/3-transform/client/index.js +388 -50
  11. package/src/compiler/phases/3-transform/segments.js +123 -39
  12. package/src/compiler/phases/3-transform/server/index.js +266 -13
  13. package/src/compiler/types/index.d.ts +16 -3
  14. package/src/compiler/utils.js +1 -15
  15. package/src/constants.js +0 -2
  16. package/src/helpers.d.ts +4 -0
  17. package/src/html-tree-validation.js +211 -0
  18. package/src/jsx-runtime.d.ts +260 -259
  19. package/src/jsx-runtime.js +12 -12
  20. package/src/runtime/array.js +17 -17
  21. package/src/runtime/create-subscriber.js +1 -1
  22. package/src/runtime/index-client.js +1 -5
  23. package/src/runtime/index-server.js +15 -0
  24. package/src/runtime/internal/client/compat.js +3 -3
  25. package/src/runtime/internal/client/composite.js +6 -1
  26. package/src/runtime/internal/client/head.js +50 -4
  27. package/src/runtime/internal/client/html.js +73 -12
  28. package/src/runtime/internal/client/hydration.js +12 -0
  29. package/src/runtime/internal/client/index.js +1 -1
  30. package/src/runtime/internal/client/portal.js +54 -29
  31. package/src/runtime/internal/client/rpc.js +3 -1
  32. package/src/runtime/internal/client/switch.js +5 -0
  33. package/src/runtime/internal/client/template.js +117 -11
  34. package/src/runtime/internal/client/try.js +1 -0
  35. package/src/runtime/internal/server/index.js +113 -1
  36. package/src/runtime/internal/server/rpc.js +4 -4
  37. package/src/runtime/map.js +2 -2
  38. package/src/runtime/object.js +6 -6
  39. package/src/runtime/proxy.js +12 -11
  40. package/src/runtime/reactive-value.js +9 -1
  41. package/src/runtime/set.js +12 -7
  42. package/src/runtime/url-search-params.js +0 -1
  43. package/src/server/index.js +4 -0
  44. package/src/utils/hashing.js +15 -0
  45. package/src/utils/normalize_css_property_name.js +1 -1
  46. package/tests/client/array/array.mutations.test.ripple +8 -8
  47. package/tests/client/basic/basic.errors.test.ripple +28 -0
  48. package/tests/client/basic/basic.events.test.ripple +6 -3
  49. package/tests/client/basic/basic.utilities.test.ripple +1 -1
  50. package/tests/client/compiler/compiler.regex.test.ripple +10 -8
  51. package/tests/client/composite/composite.generics.test.ripple +5 -2
  52. package/tests/client/dynamic-elements.test.ripple +30 -1
  53. package/tests/client/function-overload-import.ripple +6 -7
  54. package/tests/client/html.test.ripple +0 -1
  55. package/tests/client/object.test.ripple +2 -2
  56. package/tests/client/portal.test.ripple +3 -3
  57. package/tests/client/return.test.ripple +2500 -0
  58. package/tests/client/try.test.ripple +69 -0
  59. package/tests/client/typescript-generics.test.ripple +1 -1
  60. package/tests/client/url/url.derived.test.ripple +1 -1
  61. package/tests/client/url/url.parsing.test.ripple +3 -3
  62. package/tests/client/url/url.partial-removal.test.ripple +7 -7
  63. package/tests/client/url/url.reactivity.test.ripple +15 -15
  64. package/tests/client/url/url.serialization.test.ripple +2 -2
  65. package/tests/hydration/basic.test.js +23 -0
  66. package/tests/hydration/build-components.js +10 -4
  67. package/tests/hydration/compiled/client/basic.js +165 -3
  68. package/tests/hydration/compiled/client/for.js +1140 -23
  69. package/tests/hydration/compiled/client/head.js +234 -0
  70. package/tests/hydration/compiled/client/html.js +135 -0
  71. package/tests/hydration/compiled/client/portal.js +172 -0
  72. package/tests/hydration/compiled/client/reactivity.js +3 -1
  73. package/tests/hydration/compiled/client/return.js +1976 -0
  74. package/tests/hydration/compiled/client/switch.js +162 -0
  75. package/tests/hydration/compiled/server/basic.js +249 -0
  76. package/tests/hydration/compiled/server/events.js +1 -1
  77. package/tests/hydration/compiled/server/for.js +891 -1
  78. package/tests/hydration/compiled/server/head.js +291 -0
  79. package/tests/hydration/compiled/server/html.js +133 -0
  80. package/tests/hydration/compiled/server/if.js +1 -1
  81. package/tests/hydration/compiled/server/portal.js +250 -0
  82. package/tests/hydration/compiled/server/reactivity.js +1 -1
  83. package/tests/hydration/compiled/server/return.js +1969 -0
  84. package/tests/hydration/compiled/server/switch.js +130 -0
  85. package/tests/hydration/components/basic.ripple +55 -0
  86. package/tests/hydration/components/for.ripple +403 -0
  87. package/tests/hydration/components/head.ripple +111 -0
  88. package/tests/hydration/components/html.ripple +38 -0
  89. package/tests/hydration/components/portal.ripple +49 -0
  90. package/tests/hydration/components/return.ripple +564 -0
  91. package/tests/hydration/components/switch.ripple +51 -0
  92. package/tests/hydration/for.test.js +363 -0
  93. package/tests/hydration/head.test.js +105 -0
  94. package/tests/hydration/html.test.js +46 -0
  95. package/tests/hydration/portal.test.js +71 -0
  96. package/tests/hydration/return.test.js +544 -0
  97. package/tests/hydration/switch.test.js +42 -0
  98. package/tests/server/basic.attributes.test.ripple +1 -1
  99. package/tests/server/compiler.test.ripple +22 -0
  100. package/tests/server/composite.test.ripple +5 -2
  101. package/tests/server/html-nesting-validation.test.ripple +237 -0
  102. package/tests/server/return.test.ripple +1379 -0
  103. package/tests/setup-hydration.js +6 -1
  104. package/tests/utils/escaping.test.js +3 -1
  105. package/tests/utils/normalize_css_property_name.test.js +0 -1
  106. package/tests/utils/patterns.test.js +6 -2
  107. package/tests/utils/sanitize_template_string.test.js +3 -2
  108. package/types/server.d.ts +16 -0
@@ -0,0 +1,2500 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { track, flushSync } from 'ripple';
3
+ import { compile } from 'ripple/compiler';
4
+
5
+ describe('returns in prohibited scopes', () => {
6
+ it('throws error when return is used in module scope', () => {
7
+ expect(
8
+ () => compile(`
9
+ return;
10
+ component App() {
11
+ <div>{'hello'}</div>
12
+ }
13
+ `, 'test.ripple', {
14
+ mode: 'client',
15
+ }),
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();
312
+ });
313
+
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
+ }
328
+ }
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();
480
+ });
481
+
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;
539
+ }
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();
549
+ });
550
+
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();
573
+ });
574
+
575
+ it('handles return at the beginning of component', () => {
576
+ component App() {
577
+ if (true) {
578
+ <div class="early">{'early exit'}</div>
579
+ return;
580
+ }
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
+
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
+ }
599
+ }
600
+
601
+ render(App);
602
+ expect(container.querySelector('.first')).toBeTruthy();
603
+ expect(container.querySelector('.second')).toBeTruthy();
604
+ expect(container.querySelector('.third')).toBeTruthy();
605
+ });
606
+
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>
618
+ }
619
+
620
+ render(App);
621
+ expect(container.querySelector('.func')).toBeTruthy();
622
+ expect(container.querySelector('.no-func')).toBeFalsy();
623
+ });
624
+
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>
635
+ }
636
+
637
+ 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.ripple', {
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.ripple', {
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.ripple', {
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(
2012
+ `component App() {
2013
+ if (true) {
2014
+ <div>{'test'}</div>
2015
+ return undefined;
2016
+ }
2017
+ }`,
2018
+ 'test.ripple',
2019
+ { mode: 'client' },
2020
+ );
2021
+ }).toThrow('Return statements inside components cannot have a return value.');
2022
+ });
2023
+
2024
+ it('throws error for return with object value', () => {
2025
+ expect(() => {
2026
+ compile(
2027
+ `component App() {
2028
+ if (true) {
2029
+ <div>{'test'}</div>
2030
+ return { foo: 'bar' };
2031
+ }
2032
+ }`,
2033
+ 'test.ripple',
2034
+ { mode: 'client' },
2035
+ );
2036
+ }).toThrow('Return statements inside components cannot have a return value.');
2037
+ });
2038
+
2039
+ it('throws error for return with variable', () => {
2040
+ expect(() => {
2041
+ compile(
2042
+ `component App() {
2043
+ let x = 5;
2044
+ if (true) {
2045
+ <div>{'test'}</div>
2046
+ return x;
2047
+ }
2048
+ }`,
2049
+ 'test.ripple',
2050
+ { mode: 'client' },
2051
+ );
2052
+ }).toThrow('Return statements inside components cannot have a return value.');
2053
+ });
2054
+
2055
+ it('accepts void return', () => {
2056
+ component App() {
2057
+ let show = true;
2058
+
2059
+ if (show) {
2060
+ <div>{'test'}</div>
2061
+ return;
2062
+ }
2063
+ <div>{'fallback'}</div>
2064
+ }
2065
+
2066
+ render(App);
2067
+ expect(container.querySelector('div')).toBeTruthy();
2068
+ });
2069
+ });
2070
+
2071
+ describe('deeply nested conditions with returns', () => {
2072
+ it('handles return inside nested div > if > div > if chain', () => {
2073
+ component App() {
2074
+ let a = false;
2075
+ let b = false;
2076
+ let c = false;
2077
+ let d = false;
2078
+
2079
+ <div class="outer">
2080
+ if (a) {
2081
+ <span class="a">{'branch a'}</span>
2082
+ }
2083
+ <div class="inner">
2084
+ if (b) {
2085
+ <span class="b">{'branch b'}</span>
2086
+ }
2087
+ if (c) {
2088
+ return;
2089
+ }
2090
+ if (d) {
2091
+ <span class="d">{'branch d'}</span>
2092
+ return;
2093
+ }
2094
+ </div>
2095
+ </div>
2096
+ <div class="after">{'after'}</div>
2097
+ }
2098
+
2099
+ render(App);
2100
+ expect(container.querySelector('.outer')).toBeTruthy();
2101
+ expect(container.querySelector('.inner')).toBeTruthy();
2102
+ expect(container.querySelector('.after')).toBeTruthy();
2103
+ expect(container.querySelector('.a')).toBeFalsy();
2104
+ expect(container.querySelector('.b')).toBeFalsy();
2105
+ expect(container.querySelector('.d')).toBeFalsy();
2106
+ });
2107
+
2108
+ it('nested: first return triggers, hides content after outer div', () => {
2109
+ component App() {
2110
+ let a = false;
2111
+ let b = false;
2112
+ let c = true;
2113
+ let d = false;
2114
+
2115
+ <div class="outer">
2116
+ if (a) {
2117
+ <span class="a">{'branch a'}</span>
2118
+ }
2119
+ <div class="inner">
2120
+ if (b) {
2121
+ <span class="b">{'branch b'}</span>
2122
+ }
2123
+ if (c) {
2124
+ return;
2125
+ }
2126
+ if (d) {
2127
+ <span class="d">{'branch d'}</span>
2128
+ return;
2129
+ }
2130
+ </div>
2131
+ </div>
2132
+ <div class="after">{'after'}</div>
2133
+ }
2134
+
2135
+ render(App);
2136
+ expect(container.querySelector('.outer')).toBeTruthy();
2137
+ expect(container.querySelector('.inner')).toBeTruthy();
2138
+ expect(container.querySelector('.after')).toBeFalsy();
2139
+ });
2140
+
2141
+ it('nested: second return triggers with template, hides content after', () => {
2142
+ component App() {
2143
+ let a = true;
2144
+ let b = true;
2145
+ let c = false;
2146
+ let d = true;
2147
+
2148
+ <div class="outer">
2149
+ if (a) {
2150
+ <span class="a">{'branch a'}</span>
2151
+ }
2152
+ <div class="inner">
2153
+ if (b) {
2154
+ <span class="b">{'branch b'}</span>
2155
+ }
2156
+ if (c) {
2157
+ return;
2158
+ }
2159
+ if (d) {
2160
+ <span class="d">{'branch d'}</span>
2161
+ return;
2162
+ }
2163
+ </div>
2164
+ </div>
2165
+ <div class="after">{'after'}</div>
2166
+ }
2167
+
2168
+ render(App);
2169
+ expect(container.querySelector('.a')).toBeTruthy();
2170
+ expect(container.querySelector('.b')).toBeTruthy();
2171
+ expect(container.querySelector('.d')).toBeTruthy();
2172
+ expect(container.querySelector('.after')).toBeFalsy();
2173
+ });
2174
+
2175
+ it('nested: both returns active, first wins', () => {
2176
+ component App() {
2177
+ let a = false;
2178
+ let b = false;
2179
+ let c = true;
2180
+ let d = true;
2181
+
2182
+ <div class="outer">
2183
+ if (a) {
2184
+ <span class="a">{'branch a'}</span>
2185
+ }
2186
+ <div class="inner">
2187
+ if (b) {
2188
+ <span class="b">{'branch b'}</span>
2189
+ }
2190
+ if (c) {
2191
+ return;
2192
+ }
2193
+ if (d) {
2194
+ <span class="d">{'branch d'}</span>
2195
+ return;
2196
+ }
2197
+ </div>
2198
+ </div>
2199
+ <div class="after">{'after'}</div>
2200
+ }
2201
+
2202
+ render(App);
2203
+ expect(container.querySelector('.outer')).toBeTruthy();
2204
+ expect(container.querySelector('.inner')).toBeTruthy();
2205
+ expect(container.querySelector('.d')).toBeFalsy();
2206
+ expect(container.querySelector('.after')).toBeFalsy();
2207
+ });
2208
+
2209
+ it('nested reactive: toggling conditions updates DOM', () => {
2210
+ component App() {
2211
+ let a = track(false);
2212
+ let b = track(false);
2213
+ let c = track(false);
2214
+ let d = track(false);
2215
+
2216
+ <button
2217
+ class="toggle-c"
2218
+ onClick={() => {
2219
+ @c = !@c;
2220
+ }}
2221
+ >
2222
+ {'toggle c'}
2223
+ </button>
2224
+ <button
2225
+ class="toggle-d"
2226
+ onClick={() => {
2227
+ @d = !@d;
2228
+ }}
2229
+ >
2230
+ {'toggle d'}
2231
+ </button>
2232
+
2233
+ <div class="outer">
2234
+ if (@a) {
2235
+ <span class="a">{'branch a'}</span>
2236
+ }
2237
+ <div class="inner">
2238
+ if (@b) {
2239
+ <span class="b">{'branch b'}</span>
2240
+ }
2241
+ if (@c) {
2242
+ return;
2243
+ }
2244
+ if (@d) {
2245
+ <span class="d">{'branch d'}</span>
2246
+ return;
2247
+ }
2248
+ </div>
2249
+ </div>
2250
+ <div class="after">{'after'}</div>
2251
+ }
2252
+
2253
+ render(App);
2254
+ expect(container.querySelector('.after')).toBeTruthy();
2255
+ expect(container.querySelector('.d')).toBeFalsy();
2256
+
2257
+ // trigger c return
2258
+ container.querySelector('.toggle-c').click();
2259
+ flushSync();
2260
+ expect(container.querySelector('.after')).toBeFalsy();
2261
+ expect(container.querySelector('.d')).toBeFalsy();
2262
+
2263
+ // untrigger c, trigger d
2264
+ container.querySelector('.toggle-c').click();
2265
+ flushSync();
2266
+ container.querySelector('.toggle-d').click();
2267
+ flushSync();
2268
+ expect(container.querySelector('.d')).toBeTruthy();
2269
+ expect(container.querySelector('.after')).toBeFalsy();
2270
+
2271
+ // untrigger d, everything visible again
2272
+ container.querySelector('.toggle-d').click();
2273
+ flushSync();
2274
+ expect(container.querySelector('.after')).toBeTruthy();
2275
+ });
2276
+
2277
+ it(
2278
+ 'nested return with conditional parent nodes should be rendered when the would be parent is false',
2279
+ () => {
2280
+ component App() {
2281
+ let a = true;
2282
+ let b = track(true);
2283
+
2284
+ <button
2285
+ class="toggle"
2286
+ onClick={() => {
2287
+ @b = !@b;
2288
+ }}
2289
+ >
2290
+ {'Toggle'}
2291
+ </button>
2292
+ if (a) {
2293
+ <div class="a">{'a'}</div>
2294
+ if (@b) {
2295
+ <div class="b">{'b'}</div>
2296
+ return;
2297
+ }
2298
+ }
2299
+ <div class="rest">{'rest'}</div>
2300
+ }
2301
+
2302
+ render(App);
2303
+
2304
+ // Initially a=true and b=true - shows a, b, hides rest
2305
+ expect(container.querySelector('.a')?.textContent).toBe('a');
2306
+ expect(container.querySelector('.b')?.textContent).toBe('b');
2307
+ expect(container.querySelector('.rest')).toBeNull();
2308
+
2309
+ // Toggle b to false - rest should appear
2310
+ container.querySelector('.toggle')?.click();
2311
+ flushSync();
2312
+
2313
+ expect(container.querySelector('.a')?.textContent).toBe('a');
2314
+ expect(container.querySelector('.b')).toBeNull();
2315
+ expect(container.querySelector('.rest')?.textContent).toBe('rest');
2316
+
2317
+ // Toggle b back to true
2318
+ container.querySelector('.toggle')?.click();
2319
+ flushSync();
2320
+
2321
+ expect(container.querySelector('.a')?.textContent).toBe('a');
2322
+ expect(container.querySelector('.b')?.textContent).toBe('b');
2323
+ expect(container.querySelector('.rest')).toBeNull();
2324
+ },
2325
+ );
2326
+
2327
+ it('reactive sibling returns cycle through first, second, and fallback branches', () => {
2328
+ component App() {
2329
+ let mode = track('first');
2330
+
2331
+ <button
2332
+ class="toggle"
2333
+ onClick={() => {
2334
+ if (@mode === 'first') {
2335
+ @mode = 'second';
2336
+ } else if (@mode === 'second') {
2337
+ @mode = 'none';
2338
+ } else {
2339
+ @mode = 'first';
2340
+ }
2341
+ }}
2342
+ >
2343
+ {'toggle'}
2344
+ </button>
2345
+
2346
+ if (@mode === 'first') {
2347
+ <div class="first">{'first guard'}</div>
2348
+ return;
2349
+ }
2350
+
2351
+ if (@mode === 'second') {
2352
+ <div class="second">{'second guard'}</div>
2353
+ return;
2354
+ }
2355
+
2356
+ <div class="rest">{'rest'}</div>
2357
+ }
2358
+
2359
+ render(App);
2360
+ expect(container.querySelector('.first')?.textContent).toBe('first guard');
2361
+ expect(container.querySelector('.second')).toBeNull();
2362
+ expect(container.querySelector('.rest')).toBeNull();
2363
+
2364
+ container.querySelector('.toggle')?.click();
2365
+ flushSync();
2366
+ expect(container.querySelector('.first')).toBeNull();
2367
+ expect(container.querySelector('.second')?.textContent).toBe('second guard');
2368
+ expect(container.querySelector('.rest')).toBeNull();
2369
+
2370
+ container.querySelector('.toggle')?.click();
2371
+ flushSync();
2372
+ expect(container.querySelector('.first')).toBeNull();
2373
+ expect(container.querySelector('.second')).toBeNull();
2374
+ expect(container.querySelector('.rest')?.textContent).toBe('rest');
2375
+
2376
+ container.querySelector('.toggle')?.click();
2377
+ flushSync();
2378
+ expect(container.querySelector('.first')?.textContent).toBe('first guard');
2379
+ expect(container.querySelector('.second')).toBeNull();
2380
+ expect(container.querySelector('.rest')).toBeNull();
2381
+ });
2382
+
2383
+ it('reactive nested returns with tracked outer and inner flags transition correctly', () => {
2384
+ component App() {
2385
+ let a = track(true);
2386
+ let b = track(true);
2387
+
2388
+ <button
2389
+ class="toggle-a"
2390
+ onClick={() => {
2391
+ @a = !@a;
2392
+ }}
2393
+ >
2394
+ {'toggle a'}
2395
+ </button>
2396
+
2397
+ <button
2398
+ class="toggle-b"
2399
+ onClick={() => {
2400
+ @b = !@b;
2401
+ }}
2402
+ >
2403
+ {'toggle b'}
2404
+ </button>
2405
+
2406
+ if (@a) {
2407
+ <div class="a">{'a'}</div>
2408
+ if (@b) {
2409
+ <div class="b">{'b'}</div>
2410
+ return;
2411
+ }
2412
+ }
2413
+
2414
+ <div class="rest">{@a ? 'a-on rest' : 'a-off rest'}</div>
2415
+ }
2416
+
2417
+ render(App);
2418
+ expect(container.querySelector('.a')?.textContent).toBe('a');
2419
+ expect(container.querySelector('.b')?.textContent).toBe('b');
2420
+ expect(container.querySelector('.rest')).toBeNull();
2421
+
2422
+ container.querySelector('.toggle-b')?.click();
2423
+ flushSync();
2424
+ expect(container.querySelector('.a')?.textContent).toBe('a');
2425
+ expect(container.querySelector('.b')).toBeNull();
2426
+ expect(container.querySelector('.rest')?.textContent).toBe('a-on rest');
2427
+
2428
+ container.querySelector('.toggle-a')?.click();
2429
+ flushSync();
2430
+ expect(container.querySelector('.a')).toBeNull();
2431
+ expect(container.querySelector('.b')).toBeNull();
2432
+ expect(container.querySelector('.rest')?.textContent).toBe('a-off rest');
2433
+
2434
+ container.querySelector('.toggle-a')?.click();
2435
+ flushSync();
2436
+ expect(container.querySelector('.a')?.textContent).toBe('a');
2437
+ expect(container.querySelector('.b')).toBeNull();
2438
+ expect(container.querySelector('.rest')?.textContent).toBe('a-on rest');
2439
+
2440
+ container.querySelector('.toggle-b')?.click();
2441
+ flushSync();
2442
+ expect(container.querySelector('.a')?.textContent).toBe('a');
2443
+ expect(container.querySelector('.b')?.textContent).toBe('b');
2444
+ expect(container.querySelector('.rest')).toBeNull();
2445
+ });
2446
+
2447
+ it('reactive else-if return chain transitions between return and non-return states', () => {
2448
+ component App() {
2449
+ let status = track(0);
2450
+
2451
+ <button
2452
+ class="toggle"
2453
+ onClick={() => {
2454
+ @status = (@status + 1) % 3;
2455
+ }}
2456
+ >
2457
+ {'toggle'}
2458
+ </button>
2459
+
2460
+ if (@status === 0) {
2461
+ <div class="zero">{'zero'}</div>
2462
+ return;
2463
+ } else if (@status === 1) {
2464
+ <div class="one">{'one'}</div>
2465
+ return;
2466
+ }
2467
+
2468
+ <div class="rest">{'rest'}</div>
2469
+ <div class="tail">{'tail'}</div>
2470
+ }
2471
+
2472
+ render(App);
2473
+ expect(container.querySelector('.zero')?.textContent).toBe('zero');
2474
+ expect(container.querySelector('.one')).toBeNull();
2475
+ expect(container.querySelector('.rest')).toBeNull();
2476
+ expect(container.querySelector('.tail')).toBeNull();
2477
+
2478
+ container.querySelector('.toggle')?.click();
2479
+ flushSync();
2480
+ expect(container.querySelector('.zero')).toBeNull();
2481
+ expect(container.querySelector('.one')?.textContent).toBe('one');
2482
+ expect(container.querySelector('.rest')).toBeNull();
2483
+ expect(container.querySelector('.tail')).toBeNull();
2484
+
2485
+ container.querySelector('.toggle')?.click();
2486
+ flushSync();
2487
+ expect(container.querySelector('.zero')).toBeNull();
2488
+ expect(container.querySelector('.one')).toBeNull();
2489
+ expect(container.querySelector('.rest')?.textContent).toBe('rest');
2490
+ expect(container.querySelector('.tail')?.textContent).toBe('tail');
2491
+
2492
+ container.querySelector('.toggle')?.click();
2493
+ flushSync();
2494
+ expect(container.querySelector('.zero')?.textContent).toBe('zero');
2495
+ expect(container.querySelector('.one')).toBeNull();
2496
+ expect(container.querySelector('.rest')).toBeNull();
2497
+ expect(container.querySelector('.tail')).toBeNull();
2498
+ });
2499
+ });
2500
+ });