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.
- package/CHANGELOG.md +43 -0
- package/package.json +8 -2
- package/src/compiler/phases/1-parse/index.js +73 -30
- package/src/compiler/phases/2-analyze/index.js +28 -58
- package/src/compiler/phases/3-transform/client/index.js +127 -164
- package/src/compiler/phases/3-transform/segments.js +4 -8
- package/src/compiler/phases/3-transform/server/index.js +210 -360
- package/src/compiler/types/import.d.ts +0 -12
- package/src/compiler/types/index.d.ts +12 -5
- package/src/compiler/types/parse.d.ts +2 -0
- package/src/compiler/utils.js +39 -44
- package/src/helpers.d.ts +2 -0
- package/src/runtime/index-client.js +15 -13
- package/src/runtime/index-server.js +18 -11
- package/src/runtime/internal/client/blocks.js +19 -23
- package/src/runtime/internal/client/constants.js +20 -9
- package/src/runtime/internal/client/index.js +14 -4
- package/src/runtime/internal/client/runtime.js +435 -173
- package/src/runtime/internal/client/try.js +334 -156
- package/src/runtime/internal/client/types.d.ts +26 -0
- package/src/runtime/internal/server/blocks.js +183 -0
- package/src/runtime/internal/server/constants.js +7 -0
- package/src/runtime/internal/server/index.js +780 -148
- package/src/runtime/internal/server/types.d.ts +35 -0
- package/src/server/index.js +1 -1
- package/src/utils/async.js +35 -0
- package/src/utils/builders.js +3 -1
- package/tests/client/__snapshots__/computed-properties.test.rsrx.snap +49 -0
- package/tests/client/__snapshots__/for.test.rsrx.snap +319 -0
- package/tests/client/__snapshots__/html.test.rsrx.snap +40 -0
- package/tests/client/_etc.test.rsrx +7 -0
- package/tests/client/array/{array.static.test.ripple → array.static.test.rsrx} +18 -20
- package/tests/client/async-suspend.test.rsrx +662 -0
- package/tests/client/basic/__snapshots__/basic.attributes.test.rsrx.snap +60 -0
- package/tests/client/basic/__snapshots__/basic.rendering.test.rsrx.snap +59 -0
- package/tests/client/basic/{basic.errors.test.ripple → basic.errors.test.rsrx} +2 -2
- package/tests/client/compiler/__snapshots__/compiler.assignments.test.rsrx.snap +12 -0
- package/tests/client/compiler/__snapshots__/compiler.typescript.test.rsrx.snap +46 -0
- package/tests/client/compiler/{compiler.try-in-function.test.ripple → compiler.try-in-function.test.rsrx} +8 -6
- package/tests/client/composite/__snapshots__/composite.render.test.rsrx.snap +37 -0
- package/tests/client/{function-overload.test.ripple → function-overload.test.rsrx} +1 -1
- package/tests/client/try.test.rsrx +1702 -0
- package/tests/hydration/build-components.js +5 -3
- package/tests/hydration/compiled/client/head.js +11 -11
- package/tests/hydration/compiled/client/mixed-control-flow.js +55 -70
- package/tests/hydration/compiled/client/nested-control-flow.js +72 -88
- package/tests/hydration/compiled/client/try.js +42 -54
- package/tests/hydration/compiled/server/basic.js +491 -369
- package/tests/hydration/compiled/server/composite.js +153 -128
- package/tests/hydration/compiled/server/events.js +166 -145
- package/tests/hydration/compiled/server/for.js +821 -677
- package/tests/hydration/compiled/server/head.js +200 -165
- package/tests/hydration/compiled/server/hmr.js +62 -54
- package/tests/hydration/compiled/server/html-in-template.js +64 -55
- package/tests/hydration/compiled/server/html.js +1477 -1360
- package/tests/hydration/compiled/server/if-children.js +448 -408
- package/tests/hydration/compiled/server/if.js +204 -171
- package/tests/hydration/compiled/server/mixed-control-flow.js +237 -195
- package/tests/hydration/compiled/server/nested-control-flow.js +533 -467
- package/tests/hydration/compiled/server/portal.js +94 -107
- package/tests/hydration/compiled/server/reactivity.js +87 -64
- package/tests/hydration/compiled/server/return.js +1424 -1174
- package/tests/hydration/compiled/server/switch.js +268 -238
- package/tests/hydration/compiled/server/try.js +98 -87
- package/tests/hydration/components/{mixed-control-flow.ripple → mixed-control-flow.rsrx} +2 -2
- package/tests/hydration/components/{try.ripple → try.rsrx} +4 -2
- package/tests/hydration/mixed-control-flow.test.js +14 -0
- package/tests/hydration/nested-control-flow.test.js +50 -48
- package/tests/hydration/try.test.js +25 -0
- package/tests/server/__snapshots__/compiler.test.ripple.snap +0 -32
- package/tests/server/__snapshots__/compiler.test.rsrx.snap +95 -0
- package/tests/server/{compiler.test.ripple → compiler.test.rsrx} +0 -17
- package/tests/server/{html-nesting-validation.test.ripple → html-nesting-validation.test.rsrx} +3 -3
- package/tests/server/streaming-ssr.test.rsrx +115 -0
- package/tests/server/try.test.rsrx +503 -0
- package/tests/utils/compiler-compat-config.test.js +3 -3
- package/tests/utils/vite-plugin-config.test.js +1 -1
- package/tests/utils/vite-plugin-hmr.test.js +5 -5
- package/tsconfig.json +4 -0
- package/types/index.d.ts +13 -23
- package/types/server.d.ts +43 -16
- package/tests/client/_etc.test.ripple +0 -5
- package/tests/client/async-suspend.test.ripple +0 -94
- package/tests/client/try.test.ripple +0 -196
- package/tests/server/streaming-ssr.test.ripple +0 -68
- package/tests/server/try.test.ripple +0 -82
- /package/tests/client/array/{array.copy-within.test.ripple → array.copy-within.test.rsrx} +0 -0
- /package/tests/client/array/{array.derived.test.ripple → array.derived.test.rsrx} +0 -0
- /package/tests/client/array/{array.iteration.test.ripple → array.iteration.test.rsrx} +0 -0
- /package/tests/client/array/{array.mutations.test.ripple → array.mutations.test.rsrx} +0 -0
- /package/tests/client/array/{array.to-methods.test.ripple → array.to-methods.test.rsrx} +0 -0
- /package/tests/client/basic/{basic.attributes.test.ripple → basic.attributes.test.rsrx} +0 -0
- /package/tests/client/basic/{basic.collections.test.ripple → basic.collections.test.rsrx} +0 -0
- /package/tests/client/basic/{basic.components.test.ripple → basic.components.test.rsrx} +0 -0
- /package/tests/client/basic/{basic.events.test.ripple → basic.events.test.rsrx} +0 -0
- /package/tests/client/basic/{basic.get-set.test.ripple → basic.get-set.test.rsrx} +0 -0
- /package/tests/client/basic/{basic.hmr.test.ripple → basic.hmr.test.rsrx} +0 -0
- /package/tests/client/basic/{basic.reactivity.test.ripple → basic.reactivity.test.rsrx} +0 -0
- /package/tests/client/basic/{basic.rendering.test.ripple → basic.rendering.test.rsrx} +0 -0
- /package/tests/client/basic/{basic.styling.test.ripple → basic.styling.test.rsrx} +0 -0
- /package/tests/client/basic/{basic.utilities.test.ripple → basic.utilities.test.rsrx} +0 -0
- /package/tests/client/{boundaries.test.ripple → boundaries.test.rsrx} +0 -0
- /package/tests/client/compiler/{compiler.assignments.test.ripple → compiler.assignments.test.rsrx} +0 -0
- /package/tests/client/compiler/{compiler.attributes.test.ripple → compiler.attributes.test.rsrx} +0 -0
- /package/tests/client/compiler/{compiler.basic.test.ripple → compiler.basic.test.rsrx} +0 -0
- /package/tests/client/compiler/{compiler.regex.test.ripple → compiler.regex.test.rsrx} +0 -0
- /package/tests/client/compiler/{compiler.tracked-access.test.ripple → compiler.tracked-access.test.rsrx} +0 -0
- /package/tests/client/compiler/{compiler.typescript.test.ripple → compiler.typescript.test.rsrx} +0 -0
- /package/tests/client/composite/{composite.dynamic-components.test.ripple → composite.dynamic-components.test.rsrx} +0 -0
- /package/tests/client/composite/{composite.generics.test.ripple → composite.generics.test.rsrx} +0 -0
- /package/tests/client/composite/{composite.props.test.ripple → composite.props.test.rsrx} +0 -0
- /package/tests/client/composite/{composite.reactivity.test.ripple → composite.reactivity.test.rsrx} +0 -0
- /package/tests/client/composite/{composite.render.test.ripple → composite.render.test.rsrx} +0 -0
- /package/tests/client/{computed-properties.test.ripple → computed-properties.test.rsrx} +0 -0
- /package/tests/client/{context.test.ripple → context.test.rsrx} +0 -0
- /package/tests/client/css/{global-additional-cases.test.ripple → global-additional-cases.test.rsrx} +0 -0
- /package/tests/client/css/{global-advanced-selectors.test.ripple → global-advanced-selectors.test.rsrx} +0 -0
- /package/tests/client/css/{global-at-rules.test.ripple → global-at-rules.test.rsrx} +0 -0
- /package/tests/client/css/{global-basic.test.ripple → global-basic.test.rsrx} +0 -0
- /package/tests/client/css/{global-classes-ids.test.ripple → global-classes-ids.test.rsrx} +0 -0
- /package/tests/client/css/{global-combinators.test.ripple → global-combinators.test.rsrx} +0 -0
- /package/tests/client/css/{global-complex-nesting.test.ripple → global-complex-nesting.test.rsrx} +0 -0
- /package/tests/client/css/{global-edge-cases.test.ripple → global-edge-cases.test.rsrx} +0 -0
- /package/tests/client/css/{global-keyframes.test.ripple → global-keyframes.test.rsrx} +0 -0
- /package/tests/client/css/{global-nested.test.ripple → global-nested.test.rsrx} +0 -0
- /package/tests/client/css/{global-pseudo.test.ripple → global-pseudo.test.rsrx} +0 -0
- /package/tests/client/css/{global-scoping.test.ripple → global-scoping.test.rsrx} +0 -0
- /package/tests/client/css/{style-identifier.test.ripple → style-identifier.test.rsrx} +0 -0
- /package/tests/client/{date.test.ripple → date.test.rsrx} +0 -0
- /package/tests/client/{dynamic-elements.test.ripple → dynamic-elements.test.rsrx} +0 -0
- /package/tests/client/{events.test.ripple → events.test.rsrx} +0 -0
- /package/tests/client/{for.test.ripple → for.test.rsrx} +0 -0
- /package/tests/client/{function-overload-import.ripple → function-overload-import.rsrx} +0 -0
- /package/tests/client/{head.test.ripple → head.test.rsrx} +0 -0
- /package/tests/client/{html.test.ripple → html.test.rsrx} +0 -0
- /package/tests/client/{input-value.test.ripple → input-value.test.rsrx} +0 -0
- /package/tests/client/{lazy-destructuring.test.ripple → lazy-destructuring.test.rsrx} +0 -0
- /package/tests/client/{map.test.ripple → map.test.rsrx} +0 -0
- /package/tests/client/{media-query.test.ripple → media-query.test.rsrx} +0 -0
- /package/tests/client/{object.test.ripple → object.test.rsrx} +0 -0
- /package/tests/client/{portal.test.ripple → portal.test.rsrx} +0 -0
- /package/tests/client/{ref.test.ripple → ref.test.rsrx} +0 -0
- /package/tests/client/{return.test.ripple → return.test.rsrx} +0 -0
- /package/tests/client/{set.test.ripple → set.test.rsrx} +0 -0
- /package/tests/client/{svg.test.ripple → svg.test.rsrx} +0 -0
- /package/tests/client/{switch.test.ripple → switch.test.rsrx} +0 -0
- /package/tests/client/{tsx.test.ripple → tsx.test.rsrx} +0 -0
- /package/tests/client/{typescript-generics.test.ripple → typescript-generics.test.rsrx} +0 -0
- /package/tests/client/url/{url.derived.test.ripple → url.derived.test.rsrx} +0 -0
- /package/tests/client/url/{url.parsing.test.ripple → url.parsing.test.rsrx} +0 -0
- /package/tests/client/url/{url.partial-removal.test.ripple → url.partial-removal.test.rsrx} +0 -0
- /package/tests/client/url/{url.reactivity.test.ripple → url.reactivity.test.rsrx} +0 -0
- /package/tests/client/url/{url.serialization.test.ripple → url.serialization.test.rsrx} +0 -0
- /package/tests/client/url-search-params/{url-search-params.derived.test.ripple → url-search-params.derived.test.rsrx} +0 -0
- /package/tests/client/url-search-params/{url-search-params.initialization.test.ripple → url-search-params.initialization.test.rsrx} +0 -0
- /package/tests/client/url-search-params/{url-search-params.iteration.test.ripple → url-search-params.iteration.test.rsrx} +0 -0
- /package/tests/client/url-search-params/{url-search-params.mutation.test.ripple → url-search-params.mutation.test.rsrx} +0 -0
- /package/tests/client/url-search-params/{url-search-params.retrieval.test.ripple → url-search-params.retrieval.test.rsrx} +0 -0
- /package/tests/client/url-search-params/{url-search-params.serialization.test.ripple → url-search-params.serialization.test.rsrx} +0 -0
- /package/tests/client/url-search-params/{url-search-params.tracked-url.test.ripple → url-search-params.tracked-url.test.rsrx} +0 -0
- /package/tests/hydration/components/{basic.ripple → basic.rsrx} +0 -0
- /package/tests/hydration/components/{composite.ripple → composite.rsrx} +0 -0
- /package/tests/hydration/components/{events.ripple → events.rsrx} +0 -0
- /package/tests/hydration/components/{for.ripple → for.rsrx} +0 -0
- /package/tests/hydration/components/{head.ripple → head.rsrx} +0 -0
- /package/tests/hydration/components/{hmr.ripple → hmr.rsrx} +0 -0
- /package/tests/hydration/components/{html-in-template.ripple → html-in-template.rsrx} +0 -0
- /package/tests/hydration/components/{html.ripple → html.rsrx} +0 -0
- /package/tests/hydration/components/{if-children.ripple → if-children.rsrx} +0 -0
- /package/tests/hydration/components/{if.ripple → if.rsrx} +0 -0
- /package/tests/hydration/components/{nested-control-flow.ripple → nested-control-flow.rsrx} +0 -0
- /package/tests/hydration/components/{portal.ripple → portal.rsrx} +0 -0
- /package/tests/hydration/components/{reactivity.ripple → reactivity.rsrx} +0 -0
- /package/tests/hydration/components/{return.ripple → return.rsrx} +0 -0
- /package/tests/hydration/components/{switch.ripple → switch.rsrx} +0 -0
- /package/tests/server/{await.test.ripple → await.test.rsrx} +0 -0
- /package/tests/server/{basic.attributes.test.ripple → basic.attributes.test.rsrx} +0 -0
- /package/tests/server/{basic.components.test.ripple → basic.components.test.rsrx} +0 -0
- /package/tests/server/{basic.test.ripple → basic.test.rsrx} +0 -0
- /package/tests/server/{composite.props.test.ripple → composite.props.test.rsrx} +0 -0
- /package/tests/server/{composite.test.ripple → composite.test.rsrx} +0 -0
- /package/tests/server/{context.test.ripple → context.test.rsrx} +0 -0
- /package/tests/server/{dynamic-elements.test.ripple → dynamic-elements.test.rsrx} +0 -0
- /package/tests/server/{for.test.ripple → for.test.rsrx} +0 -0
- /package/tests/server/{head.test.ripple → head.test.rsrx} +0 -0
- /package/tests/server/{if.test.ripple → if.test.rsrx} +0 -0
- /package/tests/server/{lazy-destructuring.test.ripple → lazy-destructuring.test.rsrx} +0 -0
- /package/tests/server/{return.test.ripple → return.test.rsrx} +0 -0
- /package/tests/server/{style-identifier.test.ripple → style-identifier.test.rsrx} +0 -0
- /package/tests/server/{switch.test.ripple → switch.test.rsrx} +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { create_ssr_stream } from 'ripple/server';
|
|
2
|
+
|
|
3
|
+
describe('create_ssr_stream', () => {
|
|
4
|
+
it('renders SSR HTML into the injected sink and exposes a web stream', async () => {
|
|
5
|
+
component App() {
|
|
6
|
+
<div>{'Hello, streaming SSR!'}</div>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { stream, sink } = create_ssr_stream();
|
|
10
|
+
|
|
11
|
+
expect(stream).toBeInstanceOf(ReadableStream);
|
|
12
|
+
await render(App, { stream: sink });
|
|
13
|
+
|
|
14
|
+
const html = await new Response(stream).text();
|
|
15
|
+
expect(html).toBeHtml('<div>Hello, streaming SSR!</div>');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('closes the public stream when streaming finishes successfully', async () => {
|
|
19
|
+
component App() {
|
|
20
|
+
<p>{'stream closed'}</p>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { stream, sink } = create_ssr_stream();
|
|
24
|
+
const reader = stream.getReader();
|
|
25
|
+
const decoder = new TextDecoder();
|
|
26
|
+
let html = '';
|
|
27
|
+
|
|
28
|
+
await render(App, { stream: sink });
|
|
29
|
+
|
|
30
|
+
while (true) {
|
|
31
|
+
const { done, value } = await reader.read();
|
|
32
|
+
if (done) {
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
html += decoder.decode(value, { stream: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
html += decoder.decode();
|
|
39
|
+
expect(html).toBeHtml('<p>stream closed</p>');
|
|
40
|
+
|
|
41
|
+
const terminal_read = await reader.read();
|
|
42
|
+
expect(terminal_read.done).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// import { renderToStream } from 'ripple/server';
|
|
47
|
+
|
|
48
|
+
// describe('render to stream', () => {
|
|
49
|
+
// test('renderToStream renders a simple component', async ({ expect }) => {
|
|
50
|
+
// component Basic() {
|
|
51
|
+
// <div>{'Hello, streaming SSR!'}</div>
|
|
52
|
+
// }
|
|
53
|
+
|
|
54
|
+
// const stream = renderToStream(Basic);
|
|
55
|
+
|
|
56
|
+
// let result = '';
|
|
57
|
+
// await new Promise((resolve) => {
|
|
58
|
+
// stream.on('data', (chunk) => {
|
|
59
|
+
// result += chunk.toString();
|
|
60
|
+
// });
|
|
61
|
+
// stream.on('end', resolve);
|
|
62
|
+
// });
|
|
63
|
+
|
|
64
|
+
// expect(result).toBe('<div>Hello, streaming SSR!</div>');
|
|
65
|
+
// });
|
|
66
|
+
|
|
67
|
+
// test('renderToStream handles async components', async ({ expect }) => {
|
|
68
|
+
// component AsyncComponent() {
|
|
69
|
+
// await new Promise((resolve) => setTimeout(resolve, 10));
|
|
70
|
+
// <p>{'Async content loaded.'}</p>
|
|
71
|
+
// }
|
|
72
|
+
|
|
73
|
+
// const stream = renderToStream(AsyncComponent);
|
|
74
|
+
|
|
75
|
+
// let result = '';
|
|
76
|
+
// await new Promise((resolve) => {
|
|
77
|
+
// stream.on('data', (chunk) => {
|
|
78
|
+
// result += chunk.toString();
|
|
79
|
+
// });
|
|
80
|
+
// stream.on('end', resolve);
|
|
81
|
+
// });
|
|
82
|
+
|
|
83
|
+
// expect(result).toBe('<p>Async content loaded.</p>');
|
|
84
|
+
// });
|
|
85
|
+
|
|
86
|
+
// test('renderToStream handles await blocks with pending state', async ({ expect }) => {
|
|
87
|
+
// component AwaitComponent() {
|
|
88
|
+
// let data = 'initial';
|
|
89
|
+
// await new Promise((resolve) => setTimeout(() => {
|
|
90
|
+
// data = 'resolved';
|
|
91
|
+
// resolve('');
|
|
92
|
+
// }, 20));
|
|
93
|
+
// try {
|
|
94
|
+
// <div>
|
|
95
|
+
// {'Data: '}
|
|
96
|
+
// {data}
|
|
97
|
+
// </div>
|
|
98
|
+
// } pending {
|
|
99
|
+
// <div>{'Loading...'}</div>
|
|
100
|
+
// }
|
|
101
|
+
// }
|
|
102
|
+
|
|
103
|
+
// const stream = renderToStream(AwaitComponent);
|
|
104
|
+
|
|
105
|
+
// let result = '';
|
|
106
|
+
// await new Promise((resolve) => {
|
|
107
|
+
// stream.on('data', (chunk) => {
|
|
108
|
+
// result += chunk.toString();
|
|
109
|
+
// });
|
|
110
|
+
// stream.on('end', resolve);
|
|
111
|
+
// });
|
|
112
|
+
|
|
113
|
+
// expect(result).toBe('<!--[--><div>Loading...</div><div>Data: resolved</div><!--]-->');
|
|
114
|
+
// });
|
|
115
|
+
// });
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import { trackAsync } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('try block with catch and pending (server)', () => {
|
|
4
|
+
it('catch block works when component throws before await with pending block', async () => {
|
|
5
|
+
component ThrowingChild() {
|
|
6
|
+
throw new Error('sync error');
|
|
7
|
+
let &[data] = trackAsync(() => Promise.resolve('hello'));
|
|
8
|
+
<p>{data}</p>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
component App() {
|
|
12
|
+
try {
|
|
13
|
+
<ThrowingChild />
|
|
14
|
+
} pending {
|
|
15
|
+
<p>{'loading...'}</p>
|
|
16
|
+
} catch (err) {
|
|
17
|
+
<p>{'caught error'}</p>
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { body } = await render(App);
|
|
22
|
+
expect(body).toContain('caught error');
|
|
23
|
+
expect(body).not.toContain('loading...');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('catch block works when component throws after await with pending block', async () => {
|
|
27
|
+
component ThrowingAfterAwait() {
|
|
28
|
+
let &[data] = trackAsync(() => Promise.resolve('hello'));
|
|
29
|
+
throw new Error('error after await');
|
|
30
|
+
<p>{data}</p>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
component App() {
|
|
34
|
+
try {
|
|
35
|
+
<ThrowingAfterAwait />
|
|
36
|
+
} pending {
|
|
37
|
+
<p>{'loading...'}</p>
|
|
38
|
+
} catch (err) {
|
|
39
|
+
<p>{'caught error'}</p>
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { body } = await render(App);
|
|
44
|
+
expect(body).toContain('caught error');
|
|
45
|
+
expect(body).not.toContain('loading...');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('catch block works with try/catch/pending when async body rejects', async () => {
|
|
49
|
+
component App() {
|
|
50
|
+
try {
|
|
51
|
+
let &[data] = trackAsync(() => Promise.reject(new Error('rejected')));
|
|
52
|
+
<p>{data}</p>
|
|
53
|
+
} pending {
|
|
54
|
+
<p>{'loading...'}</p>
|
|
55
|
+
} catch (err) {
|
|
56
|
+
<p>{'caught rejection'}</p>
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { body } = await render(App);
|
|
61
|
+
expect(body).toContain('caught rejection');
|
|
62
|
+
expect(body).not.toContain('loading...');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('removes pending content for nested try/pending blocks', async () => {
|
|
66
|
+
component App() {
|
|
67
|
+
try {
|
|
68
|
+
try {
|
|
69
|
+
let &[data] = trackAsync(() => Promise.resolve('resolved'));
|
|
70
|
+
<p>{data}</p>
|
|
71
|
+
} pending {
|
|
72
|
+
<p>{'inner loading...'}</p>
|
|
73
|
+
}
|
|
74
|
+
} pending {
|
|
75
|
+
<p>{'outer loading...'}</p>
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { body } = await render(App);
|
|
80
|
+
expect(body).toContain('resolved');
|
|
81
|
+
expect(body).not.toContain('outer loading...');
|
|
82
|
+
expect(body).not.toContain('inner loading...');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('trackAsync directly in component body (server)', () => {
|
|
87
|
+
it('resolves trackAsync used directly in a component', async () => {
|
|
88
|
+
component App() {
|
|
89
|
+
try {
|
|
90
|
+
<DataChild />
|
|
91
|
+
} catch (err) {
|
|
92
|
+
<p>{'error'}</p>
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
component DataChild() {
|
|
97
|
+
let &[data] = trackAsync(() => Promise.resolve('from child'));
|
|
98
|
+
<p>{data}</p>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const { body } = await render(App);
|
|
102
|
+
expect(body).toContain('from child');
|
|
103
|
+
expect(body).not.toContain('error');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('resolves multiple trackAsync values in the same component', async () => {
|
|
107
|
+
component App() {
|
|
108
|
+
try {
|
|
109
|
+
let &[a] = trackAsync(() => Promise.resolve('hello'));
|
|
110
|
+
let &[b] = trackAsync(() => Promise.resolve('world'));
|
|
111
|
+
<p>
|
|
112
|
+
{a}
|
|
113
|
+
{' '}
|
|
114
|
+
{b}
|
|
115
|
+
</p>
|
|
116
|
+
} catch (err) {
|
|
117
|
+
<p>{'error'}</p>
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const { body } = await render(App);
|
|
122
|
+
expect(body).toContain('hello');
|
|
123
|
+
expect(body).toContain('world');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('renders catch when trackAsync rejects in a child component without its own try', async () => {
|
|
127
|
+
component RejectChild() {
|
|
128
|
+
let &[data] = trackAsync(() => Promise.reject(new Error('child rejected')));
|
|
129
|
+
<p>{data}</p>
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
component App() {
|
|
133
|
+
try {
|
|
134
|
+
<RejectChild />
|
|
135
|
+
} catch (err) {
|
|
136
|
+
<p>{'parent caught it'}</p>
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const { body } = await render(App);
|
|
141
|
+
expect(body).toContain('parent caught it');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('chained trackAsync resolves both values', async () => {
|
|
145
|
+
component App() {
|
|
146
|
+
try {
|
|
147
|
+
let &[name] = trackAsync(() => Promise.resolve('ripple'));
|
|
148
|
+
let &[upper] = trackAsync(() => {
|
|
149
|
+
const n = name;
|
|
150
|
+
return Promise.resolve(n.toUpperCase());
|
|
151
|
+
});
|
|
152
|
+
<p>{upper}</p>
|
|
153
|
+
} catch (err) {
|
|
154
|
+
<p>{'error'}</p>
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const { body } = await render(App);
|
|
159
|
+
expect(body).toContain('RIPPLE');
|
|
160
|
+
expect(body).not.toContain('error');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('chained trackAsync catches when first rejects', async () => {
|
|
164
|
+
component App() {
|
|
165
|
+
try {
|
|
166
|
+
let &[name] = trackAsync(() => Promise.reject<string>(new Error('first failed')));
|
|
167
|
+
let &[upper] = trackAsync(() => {
|
|
168
|
+
const n = name;
|
|
169
|
+
return Promise.resolve(n.toUpperCase());
|
|
170
|
+
});
|
|
171
|
+
<p>{upper}</p>
|
|
172
|
+
} catch (err: Error) {
|
|
173
|
+
<p>{err.message}</p>
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const { body } = await render(App);
|
|
178
|
+
expect(body).toContain('first failed');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('chained trackAsync catches when second rejects', async () => {
|
|
182
|
+
component App() {
|
|
183
|
+
try {
|
|
184
|
+
let &[name] = trackAsync(() => Promise.resolve('ripple'));
|
|
185
|
+
let &[upper] = trackAsync(() => {
|
|
186
|
+
const n = name;
|
|
187
|
+
return Promise.reject(new Error('second failed'));
|
|
188
|
+
});
|
|
189
|
+
<p>{upper}</p>
|
|
190
|
+
} catch (err: Error) {
|
|
191
|
+
<p>{err.message}</p>
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const { body } = await render(App);
|
|
196
|
+
expect(body).toContain('second failed');
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('nested child components with try/catch boundaries (server)', () => {
|
|
201
|
+
it('inner try/catch catches error from its own child', async () => {
|
|
202
|
+
component ThrowingChild() {
|
|
203
|
+
throw new Error('inner error');
|
|
204
|
+
<p>{'should not render'}</p>
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
component Inner() {
|
|
208
|
+
try {
|
|
209
|
+
<ThrowingChild />
|
|
210
|
+
} catch (err: Error) {
|
|
211
|
+
<p>{err.message}</p>
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
component App() {
|
|
216
|
+
try {
|
|
217
|
+
<Inner />
|
|
218
|
+
} catch (err) {
|
|
219
|
+
<p>{'outer caught'}</p>
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const { body } = await render(App);
|
|
224
|
+
expect(body).toContain('inner error');
|
|
225
|
+
expect(body).not.toContain('outer caught');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('inner try/catch catches async rejection from its own child', async () => {
|
|
229
|
+
component AsyncChild() {
|
|
230
|
+
let &[data] = trackAsync(() => Promise.reject(new Error('async inner error')));
|
|
231
|
+
<p>{data}</p>
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
component Inner() {
|
|
235
|
+
try {
|
|
236
|
+
<AsyncChild />
|
|
237
|
+
} catch (err: Error) {
|
|
238
|
+
<p>{err.message}</p>
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
component App() {
|
|
243
|
+
try {
|
|
244
|
+
<Inner />
|
|
245
|
+
} catch (err) {
|
|
246
|
+
<p>{'outer caught'}</p>
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const { body } = await render(App);
|
|
251
|
+
expect(body).toContain('async inner error');
|
|
252
|
+
expect(body).not.toContain('outer caught');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('error propagates to outer catch when inner try has no catch', async () => {
|
|
256
|
+
component ThrowingChild() {
|
|
257
|
+
throw new Error('propagated error');
|
|
258
|
+
<p>{'should not render'}</p>
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
component Inner() {
|
|
262
|
+
try {
|
|
263
|
+
<ThrowingChild />
|
|
264
|
+
} pending {
|
|
265
|
+
<p>{'loading...'}</p>
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
component App() {
|
|
270
|
+
try {
|
|
271
|
+
<Inner />
|
|
272
|
+
} catch (err: Error) {
|
|
273
|
+
<p>{err.message}</p>
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const { body } = await render(App);
|
|
278
|
+
expect(body).toContain('propagated error');
|
|
279
|
+
expect(body).not.toContain('loading...');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('async rejection propagates to outer catch when inner try has no catch', async () => {
|
|
283
|
+
component AsyncChild() {
|
|
284
|
+
let &[data] = trackAsync(() => Promise.reject(new Error('async propagated')));
|
|
285
|
+
<p>{data}</p>
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
component Inner() {
|
|
289
|
+
try {
|
|
290
|
+
<AsyncChild />
|
|
291
|
+
} pending {
|
|
292
|
+
<p>{'loading...'}</p>
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
component App() {
|
|
297
|
+
try {
|
|
298
|
+
<Inner />
|
|
299
|
+
} catch (err: Error) {
|
|
300
|
+
<p>{err.message}</p>
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const { body } = await render(App);
|
|
305
|
+
expect(body).toContain('async propagated');
|
|
306
|
+
expect(body).not.toContain('loading...');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it(
|
|
310
|
+
'multiple nested levels: error propagates through pending-only boundaries to nearest catch',
|
|
311
|
+
async () => {
|
|
312
|
+
component ThrowingChild() {
|
|
313
|
+
throw new Error('deep error');
|
|
314
|
+
<p>{'should not render'}</p>
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
component Level3() {
|
|
318
|
+
try {
|
|
319
|
+
<ThrowingChild />
|
|
320
|
+
} pending {
|
|
321
|
+
<p>{'level3 loading'}</p>
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
component Level2() {
|
|
326
|
+
try {
|
|
327
|
+
<Level3 />
|
|
328
|
+
} pending {
|
|
329
|
+
<p>{'level2 loading'}</p>
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
component App() {
|
|
334
|
+
try {
|
|
335
|
+
<Level2 />
|
|
336
|
+
} catch (err: Error) {
|
|
337
|
+
<p>{err.message}</p>
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const { body } = await render(App);
|
|
342
|
+
expect(body).toContain('deep error');
|
|
343
|
+
},
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
it('sibling components: one fails, the other does not affect catch', async () => {
|
|
347
|
+
component GoodChild() {
|
|
348
|
+
let &[data] = trackAsync(() => Promise.resolve('good'));
|
|
349
|
+
<p>{data}</p>
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
component BadChild() {
|
|
353
|
+
let &[data] = trackAsync(() => Promise.reject(new Error('bad child')));
|
|
354
|
+
<p>{data}</p>
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
component App() {
|
|
358
|
+
try {
|
|
359
|
+
<GoodChild />
|
|
360
|
+
<BadChild />
|
|
361
|
+
} catch (err: Error) {
|
|
362
|
+
<p>{err.message}</p>
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const { body } = await render(App);
|
|
367
|
+
expect(body).toContain('bad child');
|
|
368
|
+
expect(body).not.toContain('good');
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('independent try/catch boundaries each handle their own errors', async () => {
|
|
372
|
+
component FailChild() {
|
|
373
|
+
let &[data] = trackAsync(() => Promise.reject(new Error('fail')));
|
|
374
|
+
<p>{data}</p>
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
component SuccessChild() {
|
|
378
|
+
let &[data] = trackAsync(() => Promise.resolve('success'));
|
|
379
|
+
<p>{data}</p>
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
component App() {
|
|
383
|
+
try {
|
|
384
|
+
<FailChild />
|
|
385
|
+
} catch (err: Error) {
|
|
386
|
+
<p>{err.message}</p>
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
<SuccessChild />
|
|
390
|
+
} catch (err) {
|
|
391
|
+
<p>{'should not catch'}</p>
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const { body } = await render(App);
|
|
396
|
+
expect(body).toContain('fail');
|
|
397
|
+
expect(body).toContain('success');
|
|
398
|
+
expect(body).not.toContain('should not catch');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('inner catch handles rejection, outer renders normally', async () => {
|
|
402
|
+
component AsyncChild() {
|
|
403
|
+
let &[data] = trackAsync(() => Promise.reject(new Error('handled inside')));
|
|
404
|
+
<p>{data}</p>
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
component Inner() {
|
|
408
|
+
try {
|
|
409
|
+
<AsyncChild />
|
|
410
|
+
} catch (err: Error) {
|
|
411
|
+
<span>{err.message}</span>
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
component App() {
|
|
416
|
+
<div>
|
|
417
|
+
<h1>{'App'}</h1>
|
|
418
|
+
<Inner />
|
|
419
|
+
</div>
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const { body } = await render(App);
|
|
423
|
+
expect(body).toContain('App');
|
|
424
|
+
expect(body).toContain('handled inside');
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('sync error in child after trackAsync routes to catch boundary', async () => {
|
|
428
|
+
component BrokenChild() {
|
|
429
|
+
let &[data] = trackAsync(() => Promise.resolve('loaded'));
|
|
430
|
+
throw new Error('sync after async');
|
|
431
|
+
<p>{data}</p>
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
component Inner() {
|
|
435
|
+
try {
|
|
436
|
+
<BrokenChild />
|
|
437
|
+
} catch (err: Error) {
|
|
438
|
+
<p>{err.message}</p>
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
component App() {
|
|
443
|
+
<Inner />
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const { body } = await render(App);
|
|
447
|
+
expect(body).toContain('sync after async');
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('outer try with pending, inner try with catch: rejection goes to inner catch', async () => {
|
|
451
|
+
component AsyncChild() {
|
|
452
|
+
let &[data] = trackAsync(() => Promise.reject(new Error('inner rejection')));
|
|
453
|
+
<p>{data}</p>
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
component Inner() {
|
|
457
|
+
try {
|
|
458
|
+
<AsyncChild />
|
|
459
|
+
} catch (err: Error) {
|
|
460
|
+
<p>{err.message}</p>
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
component App() {
|
|
465
|
+
try {
|
|
466
|
+
<Inner />
|
|
467
|
+
} pending {
|
|
468
|
+
<p>{'outer loading'}</p>
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const { body } = await render(App);
|
|
473
|
+
expect(body).toContain('inner rejection');
|
|
474
|
+
expect(body).not.toContain('outer loading');
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('deeply nested: async resolves through multiple component layers', async () => {
|
|
478
|
+
component DataFetcher() {
|
|
479
|
+
let &[data] = trackAsync(() => Promise.resolve('deep data'));
|
|
480
|
+
<span>{data}</span>
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
component Level2() {
|
|
484
|
+
<div><DataFetcher /></div>
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
component Level1() {
|
|
488
|
+
<section><Level2 /></section>
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
component App() {
|
|
492
|
+
try {
|
|
493
|
+
<Level1 />
|
|
494
|
+
} catch (err) {
|
|
495
|
+
<p>{'error'}</p>
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const { body } = await render(App);
|
|
500
|
+
expect(body).toContain('deep data');
|
|
501
|
+
expect(body).not.toContain('error');
|
|
502
|
+
});
|
|
503
|
+
});
|
|
@@ -12,7 +12,7 @@ component App() {
|
|
|
12
12
|
describe('compiler tsx compat configuration', () => {
|
|
13
13
|
it('allows tsx compat when no compat config is provided', () => {
|
|
14
14
|
expect(() =>
|
|
15
|
-
compile(source, '/src/App.
|
|
15
|
+
compile(source, '/src/App.rsrx', {
|
|
16
16
|
mode: 'client',
|
|
17
17
|
}),
|
|
18
18
|
).not.toThrow();
|
|
@@ -20,7 +20,7 @@ describe('compiler tsx compat configuration', () => {
|
|
|
20
20
|
|
|
21
21
|
it('throws when tsx compat kind is not configured', () => {
|
|
22
22
|
expect(() =>
|
|
23
|
-
compile(source, '/src/App.
|
|
23
|
+
compile(source, '/src/App.rsrx', {
|
|
24
24
|
mode: 'client',
|
|
25
25
|
compat_kinds: [],
|
|
26
26
|
}),
|
|
@@ -29,7 +29,7 @@ describe('compiler tsx compat configuration', () => {
|
|
|
29
29
|
|
|
30
30
|
it('allows tsx compat kinds that are configured', () => {
|
|
31
31
|
expect(() =>
|
|
32
|
-
compile(source, '/src/App.
|
|
32
|
+
compile(source, '/src/App.rsrx', {
|
|
33
33
|
mode: 'client',
|
|
34
34
|
compat_kinds: ['react'],
|
|
35
35
|
}),
|
|
@@ -28,7 +28,7 @@ describe('vite-plugin-ripple hotUpdate', () => {
|
|
|
28
28
|
},
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
|
-
file: '/workspace/src/non-component.
|
|
31
|
+
file: '/workspace/src/non-component.rsrx',
|
|
32
32
|
modules: [{ id: 'client:non-component', isSelfAccepting: false }],
|
|
33
33
|
server: {
|
|
34
34
|
environments: {
|
|
@@ -43,8 +43,8 @@ describe('vite-plugin-ripple hotUpdate', () => {
|
|
|
43
43
|
},
|
|
44
44
|
);
|
|
45
45
|
|
|
46
|
-
expect(transform_request).toHaveBeenCalledWith('/src/non-component.
|
|
47
|
-
expect(get_ssr_modules).toHaveBeenCalledWith('/workspace/src/non-component.
|
|
46
|
+
expect(transform_request).toHaveBeenCalledWith('/src/non-component.rsrx');
|
|
47
|
+
expect(get_ssr_modules).toHaveBeenCalledWith('/workspace/src/non-component.rsrx');
|
|
48
48
|
expect(invalidate_ssr_module).toHaveBeenCalledTimes(2);
|
|
49
49
|
expect(send_hot_update).toHaveBeenCalledWith({ type: 'full-reload' });
|
|
50
50
|
expect(result).toEqual([]);
|
|
@@ -74,7 +74,7 @@ describe('vite-plugin-ripple hotUpdate', () => {
|
|
|
74
74
|
},
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
|
-
file: '/workspace/src/component.
|
|
77
|
+
file: '/workspace/src/component.rsrx',
|
|
78
78
|
modules: [{ id: 'client:component', isSelfAccepting: true }],
|
|
79
79
|
server: {
|
|
80
80
|
environments: {
|
|
@@ -89,7 +89,7 @@ describe('vite-plugin-ripple hotUpdate', () => {
|
|
|
89
89
|
},
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
-
expect(transform_request).toHaveBeenCalledWith('/src/component.
|
|
92
|
+
expect(transform_request).toHaveBeenCalledWith('/src/component.rsrx');
|
|
93
93
|
expect(get_ssr_modules).not.toHaveBeenCalled();
|
|
94
94
|
expect(invalidate_ssr_module).not.toHaveBeenCalled();
|
|
95
95
|
expect(send_hot_update).not.toHaveBeenCalled();
|
package/tsconfig.json
CHANGED