ripple 0.3.11 → 0.3.13

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 (190) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/package.json +8 -2
  3. package/src/compiler/phases/1-parse/index.js +73 -30
  4. package/src/compiler/phases/2-analyze/index.js +28 -58
  5. package/src/compiler/phases/3-transform/client/index.js +127 -164
  6. package/src/compiler/phases/3-transform/segments.js +4 -8
  7. package/src/compiler/phases/3-transform/server/index.js +210 -360
  8. package/src/compiler/types/import.d.ts +0 -12
  9. package/src/compiler/types/index.d.ts +12 -5
  10. package/src/compiler/types/parse.d.ts +2 -0
  11. package/src/compiler/utils.js +39 -44
  12. package/src/helpers.d.ts +2 -0
  13. package/src/runtime/index-client.js +15 -13
  14. package/src/runtime/index-server.js +18 -11
  15. package/src/runtime/internal/client/blocks.js +19 -23
  16. package/src/runtime/internal/client/constants.js +20 -9
  17. package/src/runtime/internal/client/index.js +14 -4
  18. package/src/runtime/internal/client/runtime.js +435 -173
  19. package/src/runtime/internal/client/try.js +334 -156
  20. package/src/runtime/internal/client/types.d.ts +26 -0
  21. package/src/runtime/internal/server/blocks.js +183 -0
  22. package/src/runtime/internal/server/constants.js +7 -0
  23. package/src/runtime/internal/server/index.js +780 -148
  24. package/src/runtime/internal/server/types.d.ts +35 -0
  25. package/src/server/index.js +1 -1
  26. package/src/utils/async.js +35 -0
  27. package/src/utils/builders.js +3 -1
  28. package/tests/client/__snapshots__/computed-properties.test.rsrx.snap +49 -0
  29. package/tests/client/__snapshots__/for.test.rsrx.snap +319 -0
  30. package/tests/client/__snapshots__/html.test.rsrx.snap +40 -0
  31. package/tests/client/_etc.test.rsrx +7 -0
  32. package/tests/client/array/{array.static.test.ripple → array.static.test.rsrx} +18 -20
  33. package/tests/client/async-suspend.test.rsrx +662 -0
  34. package/tests/client/basic/__snapshots__/basic.attributes.test.rsrx.snap +60 -0
  35. package/tests/client/basic/__snapshots__/basic.rendering.test.rsrx.snap +59 -0
  36. package/tests/client/basic/{basic.errors.test.ripple → basic.errors.test.rsrx} +2 -2
  37. package/tests/client/compiler/__snapshots__/compiler.assignments.test.rsrx.snap +12 -0
  38. package/tests/client/compiler/__snapshots__/compiler.typescript.test.rsrx.snap +46 -0
  39. package/tests/client/compiler/{compiler.try-in-function.test.ripple → compiler.try-in-function.test.rsrx} +8 -6
  40. package/tests/client/composite/__snapshots__/composite.render.test.rsrx.snap +37 -0
  41. package/tests/client/{function-overload.test.ripple → function-overload.test.rsrx} +1 -1
  42. package/tests/client/try.test.rsrx +1702 -0
  43. package/tests/hydration/build-components.js +5 -3
  44. package/tests/hydration/compiled/client/head.js +11 -11
  45. package/tests/hydration/compiled/client/mixed-control-flow.js +55 -70
  46. package/tests/hydration/compiled/client/nested-control-flow.js +72 -88
  47. package/tests/hydration/compiled/client/try.js +42 -54
  48. package/tests/hydration/compiled/server/basic.js +491 -369
  49. package/tests/hydration/compiled/server/composite.js +153 -128
  50. package/tests/hydration/compiled/server/events.js +166 -145
  51. package/tests/hydration/compiled/server/for.js +821 -677
  52. package/tests/hydration/compiled/server/head.js +200 -165
  53. package/tests/hydration/compiled/server/hmr.js +62 -54
  54. package/tests/hydration/compiled/server/html-in-template.js +64 -55
  55. package/tests/hydration/compiled/server/html.js +1477 -1360
  56. package/tests/hydration/compiled/server/if-children.js +448 -408
  57. package/tests/hydration/compiled/server/if.js +204 -171
  58. package/tests/hydration/compiled/server/mixed-control-flow.js +237 -195
  59. package/tests/hydration/compiled/server/nested-control-flow.js +533 -467
  60. package/tests/hydration/compiled/server/portal.js +94 -107
  61. package/tests/hydration/compiled/server/reactivity.js +87 -64
  62. package/tests/hydration/compiled/server/return.js +1424 -1174
  63. package/tests/hydration/compiled/server/switch.js +268 -238
  64. package/tests/hydration/compiled/server/try.js +98 -87
  65. package/tests/hydration/components/{mixed-control-flow.ripple → mixed-control-flow.rsrx} +2 -2
  66. package/tests/hydration/components/{try.ripple → try.rsrx} +4 -2
  67. package/tests/hydration/mixed-control-flow.test.js +14 -0
  68. package/tests/hydration/nested-control-flow.test.js +50 -48
  69. package/tests/hydration/try.test.js +25 -0
  70. package/tests/server/__snapshots__/compiler.test.ripple.snap +0 -32
  71. package/tests/server/__snapshots__/compiler.test.rsrx.snap +95 -0
  72. package/tests/server/{compiler.test.ripple → compiler.test.rsrx} +0 -17
  73. package/tests/server/{html-nesting-validation.test.ripple → html-nesting-validation.test.rsrx} +3 -3
  74. package/tests/server/streaming-ssr.test.rsrx +115 -0
  75. package/tests/server/try.test.rsrx +503 -0
  76. package/tests/utils/compiler-compat-config.test.js +3 -3
  77. package/tests/utils/vite-plugin-config.test.js +1 -1
  78. package/tests/utils/vite-plugin-hmr.test.js +5 -5
  79. package/tsconfig.json +4 -0
  80. package/types/index.d.ts +13 -23
  81. package/types/server.d.ts +43 -16
  82. package/tests/client/_etc.test.ripple +0 -5
  83. package/tests/client/async-suspend.test.ripple +0 -94
  84. package/tests/client/try.test.ripple +0 -196
  85. package/tests/server/streaming-ssr.test.ripple +0 -68
  86. package/tests/server/try.test.ripple +0 -82
  87. /package/tests/client/array/{array.copy-within.test.ripple → array.copy-within.test.rsrx} +0 -0
  88. /package/tests/client/array/{array.derived.test.ripple → array.derived.test.rsrx} +0 -0
  89. /package/tests/client/array/{array.iteration.test.ripple → array.iteration.test.rsrx} +0 -0
  90. /package/tests/client/array/{array.mutations.test.ripple → array.mutations.test.rsrx} +0 -0
  91. /package/tests/client/array/{array.to-methods.test.ripple → array.to-methods.test.rsrx} +0 -0
  92. /package/tests/client/basic/{basic.attributes.test.ripple → basic.attributes.test.rsrx} +0 -0
  93. /package/tests/client/basic/{basic.collections.test.ripple → basic.collections.test.rsrx} +0 -0
  94. /package/tests/client/basic/{basic.components.test.ripple → basic.components.test.rsrx} +0 -0
  95. /package/tests/client/basic/{basic.events.test.ripple → basic.events.test.rsrx} +0 -0
  96. /package/tests/client/basic/{basic.get-set.test.ripple → basic.get-set.test.rsrx} +0 -0
  97. /package/tests/client/basic/{basic.hmr.test.ripple → basic.hmr.test.rsrx} +0 -0
  98. /package/tests/client/basic/{basic.reactivity.test.ripple → basic.reactivity.test.rsrx} +0 -0
  99. /package/tests/client/basic/{basic.rendering.test.ripple → basic.rendering.test.rsrx} +0 -0
  100. /package/tests/client/basic/{basic.styling.test.ripple → basic.styling.test.rsrx} +0 -0
  101. /package/tests/client/basic/{basic.utilities.test.ripple → basic.utilities.test.rsrx} +0 -0
  102. /package/tests/client/{boundaries.test.ripple → boundaries.test.rsrx} +0 -0
  103. /package/tests/client/compiler/{compiler.assignments.test.ripple → compiler.assignments.test.rsrx} +0 -0
  104. /package/tests/client/compiler/{compiler.attributes.test.ripple → compiler.attributes.test.rsrx} +0 -0
  105. /package/tests/client/compiler/{compiler.basic.test.ripple → compiler.basic.test.rsrx} +0 -0
  106. /package/tests/client/compiler/{compiler.regex.test.ripple → compiler.regex.test.rsrx} +0 -0
  107. /package/tests/client/compiler/{compiler.tracked-access.test.ripple → compiler.tracked-access.test.rsrx} +0 -0
  108. /package/tests/client/compiler/{compiler.typescript.test.ripple → compiler.typescript.test.rsrx} +0 -0
  109. /package/tests/client/composite/{composite.dynamic-components.test.ripple → composite.dynamic-components.test.rsrx} +0 -0
  110. /package/tests/client/composite/{composite.generics.test.ripple → composite.generics.test.rsrx} +0 -0
  111. /package/tests/client/composite/{composite.props.test.ripple → composite.props.test.rsrx} +0 -0
  112. /package/tests/client/composite/{composite.reactivity.test.ripple → composite.reactivity.test.rsrx} +0 -0
  113. /package/tests/client/composite/{composite.render.test.ripple → composite.render.test.rsrx} +0 -0
  114. /package/tests/client/{computed-properties.test.ripple → computed-properties.test.rsrx} +0 -0
  115. /package/tests/client/{context.test.ripple → context.test.rsrx} +0 -0
  116. /package/tests/client/css/{global-additional-cases.test.ripple → global-additional-cases.test.rsrx} +0 -0
  117. /package/tests/client/css/{global-advanced-selectors.test.ripple → global-advanced-selectors.test.rsrx} +0 -0
  118. /package/tests/client/css/{global-at-rules.test.ripple → global-at-rules.test.rsrx} +0 -0
  119. /package/tests/client/css/{global-basic.test.ripple → global-basic.test.rsrx} +0 -0
  120. /package/tests/client/css/{global-classes-ids.test.ripple → global-classes-ids.test.rsrx} +0 -0
  121. /package/tests/client/css/{global-combinators.test.ripple → global-combinators.test.rsrx} +0 -0
  122. /package/tests/client/css/{global-complex-nesting.test.ripple → global-complex-nesting.test.rsrx} +0 -0
  123. /package/tests/client/css/{global-edge-cases.test.ripple → global-edge-cases.test.rsrx} +0 -0
  124. /package/tests/client/css/{global-keyframes.test.ripple → global-keyframes.test.rsrx} +0 -0
  125. /package/tests/client/css/{global-nested.test.ripple → global-nested.test.rsrx} +0 -0
  126. /package/tests/client/css/{global-pseudo.test.ripple → global-pseudo.test.rsrx} +0 -0
  127. /package/tests/client/css/{global-scoping.test.ripple → global-scoping.test.rsrx} +0 -0
  128. /package/tests/client/css/{style-identifier.test.ripple → style-identifier.test.rsrx} +0 -0
  129. /package/tests/client/{date.test.ripple → date.test.rsrx} +0 -0
  130. /package/tests/client/{dynamic-elements.test.ripple → dynamic-elements.test.rsrx} +0 -0
  131. /package/tests/client/{events.test.ripple → events.test.rsrx} +0 -0
  132. /package/tests/client/{for.test.ripple → for.test.rsrx} +0 -0
  133. /package/tests/client/{function-overload-import.ripple → function-overload-import.rsrx} +0 -0
  134. /package/tests/client/{head.test.ripple → head.test.rsrx} +0 -0
  135. /package/tests/client/{html.test.ripple → html.test.rsrx} +0 -0
  136. /package/tests/client/{input-value.test.ripple → input-value.test.rsrx} +0 -0
  137. /package/tests/client/{lazy-destructuring.test.ripple → lazy-destructuring.test.rsrx} +0 -0
  138. /package/tests/client/{map.test.ripple → map.test.rsrx} +0 -0
  139. /package/tests/client/{media-query.test.ripple → media-query.test.rsrx} +0 -0
  140. /package/tests/client/{object.test.ripple → object.test.rsrx} +0 -0
  141. /package/tests/client/{portal.test.ripple → portal.test.rsrx} +0 -0
  142. /package/tests/client/{ref.test.ripple → ref.test.rsrx} +0 -0
  143. /package/tests/client/{return.test.ripple → return.test.rsrx} +0 -0
  144. /package/tests/client/{set.test.ripple → set.test.rsrx} +0 -0
  145. /package/tests/client/{svg.test.ripple → svg.test.rsrx} +0 -0
  146. /package/tests/client/{switch.test.ripple → switch.test.rsrx} +0 -0
  147. /package/tests/client/{tsx.test.ripple → tsx.test.rsrx} +0 -0
  148. /package/tests/client/{typescript-generics.test.ripple → typescript-generics.test.rsrx} +0 -0
  149. /package/tests/client/url/{url.derived.test.ripple → url.derived.test.rsrx} +0 -0
  150. /package/tests/client/url/{url.parsing.test.ripple → url.parsing.test.rsrx} +0 -0
  151. /package/tests/client/url/{url.partial-removal.test.ripple → url.partial-removal.test.rsrx} +0 -0
  152. /package/tests/client/url/{url.reactivity.test.ripple → url.reactivity.test.rsrx} +0 -0
  153. /package/tests/client/url/{url.serialization.test.ripple → url.serialization.test.rsrx} +0 -0
  154. /package/tests/client/url-search-params/{url-search-params.derived.test.ripple → url-search-params.derived.test.rsrx} +0 -0
  155. /package/tests/client/url-search-params/{url-search-params.initialization.test.ripple → url-search-params.initialization.test.rsrx} +0 -0
  156. /package/tests/client/url-search-params/{url-search-params.iteration.test.ripple → url-search-params.iteration.test.rsrx} +0 -0
  157. /package/tests/client/url-search-params/{url-search-params.mutation.test.ripple → url-search-params.mutation.test.rsrx} +0 -0
  158. /package/tests/client/url-search-params/{url-search-params.retrieval.test.ripple → url-search-params.retrieval.test.rsrx} +0 -0
  159. /package/tests/client/url-search-params/{url-search-params.serialization.test.ripple → url-search-params.serialization.test.rsrx} +0 -0
  160. /package/tests/client/url-search-params/{url-search-params.tracked-url.test.ripple → url-search-params.tracked-url.test.rsrx} +0 -0
  161. /package/tests/hydration/components/{basic.ripple → basic.rsrx} +0 -0
  162. /package/tests/hydration/components/{composite.ripple → composite.rsrx} +0 -0
  163. /package/tests/hydration/components/{events.ripple → events.rsrx} +0 -0
  164. /package/tests/hydration/components/{for.ripple → for.rsrx} +0 -0
  165. /package/tests/hydration/components/{head.ripple → head.rsrx} +0 -0
  166. /package/tests/hydration/components/{hmr.ripple → hmr.rsrx} +0 -0
  167. /package/tests/hydration/components/{html-in-template.ripple → html-in-template.rsrx} +0 -0
  168. /package/tests/hydration/components/{html.ripple → html.rsrx} +0 -0
  169. /package/tests/hydration/components/{if-children.ripple → if-children.rsrx} +0 -0
  170. /package/tests/hydration/components/{if.ripple → if.rsrx} +0 -0
  171. /package/tests/hydration/components/{nested-control-flow.ripple → nested-control-flow.rsrx} +0 -0
  172. /package/tests/hydration/components/{portal.ripple → portal.rsrx} +0 -0
  173. /package/tests/hydration/components/{reactivity.ripple → reactivity.rsrx} +0 -0
  174. /package/tests/hydration/components/{return.ripple → return.rsrx} +0 -0
  175. /package/tests/hydration/components/{switch.ripple → switch.rsrx} +0 -0
  176. /package/tests/server/{await.test.ripple → await.test.rsrx} +0 -0
  177. /package/tests/server/{basic.attributes.test.ripple → basic.attributes.test.rsrx} +0 -0
  178. /package/tests/server/{basic.components.test.ripple → basic.components.test.rsrx} +0 -0
  179. /package/tests/server/{basic.test.ripple → basic.test.rsrx} +0 -0
  180. /package/tests/server/{composite.props.test.ripple → composite.props.test.rsrx} +0 -0
  181. /package/tests/server/{composite.test.ripple → composite.test.rsrx} +0 -0
  182. /package/tests/server/{context.test.ripple → context.test.rsrx} +0 -0
  183. /package/tests/server/{dynamic-elements.test.ripple → dynamic-elements.test.rsrx} +0 -0
  184. /package/tests/server/{for.test.ripple → for.test.rsrx} +0 -0
  185. /package/tests/server/{head.test.ripple → head.test.rsrx} +0 -0
  186. /package/tests/server/{if.test.ripple → if.test.rsrx} +0 -0
  187. /package/tests/server/{lazy-destructuring.test.ripple → lazy-destructuring.test.rsrx} +0 -0
  188. /package/tests/server/{return.test.ripple → return.test.rsrx} +0 -0
  189. /package/tests/server/{style-identifier.test.ripple → style-identifier.test.rsrx} +0 -0
  190. /package/tests/server/{switch.test.ripple → switch.test.rsrx} +0 -0
