ripple 0.3.128 → 0.4.1
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 +294 -0
- package/package.json +11 -4
- package/src/jsx-runtime.d.ts +12 -2
- 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 +44 -43
- 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 +329 -277
- 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 +123 -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 +70 -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/jsx-runtime-types.test.js +67 -2
- package/tests/utils/tracked-types.test.js +117 -0
- package/tests/utils/vite-plugin-config.test.js +23 -0
- package/types/index.d.ts +49 -9
- 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/compiler/__snapshots__/compiler.assignments.test.rsrx.snap +0 -12
- package/tests/client/compiler/__snapshots__/compiler.typescript.test.rsrx.snap +0 -46
- 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);
|
|
@@ -235,10 +235,38 @@ describe('basic server > attribute rendering', () => {
|
|
|
235
235
|
expect(div.style.fontWeight).toBe('bold');
|
|
236
236
|
});
|
|
237
237
|
|
|
238
|
+
it.each([undefined, null])('omits %s style values from HTML and SVG attributes', async (
|
|
239
|
+
empty,
|
|
240
|
+
) => {
|
|
241
|
+
function Basic() @{
|
|
242
|
+
const styles = {
|
|
243
|
+
width: empty,
|
|
244
|
+
'--scale': empty,
|
|
245
|
+
margin: 0,
|
|
246
|
+
opacity: 0,
|
|
247
|
+
'--literal': 'undefined',
|
|
248
|
+
'--literal-null': 'null',
|
|
249
|
+
};
|
|
250
|
+
<>
|
|
251
|
+
<div style={styles} />
|
|
252
|
+
<svg {...{ style: styles }} />
|
|
253
|
+
</>
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const { body } = await render(Basic);
|
|
257
|
+
const { document } = parseHtml(body);
|
|
258
|
+
|
|
259
|
+
for (const element of document.querySelectorAll('div, svg')) {
|
|
260
|
+
expect(element.getAttribute('style')).toBe(
|
|
261
|
+
'margin: 0; opacity: 0; --literal: undefined; --literal-null: null;',
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
238
266
|
it('render tracked variable as style attribute', async () => {
|
|
239
267
|
function Basic() @{
|
|
240
|
-
|
|
241
|
-
<div {style}>{'Dynamic Style'}</div>
|
|
268
|
+
const style = track({ color: 'red', fontWeight: 'bold' });
|
|
269
|
+
<div style={style.value}>{'Dynamic Style'}</div>
|
|
242
270
|
}
|
|
243
271
|
|
|
244
272
|
const { body } = await render(Basic);
|
|
@@ -363,9 +391,9 @@ describe('basic server > attribute rendering', () => {
|
|
|
363
391
|
|
|
364
392
|
it('render dynamic boolean attributes as false', async () => {
|
|
365
393
|
function Basic() @{
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
<input type="checkbox" {disabled} {checked} />
|
|
394
|
+
const disabled = track(false);
|
|
395
|
+
const checked = track(false);
|
|
396
|
+
<input type="checkbox" disabled={disabled.value} checked={checked.value} />
|
|
369
397
|
}
|
|
370
398
|
|
|
371
399
|
const { body } = await render(Basic);
|
|
@@ -396,9 +424,9 @@ describe('basic server > attribute rendering', () => {
|
|
|
396
424
|
|
|
397
425
|
it('render dynamic boolean attributes as true', async () => {
|
|
398
426
|
function Basic() @{
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
<input type="checkbox" {disabled} {checked} />
|
|
427
|
+
const disabled = track(true);
|
|
428
|
+
const checked = track(true);
|
|
429
|
+
<input type="checkbox" disabled={disabled.value} checked={checked.value} />
|
|
402
430
|
}
|
|
403
431
|
|
|
404
432
|
const { body } = await render(Basic);
|
|
@@ -429,8 +457,8 @@ describe('basic server > attribute rendering', () => {
|
|
|
429
457
|
|
|
430
458
|
it('renders formnovalidate as a boolean attribute', async () => {
|
|
431
459
|
function Basic() @{
|
|
432
|
-
|
|
433
|
-
<button {formnovalidate}>{'Submit'}</button>
|
|
460
|
+
const formnovalidate = track(true);
|
|
461
|
+
<button formnovalidate={formnovalidate.value}>{'Submit'}</button>
|
|
434
462
|
}
|
|
435
463
|
|
|
436
464
|
const { body } = await render(Basic);
|
|
@@ -445,8 +473,8 @@ describe('basic server > attribute rendering', () => {
|
|
|
445
473
|
|
|
446
474
|
it('renders hidden as a boolean attribute when true', async () => {
|
|
447
475
|
function Basic() @{
|
|
448
|
-
|
|
449
|
-
<div {hidden}>{'Hidden content'}</div>
|
|
476
|
+
const hidden = track(true);
|
|
477
|
+
<div hidden={hidden.value}>{'Hidden content'}</div>
|
|
450
478
|
}
|
|
451
479
|
|
|
452
480
|
const { body } = await render(Basic);
|
|
@@ -461,8 +489,8 @@ describe('basic server > attribute rendering', () => {
|
|
|
461
489
|
|
|
462
490
|
it('does not render hidden when false', async () => {
|
|
463
491
|
function Basic() @{
|
|
464
|
-
|
|
465
|
-
<div {hidden}>{'Visible content'}</div>
|
|
492
|
+
const hidden = track(false);
|
|
493
|
+
<div hidden={hidden.value}>{'Visible content'}</div>
|
|
466
494
|
}
|
|
467
495
|
|
|
468
496
|
const { body } = await render(Basic);
|
|
@@ -476,12 +504,12 @@ describe('basic server > attribute rendering', () => {
|
|
|
476
504
|
|
|
477
505
|
it('render multiple dynamic attributes', async () => {
|
|
478
506
|
function Basic() @{
|
|
479
|
-
|
|
480
|
-
|
|
507
|
+
const theme = track('light');
|
|
508
|
+
const size = track('medium');
|
|
481
509
|
<div
|
|
482
|
-
class={`theme-${theme} size-${size}`}
|
|
483
|
-
data-theme={theme}
|
|
484
|
-
data-size={size}
|
|
510
|
+
class={`theme-${theme.value} size-${size.value}`}
|
|
511
|
+
data-theme={theme.value}
|
|
512
|
+
data-size={size.value}
|
|
485
513
|
>{'Multiple Dynamic Attributes'}</div>
|
|
486
514
|
}
|
|
487
515
|
|
|
@@ -497,11 +525,11 @@ describe('basic server > attribute rendering', () => {
|
|
|
497
525
|
|
|
498
526
|
it('render conditional attributes', async () => {
|
|
499
527
|
function Basic() @{
|
|
500
|
-
|
|
501
|
-
|
|
528
|
+
const showTitle = track(false);
|
|
529
|
+
const showAria = track(false);
|
|
502
530
|
<div
|
|
503
|
-
title={showTitle ? 'This is a title' : undefined}
|
|
504
|
-
aria-label={showAria ? 'Accessible label' : undefined}
|
|
531
|
+
title={showTitle.value ? 'This is a title' : undefined}
|
|
532
|
+
aria-label={showAria.value ? 'Accessible label' : undefined}
|
|
505
533
|
>{'Conditional Attributes'}</div>
|
|
506
534
|
}
|
|
507
535
|
|
|
@@ -516,11 +544,11 @@ describe('basic server > attribute rendering', () => {
|
|
|
516
544
|
|
|
517
545
|
it('render spread attributes', async () => {
|
|
518
546
|
function Basic() @{
|
|
519
|
-
|
|
547
|
+
const attrs = track<TestAttributes>({
|
|
520
548
|
class: 'initial',
|
|
521
549
|
id: 'test-1',
|
|
522
550
|
});
|
|
523
|
-
<div {...attrs}>{'Spread Attributes'}</div>
|
|
551
|
+
<div {...attrs.value}>{'Spread Attributes'}</div>
|
|
524
552
|
}
|
|
525
553
|
|
|
526
554
|
const { body } = await render(Basic);
|
|
@@ -535,12 +563,12 @@ describe('basic server > attribute rendering', () => {
|
|
|
535
563
|
|
|
536
564
|
it('renders with reactive attributes with nested reactive attributes', async () => {
|
|
537
565
|
function Basic() @{
|
|
538
|
-
|
|
539
|
-
|
|
566
|
+
const value = track('parent-class');
|
|
567
|
+
const nested = track('nested-class');
|
|
540
568
|
<>
|
|
541
|
-
<p class={value}>{'Colored parent value'}</p>
|
|
569
|
+
<p class={value.value}>{'Colored parent value'}</p>
|
|
542
570
|
<div>
|
|
543
|
-
<p class={nested}>{'Colored nested value'}</p>
|
|
571
|
+
<p class={nested.value}>{'Colored nested value'}</p>
|
|
544
572
|
</div>
|
|
545
573
|
</>
|
|
546
574
|
}
|
|
@@ -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>
|