ripple 0.3.128 → 0.4.0
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 +267 -0
- package/package.json +10 -4
- package/src/runtime/element.js +23 -6
- package/src/runtime/index-client.js +81 -41
- package/src/runtime/index-server.js +1 -0
- package/src/runtime/internal/client/blocks.js +173 -91
- package/src/runtime/internal/client/component.js +4 -30
- package/src/runtime/internal/client/composite.js +12 -13
- package/src/runtime/internal/client/constants.js +9 -0
- package/src/runtime/internal/client/context.js +12 -19
- package/src/runtime/internal/client/css.js +7 -9
- package/src/runtime/internal/client/events.js +178 -90
- package/src/runtime/internal/client/expression.js +45 -25
- package/src/runtime/internal/client/for.js +409 -129
- package/src/runtime/internal/client/head.js +10 -5
- package/src/runtime/internal/client/html.js +1 -0
- package/src/runtime/internal/client/hydration.js +152 -10
- package/src/runtime/internal/client/if.js +239 -50
- package/src/runtime/internal/client/index.js +4 -12
- package/src/runtime/internal/client/operations.js +40 -61
- package/src/runtime/internal/client/portal.js +163 -85
- package/src/runtime/internal/client/render.js +39 -39
- package/src/runtime/internal/client/rpc.js +3 -3
- package/src/runtime/internal/client/runtime.js +592 -433
- package/src/runtime/internal/client/selector.js +5 -2
- package/src/runtime/internal/client/template.js +133 -158
- package/src/runtime/internal/client/transport.js +38 -0
- package/src/runtime/internal/client/try.js +465 -426
- package/src/runtime/internal/client/types.d.ts +56 -23
- package/src/runtime/internal/client/utils.js +1 -3
- package/src/runtime/internal/server/dynamic.js +20 -21
- package/src/runtime/internal/server/index.js +325 -275
- package/src/runtime/internal/server/rpc.js +3 -2
- package/src/runtime/internal/server/transport.js +16 -0
- package/src/runtime/transport.js +21 -0
- package/tests/client/__snapshots__/for.test.tsrx.snap +0 -5
- package/tests/client/array/array.copy-within.test.tsrx +12 -12
- package/tests/client/array/array.derived.test.tsrx +46 -46
- package/tests/client/array/array.iteration.test.tsrx +10 -10
- package/tests/client/array/array.mutations.test.tsrx +20 -20
- package/tests/client/array/array.static.test.tsrx +11 -11
- package/tests/client/array/array.to-methods.test.tsrx +6 -6
- package/tests/client/async-suspend.test.tsrx +74 -75
- package/tests/client/basic/basic.attributes.test.tsrx +86 -83
- package/tests/client/basic/basic.collections.test.tsrx +12 -12
- package/tests/client/basic/basic.components.test.tsrx +99 -25
- package/tests/client/basic/basic.errors.test.tsrx +18 -18
- package/tests/client/basic/basic.events.test.tsrx +42 -42
- package/tests/client/basic/basic.get-set.test.tsrx +8 -8
- package/tests/client/basic/basic.reactivity.test.tsrx +73 -38
- package/tests/client/basic/basic.rendering.test.tsrx +25 -25
- package/tests/client/basic/basic.utilities.test.tsrx +3 -3
- package/tests/client/boundaries.test.tsrx +8 -8
- package/tests/client/boxed-locals.test.tsrx +452 -0
- package/tests/client/compiler/__snapshots__/compiler.assignments.test.tsrx.snap +6 -6
- package/tests/client/compiler/compiler.assignments.test.tsrx +9 -73
- package/tests/client/compiler/compiler.attributes.test.tsrx +23 -23
- package/tests/client/compiler/compiler.basic.test.tsrx +15 -60
- package/tests/client/compiler/compiler.tracked-access.test.tsrx +18 -43
- package/tests/client/compiler/compiler.try-in-function.test.tsrx +2 -2
- package/tests/client/composite/__snapshots__/composite.render.test.tsrx.snap +0 -1
- package/tests/client/composite/composite.dynamic-components.test.tsrx +77 -6
- package/tests/client/composite/composite.props.test.tsrx +149 -12
- package/tests/client/composite/composite.reactivity.test.tsrx +51 -52
- package/tests/client/composite/composite.render.test.tsrx +26 -9
- package/tests/client/css/scoped-styles.test.tsrx +8 -8
- package/tests/client/date.test.tsrx +42 -42
- package/tests/client/derived-release.test.tsrx +370 -0
- package/tests/client/dynamic-elements.test.tsrx +45 -45
- package/tests/client/events.test.tsrx +97 -71
- package/tests/client/for-selector.test.tsrx +14 -14
- package/tests/client/for-single-root-items.test.tsrx +118 -3
- package/tests/client/for.test.tsrx +332 -31
- package/tests/client/head.test.tsrx +19 -19
- package/tests/client/html.test.tsrx +11 -9
- package/tests/client/if-block.test.tsrx +80 -0
- package/tests/client/map.test.tsrx +16 -16
- package/tests/client/media-query.test.tsrx +7 -7
- package/tests/client/portal.test.tsrx +224 -26
- package/tests/client/primitive-text.test.tsrx +50 -0
- package/tests/client/prop-kind-switch.test.tsrx +141 -0
- package/tests/client/ref.test.tsrx +17 -17
- package/tests/client/return.test.tsrx +26 -3
- package/tests/client/root-boundary.test.tsrx +42 -0
- package/tests/client/scoped-flush.test.tsrx +8 -8
- package/tests/client/set.test.tsrx +6 -6
- package/tests/client/svg.test.tsrx +11 -11
- package/tests/client/switch.test.tsrx +44 -44
- package/tests/client/track-async-hydration.test.tsrx +4 -4
- package/tests/client/try.test.tsrx +151 -83
- package/tests/client/tsx.test.tsrx +43 -40
- package/tests/client/url/url.derived.test.tsrx +6 -6
- package/tests/client/url-search-params/url-search-params.derived.test.tsrx +8 -8
- package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +10 -10
- package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +10 -10
- package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +18 -18
- package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +2 -2
- package/tests/hydration/basic.test.js +74 -1
- package/tests/hydration/build-components.js +22 -1
- package/tests/hydration/compiled/client/basic.js +1142 -713
- package/tests/hydration/compiled/client/composite.js +151 -100
- package/tests/hydration/compiled/client/events.js +184 -171
- package/tests/hydration/compiled/client/for.js +1472 -1132
- package/tests/hydration/compiled/client/fragment-cursor.js +2341 -0
- package/tests/hydration/compiled/client/head.js +254 -201
- package/tests/hydration/compiled/client/hmr.js +54 -41
- package/tests/hydration/compiled/client/html-in-template.js +54 -39
- package/tests/hydration/compiled/client/html.js +1633 -1263
- package/tests/hydration/compiled/client/if-children.js +420 -270
- package/tests/hydration/compiled/client/if-fragment-controlflow.js +363 -317
- package/tests/hydration/compiled/client/if.js +229 -219
- package/tests/hydration/compiled/client/mixed-control-flow.js +420 -404
- package/tests/hydration/compiled/client/nested-control-flow.js +1326 -1277
- package/tests/hydration/compiled/client/portal.js +87 -95
- package/tests/hydration/compiled/client/reactivity.js +135 -101
- package/tests/hydration/compiled/client/return.js +28 -19
- package/tests/hydration/compiled/client/streaming.js +390 -306
- package/tests/hydration/compiled/client/switch.js +258 -266
- package/tests/hydration/compiled/client/track-async-serialization.js +448 -280
- package/tests/hydration/compiled/client/try.js +173 -129
- package/tests/hydration/compiled/client/typed-text.js +71 -0
- package/tests/hydration/compiled/fixtures/money.js +10 -0
- package/tests/hydration/compiled/server/basic.js +123 -6
- package/tests/hydration/compiled/server/composite.js +16 -30
- package/tests/hydration/compiled/server/events.js +66 -18
- package/tests/hydration/compiled/server/for.js +117 -41
- package/tests/hydration/compiled/server/fragment-cursor.js +1687 -0
- package/tests/hydration/compiled/server/head.js +20 -20
- package/tests/hydration/compiled/server/hmr.js +2 -2
- package/tests/hydration/compiled/server/html.js +70 -238
- package/tests/hydration/compiled/server/if-children.js +107 -15
- package/tests/hydration/compiled/server/if.js +11 -15
- package/tests/hydration/compiled/server/mixed-control-flow.js +8 -8
- package/tests/hydration/compiled/server/portal.js +14 -22
- package/tests/hydration/compiled/server/reactivity.js +14 -14
- package/tests/hydration/compiled/server/streaming.js +19 -19
- package/tests/hydration/compiled/server/switch.js +8 -8
- package/tests/hydration/compiled/server/track-async-serialization.js +150 -23
- package/tests/hydration/compiled/server/try.js +8 -8
- package/tests/hydration/compiled/server/typed-text.js +17 -0
- package/tests/hydration/components/basic.tsrx +44 -8
- package/tests/hydration/components/composite.tsrx +2 -2
- package/tests/hydration/components/events.tsrx +25 -25
- package/tests/hydration/components/for.tsrx +115 -67
- package/tests/hydration/components/fragment-cursor.tsrx +650 -0
- package/tests/hydration/components/head.tsrx +16 -16
- package/tests/hydration/components/hmr.tsrx +2 -2
- package/tests/hydration/components/html.tsrx +7 -7
- package/tests/hydration/components/if-children.tsrx +72 -25
- package/tests/hydration/components/if.tsrx +18 -18
- package/tests/hydration/components/mixed-control-flow.tsrx +11 -11
- package/tests/hydration/components/portal.tsrx +3 -3
- package/tests/hydration/components/reactivity.tsrx +16 -16
- package/tests/hydration/components/streaming.tsrx +20 -20
- package/tests/hydration/components/switch.tsrx +20 -20
- package/tests/hydration/components/text-types.ts +4 -0
- package/tests/hydration/components/track-async-serialization.tsrx +80 -26
- package/tests/hydration/components/try.tsrx +8 -8
- package/tests/hydration/components/typed-text.tsrx +22 -0
- package/tests/hydration/fixtures/money.js +10 -0
- package/tests/hydration/for.test.js +36 -0
- package/tests/hydration/fragment-cursor.test.js +111 -0
- package/tests/hydration/if-children.test.js +24 -0
- package/tests/hydration/streaming.test.js +19 -0
- package/tests/hydration/track-async-serialization.test.js +87 -2
- package/tests/hydration/tsconfig.text-types.json +10 -0
- package/tests/hydration/typed-text.test.js +34 -0
- package/tests/server/await.test.tsrx +3 -3
- package/tests/server/basic.attributes.test.tsrx +42 -42
- package/tests/server/basic.components.test.tsrx +64 -8
- package/tests/server/basic.test.tsrx +34 -32
- package/tests/server/compiler.test.tsrx +1 -70
- package/tests/server/composite.props.test.tsrx +6 -6
- package/tests/server/dynamic-elements.test.tsrx +31 -8
- package/tests/server/for.test.tsrx +94 -0
- package/tests/server/head.test.tsrx +11 -11
- package/tests/server/scoped-styles.test.tsrx +2 -2
- package/tests/server/streaming-ssr.test.tsrx +48 -43
- package/tests/server/track-async-serialization.test.tsrx +204 -21
- package/tests/server/try.test.tsrx +48 -48
- package/tests/types/transport.ts +40 -0
- package/tests/utils/vite-plugin-config.test.js +23 -0
- package/types/index.d.ts +48 -7
- package/types/server.d.ts +3 -0
- package/types/transport.d.ts +15 -0
- package/src/runtime/internal/client/switch.js +0 -113
- package/src/utils/errors.js +0 -13
- package/tests/client/lazy-array.test.tsrx +0 -31
- package/tests/client/lazy-destructuring.test.tsrx +0 -438
- package/tests/client/tracked-index-access.test.tsrx +0 -113
- package/tests/server/lazy-destructuring.test.tsrx +0 -595
- package/tests/server/tracked-index-access.test.tsrx +0 -76
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { flushSync as flush_sync, hydrate } from 'ripple';
|
|
3
|
+
import { render } from 'ripple/server';
|
|
4
|
+
import { container } from '../setup-hydration.js';
|
|
5
|
+
import { TypedText as server_typed_text } from './compiled/server/typed-text.js';
|
|
6
|
+
import { TypedText as client_typed_text } from './compiled/client/typed-text.js';
|
|
7
|
+
|
|
8
|
+
describe('typed text hydration', () => {
|
|
9
|
+
it('reuses server text nodes and updates imported primitive types', async () => {
|
|
10
|
+
const { body } = await render(server_typed_text);
|
|
11
|
+
expect(body).toContain('<first>');
|
|
12
|
+
container.innerHTML = body;
|
|
13
|
+
const run = container.querySelector('.typed-run');
|
|
14
|
+
const text = run.firstChild;
|
|
15
|
+
const number = container.querySelector('.typed-number').firstChild;
|
|
16
|
+
const tail = run.querySelector('span');
|
|
17
|
+
const unmount = hydrate(client_typed_text, { target: container });
|
|
18
|
+
try {
|
|
19
|
+
expect(container.querySelector('.typed-run')).toBe(run);
|
|
20
|
+
expect(run.firstChild).toBe(text);
|
|
21
|
+
expect(run.textContent).toBe('value: <first>2tail');
|
|
22
|
+
expect(container.querySelector('.typed-number').firstChild).toBe(number);
|
|
23
|
+
container.querySelector('button').click();
|
|
24
|
+
flush_sync();
|
|
25
|
+
expect(run.textContent).toBe('value: &next3tail');
|
|
26
|
+
expect(run.firstChild).toBe(text);
|
|
27
|
+
expect(run.querySelector('span')).toBe(tail);
|
|
28
|
+
expect(container.querySelector('.typed-number').firstChild).toBe(number);
|
|
29
|
+
expect(number.textContent).toBe('3');
|
|
30
|
+
} finally {
|
|
31
|
+
unmount();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -8,16 +8,16 @@ describe('await in control flow', () => {
|
|
|
8
8
|
// it('should handle await inside if statement', async () => {
|
|
9
9
|
// function App() @{
|
|
10
10
|
// let condition = true;
|
|
11
|
-
//
|
|
11
|
+
// const data = track('loading');
|
|
12
12
|
|
|
13
13
|
// if (condition) {
|
|
14
14
|
// await new Promise((resolve) => setTimeout(() => {
|
|
15
|
-
// data = 'loaded';
|
|
15
|
+
// data.value = 'loaded';
|
|
16
16
|
// resolve();
|
|
17
17
|
// }, 10));
|
|
18
18
|
// }
|
|
19
19
|
|
|
20
|
-
// <div>{data}</div>
|
|
20
|
+
// <div>{data.value}</div>
|
|
21
21
|
// }
|
|
22
22
|
|
|
23
23
|
// const { body } = await render(App);
|
|
@@ -19,9 +19,9 @@ describe('basic server > attribute rendering', () => {
|
|
|
19
19
|
|
|
20
20
|
it('render dynamic class attribute', async () => {
|
|
21
21
|
function Basic() @{
|
|
22
|
-
|
|
22
|
+
const active = track(false);
|
|
23
23
|
<>
|
|
24
|
-
<div class={active ? 'active' : 'inactive'}>{'Dynamic Class'}</div>
|
|
24
|
+
<div class={active.value ? 'active' : 'inactive'}>{'Dynamic Class'}</div>
|
|
25
25
|
<style>
|
|
26
26
|
.active {
|
|
27
27
|
color: green;
|
|
@@ -135,9 +135,9 @@ describe('basic server > attribute rendering', () => {
|
|
|
135
135
|
|
|
136
136
|
it('render dynamic class object', async () => {
|
|
137
137
|
function Basic() @{
|
|
138
|
-
|
|
138
|
+
const active = track(false);
|
|
139
139
|
<>
|
|
140
|
-
<div class={{ active: active, inactive: !active }}>{'Dynamic Class'}</div>
|
|
140
|
+
<div class={{ active: active.value, inactive: !active.value }}>{'Dynamic Class'}</div>
|
|
141
141
|
<style>
|
|
142
142
|
.active {
|
|
143
143
|
color: green;
|
|
@@ -162,10 +162,10 @@ describe('basic server > attribute rendering', () => {
|
|
|
162
162
|
'applies scoped ripple class to multiple elements with dynamic class expressions',
|
|
163
163
|
async () => {
|
|
164
164
|
function Basic() @{
|
|
165
|
-
|
|
165
|
+
const selected = track(1);
|
|
166
166
|
<>
|
|
167
|
-
<div class={selected === 0 ? 'selected' : ''}>{`div 1`}</div>
|
|
168
|
-
<div class={selected === 0 ? 'selected' : ''}>{`div 2`}</div>
|
|
167
|
+
<div class={selected.value === 0 ? 'selected' : ''}>{`div 1`}</div>
|
|
168
|
+
<div class={selected.value === 0 ? 'selected' : ''}>{`div 2`}</div>
|
|
169
169
|
<style>
|
|
170
170
|
div {
|
|
171
171
|
background: green;
|
|
@@ -193,8 +193,8 @@ describe('basic server > attribute rendering', () => {
|
|
|
193
193
|
|
|
194
194
|
it('render dynamic id attribute', async () => {
|
|
195
195
|
function Basic() @{
|
|
196
|
-
|
|
197
|
-
<div id={`item-${count}`}>{'Dynamic ID'}</div>
|
|
196
|
+
const count = track(0);
|
|
197
|
+
<div id={`item-${count.value}`}>{'Dynamic ID'}</div>
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
const { body } = await render(Basic);
|
|
@@ -207,8 +207,8 @@ describe('basic server > attribute rendering', () => {
|
|
|
207
207
|
|
|
208
208
|
it('render dynamic style attribute', async () => {
|
|
209
209
|
function Basic() @{
|
|
210
|
-
|
|
211
|
-
<div style={`color: ${color}; font-weight: bold;`}>{'Dynamic Style'}</div>
|
|
210
|
+
const color = track('red');
|
|
211
|
+
<div style={`color: ${color.value}; font-weight: bold;`}>{'Dynamic Style'}</div>
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
const { body } = await render(Basic);
|
|
@@ -222,8 +222,8 @@ describe('basic server > attribute rendering', () => {
|
|
|
222
222
|
|
|
223
223
|
it('render style attribute as dynamic object', async () => {
|
|
224
224
|
function Basic() @{
|
|
225
|
-
|
|
226
|
-
<div style={{ color: color, fontWeight: 'bold' }}>{'Dynamic Style'}</div>
|
|
225
|
+
const color = track('red');
|
|
226
|
+
<div style={{ color: color.value, fontWeight: 'bold' }}>{'Dynamic Style'}</div>
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
const { body } = await render(Basic);
|
|
@@ -237,8 +237,8 @@ describe('basic server > attribute rendering', () => {
|
|
|
237
237
|
|
|
238
238
|
it('render tracked variable as style attribute', async () => {
|
|
239
239
|
function Basic() @{
|
|
240
|
-
|
|
241
|
-
<div {style}>{'Dynamic Style'}</div>
|
|
240
|
+
const style = track({ color: 'red', fontWeight: 'bold' });
|
|
241
|
+
<div style={style.value}>{'Dynamic Style'}</div>
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
const { body } = await render(Basic);
|
|
@@ -363,9 +363,9 @@ describe('basic server > attribute rendering', () => {
|
|
|
363
363
|
|
|
364
364
|
it('render dynamic boolean attributes as false', async () => {
|
|
365
365
|
function Basic() @{
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
<input type="checkbox" {disabled} {checked} />
|
|
366
|
+
const disabled = track(false);
|
|
367
|
+
const checked = track(false);
|
|
368
|
+
<input type="checkbox" disabled={disabled.value} checked={checked.value} />
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
const { body } = await render(Basic);
|
|
@@ -396,9 +396,9 @@ describe('basic server > attribute rendering', () => {
|
|
|
396
396
|
|
|
397
397
|
it('render dynamic boolean attributes as true', async () => {
|
|
398
398
|
function Basic() @{
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
<input type="checkbox" {disabled} {checked} />
|
|
399
|
+
const disabled = track(true);
|
|
400
|
+
const checked = track(true);
|
|
401
|
+
<input type="checkbox" disabled={disabled.value} checked={checked.value} />
|
|
402
402
|
}
|
|
403
403
|
|
|
404
404
|
const { body } = await render(Basic);
|
|
@@ -429,8 +429,8 @@ describe('basic server > attribute rendering', () => {
|
|
|
429
429
|
|
|
430
430
|
it('renders formnovalidate as a boolean attribute', async () => {
|
|
431
431
|
function Basic() @{
|
|
432
|
-
|
|
433
|
-
<button {formnovalidate}>{'Submit'}</button>
|
|
432
|
+
const formnovalidate = track(true);
|
|
433
|
+
<button formnovalidate={formnovalidate.value}>{'Submit'}</button>
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
const { body } = await render(Basic);
|
|
@@ -445,8 +445,8 @@ describe('basic server > attribute rendering', () => {
|
|
|
445
445
|
|
|
446
446
|
it('renders hidden as a boolean attribute when true', async () => {
|
|
447
447
|
function Basic() @{
|
|
448
|
-
|
|
449
|
-
<div {hidden}>{'Hidden content'}</div>
|
|
448
|
+
const hidden = track(true);
|
|
449
|
+
<div hidden={hidden.value}>{'Hidden content'}</div>
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
const { body } = await render(Basic);
|
|
@@ -461,8 +461,8 @@ describe('basic server > attribute rendering', () => {
|
|
|
461
461
|
|
|
462
462
|
it('does not render hidden when false', async () => {
|
|
463
463
|
function Basic() @{
|
|
464
|
-
|
|
465
|
-
<div {hidden}>{'Visible content'}</div>
|
|
464
|
+
const hidden = track(false);
|
|
465
|
+
<div hidden={hidden.value}>{'Visible content'}</div>
|
|
466
466
|
}
|
|
467
467
|
|
|
468
468
|
const { body } = await render(Basic);
|
|
@@ -476,12 +476,12 @@ describe('basic server > attribute rendering', () => {
|
|
|
476
476
|
|
|
477
477
|
it('render multiple dynamic attributes', async () => {
|
|
478
478
|
function Basic() @{
|
|
479
|
-
|
|
480
|
-
|
|
479
|
+
const theme = track('light');
|
|
480
|
+
const size = track('medium');
|
|
481
481
|
<div
|
|
482
|
-
class={`theme-${theme} size-${size}`}
|
|
483
|
-
data-theme={theme}
|
|
484
|
-
data-size={size}
|
|
482
|
+
class={`theme-${theme.value} size-${size.value}`}
|
|
483
|
+
data-theme={theme.value}
|
|
484
|
+
data-size={size.value}
|
|
485
485
|
>{'Multiple Dynamic Attributes'}</div>
|
|
486
486
|
}
|
|
487
487
|
|
|
@@ -497,11 +497,11 @@ describe('basic server > attribute rendering', () => {
|
|
|
497
497
|
|
|
498
498
|
it('render conditional attributes', async () => {
|
|
499
499
|
function Basic() @{
|
|
500
|
-
|
|
501
|
-
|
|
500
|
+
const showTitle = track(false);
|
|
501
|
+
const showAria = track(false);
|
|
502
502
|
<div
|
|
503
|
-
title={showTitle ? 'This is a title' : undefined}
|
|
504
|
-
aria-label={showAria ? 'Accessible label' : undefined}
|
|
503
|
+
title={showTitle.value ? 'This is a title' : undefined}
|
|
504
|
+
aria-label={showAria.value ? 'Accessible label' : undefined}
|
|
505
505
|
>{'Conditional Attributes'}</div>
|
|
506
506
|
}
|
|
507
507
|
|
|
@@ -516,11 +516,11 @@ describe('basic server > attribute rendering', () => {
|
|
|
516
516
|
|
|
517
517
|
it('render spread attributes', async () => {
|
|
518
518
|
function Basic() @{
|
|
519
|
-
|
|
519
|
+
const attrs = track<TestAttributes>({
|
|
520
520
|
class: 'initial',
|
|
521
521
|
id: 'test-1',
|
|
522
522
|
});
|
|
523
|
-
<div {...attrs}>{'Spread Attributes'}</div>
|
|
523
|
+
<div {...attrs.value}>{'Spread Attributes'}</div>
|
|
524
524
|
}
|
|
525
525
|
|
|
526
526
|
const { body } = await render(Basic);
|
|
@@ -535,12 +535,12 @@ describe('basic server > attribute rendering', () => {
|
|
|
535
535
|
|
|
536
536
|
it('renders with reactive attributes with nested reactive attributes', async () => {
|
|
537
537
|
function Basic() @{
|
|
538
|
-
|
|
539
|
-
|
|
538
|
+
const value = track('parent-class');
|
|
539
|
+
const nested = track('nested-class');
|
|
540
540
|
<>
|
|
541
|
-
<p class={value}>{'Colored parent value'}</p>
|
|
541
|
+
<p class={value.value}>{'Colored parent value'}</p>
|
|
542
542
|
<div>
|
|
543
|
-
<p class={nested}>{'Colored nested value'}</p>
|
|
543
|
+
<p class={nested.value}>{'Colored nested value'}</p>
|
|
544
544
|
</div>
|
|
545
545
|
</>
|
|
546
546
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { track } from 'ripple';
|
|
2
2
|
import type {
|
|
3
|
+
Derived,
|
|
3
4
|
Tracked,
|
|
4
5
|
PropsWithChildren,
|
|
5
6
|
PropsWithExtras,
|
|
@@ -8,6 +9,61 @@ import type {
|
|
|
8
9
|
} from 'ripple';
|
|
9
10
|
|
|
10
11
|
describe('basic server > components & composition', () => {
|
|
12
|
+
it('renders a read-only view passed through readOnly()', async () => {
|
|
13
|
+
function Child({ count }: { count: Derived<number> }) @{
|
|
14
|
+
<span>{count.value}</span>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function App() @{
|
|
18
|
+
const count = track(5);
|
|
19
|
+
<div>
|
|
20
|
+
<Child count={count.readOnly()} />
|
|
21
|
+
</div>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { body } = await render(App);
|
|
25
|
+
|
|
26
|
+
expect(body).toBeHtml('<div><span>5</span></div>');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders rest and default pattern names in a keyed loop', async () => {
|
|
30
|
+
function App() @{
|
|
31
|
+
const rows = [
|
|
32
|
+
{ id: 1, name: 'one', tags: ['a', 'b'] },
|
|
33
|
+
];
|
|
34
|
+
<ul>
|
|
35
|
+
@for (const { id, ...rest } of rows; key id) {
|
|
36
|
+
<li>{rest.name}</li>
|
|
37
|
+
}
|
|
38
|
+
@for (const [head, second = 'none', ...others] of rows.map((row) => row.tags); key head) {
|
|
39
|
+
<li>{head + ':' + second + ':' + others.length}</li>
|
|
40
|
+
}
|
|
41
|
+
</ul>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { body } = await render(App);
|
|
45
|
+
|
|
46
|
+
expect(body).toBeHtml('<ul><li>one</li><li>a:b:0</li></ul>');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders nothing for a component prop that is undefined', async () => {
|
|
50
|
+
function Child({ Comp }: { Comp?: Component }) @{
|
|
51
|
+
<div>
|
|
52
|
+
{'before'}
|
|
53
|
+
<Comp />
|
|
54
|
+
{'after'}
|
|
55
|
+
</div>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function App() @{
|
|
59
|
+
<Child />
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { body } = await render(App);
|
|
63
|
+
|
|
64
|
+
expect(body).toBeHtml('<div>beforeafter</div>');
|
|
65
|
+
});
|
|
66
|
+
|
|
11
67
|
async function render_expected_invalid_component(App: Component) {
|
|
12
68
|
const console_error_spy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
13
69
|
try {
|
|
@@ -186,16 +242,16 @@ describe('basic server > components & composition', () => {
|
|
|
186
242
|
}
|
|
187
243
|
|
|
188
244
|
function Basic() @{
|
|
189
|
-
|
|
245
|
+
const clicked = track(false);
|
|
190
246
|
<>
|
|
191
247
|
<Card
|
|
192
248
|
title="Test Card"
|
|
193
249
|
content="This is a test card"
|
|
194
250
|
buttonText="Click me"
|
|
195
|
-
onAction={() => (clicked = true)}
|
|
251
|
+
onAction={() => (clicked.value = true)}
|
|
196
252
|
/>
|
|
197
253
|
<div class="status">
|
|
198
|
-
{clicked ? 'Clicked' : 'Not clicked'}
|
|
254
|
+
{clicked.value ? 'Clicked' : 'Not clicked'}
|
|
199
255
|
</div>
|
|
200
256
|
</>
|
|
201
257
|
}
|
|
@@ -228,14 +284,14 @@ describe('basic server > components & composition', () => {
|
|
|
228
284
|
}
|
|
229
285
|
|
|
230
286
|
function Basic() @{
|
|
231
|
-
|
|
232
|
-
|
|
287
|
+
const message = track('Hello');
|
|
288
|
+
const number = track(1);
|
|
233
289
|
<>
|
|
234
|
-
<ChildComponent text={
|
|
290
|
+
<ChildComponent text={message} count={number} />
|
|
235
291
|
<button
|
|
236
292
|
onClick={() => {
|
|
237
|
-
message = message === 'Hello' ? 'Goodbye' : 'Hello';
|
|
238
|
-
number++;
|
|
293
|
+
message.value = message.value === 'Hello' ? 'Goodbye' : 'Hello';
|
|
294
|
+
number.value++;
|
|
239
295
|
}}
|
|
240
296
|
>{'Update Props'}</button>
|
|
241
297
|
</>
|
|
@@ -397,10 +397,10 @@ second
|
|
|
397
397
|
|
|
398
398
|
it('renders tracked state updates', async () => {
|
|
399
399
|
function Counter() @{
|
|
400
|
-
|
|
401
|
-
count++;
|
|
402
|
-
count = count + 5;
|
|
403
|
-
<div>{count}</div>
|
|
400
|
+
const count = track(0);
|
|
401
|
+
count.value++;
|
|
402
|
+
count.value = count.value + 5;
|
|
403
|
+
<div>{count.value}</div>
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
const { body } = await render(Counter);
|
|
@@ -445,10 +445,12 @@ second
|
|
|
445
445
|
|
|
446
446
|
it('renders spread props with tracked values', async () => {
|
|
447
447
|
function SpreadProps() @{
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
<div
|
|
448
|
+
const id = track('unique-id');
|
|
449
|
+
const isActive = track(true);
|
|
450
|
+
const styles = track({ color: 'red', fontSize: '16px' });
|
|
451
|
+
<div
|
|
452
|
+
{...{ id: id.value, class: { active: isActive.value }, style: styles.value }}
|
|
453
|
+
>{'Spread Props'}</div>
|
|
452
454
|
}
|
|
453
455
|
|
|
454
456
|
const { body } = await render(SpreadProps);
|
|
@@ -460,12 +462,12 @@ second
|
|
|
460
462
|
|
|
461
463
|
it('handles AssignExpressions with tracked values or properties correctly', async () => {
|
|
462
464
|
function Assignments() @{
|
|
463
|
-
|
|
465
|
+
const count = track(0);
|
|
464
466
|
const obj = { value: track(5) };
|
|
465
|
-
count += 10;
|
|
467
|
+
count.value += 10;
|
|
466
468
|
obj.value.value *= 2;
|
|
467
469
|
<>
|
|
468
|
-
<div>{count}</div>
|
|
470
|
+
<div>{count.value}</div>
|
|
469
471
|
<div>{obj.value.value}</div>
|
|
470
472
|
</>
|
|
471
473
|
}
|
|
@@ -477,12 +479,12 @@ second
|
|
|
477
479
|
|
|
478
480
|
it(`handles derived changes via tracked dependencies' changes`, async () => {
|
|
479
481
|
function Derived() @{
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const before = derived;
|
|
484
|
-
base += 2;
|
|
485
|
-
const after = derived;
|
|
482
|
+
const base = track(5);
|
|
483
|
+
const multiplier = track(3);
|
|
484
|
+
const derived = track(() => base.value * multiplier.value);
|
|
485
|
+
const before = derived.value;
|
|
486
|
+
base.value += 2;
|
|
487
|
+
const after = derived.value;
|
|
486
488
|
<>
|
|
487
489
|
<div>{before}</div>
|
|
488
490
|
<div>{after}</div>
|
|
@@ -496,13 +498,13 @@ second
|
|
|
496
498
|
|
|
497
499
|
it(`handles derived changes based on another derived's dependencies' changes`, async () => {
|
|
498
500
|
function NestedDerived() @{
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const before = product;
|
|
504
|
-
a = 4;
|
|
505
|
-
const after = product;
|
|
501
|
+
const a = track(2);
|
|
502
|
+
const b = track(3);
|
|
503
|
+
const sum = track(() => a.value + b.value);
|
|
504
|
+
const product = track(() => sum.value * 2);
|
|
505
|
+
const before = product.value;
|
|
506
|
+
a.value = 4;
|
|
507
|
+
const after = product.value;
|
|
506
508
|
<>
|
|
507
509
|
<div>{before}</div>
|
|
508
510
|
<div>{after}</div>
|
|
@@ -516,24 +518,24 @@ second
|
|
|
516
518
|
|
|
517
519
|
it('handles lexical scopes correctly', async () => {
|
|
518
520
|
function LexicalScopes() @{
|
|
519
|
-
|
|
521
|
+
const x = track(1);
|
|
520
522
|
let nested = '';
|
|
521
523
|
let deep = '';
|
|
522
524
|
let deeper = '';
|
|
523
525
|
{
|
|
524
|
-
|
|
525
|
-
nested = String(x);
|
|
526
|
+
const x = track(10);
|
|
527
|
+
nested = String(x.value);
|
|
526
528
|
}
|
|
527
529
|
{
|
|
528
|
-
|
|
529
|
-
deep = String(x);
|
|
530
|
+
const x = track(12);
|
|
531
|
+
deep = String(x.value);
|
|
530
532
|
{
|
|
531
|
-
|
|
532
|
-
deeper = String(x);
|
|
533
|
+
const x = track(15);
|
|
534
|
+
deeper = String(x.value);
|
|
533
535
|
}
|
|
534
536
|
}
|
|
535
537
|
<>
|
|
536
|
-
<div>{x}</div>
|
|
538
|
+
<div>{x.value}</div>
|
|
537
539
|
<div>{nested}</div>
|
|
538
540
|
<div>
|
|
539
541
|
{deep}
|
|
@@ -47,52 +47,6 @@ beforeAll(() => {
|
|
|
47
47
|
expect(code).not.toContain('!');
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
it(
|
|
51
|
-
'compiles writes to unknown lazy array index 1 through lazy array helpers in SSR output',
|
|
52
|
-
() => {
|
|
53
|
-
const source = `function Child({ pair: &[first, second] }) @{
|
|
54
|
-
second = 10;
|
|
55
|
-
<div>{first}</div>
|
|
56
|
-
}
|
|
57
|
-
function App() @{
|
|
58
|
-
<Child pair={[0, 1]} />
|
|
59
|
-
}`;
|
|
60
|
-
const { code } = compile(source, 'test.tsrx', { mode: 'server' });
|
|
61
|
-
|
|
62
|
-
expect(code).toContain('_$_.lazy_array_set(lazy, 10, 1)');
|
|
63
|
-
expect(code).not.toContain('lazy[1] =');
|
|
64
|
-
},
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
it('does not double-wrap member access on lazy array value bindings in SSR output', () => {
|
|
68
|
-
const source = `function Child({ pair: &[first] }) @{
|
|
69
|
-
let value = first[0];
|
|
70
|
-
<div>{value}</div>
|
|
71
|
-
}
|
|
72
|
-
function App() @{
|
|
73
|
-
<Child pair={[{ 0: 'x' }]} />
|
|
74
|
-
}`;
|
|
75
|
-
const { code } = compile(source, 'test.tsrx', { mode: 'server' });
|
|
76
|
-
|
|
77
|
-
expect(code).toContain('let value = _$_.lazy_array_get(lazy, 0)[0];');
|
|
78
|
-
expect(code).not.toContain('_$_.lazy_array_get(_$_.lazy_array_get(lazy, 0), 0)');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('throws on indexed access through known tracked lazy destructures in SSR output', () => {
|
|
82
|
-
const source = `import { track } from 'ripple';
|
|
83
|
-
function App() @{
|
|
84
|
-
let &[value, tracked_ref] = track({ 0: 'x' });
|
|
85
|
-
let nested = value[0];
|
|
86
|
-
tracked_ref[0] = { 0: 'y' };
|
|
87
|
-
let next = value[0];
|
|
88
|
-
<div>{nested}{next}</div>
|
|
89
|
-
}`;
|
|
90
|
-
|
|
91
|
-
expect(() => compile(source, 'test.tsrx', { mode: 'server' })).toThrow(
|
|
92
|
-
/Use \.value or &\[\] lazy destructuring/,
|
|
93
|
-
);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
50
|
it('throws on known tracked indexed access in SSR output', () => {
|
|
97
51
|
const source = `import { track } from 'ripple';
|
|
98
52
|
function App() @{
|
|
@@ -106,33 +60,10 @@ function App() @{
|
|
|
106
60
|
}`;
|
|
107
61
|
|
|
108
62
|
expect(() => compile(source, 'test.tsrx', { mode: 'server' })).toThrow(
|
|
109
|
-
/
|
|
63
|
+
/Accessing a tracked object directly is not allowed, use `\.value`/,
|
|
110
64
|
);
|
|
111
65
|
});
|
|
112
66
|
|
|
113
|
-
it(
|
|
114
|
-
'compiles indexed access on unknown lazy tracked refs through lazy array helpers in SSR output',
|
|
115
|
-
() => {
|
|
116
|
-
const source = `import { track } from 'ripple';
|
|
117
|
-
function Child({ pair: &[value, tracked_ref] }) @{
|
|
118
|
-
++tracked_ref[0];
|
|
119
|
-
tracked_ref[0]++;
|
|
120
|
-
<div>{value}</div>
|
|
121
|
-
}
|
|
122
|
-
function App() @{
|
|
123
|
-
let tracked = track(0);
|
|
124
|
-
<Child pair={tracked} />
|
|
125
|
-
}`;
|
|
126
|
-
const { code } = compile(source, 'test.tsrx', { mode: 'server' });
|
|
127
|
-
|
|
128
|
-
expect(code).toContain('_$_.lazy_array_get(lazy, 1)');
|
|
129
|
-
expect(code).toContain('_$_.lazy_array_update_pre(_$_.lazy_array_get(lazy, 1), 0)');
|
|
130
|
-
expect(code).toContain('_$_.lazy_array_update(_$_.lazy_array_get(lazy, 1), 0)');
|
|
131
|
-
expect(code).not.toContain('lazy[1][0]');
|
|
132
|
-
expect(code).not.toContain('lazy_array_get(lazy, 1)[0]');
|
|
133
|
-
},
|
|
134
|
-
);
|
|
135
|
-
|
|
136
67
|
it('removes class TypeScript syntax from JS output', () => {
|
|
137
68
|
const source = `interface BaseEvent {}
|
|
138
69
|
|
|
@@ -8,10 +8,10 @@ describe('composite > props', () => {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
function App() @{
|
|
11
|
-
|
|
11
|
+
const foo = track(123);
|
|
12
12
|
<>
|
|
13
13
|
<Child />
|
|
14
|
-
<Child {foo} />
|
|
14
|
+
<Child foo={foo.value} />
|
|
15
15
|
</>
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -70,10 +70,10 @@ describe('composite > props', () => {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function App() @{
|
|
73
|
-
|
|
73
|
+
const foo = track(123);
|
|
74
74
|
<>
|
|
75
75
|
<Child />
|
|
76
|
-
<Child {foo} />
|
|
76
|
+
<Child foo={foo.value} />
|
|
77
77
|
</>
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -85,7 +85,7 @@ describe('composite > props', () => {
|
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
it('correctly retains prop accessors and reactivity when using rest props', async () => {
|
|
88
|
-
function Button(
|
|
88
|
+
function Button({ children, ...rest }: Props) @{
|
|
89
89
|
<>
|
|
90
90
|
<button {...rest}>{children}</button>
|
|
91
91
|
<style>
|
|
@@ -99,7 +99,7 @@ describe('composite > props', () => {
|
|
|
99
99
|
</>
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
function Toggle(
|
|
102
|
+
function Toggle({ pressed, ...rest }: { pressed: Tracked<boolean> }) @{
|
|
103
103
|
const onClick = () => (pressed.value = !pressed.value);
|
|
104
104
|
<>
|
|
105
105
|
<Button {...rest} class={pressed.value ? 'on' : 'off'} {onClick}>{'button 1'}</Button>
|