package/types/index.d.ts CHANGED
@@ -141,18 +141,21 @@ declare global {
141
141
 
142
142
  export function createRefKey(): symbol;
143
143
 
144
+ export const UNINITIALIZED: unique symbol;
145
+ export const DERIVED_UPDATED: unique symbol;
146
+ export const SUSPENSE_PENDING: unique symbol;
147
+ export const SUSPENSE_REJECTED: unique symbol;
148
+
144
149
  // Base Tracked interface - all tracked values have a '#v' property containing the actual value
145
150
  interface TrackedBase<V> {
146
151
  '#v': V;
147
152
  value: V;
148
153
  }
149
-
150
154
  // Augment Tracked to be callable when V is a Component
151
155
  // This allows <@Something /> to work in JSX when Something is Tracked<Component>
152
156
  interface TrackedCallable<V> {
153
157
  (props: V extends Component<infer P> ? P : never): V extends Component ? void : never;
154
158
  }
155
-
156
159
  // Supports indexed access: track(0)[0] → value, track(0)[1] → Tracked<V>
157
160
  // And destructuring `const [one, two] = track(0);`
158
161
  export type Tracked<V> = [V, Tracked<V>] & TrackedBase<V> & TrackedCallable<V>;
@@ -188,6 +191,14 @@ export function track<V>(
188
191
  // Overload for non-function values
189
192
  export function track<V>(value?: V, get?: (v: V) => V, set?: (next: V, prev: V) => V): Tracked<V>;
190
193
 
194
+ export function trackAsync<V>(
195
+ value: () => PromiseLike<V> | { promise: PromiseLike<V>; abortController: AbortController },
196
+ ): Tracked<V>;
197
+
198
+ export function trackPending<V>(value: Tracked<V> | (() => any)): boolean;
199
+
200
+ export function peek<V>(tracked: Tracked<V>): V;
201
+
191
202
  export interface AddEventOptions extends ExtendedEventOptions {
192
203
  customName?: string;
193
204
  }
@@ -531,24 +542,3 @@ export function bindFiles<V extends FileList>(
531
542
  tracked: Tracked<V | null | undefined> | GetFunction<V | null | undefined>,
532
543
  setter?: SetFunction<V>,
533
544
  ): (node: HTMLInputElement) => void;
534
-
535
- type ServerBlock = {};
536
-
537
- export interface RippleNamespace {
538
- array: RippleArrayCallable;
539
- object: RippleObjectCallable;
540
- context: ContextCallable;
541
- date: RippleDateCallable;
542
- effect: typeof effect;
543
- map: RippleMapCallable;
544
- mediaQuery: MediaQueryCallable;
545
- set: RippleSetCallable;
546
- url: RippleURLCallable;
547
- urlSearchParams: RippleURLSearchParamsCallable;
548
- untrack: typeof untrack;
549
- track: typeof track;
550
- style: Record<string, string>;
551
- server: ServerBlock;
552
- }
553
-
554
- export declare const ripple_namespace: RippleNamespace;
package/types/server.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { Props } from '#public';
2
- import type { Readable } from 'node:stream';
1
+ import type { Component } from '#public';
3
2
 
4
3
  // Re-export runtime types for server-compiled components
5
4
  export {
@@ -17,28 +16,56 @@ export {
17
16
  RippleURLSearchParams,
18
17
  } from './index.js';
19
18
 
20
- export interface SSRRenderOutput {
19
+ export interface RenderResult {
21
20
  head: string;
22
21
  body: string;
23
22
  css: Set<string>;
23
+ topLevelError?: Error | null;
24
+ }
25
+
26
+ export interface RenderStreamResult {
27
+ stream: StreamSink;
28
+ topLevelError?: Error | null;
29
+ }
30
+
31
+ export interface StreamSink {
24
32
  push(chunk: string): void;
25
- register_css(hash: string): void;
33
+ close(): void;
34
+ error(reason: unknown): void;
26
35
  }
27
36
 
28
- export interface SSRComponent {
29
- (output: SSRRenderOutput, props?: Props): void | Promise<void>;
30
- async?: boolean;
37
+ export type WebStream = ReadableStream<Uint8Array>;
38
+
39
+ export interface Stream {
40
+ controller: ReadableStreamDefaultController<Uint8Array>;
41
+ textEncoder: TextEncoder;
42
+ stream: WebStream;
43
+ sink: StreamSink;
31
44
  }
32
45
 
33
- export interface SSRRenderResult {
34
- head: string;
35
- body: string;
36
- css: Set<string>;
46
+ export interface BaseRenderOptions {
47
+ stream?: StreamSink;
48
+ // defaults to true
49
+ // set to false to add more content
50
+ closeStream?: boolean;
51
+ }
52
+
53
+ export interface StreamingRenderOptions extends BaseRenderOptions {
54
+ stream: StreamSink;
37
55
  }
38
56
 
39
- export type SSRRender = (component: SSRComponent) => Promise<SSRRenderResult>;
40
- export type render = (component: SSRComponent) => Promise<SSRRenderResult>;
41
- export type renderToStream = (component: SSRComponent) => Readable;
57
+ export interface RenderOptions extends BaseRenderOptions {
58
+ stream?: undefined;
59
+ }
60
+
61
+ export declare function create_ssr_stream(): Stream;
62
+
63
+ export declare function render(
64
+ component: Component,
65
+ options?: RenderOptions,
66
+ ): Promise<RenderResult>;
42
67
 
43
- export const render: render;
44
- export const renderToStream: renderToStream;
68
+ export declare function render(
69
+ component: Component,
70
+ options: StreamingRenderOptions,
71
+ ): Promise<RenderStreamResult>;
@@ -1,5 +0,0 @@
1
- // This file is for tests that don't fit anywhere else
2
-
3
- describe('etc', () => {
4
- it.skip('this is here so the empty test suite does not fail');
5
- });
@@ -1,94 +0,0 @@
1
- import { flushSync, track } from 'ripple';
2
-
3
- describe('async suspense', () => {
4
- it('hides child content during re-suspension when tracked dependency changes', async () => {
5
- let resolve_fn: (() => void) | null = null;
6
-
7
- component Child({ countTracked }: { countTracked: any }) {
8
- await track(() => {
9
- countTracked.value;
10
- return new Promise<void>((resolve) => {
11
- resolve_fn = resolve;
12
- });
13
- });
14
-
15
- <div class="child-content">{'child content'}</div>
16
- }
17
-
18
- component App() {
19
- let &[count, countTracked] = track(0);
20
-
21
- try {
22
- <Child {countTracked} />
23
- } pending {
24
- <div class="pending">{'pending...'}</div>
25
- }
26
-
27
- <button onClick={() => count++}>{'Increment'}</button>
28
- }
29
-
30
- render(App);
31
-
32
- // Initial state: should show pending
33
- await new Promise((resolve) => setTimeout(resolve, 0));
34
- flushSync();
35
- expect(container.innerHTML).toContain('pending...');
36
- expect(container.innerHTML).not.toContain('child content');
37
-
38
- // Resolve the first promise
39
- (resolve_fn as () => void)?.();
40
- await new Promise((resolve) => setTimeout(resolve, 0));
41
- flushSync();
42
-
43
- // After resolution: should show child content, not pending
44
- expect(container.innerHTML).toContain('child content');
45
- expect(container.innerHTML).not.toContain('pending...');
46
-
47
- // Now trigger re-suspension by changing count
48
- const button = container.querySelector('button');
49
- button?.click();
50
- flushSync();
51
-
52
- // Wait for microtask to process
53
- await new Promise((resolve) => setTimeout(resolve, 0));
54
- flushSync();
55
-
56
- // BUG: During re-suspension, we should only see pending, not child content
57
- // The child content should be hidden when the new promise is pending
58
- expect(container.innerHTML).toContain('pending...');
59
- expect(container.innerHTML).not.toContain('child content');
60
- });
61
-
62
- it('shows pending UI immediately when child suspends', async () => {
63
- component Child() {
64
- await new Promise((resolve) => setTimeout(resolve, 50));
65
- <div class="child">{'loaded'}</div>
66
- }
67
-
68
- component App() {
69
- try {
70
- <Child />
71
- } pending {
72
- <div class="pending">{'loading...'}</div>
73
- }
74
- }
75
-
76
- render(App);
77
-
78
- // Wait for microtask
79
- await new Promise((resolve) => setTimeout(resolve, 0));
80
- flushSync();
81
-
82
- // Should show pending, not child content
83
- expect(container.innerHTML).toContain('loading...');
84
- expect(container.innerHTML).not.toContain('loaded');
85
-
86
- // Wait for child to resolve
87
- await new Promise((resolve) => setTimeout(resolve, 100));
88
- flushSync();
89
-
90
- // Should show child content, not pending
91
- expect(container.innerHTML).toContain('loaded');
92
- expect(container.innerHTML).not.toContain('loading...');
93
- });
94
- });
@@ -1,196 +0,0 @@
1
- import { RippleArray, bindValue, flushSync, track } from 'ripple';
2
-
3
- describe('try block with catch and pending', () => {
4
- it('catch block works when component throws before await with pending block', async () => {
5
- component App() {
6
- try {
7
- <ThrowingChild />
8
- } pending {
9
- <p>{'loading...'}</p>
10
- } catch (err) {
11
- <p>{'caught error'}</p>
12
- }
13
- }
14
-
15
- component ThrowingChild() {
16
- throw new Error('sync error');
17
- let data = await Promise.resolve('hello');
18
- <p>{data}</p>
19
- }
20
-
21
- render(App);
22
-
23
- await new Promise((resolve) => setTimeout(resolve, 0));
24
- flushSync();
25
-
26
- expect(container.innerHTML).toContain('caught error');
27
- expect(container.innerHTML).not.toContain('loading...');
28
- });
29
-
30
- it('catch block works when component throws after await with pending block', async () => {
31
- component App() {
32
- try {
33
- <ThrowingAfterAwait />
34
- } pending {
35
- <p>{'loading...'}</p>
36
- } catch (err) {
37
- <p>{'caught error'}</p>
38
- }
39
- }
40
-
41
- component ThrowingAfterAwait() {
42
- let data = await Promise.resolve('hello');
43
- throw new Error('error after await');
44
- <p>{data}</p>
45
- }
46
-
47
- render(App);
48
-
49
- await new Promise((resolve) => setTimeout(resolve, 0));
50
- flushSync();
51
-
52
- expect(container.innerHTML).toContain('caught error');
53
- expect(container.innerHTML).not.toContain('loading...');
54
- });
55
-
56
- it('catch block works with try/catch/pending when async body rejects', async () => {
57
- component App() {
58
- try {
59
- let data = await Promise.reject(new Error('rejected'));
60
- <p>{data}</p>
61
- } pending {
62
- <p>{'loading...'}</p>
63
- } catch (err) {
64
- <p>{'caught rejection'}</p>
65
- }
66
- }
67
-
68
- render(App);
69
-
70
- await new Promise((resolve) => setTimeout(resolve, 0));
71
- flushSync();
72
-
73
- expect(container.innerHTML).toContain('caught rejection');
74
- expect(container.innerHTML).not.toContain('loading...');
75
- });
76
- });
77
-
78
- describe('try block', () => {
79
- it(
80
- 'does not compile ref binds as async callbacks inside try/pending async branches',
81
- async () => {
82
- component App() {
83
- try {
84
- <Child />
85
- } pending {
86
- <p>{'loading...'}</p>
87
- }
88
- }
89
-
90
- component Child() {
91
- let &[value, valueTracked] = track(1);
92
- await Promise.resolve(value + 1);
93
-
94
- <input type="number" {ref bindValue(valueTracked)} />
95
- }
96
-
97
- render(App);
98
-
99
- await new Promise((resolve) => setTimeout(resolve, 0));
100
- flushSync();
101
-
102
- const input = container.querySelector('input') as HTMLInputElement | null;
103
- expect(input?.value).toBe('1');
104
- expect(container.innerHTML).not.toContain('loading...');
105
- },
106
- );
107
-
108
- it('does not crash when async component is used inside try/pending', async () => {
109
- component App() {
110
- try {
111
- <AsyncChild />
112
- } pending {
113
- <p>{'loading...'}</p>
114
- }
115
- }
116
-
117
- component AsyncChild() {
118
- let data = await Promise.resolve(['a', 'b', 'c']);
119
-
120
- <ul>
121
- for (let item of data) {
122
- <li>{item}</li>
123
- }
124
- </ul>
125
- }
126
-
127
- render(App);
128
-
129
- await new Promise((resolve) => setTimeout(resolve, 0));
130
- flushSync();
131
-
132
- const items = container.querySelectorAll('li');
133
- expect(items.length).toBe(3);
134
- expect(items[0].textContent).toBe('a');
135
- expect(items[1].textContent).toBe('b');
136
- expect(items[2].textContent).toBe('c');
137
- });
138
-
139
- it(
140
- 'does not crash when async component with tracked state is used inside try/pending',
141
- async () => {
142
- component App() {
143
- let query = track('');
144
-
145
- try {
146
- <FilteredList {query} />
147
- } pending {
148
- <p>{'loading...'}</p>
149
- }
150
- }
151
-
152
- component FilteredList({ query }: { query: any }) {
153
- let items = await Promise.resolve(['apple', 'banana', 'cherry']);
154
- let list = RippleArray.from(items);
155
- let &[filtered] = track(() => list.filter((item: string) => item.includes(query.value)));
156
-
157
- <ul>
158
- for (let item of filtered) {
159
- <li>{item}</li>
160
- }
161
- </ul>
162
- }
163
-
164
- render(App);
165
-
166
- await new Promise((resolve) => setTimeout(resolve, 0));
167
- flushSync();
168
-
169
- const listItems = container.querySelectorAll('li');
170
- expect(listItems.length).toBe(3);
171
- },
172
- );
173
-
174
- it('if test condition does not become async within try/pending', async () => {
175
- component App() {
176
- try {
177
- let items = await Promise.resolve(['apple', 'banana', 'cherry']);
178
-
179
- if (items.includes('not-in-list')) {
180
- <p>{'not-in-list is in the list!'}</p>
181
- } else {
182
- <p>{'not-in-list is not in the list.'}</p>
183
- }
184
- } pending {
185
- <p>{'loading...'}</p>
186
- }
187
- }
188
-
189
- render(App);
190
-
191
- await new Promise((resolve) => setTimeout(resolve, 0));
192
- flushSync();
193
-
194
- expect(container.innerHTML).toContain('not-in-list is not in the list.');
195
- });
196
- });
@@ -1,68 +0,0 @@
1
- import { renderToStream } from 'ripple/server';
2
-
3
- test('renderToStream renders a simple component', async ({ expect }) => {
4
- component Basic() {
5
- <div>{'Hello, streaming SSR!'}</div>
6
- }
7
-
8
- const stream = renderToStream(Basic);
9
-
10
- let result = '';
11
- await new Promise((resolve) => {
12
- stream.on('data', (chunk) => {
13
- result += chunk.toString();
14
- });
15
- stream.on('end', resolve);
16
- });
17
-
18
- expect(result).toBe('<div>Hello, streaming SSR!</div>');
19
- });
20
-
21
- test('renderToStream handles async components', async ({ expect }) => {
22
- component AsyncComponent() {
23
- await new Promise((resolve) => setTimeout(resolve, 10));
24
- <p>{'Async content loaded.'}</p>
25
- }
26
-
27
- const stream = renderToStream(AsyncComponent);
28
-
29
- let result = '';
30
- await new Promise((resolve) => {
31
- stream.on('data', (chunk) => {
32
- result += chunk.toString();
33
- });
34
- stream.on('end', resolve);
35
- });
36
-
37
- expect(result).toBe('<p>Async content loaded.</p>');
38
- });
39
-
40
- test('renderToStream handles await blocks with pending state', async ({ expect }) => {
41
- component AwaitComponent() {
42
- let data = 'initial';
43
- await new Promise((resolve) => setTimeout(() => {
44
- data = 'resolved';
45
- resolve('');
46
- }, 20));
47
- try {
48
- <div>
49
- {'Data: '}
50
- {data}
51
- </div>
52
- } pending {
53
- <div>{'Loading...'}</div>
54
- }
55
- }
56
-
57
- const stream = renderToStream(AwaitComponent);
58
-
59
- let result = '';
60
- await new Promise((resolve) => {
61
- stream.on('data', (chunk) => {
62
- result += chunk.toString();
63
- });
64
- stream.on('end', resolve);
65
- });
66
-
67
- expect(result).toBe('<!--[--><div>Loading...</div><div>Data: resolved</div><!--]-->');
68
- });
@@ -1,82 +0,0 @@
1
- describe('try block with catch and pending (server)', () => {
2
- it('catch block works when component throws before await with pending block', async () => {
3
- component ThrowingChild() {
4
- throw new Error('sync error');
5
- let data = await Promise.resolve('hello');
6
- <p>{data}</p>
7
- }
8
-
9
- component App() {
10
- try {
11
- <ThrowingChild />
12
- } pending {
13
- <p>{'loading...'}</p>
14
- } catch (err) {
15
- <p>{'caught error'}</p>
16
- }
17
- }
18
-
19
- const { body } = await render(App);
20
- expect(body).toContain('caught error');
21
- expect(body).not.toContain('loading...');
22
- });
23
-
24
- it('catch block works when component throws after await with pending block', async () => {
25
- component ThrowingAfterAwait() {
26
- let data = await Promise.resolve('hello');
27
- throw new Error('error after await');
28
- <p>{data}</p>
29
- }
30
-
31
- component App() {
32
- try {
33
- <ThrowingAfterAwait />
34
- } pending {
35
- <p>{'loading...'}</p>
36
- } catch (err) {
37
- <p>{'caught error'}</p>
38
- }
39
- }
40
-
41
- const { body } = await render(App);
42
- expect(body).toContain('caught error');
43
- expect(body).not.toContain('loading...');
44
- });
45
-
46
- it('catch block works with try/catch/pending when async body rejects', async () => {
47
- component App() {
48
- try {
49
- let data = await Promise.reject(new Error('rejected'));
50
- <p>{data}</p>
51
- } pending {
52
- <p>{'loading...'}</p>
53
- } catch (err) {
54
- <p>{'caught rejection'}</p>
55
- }
56
- }
57
-
58
- const { body } = await render(App);
59
- expect(body).toContain('caught rejection');
60
- expect(body).not.toContain('loading...');
61
- });
62
-
63
- it('removes pending content for nested try/pending blocks', async () => {
64
- component App() {
65
- try {
66
- try {
67
- let data = await Promise.resolve('resolved');
68
- <p>{data}</p>
69
- } pending {
70
- <p>{'inner loading...'}</p>
71
- }
72
- } pending {
73
- <p>{'outer loading...'}</p>
74
- }
75
- }
76
-
77
- const { body } = await render(App);
78
- expect(body).toContain('resolved');
79
- expect(body).not.toContain('outer loading...');
80
- expect(body).not.toContain('inner loading...');
81
- });
82
- });