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,544 +1,25 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { flushSync } from 'ripple';
3
2
  import { hydrateComponent, container } from '../setup-hydration.js';
4
3
 
5
- // Import server-compiled components
6
4
  import * as ServerComponents from './compiled/server/return.js';
7
- // Import client-compiled components
8
5
  import * as ClientComponents from './compiled/client/return.js';
9
6
 
10
- describe('hydration > return statements', () => {
11
- describe('basic returns', () => {
12
- it('hydrates direct return - skips content after return', async () => {
13
- await hydrateComponent(ServerComponents.DirectReturn, ClientComponents.DirectReturn);
14
- expect(container.querySelector('.before')?.textContent).toBe('before');
15
- expect(container.querySelector('.after')).toBeNull();
16
- });
17
-
18
- it('hydrates conditional return - condition true skips rest', async () => {
19
- await hydrateComponent(
20
- ServerComponents.ConditionalReturnTrue,
21
- ClientComponents.ConditionalReturnTrue,
22
- );
23
- expect(container.querySelector('.guard')?.textContent).toBe('guard hit');
24
- expect(container.querySelector('.rest')).toBeNull();
25
- });
26
-
27
- it('hydrates conditional return - condition false shows rest', async () => {
28
- await hydrateComponent(
29
- ServerComponents.ConditionalReturnFalse,
30
- ClientComponents.ConditionalReturnFalse,
31
- );
32
- expect(container.querySelector('.guard')).toBeNull();
33
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
34
- });
35
-
36
- it('hydrates content before and after return guard', async () => {
37
- await hydrateComponent(
38
- ServerComponents.ContentBeforeAfterReturn,
39
- ClientComponents.ContentBeforeAfterReturn,
40
- );
41
- expect(container.querySelector('.before')?.textContent).toBe('before');
42
- expect(container.querySelector('.guard')?.textContent).toBe('guard');
43
- expect(container.querySelector('.after')).toBeNull();
44
- });
45
-
46
- it('hydrates multiple elements after guard when condition is false', async () => {
47
- await hydrateComponent(
48
- ServerComponents.MultipleElementsAfterGuard,
49
- ClientComponents.MultipleElementsAfterGuard,
50
- );
51
- expect(container.querySelector('.guard')).toBeNull();
52
- expect(container.querySelector('.first')?.textContent).toBe('first');
53
- expect(container.querySelector('.second')?.textContent).toBe('second');
54
- });
55
- });
56
-
57
- describe('multiple sequential returns', () => {
58
- it('hydrates multiple returns - first hits', async () => {
59
- await hydrateComponent(
60
- ServerComponents.MultipleReturnsFirstHits,
61
- ClientComponents.MultipleReturnsFirstHits,
62
- );
63
- expect(container.querySelector('.first')?.textContent).toBe('first guard');
64
- expect(container.querySelector('.second')).toBeNull();
65
- expect(container.querySelector('.rest')).toBeNull();
66
- });
67
-
68
- it('hydrates multiple returns - second hits', async () => {
69
- await hydrateComponent(
70
- ServerComponents.MultipleReturnsSecondHits,
71
- ClientComponents.MultipleReturnsSecondHits,
72
- );
73
- expect(container.querySelector('.first')).toBeNull();
74
- expect(container.querySelector('.second')?.textContent).toBe('second guard');
75
- expect(container.querySelector('.rest')).toBeNull();
76
- });
77
-
78
- it('hydrates multiple returns - none hit', async () => {
79
- await hydrateComponent(
80
- ServerComponents.MultipleReturnsNoneHit,
81
- ClientComponents.MultipleReturnsNoneHit,
82
- );
83
- expect(container.querySelector('.first')).toBeNull();
84
- expect(container.querySelector('.second')).toBeNull();
85
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
86
- });
87
-
88
- it('hydrates multiple sibling returns at same level', async () => {
89
- await hydrateComponent(
90
- ServerComponents.MultipleSiblingReturns,
91
- ClientComponents.MultipleSiblingReturns,
92
- );
93
- expect(container.querySelector('.mode-a')).toBeNull();
94
- expect(container.querySelector('.mode-b')?.textContent).toBe('mode B');
95
- expect(container.querySelector('.mode-c')).toBeNull();
96
- expect(container.querySelector('.default')).toBeNull();
97
- });
7
+ describe('hydration > function returns', () => {
8
+ it('hydrates guard returns before TSRX output', async () => {
9
+ await hydrateComponent(
10
+ ServerComponents.GuardReturnRenders,
11
+ ClientComponents.GuardReturnRenders,
12
+ );
13
+ expect(container.querySelector('.ready')?.textContent).toBe('ready');
98
14
  });
99
15
 
100
- describe('nested returns', () => {
101
- it('hydrates nested returns - all conditions true', async () => {
102
- await hydrateComponent(
103
- ServerComponents.NestedReturnsAllTrue,
104
- ClientComponents.NestedReturnsAllTrue,
105
- );
106
- expect(container.querySelector('.a')?.textContent).toBe('a is true');
107
- expect(container.querySelector('.b')?.textContent).toBe('b is true');
108
- expect(container.querySelector('.rest')).toBeNull();
109
- });
110
-
111
- it('hydrates nested returns - inner condition false', async () => {
112
- await hydrateComponent(
113
- ServerComponents.NestedReturnsInnerFalse,
114
- ClientComponents.NestedReturnsInnerFalse,
115
- );
116
- expect(container.querySelector('.a')?.textContent).toBe('a is true');
117
- expect(container.querySelector('.b')).toBeNull();
118
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
119
- });
120
-
121
- it('hydrates nested returns - outer condition false', async () => {
122
- await hydrateComponent(
123
- ServerComponents.NestedReturnsOuterFalse,
124
- ClientComponents.NestedReturnsOuterFalse,
125
- );
126
- expect(container.querySelector('.a')).toBeNull();
127
- expect(container.querySelector('.b')).toBeNull();
128
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
129
- });
130
-
131
- it('hydrates deeply nested returns (3 levels) - all true', async () => {
132
- await hydrateComponent(
133
- ServerComponents.DeeplyNestedReturnsAllTrue,
134
- ClientComponents.DeeplyNestedReturnsAllTrue,
135
- );
136
- expect(container.querySelector('.a')?.textContent).toBe('a');
137
- expect(container.querySelector('.b')?.textContent).toBe('b');
138
- expect(container.querySelector('.c')?.textContent).toBe('c');
139
- expect(container.querySelector('.rest')).toBeNull();
140
- });
141
-
142
- it('hydrates deeply nested returns (3 levels) - innermost false', async () => {
143
- await hydrateComponent(
144
- ServerComponents.DeeplyNestedReturnsInnermostFalse,
145
- ClientComponents.DeeplyNestedReturnsInnermostFalse,
146
- );
147
- expect(container.querySelector('.a')?.textContent).toBe('a');
148
- expect(container.querySelector('.b')?.textContent).toBe('b');
149
- expect(container.querySelector('.c')).toBeNull();
150
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
151
- });
16
+ it('hydrates null returns', async () => {
17
+ await hydrateComponent(ServerComponents.GuardReturnNull, ClientComponents.GuardReturnNull);
18
+ expect(container.textContent).toBe('');
152
19
  });
153
20
 
154
- describe('else-if chains with returns', () => {
155
- it('hydrates else-if chain - first condition', async () => {
156
- await hydrateComponent(ServerComponents.ElseIfChainFirst, ClientComponents.ElseIfChainFirst);
157
- expect(container.querySelector('.one')?.textContent).toBe('one');
158
- expect(container.querySelector('.two')).toBeNull();
159
- expect(container.querySelector('.other')).toBeNull();
160
- expect(container.querySelector('.never')).toBeNull();
161
- });
162
-
163
- it('hydrates else-if chain - second condition', async () => {
164
- await hydrateComponent(
165
- ServerComponents.ElseIfChainSecond,
166
- ClientComponents.ElseIfChainSecond,
167
- );
168
- expect(container.querySelector('.one')).toBeNull();
169
- expect(container.querySelector('.two')?.textContent).toBe('two');
170
- expect(container.querySelector('.other')).toBeNull();
171
- expect(container.querySelector('.never')).toBeNull();
172
- });
173
-
174
- it('hydrates else-if chain - else condition', async () => {
175
- await hydrateComponent(ServerComponents.ElseIfChainElse, ClientComponents.ElseIfChainElse);
176
- expect(container.querySelector('.one')).toBeNull();
177
- expect(container.querySelector('.two')).toBeNull();
178
- expect(container.querySelector('.other')?.textContent).toBe('other');
179
- expect(container.querySelector('.never')).toBeNull();
180
- });
181
- });
182
-
183
- describe('return with else branches', () => {
184
- it('hydrates return with else that does not return', async () => {
185
- await hydrateComponent(
186
- ServerComponents.ReturnWithElseNoReturn,
187
- ClientComponents.ReturnWithElseNoReturn,
188
- );
189
- expect(container.querySelector('.true')).toBeNull();
190
- expect(container.querySelector('.false')?.textContent).toBe('condition false');
191
- expect(container.querySelector('.after')?.textContent).toBe('after if-else');
192
- });
193
-
194
- it('hydrates return with else that also returns', async () => {
195
- await hydrateComponent(
196
- ServerComponents.ReturnWithElseBothReturn,
197
- ClientComponents.ReturnWithElseBothReturn,
198
- );
199
- expect(container.querySelector('.true')).toBeNull();
200
- expect(container.querySelector('.false')?.textContent).toBe('condition false');
201
- expect(container.querySelector('.never')).toBeNull();
202
- });
203
- });
204
-
205
- describe('reactive returns', () => {
206
- it('hydrates and toggles reactive return (true to false)', async () => {
207
- await hydrateComponent(
208
- ServerComponents.ReactiveReturnTrueToFalse,
209
- ClientComponents.ReactiveReturnTrueToFalse,
210
- );
211
-
212
- // Initially condition is true, so guard is shown, rest is hidden
213
- expect(container.querySelector('.guard')?.textContent).toBe('guard hit');
214
- expect(container.querySelector('.rest')).toBeNull();
215
-
216
- // Toggle condition to false
217
- container.querySelector('.toggle')?.click();
218
- flushSync();
219
-
220
- // Now rest should be shown
221
- expect(container.querySelector('.guard')).toBeNull();
222
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
223
-
224
- // Toggle back to true
225
- container.querySelector('.toggle')?.click();
226
- flushSync();
227
-
228
- expect(container.querySelector('.guard')?.textContent).toBe('guard hit');
229
- expect(container.querySelector('.rest')).toBeNull();
230
- });
231
-
232
- it('hydrates and toggles reactive return (false to true)', async () => {
233
- await hydrateComponent(
234
- ServerComponents.ReactiveReturnFalseToTrue,
235
- ClientComponents.ReactiveReturnFalseToTrue,
236
- );
237
-
238
- // Initially condition is false, so rest is shown
239
- expect(container.querySelector('.guard')).toBeNull();
240
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
241
-
242
- // Toggle condition to true
243
- container.querySelector('.toggle')?.click();
244
- flushSync();
245
-
246
- // Now guard should be shown, rest hidden
247
- expect(container.querySelector('.guard')?.textContent).toBe('guard hit');
248
- expect(container.querySelector('.rest')).toBeNull();
249
- });
250
-
251
- it('hydrates and toggles reactive nested return', async () => {
252
- await hydrateComponent(
253
- ServerComponents.ReactiveNestedReturn,
254
- ClientComponents.ReactiveNestedReturn,
255
- );
256
-
257
- // Initially a=true and b=true - shows a, b, hides rest
258
- expect(container.querySelector('.a')?.textContent).toBe('a');
259
- expect(container.querySelector('.b')?.textContent).toBe('b');
260
- expect(container.querySelector('.rest')).toBeNull();
261
-
262
- // Toggle b to false - rest should appear
263
- container.querySelector('.toggle')?.click();
264
- flushSync();
265
-
266
- expect(container.querySelector('.a')?.textContent).toBe('a');
267
- expect(container.querySelector('.b')).toBeNull();
268
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
269
-
270
- // Toggle b back to true
271
- container.querySelector('.toggle')?.click();
272
- flushSync();
273
-
274
- expect(container.querySelector('.a')?.textContent).toBe('a');
275
- expect(container.querySelector('.b')?.textContent).toBe('b');
276
- expect(container.querySelector('.rest')).toBeNull();
277
- });
278
-
279
- it('hydrates and cycles reactive sibling returns across all branches', async () => {
280
- await hydrateComponent(
281
- ServerComponents.ReactiveSiblingReturns,
282
- ClientComponents.ReactiveSiblingReturns,
283
- );
284
-
285
- // first
286
- expect(container.querySelector('.first')?.textContent).toBe('first guard');
287
- expect(container.querySelector('.second')).toBeNull();
288
- expect(container.querySelector('.rest')).toBeNull();
289
-
290
- // second
291
- container.querySelector('.toggle')?.click();
292
- flushSync();
293
- expect(container.querySelector('.first')).toBeNull();
294
- expect(container.querySelector('.second')?.textContent).toBe('second guard');
295
- expect(container.querySelector('.rest')).toBeNull();
296
-
297
- // fallback
298
- container.querySelector('.toggle')?.click();
299
- flushSync();
300
- expect(container.querySelector('.first')).toBeNull();
301
- expect(container.querySelector('.second')).toBeNull();
302
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
303
-
304
- // back to first
305
- container.querySelector('.toggle')?.click();
306
- flushSync();
307
- expect(container.querySelector('.first')?.textContent).toBe('first guard');
308
- expect(container.querySelector('.second')).toBeNull();
309
- expect(container.querySelector('.rest')).toBeNull();
310
- });
311
-
312
- it('hydrates nested tracked returns when outer and inner conditions both change', async () => {
313
- await hydrateComponent(
314
- ServerComponents.ReactiveOuterInnerReturns,
315
- ClientComponents.ReactiveOuterInnerReturns,
316
- );
317
-
318
- // a=true, b=true
319
- expect(container.querySelector('.a')?.textContent).toBe('a');
320
- expect(container.querySelector('.b')?.textContent).toBe('b');
321
- expect(container.querySelector('.rest')).toBeNull();
322
-
323
- // b=false => no early return, rest should appear in a-on mode
324
- container.querySelector('.toggle-b')?.click();
325
- flushSync();
326
- expect(container.querySelector('.a')?.textContent).toBe('a');
327
- expect(container.querySelector('.b')).toBeNull();
328
- expect(container.querySelector('.rest')?.textContent).toBe('a-on rest');
329
-
330
- // a=false => outer block disappears, rest switches to a-off mode
331
- container.querySelector('.toggle-a')?.click();
332
- flushSync();
333
- expect(container.querySelector('.a')).toBeNull();
334
- expect(container.querySelector('.b')).toBeNull();
335
- expect(container.querySelector('.rest')?.textContent).toBe('a-off rest');
336
-
337
- // a=true (b still false) => a returns, rest switches back to a-on mode
338
- container.querySelector('.toggle-a')?.click();
339
- flushSync();
340
- expect(container.querySelector('.a')?.textContent).toBe('a');
341
- expect(container.querySelector('.b')).toBeNull();
342
- expect(container.querySelector('.rest')?.textContent).toBe('a-on rest');
343
-
344
- // b=true => early return again, hide rest
345
- container.querySelector('.toggle-b')?.click();
346
- flushSync();
347
- expect(container.querySelector('.a')?.textContent).toBe('a');
348
- expect(container.querySelector('.b')?.textContent).toBe('b');
349
- expect(container.querySelector('.rest')).toBeNull();
350
- });
351
-
352
- it('hydrates reactive else-if return chain through return and non-return states', async () => {
353
- await hydrateComponent(
354
- ServerComponents.ReactiveElseIfReturns,
355
- ClientComponents.ReactiveElseIfReturns,
356
- );
357
-
358
- // status=0
359
- expect(container.querySelector('.zero')?.textContent).toBe('zero');
360
- expect(container.querySelector('.one')).toBeNull();
361
- expect(container.querySelector('.rest')).toBeNull();
362
- expect(container.querySelector('.tail')).toBeNull();
363
-
364
- // status=1
365
- container.querySelector('.toggle')?.click();
366
- flushSync();
367
- expect(container.querySelector('.zero')).toBeNull();
368
- expect(container.querySelector('.one')?.textContent).toBe('one');
369
- expect(container.querySelector('.rest')).toBeNull();
370
- expect(container.querySelector('.tail')).toBeNull();
371
-
372
- // status=2
373
- container.querySelector('.toggle')?.click();
374
- flushSync();
375
- expect(container.querySelector('.zero')).toBeNull();
376
- expect(container.querySelector('.one')).toBeNull();
377
- expect(container.querySelector('.rest')?.textContent).toBe('rest');
378
- expect(container.querySelector('.tail')?.textContent).toBe('tail');
379
-
380
- // status=0
381
- container.querySelector('.toggle')?.click();
382
- flushSync();
383
- expect(container.querySelector('.zero')?.textContent).toBe('zero');
384
- expect(container.querySelector('.one')).toBeNull();
385
- expect(container.querySelector('.rest')).toBeNull();
386
- expect(container.querySelector('.tail')).toBeNull();
387
- });
388
-
389
- it('hydrates deeply nested independent returns and keeps trailing root siblings aligned', async () => {
390
- await hydrateComponent(
391
- ServerComponents.ReactiveDeepNestedIndependentReturns,
392
- ClientComponents.ReactiveDeepNestedIndependentReturns,
393
- );
394
-
395
- const expect_full_content = () => {
396
- expect(container.querySelector('.top')?.textContent).toBe('top');
397
- expect(container.querySelector('.middle')?.textContent).toBe('middle');
398
- expect(container.querySelector('.nest-1-a')?.textContent).toBe('nest-1-a');
399
- expect(container.querySelector('.nest-1-b')?.textContent).toBe('nest-1-b');
400
- expect(container.querySelector('.nest-2-a')?.textContent).toBe('nest-2-a');
401
- expect(container.querySelector('.nest-2-b')?.textContent).toBe('nest-2-b');
402
- expect(container.querySelector('.root-1')?.textContent).toBe('root-1');
403
- expect(container.querySelector('.root-2')?.textContent).toBe('root-2');
404
- expect(container.querySelector('.root-3')?.textContent).toBe('root-3');
405
- expect(container.querySelector('.root-4')?.textContent).toBe('root-4');
406
- };
407
-
408
- const expect_no_hits = () => {
409
- expect(container.querySelector('.hit-1')).toBeNull();
410
- expect(container.querySelector('.hit-2')).toBeNull();
411
- expect(container.querySelector('.hit-3')).toBeNull();
412
- expect(container.querySelector('.hit-4')).toBeNull();
413
- };
414
-
415
- expect_full_content();
416
- expect_no_hits();
417
-
418
- // C3 return: deep nested return should hide trailing root siblings
419
- container.querySelector('.toggle-c3')?.click();
420
- flushSync();
421
- expect(container.querySelector('.hit-3')?.textContent).toBe('hit-3');
422
- expect(container.querySelector('.top')?.textContent).toBe('top');
423
- expect(container.querySelector('.middle')?.textContent).toBe('middle');
424
- expect(container.querySelector('.nest-1-a')?.textContent).toBe('nest-1-a');
425
- expect(container.querySelector('.nest-1-b')?.textContent).toBe('nest-1-b');
426
- expect(container.querySelector('.nest-2-a')?.textContent).toBe('nest-2-a');
427
- expect(container.querySelector('.nest-2-b')).toBeNull();
428
- expect(container.querySelector('.root-1')).toBeNull();
429
- expect(container.querySelector('.root-2')).toBeNull();
430
- expect(container.querySelector('.root-3')).toBeNull();
431
- expect(container.querySelector('.root-4')).toBeNull();
432
-
433
- container.querySelector('.toggle-c3')?.click();
434
- flushSync();
435
- expect_full_content();
436
- expect_no_hits();
437
-
438
- // C1 return: earliest return should cut everything below top
439
- container.querySelector('.toggle-c1')?.click();
440
- flushSync();
441
- expect(container.querySelector('.top')?.textContent).toBe('top');
442
- expect(container.querySelector('.hit-1')?.textContent).toBe('hit-1');
443
- expect(container.querySelector('.middle')).toBeNull();
444
- expect(container.querySelector('.nest-1-a')).toBeNull();
445
- expect(container.querySelector('.nest-1-b')).toBeNull();
446
- expect(container.querySelector('.nest-2-a')).toBeNull();
447
- expect(container.querySelector('.nest-2-b')).toBeNull();
448
- expect(container.querySelector('.root-1')).toBeNull();
449
- expect(container.querySelector('.root-2')).toBeNull();
450
- expect(container.querySelector('.root-3')).toBeNull();
451
- expect(container.querySelector('.root-4')).toBeNull();
452
-
453
- container.querySelector('.toggle-c1')?.click();
454
- flushSync();
455
- expect_full_content();
456
- expect_no_hits();
457
-
458
- // C2 return: mid-level nested return should keep upper nested nodes, hide lower/root siblings
459
- container.querySelector('.toggle-c2')?.click();
460
- flushSync();
461
- expect(container.querySelector('.top')?.textContent).toBe('top');
462
- expect(container.querySelector('.middle')?.textContent).toBe('middle');
463
- expect(container.querySelector('.nest-1-a')?.textContent).toBe('nest-1-a');
464
- expect(container.querySelector('.hit-2')?.textContent).toBe('hit-2');
465
- expect(container.querySelector('.nest-1-b')).toBeNull();
466
- expect(container.querySelector('.nest-2-a')).toBeNull();
467
- expect(container.querySelector('.nest-2-b')).toBeNull();
468
- expect(container.querySelector('.root-1')).toBeNull();
469
- expect(container.querySelector('.root-2')).toBeNull();
470
- expect(container.querySelector('.root-3')).toBeNull();
471
- expect(container.querySelector('.root-4')).toBeNull();
472
-
473
- container.querySelector('.toggle-c2')?.click();
474
- flushSync();
475
- expect_full_content();
476
- expect_no_hits();
477
-
478
- // C4 return: deepest return should keep all nested parents but still hide root siblings
479
- container.querySelector('.toggle-c4')?.click();
480
- flushSync();
481
- expect(container.querySelector('.top')?.textContent).toBe('top');
482
- expect(container.querySelector('.middle')?.textContent).toBe('middle');
483
- expect(container.querySelector('.nest-1-a')?.textContent).toBe('nest-1-a');
484
- expect(container.querySelector('.nest-1-b')?.textContent).toBe('nest-1-b');
485
- expect(container.querySelector('.nest-2-a')?.textContent).toBe('nest-2-a');
486
- expect(container.querySelector('.nest-2-b')?.textContent).toBe('nest-2-b');
487
- expect(container.querySelector('.hit-4')?.textContent).toBe('hit-4');
488
- expect(container.querySelector('.root-1')).toBeNull();
489
- expect(container.querySelector('.root-2')).toBeNull();
490
- expect(container.querySelector('.root-3')).toBeNull();
491
- expect(container.querySelector('.root-4')).toBeNull();
492
-
493
- container.querySelector('.toggle-c4')?.click();
494
- flushSync();
495
- expect_full_content();
496
- expect_no_hits();
497
- });
498
- });
499
-
500
- describe('return in element scopes', () => {
501
- it('hydrates return inside nested element scope', async () => {
502
- await hydrateComponent(
503
- ServerComponents.ReturnInNestedElement,
504
- ClientComponents.ReturnInNestedElement,
505
- );
506
- expect(container.querySelector('.outer')).not.toBeNull();
507
- expect(container.querySelector('.label')?.textContent).toBe('outer');
508
- expect(container.querySelector('.inner')?.textContent).toBe('inner');
509
- expect(container.querySelector('.after')).toBeNull();
510
- });
511
-
512
- it('hydrates return with multiple elements before and after', async () => {
513
- await hydrateComponent(
514
- ServerComponents.ReturnWithMultipleElements,
515
- ClientComponents.ReturnWithMultipleElements,
516
- );
517
- expect(container.querySelector('.title')?.textContent).toBe('title');
518
- expect(container.querySelector('.desc')?.textContent).toBe('description');
519
- expect(container.querySelector('.guard')?.textContent).toBe('guard');
520
- expect(container.querySelector('.guard-span')?.textContent).toBe('guard span');
521
- expect(container.querySelector('.footer')).toBeNull();
522
- expect(container.querySelector('.nav')).toBeNull();
523
- });
524
- });
525
-
526
- describe('return position edge cases', () => {
527
- it('hydrates return at the beginning of component', async () => {
528
- await hydrateComponent(
529
- ServerComponents.ReturnAtBeginning,
530
- ClientComponents.ReturnAtBeginning,
531
- );
532
- expect(container.querySelector('.early')?.textContent).toBe('early exit');
533
- expect(container.querySelector('.never1')).toBeNull();
534
- expect(container.querySelector('.never2')).toBeNull();
535
- });
536
-
537
- it('hydrates return at the end of component', async () => {
538
- await hydrateComponent(ServerComponents.ReturnAtEnd, ClientComponents.ReturnAtEnd);
539
- expect(container.querySelector('.first')?.textContent).toBe('first');
540
- expect(container.querySelector('.second')?.textContent).toBe('second');
541
- expect(container.querySelector('.third')?.textContent).toBe('third');
542
- });
21
+ it('hydrates string returns', async () => {
22
+ await hydrateComponent(ServerComponents.StringReturn, ClientComponents.StringReturn);
23
+ expect(container.textContent).toBe('hello');
543
24
  });
544
25
  });
@@ -6,7 +6,7 @@ describe('await in control flow', () => {
6
6
  });
7
7
 
8
8
  // it('should handle await inside if statement', async () => {
9
- // component App() {
9
+ // function App() { return <>
10
10
  // let condition = true;
11
11
  // let &[data] = track('loading');
12
12
 
@@ -25,7 +25,7 @@ describe('await in control flow', () => {
25
25
  // });
26
26
 
27
27
  // it('should handle await inside for...of loop', async () => {
28
- // component App() {
28
+ // function App() { return <>
29
29
  // const items = [1, 2, 3];
30
30
  // let result = '';
31
31
 
@@ -42,7 +42,7 @@ describe('await in control flow', () => {
42
42
  // });
43
43
 
44
44
  // it('should handle await inside switch statement', async () => {
45
- // component App() {
45
+ // function App() { return <>
46
46
  // let value = 'b';
47
47
 
48
48
  // switch (value) {