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
|
@@ -196,8 +196,8 @@ export function App() @{
|
|
|
196
196
|
const { code } = compile(source, 'primitive-merge.tsrx', { mode: 'client' });
|
|
197
197
|
|
|
198
198
|
// One merged text expression — no separate comment-anchored expression.
|
|
199
|
-
|
|
200
|
-
expect(code).toContain('String(void');
|
|
199
|
+
// The coercion itself needs no scope; only the user call inside it does.
|
|
200
|
+
expect(code).toContain('\'label:\' + String(void _$_.with_scope');
|
|
201
201
|
expect(code).not.toContain('_$_.expression(');
|
|
202
202
|
});
|
|
203
203
|
|
|
@@ -211,8 +211,10 @@ export function App() @{
|
|
|
211
211
|
|
|
212
212
|
const { code } = compile(source, 'string-merge.tsrx', { mode: 'client' });
|
|
213
213
|
|
|
214
|
-
// Already a string: no String(… ?? '') double wrap around it
|
|
215
|
-
|
|
214
|
+
// Already a string: no String(… ?? '') double wrap around it, and the
|
|
215
|
+
// scope wrapper sits on the user call, not on the coercion.
|
|
216
|
+
expect(code).toContain('String(_$_.with_scope(__prev.___block, fetchLabel))');
|
|
217
|
+
expect(code).not.toContain('_$_.with_scope(__prev.___block, () => String(');
|
|
216
218
|
expect(code).not.toContain('String(String(');
|
|
217
219
|
expect(code).not.toContain('?? \'\'');
|
|
218
220
|
});
|
|
@@ -579,23 +581,6 @@ function App() @{
|
|
|
579
581
|
expect(result).toMatch(/value:\s*value\.value/);
|
|
580
582
|
});
|
|
581
583
|
|
|
582
|
-
it('keeps lazy destructuring as plain destructuring in to_ts output', () => {
|
|
583
|
-
const track_source = `
|
|
584
|
-
import { track } from 'ripple';
|
|
585
|
-
function App() @{
|
|
586
|
-
let &[value, ...rest] = track(0);
|
|
587
|
-
const x = value;
|
|
588
|
-
}
|
|
589
|
-
`;
|
|
590
|
-
const track_result = compile_to_volar_mappings(track_source, 'test.tsrx').code;
|
|
591
|
-
expect(track_result).toContain('let [value, ...rest] = track(0);');
|
|
592
|
-
expect(track_result).toContain('const x = value;');
|
|
593
|
-
expect(track_result).not.toContain('let lazy = track(0)');
|
|
594
|
-
expect(track_result).not.toContain('.slice(');
|
|
595
|
-
expect(track_result).not.toContain('_$_.get(');
|
|
596
|
-
expect(track_result).not.toContain('lazy0');
|
|
597
|
-
});
|
|
598
|
-
|
|
599
584
|
it('lowers native expression values in to_ts output', () => {
|
|
600
585
|
const source = `
|
|
601
586
|
function App() @{
|
|
@@ -827,37 +812,6 @@ export function optionalFn(declRequired: string, declMaybe?: string) {
|
|
|
827
812
|
expect_identifier_mapping('declMaybe', 'declMaybe?: string');
|
|
828
813
|
});
|
|
829
814
|
|
|
830
|
-
it('uses tracked fast path for nested lazy params typed as Tracked', () => {
|
|
831
|
-
const source = `
|
|
832
|
-
import type { Tracked } from 'ripple';
|
|
833
|
-
function use_nested({ value: &[count, tracked] }: { value: Tracked<number> }) {
|
|
834
|
-
count++;
|
|
835
|
-
return tracked;
|
|
836
|
-
}
|
|
837
|
-
`;
|
|
838
|
-
const { code } = compile(source, 'tracked-nested-lazy.tsrx', { mode: 'client' });
|
|
839
|
-
|
|
840
|
-
// Nested lazy array should still use tracked tuple fast path from outer annotation.
|
|
841
|
-
expect(code).toContain('_$_.update(');
|
|
842
|
-
expect(code).not.toContain('[0]');
|
|
843
|
-
expect(code).not.toContain('[1]');
|
|
844
|
-
});
|
|
845
|
-
|
|
846
|
-
it('uses tracked fast path for nested lazy params at tuple rest positions', () => {
|
|
847
|
-
const source = `
|
|
848
|
-
import type { Tracked } from 'ripple';
|
|
849
|
-
function use_tuple_rest({ value: [head, &[count, tracked]] }: { value: [number, ...Tracked<number>[]] }) {
|
|
850
|
-
count++;
|
|
851
|
-
return tracked;
|
|
852
|
-
}
|
|
853
|
-
`;
|
|
854
|
-
const { code } = compile(source, 'tracked-nested-lazy-tuple-rest.tsrx', { mode: 'client' });
|
|
855
|
-
|
|
856
|
-
// Tuple rest element access should resolve to Tracked<number>, not Tracked<number>[].
|
|
857
|
-
expect(code).toContain('_$_.update(');
|
|
858
|
-
expect(code).not.toContain('[1]');
|
|
859
|
-
});
|
|
860
|
-
|
|
861
815
|
it('preserves generic type args in interface extends for Volar mappings', () => {
|
|
862
816
|
const source = `
|
|
863
817
|
interface PolymorphicProps<T extends keyof HTMLElementTagNameMap> {
|
|
@@ -882,13 +836,13 @@ export function App(props: Props) @{
|
|
|
882
836
|
const source = `
|
|
883
837
|
import { track } from 'ripple';
|
|
884
838
|
export function App() @{
|
|
885
|
-
|
|
839
|
+
const level = track(1);
|
|
886
840
|
|
|
887
841
|
<button
|
|
888
842
|
onClick={() => {
|
|
889
|
-
if (level === 1) level = 2;
|
|
890
|
-
else if (level === 2) level = 3;
|
|
891
|
-
else level = 1;
|
|
843
|
+
if (level.value === 1) level.value = 2;
|
|
844
|
+
else if (level.value === 2) level.value = 3;
|
|
845
|
+
else level.value = 1;
|
|
892
846
|
}}
|
|
893
847
|
>
|
|
894
848
|
{'Toggle'}
|
|
@@ -1026,6 +980,7 @@ export function App() @{
|
|
|
1026
980
|
const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
|
|
1027
981
|
|
|
1028
982
|
// Template children should take precedence - explicit children prop should be removed
|
|
983
|
+
// The template children win: one `children` key, holding the element.
|
|
1029
984
|
expect((result.match(/children:/g) || []).length).toBe(1);
|
|
1030
985
|
expect(result).toContain('children: _$_.tsrx_element(');
|
|
1031
986
|
});
|
|
@@ -1076,10 +1031,10 @@ export function App() @{
|
|
|
1076
1031
|
import { track, effect, untrack } from 'ripple';
|
|
1077
1032
|
|
|
1078
1033
|
function App() @{
|
|
1079
|
-
|
|
1034
|
+
const count = track(0);
|
|
1080
1035
|
|
|
1081
1036
|
effect(() => {
|
|
1082
|
-
const snapshot = untrack(() => count);
|
|
1037
|
+
const snapshot = untrack(() => count.value);
|
|
1083
1038
|
console.log(snapshot);
|
|
1084
1039
|
});
|
|
1085
1040
|
}
|
|
@@ -1275,7 +1230,7 @@ export function App() @{
|
|
|
1275
1230
|
},
|
|
1276
1231
|
);
|
|
1277
1232
|
|
|
1278
|
-
it('
|
|
1233
|
+
it('spreads props objects as plain object spreads', () => {
|
|
1279
1234
|
const source = `
|
|
1280
1235
|
function Card(props) @{
|
|
1281
1236
|
<div>{props.children}</div>
|
|
@@ -1290,7 +1245,7 @@ export function App() @{
|
|
|
1290
1245
|
|
|
1291
1246
|
const result = compile(source, 'test.tsrx', { mode: 'client' }).code;
|
|
1292
1247
|
|
|
1293
|
-
expect(result).toContain('_$_.
|
|
1248
|
+
expect(result).toContain('_$_.render_component(Card, __anchor, { ...props })');
|
|
1294
1249
|
});
|
|
1295
1250
|
|
|
1296
1251
|
it('reads a less-than at line start in element children as literal text', () => {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { compile } from '@tsrx/ripple';
|
|
2
2
|
|
|
3
|
-
const value_message = /
|
|
4
|
-
const reference_message = /Use the tracked value directly instead/;
|
|
3
|
+
const value_message = /Accessing a tracked object directly is not allowed, use `\.value`/;
|
|
5
4
|
|
|
6
5
|
describe('Compiler: Tracked Object Direct Access Checks', () => {
|
|
7
6
|
it('should error on direct access to __v of a tracked object', () => {
|
|
@@ -74,18 +73,6 @@ import { track } from 'ripple';
|
|
|
74
73
|
);
|
|
75
74
|
});
|
|
76
75
|
|
|
77
|
-
it('should compile successfully with correct @ syntax access', () => {
|
|
78
|
-
const code = `
|
|
79
|
-
import { track } from 'ripple';
|
|
80
|
-
export default function App() @{
|
|
81
|
-
let &[count] = track(0);
|
|
82
|
-
console.log(count);
|
|
83
|
-
<></>
|
|
84
|
-
}
|
|
85
|
-
`;
|
|
86
|
-
expect(() => compile(code, 'test.tsrx')).not.toThrow();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
76
|
it('should compile successfully with correct value access', () => {
|
|
90
77
|
const code = `
|
|
91
78
|
import { track } from 'ripple';
|
|
@@ -156,7 +143,7 @@ import { track } from 'ripple';
|
|
|
156
143
|
<></>
|
|
157
144
|
}
|
|
158
145
|
`;
|
|
159
|
-
expect(() => compile(code, 'test.tsrx')).toThrow(
|
|
146
|
+
expect(() => compile(code, 'test.tsrx')).toThrow(value_message);
|
|
160
147
|
});
|
|
161
148
|
|
|
162
149
|
it('should error on indexed [0] write on a tracked object', () => {
|
|
@@ -183,63 +170,49 @@ import { track } from 'ripple';
|
|
|
183
170
|
`;
|
|
184
171
|
const result = compile(code, 'test.tsrx', { loose: true });
|
|
185
172
|
|
|
186
|
-
expect(result.errors.map((error) => error.message)).toEqual(
|
|
187
|
-
expect.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
]),
|
|
191
|
-
);
|
|
173
|
+
expect(result.errors.map((error) => error.message)).toEqual([
|
|
174
|
+
expect.stringMatching(value_message),
|
|
175
|
+
expect.stringMatching(value_message),
|
|
176
|
+
]);
|
|
192
177
|
});
|
|
193
178
|
|
|
194
|
-
it('should
|
|
195
|
-
const code = `
|
|
196
|
-
import { track } from 'ripple';
|
|
197
|
-
export default function App() @{
|
|
198
|
-
let &[value, tracked_ref] = track(0);
|
|
199
|
-
tracked_ref[0]++;
|
|
200
|
-
<></>
|
|
201
|
-
}
|
|
202
|
-
`;
|
|
203
|
-
expect(() => compile(code, 'test.tsrx')).toThrow(value_message);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('should allow lazy destructuring a tracked value', () => {
|
|
179
|
+
it('should allow .value read access on a tracked object', () => {
|
|
207
180
|
const code = `
|
|
208
181
|
import { track } from 'ripple';
|
|
209
182
|
export default function App() @{
|
|
210
|
-
let
|
|
211
|
-
console.log(
|
|
183
|
+
let count = track(0);
|
|
184
|
+
console.log(count.value);
|
|
212
185
|
<></>
|
|
213
186
|
}
|
|
214
187
|
`;
|
|
215
188
|
expect(() => compile(code, 'test.tsrx')).not.toThrow();
|
|
216
189
|
});
|
|
217
190
|
|
|
218
|
-
it('should allow .value
|
|
191
|
+
it('should allow .value assignment on a tracked object', () => {
|
|
219
192
|
const code = `
|
|
220
193
|
import { track } from 'ripple';
|
|
221
194
|
export default function App() @{
|
|
222
195
|
let count = track(0);
|
|
223
|
-
|
|
196
|
+
count.value = 5;
|
|
224
197
|
<></>
|
|
225
198
|
}
|
|
226
199
|
`;
|
|
227
200
|
expect(() => compile(code, 'test.tsrx')).not.toThrow();
|
|
228
201
|
});
|
|
229
202
|
|
|
230
|
-
it('should allow .
|
|
203
|
+
it('should allow .readOnly() on a tracked object', () => {
|
|
231
204
|
const code = `
|
|
232
205
|
import { track } from 'ripple';
|
|
233
206
|
export default function App() @{
|
|
234
207
|
let count = track(0);
|
|
235
|
-
|
|
236
|
-
|
|
208
|
+
const view = count.readOnly();
|
|
209
|
+
<>{view.value}</>
|
|
237
210
|
}
|
|
238
211
|
`;
|
|
239
212
|
expect(() => compile(code, 'test.tsrx')).not.toThrow();
|
|
240
213
|
});
|
|
241
214
|
|
|
242
|
-
it('should
|
|
215
|
+
it('should error on .length access on a tracked object', () => {
|
|
243
216
|
const code = `
|
|
244
217
|
import { track } from 'ripple';
|
|
245
218
|
export default function App() @{
|
|
@@ -248,6 +221,8 @@ import { track } from 'ripple';
|
|
|
248
221
|
<></>
|
|
249
222
|
}
|
|
250
223
|
`;
|
|
251
|
-
expect(() => compile(code, 'test.tsrx')).
|
|
224
|
+
expect(() => compile(code, 'test.tsrx')).toThrow(
|
|
225
|
+
'Accessing a tracked object directly is not allowed',
|
|
226
|
+
);
|
|
252
227
|
});
|
|
253
228
|
});
|
|
@@ -74,7 +74,7 @@ describe('compiler > Volar transform does not duplicate try blocks from function
|
|
|
74
74
|
const source = `import { trackAsync } from 'ripple';
|
|
75
75
|
export function App() @{
|
|
76
76
|
try {
|
|
77
|
-
|
|
77
|
+
const data = trackAsync(() => fetch('/api'));
|
|
78
78
|
} catch (e) {}
|
|
79
79
|
<div>{"hi"}</div>
|
|
80
80
|
}`;
|
|
@@ -88,7 +88,7 @@ export function App() @{
|
|
|
88
88
|
const source = `import { trackAsync } from 'ripple';
|
|
89
89
|
export function App() @{
|
|
90
90
|
try {
|
|
91
|
-
|
|
91
|
+
const data = trackAsync(() => fetch('/init'));
|
|
92
92
|
} catch (e) {}
|
|
93
93
|
|
|
94
94
|
async function refresh() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { flushSync, track } from 'ripple';
|
|
1
|
+
import { type Component, flushSync, track } from 'ripple';
|
|
2
2
|
|
|
3
3
|
describe('composite > dynamic components', () => {
|
|
4
4
|
it('supports rendering composite components using dynamic tag syntax', () => {
|
|
@@ -47,8 +47,8 @@ describe('composite > dynamic components', () => {
|
|
|
47
47
|
const obj = {
|
|
48
48
|
tracked_basic,
|
|
49
49
|
};
|
|
50
|
-
|
|
51
|
-
const comp = inner.tracked_basic;
|
|
50
|
+
const inner = track(obj);
|
|
51
|
+
const comp = inner.value.tracked_basic;
|
|
52
52
|
<{comp} />
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -86,13 +86,13 @@ describe('composite > dynamic components', () => {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function App() @{
|
|
89
|
-
|
|
89
|
+
const thing = track(() => Child1, undefined, true);
|
|
90
90
|
<>
|
|
91
91
|
<div id="container">
|
|
92
|
-
<{thing} />
|
|
92
|
+
<{thing.value} />
|
|
93
93
|
</div>
|
|
94
94
|
<button
|
|
95
|
-
onClick={() => (thing = thing === Child1 ? Child2 : Child1)}
|
|
95
|
+
onClick={() => (thing.value = thing.value === Child1 ? Child2 : Child1)}
|
|
96
96
|
>{'Change Child'}</button>
|
|
97
97
|
</>
|
|
98
98
|
}
|
|
@@ -113,4 +113,75 @@ describe('composite > dynamic components', () => {
|
|
|
113
113
|
|
|
114
114
|
expect(container.querySelector('#container').textContent).toBe('I am child 1');
|
|
115
115
|
});
|
|
116
|
+
|
|
117
|
+
it('switches components held in a plain tracked assigned after creation', () => {
|
|
118
|
+
function Child1() @{
|
|
119
|
+
<div>{'I am child 1'}</div>
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function Child2() @{
|
|
123
|
+
<div>{'I am child 2'}</div>
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function App() @{
|
|
127
|
+
// track(Child1) would make a derived that calls the component, so an
|
|
128
|
+
// empty tracked receives the component through `.value` instead.
|
|
129
|
+
const thing = track<Component>();
|
|
130
|
+
thing.value = Child1;
|
|
131
|
+
<>
|
|
132
|
+
<div id="container">
|
|
133
|
+
<{thing.value} />
|
|
134
|
+
</div>
|
|
135
|
+
<button
|
|
136
|
+
onClick={() => (thing.value = thing.value === Child1 ? Child2 : Child1)}
|
|
137
|
+
>{'Change Child'}</button>
|
|
138
|
+
</>
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
render(App);
|
|
142
|
+
flushSync();
|
|
143
|
+
|
|
144
|
+
expect(container.querySelector('#container').textContent).toBe('I am child 1');
|
|
145
|
+
|
|
146
|
+
const button = container.querySelector('button');
|
|
147
|
+
button.click();
|
|
148
|
+
flushSync();
|
|
149
|
+
|
|
150
|
+
expect(container.querySelector('#container').textContent).toBe('I am child 2');
|
|
151
|
+
|
|
152
|
+
button.click();
|
|
153
|
+
flushSync();
|
|
154
|
+
|
|
155
|
+
expect(container.querySelector('#container').textContent).toBe('I am child 1');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('switches plain functions held in a tracked assigned after creation', () => {
|
|
159
|
+
const first = () => 'first';
|
|
160
|
+
const second = () => 'second';
|
|
161
|
+
|
|
162
|
+
function App() @{
|
|
163
|
+
const fn = track<() => string>();
|
|
164
|
+
fn.value = first;
|
|
165
|
+
<>
|
|
166
|
+
<p>{fn.value()}</p>
|
|
167
|
+
<button onClick={() => (fn.value = fn.value === first ? second : first)}>{'Swap'}</button>
|
|
168
|
+
</>
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
render(App);
|
|
172
|
+
flushSync();
|
|
173
|
+
|
|
174
|
+
expect(container.querySelector('p').textContent).toBe('first');
|
|
175
|
+
|
|
176
|
+
const button = container.querySelector('button');
|
|
177
|
+
button.click();
|
|
178
|
+
flushSync();
|
|
179
|
+
|
|
180
|
+
expect(container.querySelector('p').textContent).toBe('second');
|
|
181
|
+
|
|
182
|
+
button.click();
|
|
183
|
+
flushSync();
|
|
184
|
+
|
|
185
|
+
expect(container.querySelector('p').textContent).toBe('first');
|
|
186
|
+
});
|
|
116
187
|
});
|
|
@@ -1,17 +1,150 @@
|
|
|
1
|
-
import type { Tracked, Props } from 'ripple';
|
|
1
|
+
import type { Derived, Tracked, Props } from 'ripple';
|
|
2
2
|
import { effect, flushSync, track } from 'ripple';
|
|
3
3
|
|
|
4
4
|
describe('composite > props', () => {
|
|
5
|
+
it('hands a child a read-only view through readOnly()', () => {
|
|
6
|
+
function Child({ count }: { count: Derived<number> }) @{
|
|
7
|
+
<span class="child">{count.value}</span>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function App() @{
|
|
11
|
+
const count = track(0);
|
|
12
|
+
<>
|
|
13
|
+
<Child count={count.readOnly()} />
|
|
14
|
+
<button onClick={() => count.value++}>{'inc'}</button>
|
|
15
|
+
</>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
render(App);
|
|
19
|
+
flushSync();
|
|
20
|
+
expect(container.querySelector('.child').textContent).toBe('0');
|
|
21
|
+
|
|
22
|
+
container.querySelector('button').click();
|
|
23
|
+
flushSync();
|
|
24
|
+
expect(container.querySelector('.child').textContent).toBe('1');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns a read-only derived from readOnly() as is and wraps a writable one', () => {
|
|
28
|
+
function App() @{
|
|
29
|
+
const count = track(1);
|
|
30
|
+
const double = track(() => count.value * 2);
|
|
31
|
+
const draft = track(() => count.value, undefined, true);
|
|
32
|
+
const double_view = double.readOnly();
|
|
33
|
+
const draft_view = draft.readOnly();
|
|
34
|
+
expect(double_view).toBe(double);
|
|
35
|
+
expect(draft_view).not.toBe(draft);
|
|
36
|
+
<>
|
|
37
|
+
<span class="draft">{draft.value}</span>
|
|
38
|
+
<span class="view">{draft_view.value}</span>
|
|
39
|
+
<button onClick={() => (draft.value = 42)}>{'write'}</button>
|
|
40
|
+
</>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
render(App);
|
|
44
|
+
flushSync();
|
|
45
|
+
expect(container.querySelector('.view').textContent).toBe('1');
|
|
46
|
+
|
|
47
|
+
// A write to the writable derived is visible through its view.
|
|
48
|
+
container.querySelector('button').click();
|
|
49
|
+
flushSync();
|
|
50
|
+
expect(container.querySelector('.draft').textContent).toBe('42');
|
|
51
|
+
expect(container.querySelector('.view').textContent).toBe('42');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('releases a readOnly() view with the block that created it', () => {
|
|
55
|
+
let set_rows: (rows: number[]) => void = () => {};
|
|
56
|
+
let bump: () => void = () => {};
|
|
57
|
+
let source: Tracked<number> | undefined;
|
|
58
|
+
|
|
59
|
+
function Child({ count }: { count: Derived<number> }) @{
|
|
60
|
+
<span class="child">{count.value}</span>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function App() @{
|
|
64
|
+
const count = track(0);
|
|
65
|
+
const rows = track<number[]>([]);
|
|
66
|
+
source = count;
|
|
67
|
+
set_rows = (next) => {
|
|
68
|
+
rows.value = next;
|
|
69
|
+
};
|
|
70
|
+
bump = () => {
|
|
71
|
+
count.value++;
|
|
72
|
+
};
|
|
73
|
+
<ul>
|
|
74
|
+
@for (const row of rows.value; key row) {
|
|
75
|
+
<li>
|
|
76
|
+
<Child count={count.readOnly()} />
|
|
77
|
+
</li>
|
|
78
|
+
}
|
|
79
|
+
</ul>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Each view is a derived subscribed to the source; the subscriber list is
|
|
83
|
+
// the runtime's own (`sb` / `sn`).
|
|
84
|
+
const subscribers = () => {
|
|
85
|
+
let n = 0;
|
|
86
|
+
for (let d = (source as any).sb; d !== null; d = d.sn) n++;
|
|
87
|
+
return n;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
render(App);
|
|
91
|
+
flushSync();
|
|
92
|
+
expect(subscribers()).toBe(0);
|
|
93
|
+
|
|
94
|
+
set_rows([1, 2, 3]);
|
|
95
|
+
flushSync();
|
|
96
|
+
expect(container.querySelectorAll('.child').length).toBe(3);
|
|
97
|
+
expect(subscribers()).toBe(3);
|
|
98
|
+
|
|
99
|
+
// Removing the items destroys the blocks that created the views, so the
|
|
100
|
+
// next write to the source prunes them instead of keeping them alive.
|
|
101
|
+
set_rows([]);
|
|
102
|
+
flushSync();
|
|
103
|
+
bump();
|
|
104
|
+
flushSync();
|
|
105
|
+
expect(container.querySelectorAll('.child').length).toBe(0);
|
|
106
|
+
expect(subscribers()).toBe(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('keeps a readOnly() view from writing through to its source', () => {
|
|
110
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
111
|
+
try {
|
|
112
|
+
function App() @{
|
|
113
|
+
const count = track(1);
|
|
114
|
+
const view = count.readOnly();
|
|
115
|
+
const write = () => {
|
|
116
|
+
view.value = 99;
|
|
117
|
+
};
|
|
118
|
+
<>
|
|
119
|
+
<span class="source">{count.value}</span>
|
|
120
|
+
<span class="view">{view.value}</span>
|
|
121
|
+
<button onClick={write}>{'write'}</button>
|
|
122
|
+
</>
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
render(App);
|
|
126
|
+
flushSync();
|
|
127
|
+
container.querySelector('button').click();
|
|
128
|
+
flushSync();
|
|
129
|
+
|
|
130
|
+
// The write warned in development and never reached the source.
|
|
131
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining('read-only derived'));
|
|
132
|
+
expect(container.querySelector('.source').textContent).toBe('1');
|
|
133
|
+
} finally {
|
|
134
|
+
warn.mockRestore();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
5
138
|
it('correctly handles default prop values', () => {
|
|
6
139
|
function Child({ foo = 456 }) @{
|
|
7
140
|
<div>{foo}</div>
|
|
8
141
|
}
|
|
9
142
|
|
|
10
143
|
function App() @{
|
|
11
|
-
|
|
144
|
+
const foo = track(123);
|
|
12
145
|
<>
|
|
13
146
|
<Child />
|
|
14
|
-
<Child {foo} />
|
|
147
|
+
<Child foo={foo.value} />
|
|
15
148
|
</>
|
|
16
149
|
}
|
|
17
150
|
|
|
@@ -67,10 +200,10 @@ describe('composite > props', () => {
|
|
|
67
200
|
}
|
|
68
201
|
|
|
69
202
|
function App() @{
|
|
70
|
-
|
|
203
|
+
const foo = track(123);
|
|
71
204
|
<>
|
|
72
205
|
<Child />
|
|
73
|
-
<Child {foo} />
|
|
206
|
+
<Child foo={foo.value} />
|
|
74
207
|
</>
|
|
75
208
|
}
|
|
76
209
|
|
|
@@ -110,7 +243,7 @@ describe('composite > props', () => {
|
|
|
110
243
|
});
|
|
111
244
|
|
|
112
245
|
it('correctly retains prop accessors and reactivity when using rest props', () => {
|
|
113
|
-
function Button(
|
|
246
|
+
function Button({ children, ...rest }: Props) @{
|
|
114
247
|
<>
|
|
115
248
|
<button {...rest}>{children}</button>
|
|
116
249
|
<style>
|
|
@@ -124,11 +257,15 @@ describe('composite > props', () => {
|
|
|
124
257
|
</>
|
|
125
258
|
}
|
|
126
259
|
|
|
127
|
-
function Toggle(
|
|
260
|
+
function Toggle({ pressed, ...rest }: { pressed: Tracked<boolean> }) @{
|
|
128
261
|
const onClick = () => (pressed.value = !pressed.value);
|
|
129
262
|
<>
|
|
130
|
-
<Button
|
|
131
|
-
|
|
263
|
+
<Button
|
|
264
|
+
{...rest}
|
|
265
|
+
class={track(() => (pressed.value ? 'on' : 'off'))}
|
|
266
|
+
{onClick}
|
|
267
|
+
>{'button 1'}</Button>
|
|
268
|
+
<Button class={track(() => (pressed.value ? 'on' : 'off'))} {onClick}>{'button 2'}</Button>
|
|
132
269
|
</>
|
|
133
270
|
}
|
|
134
271
|
|
|
@@ -215,16 +352,16 @@ describe('composite > props', () => {
|
|
|
215
352
|
}
|
|
216
353
|
|
|
217
354
|
function App() @{
|
|
218
|
-
|
|
355
|
+
const ok = track(true);
|
|
219
356
|
<>
|
|
220
357
|
<button
|
|
221
358
|
onClick={() => {
|
|
222
|
-
ok = !ok;
|
|
359
|
+
ok.value = !ok.value;
|
|
223
360
|
}}
|
|
224
361
|
>{'toggle'}</button>
|
|
225
362
|
<Child
|
|
226
363
|
header={<h1>
|
|
227
|
-
@if (ok) {
|
|
364
|
+
@if (ok.value) {
|
|
228
365
|
<span>{'yes'}</span>
|
|
229
366
|
} @else {
|
|
230
367
|
<span>{'no'}</span>
|