ripple 0.3.8 → 0.3.9
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 +7 -0
- package/package.json +2 -2
- package/src/compiler/phases/1-parse/index.js +13 -157
- package/src/compiler/phases/2-analyze/index.js +289 -43
- package/src/compiler/phases/3-transform/client/index.js +9 -157
- package/src/compiler/phases/3-transform/segments.js +0 -7
- package/src/compiler/phases/3-transform/server/index.js +15 -130
- package/src/compiler/types/acorn.d.ts +1 -1
- package/src/compiler/types/estree.d.ts +1 -1
- package/src/compiler/types/import.d.ts +0 -2
- package/src/compiler/types/index.d.ts +5 -17
- package/src/compiler/types/parse.d.ts +1 -9
- package/src/compiler/utils.js +53 -20
- package/src/runtime/index-client.js +2 -13
- package/src/runtime/index-server.js +2 -2
- package/src/runtime/internal/client/bindings.js +3 -1
- package/src/runtime/internal/client/composite.js +1 -0
- package/src/runtime/internal/client/events.js +1 -1
- package/src/runtime/internal/client/head.js +3 -4
- package/src/runtime/internal/client/index.js +0 -1
- package/src/runtime/internal/client/runtime.js +0 -52
- package/src/runtime/internal/server/index.js +31 -55
- package/tests/client/basic/basic.errors.test.ripple +28 -0
- package/tests/client/basic/basic.reactivity.test.ripple +10 -155
- package/tests/client/compiler/compiler.basic.test.ripple +31 -12
- package/tests/client/composite/composite.props.test.ripple +5 -7
- package/tests/client/composite/composite.reactivity.test.ripple +35 -36
- package/tests/client/dynamic-elements.test.ripple +3 -4
- package/tests/client/lazy-destructuring.test.ripple +69 -12
- package/tests/server/compiler.test.ripple +22 -0
- package/tests/server/composite.props.test.ripple +5 -7
- package/tests/server/dynamic-elements.test.ripple +3 -4
- package/tests/server/lazy-destructuring.test.ripple +68 -12
- package/tsconfig.typecheck.json +4 -0
- package/types/index.d.ts +0 -19
- package/tests/client/__snapshots__/tracked-expression.test.ripple.snap +0 -34
- package/tests/client/tracked-expression.test.ripple +0 -26
|
@@ -102,13 +102,7 @@ export function hydrate(component, options) {
|
|
|
102
102
|
|
|
103
103
|
export { Context } from './internal/client/context.js';
|
|
104
104
|
|
|
105
|
-
export {
|
|
106
|
-
flush_sync as flushSync,
|
|
107
|
-
track,
|
|
108
|
-
track_split as trackSplit,
|
|
109
|
-
untrack,
|
|
110
|
-
tick,
|
|
111
|
-
} from './internal/client/runtime.js';
|
|
105
|
+
export { flush_sync as flushSync, track, untrack, tick } from './internal/client/runtime.js';
|
|
112
106
|
|
|
113
107
|
export { RippleArray } from './array.js';
|
|
114
108
|
|
|
@@ -165,10 +159,5 @@ import { RippleURL } from './url.js';
|
|
|
165
159
|
import { RippleURLSearchParams } from './url-search-params.js';
|
|
166
160
|
import { RippleDate } from './date.js';
|
|
167
161
|
import { MediaQuery } from './media-query.js';
|
|
168
|
-
import {
|
|
169
|
-
track,
|
|
170
|
-
track_split as trackSplit,
|
|
171
|
-
untrack,
|
|
172
|
-
ref_prop as createRefKey,
|
|
173
|
-
} from './internal/client/runtime.js';
|
|
162
|
+
import { track, untrack, ref_prop as createRefKey } from './internal/client/runtime.js';
|
|
174
163
|
import { user_effect as effect } from './internal/client/blocks.js';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { get, set, untrack, track
|
|
1
|
+
import { get, set, untrack, track } from './internal/server/index.js';
|
|
2
2
|
|
|
3
3
|
export { Context } from './internal/server/context.js';
|
|
4
4
|
|
|
5
|
-
export { get, set, untrack, track
|
|
5
|
+
export { get, set, untrack, track };
|
|
6
6
|
|
|
7
7
|
function noop() {}
|
|
8
8
|
|
|
@@ -186,7 +186,9 @@ function select_option(select, value, mounting = false) {
|
|
|
186
186
|
|
|
187
187
|
// Otherwise, update the selection
|
|
188
188
|
for (var option of select.options) {
|
|
189
|
-
option.selected = /** @type {string[]} */ (value).includes(
|
|
189
|
+
option.selected = /** @type {string[]} */ (value).includes(
|
|
190
|
+
/** @type {string} */ (get_option_value(option)),
|
|
191
|
+
);
|
|
190
192
|
}
|
|
191
193
|
|
|
192
194
|
return;
|
|
@@ -168,7 +168,7 @@ export function handle_event_propagation(event) {
|
|
|
168
168
|
null;
|
|
169
169
|
|
|
170
170
|
try {
|
|
171
|
-
var delegated = current_target['__' + event_name];
|
|
171
|
+
var delegated = /** @type {Record<string, any>} */ (current_target)['__' + event_name];
|
|
172
172
|
|
|
173
173
|
if (delegated !== undefined && !(/** @type {any} */ (current_target).disabled)) {
|
|
174
174
|
if (is_array(delegated)) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** @import { TemplateNode } from '#client' */
|
|
2
1
|
import { render } from './blocks.js';
|
|
3
2
|
import { HEAD_BLOCK } from './constants.js';
|
|
4
3
|
import { COMMENT_NODE } from '../../../constants.js';
|
|
@@ -38,8 +37,8 @@ export function head(hash, render_fn) {
|
|
|
38
37
|
if (head_anchor === null) {
|
|
39
38
|
set_hydrating(false);
|
|
40
39
|
} else {
|
|
41
|
-
var start =
|
|
42
|
-
head_anchor.remove(); // in case this component is repeated
|
|
40
|
+
var start = get_next_sibling(head_anchor);
|
|
41
|
+
/** @type {ChildNode} */ (head_anchor).remove(); // in case this component is repeated
|
|
43
42
|
|
|
44
43
|
set_hydrate_node(start);
|
|
45
44
|
}
|
|
@@ -54,7 +53,7 @@ export function head(hash, render_fn) {
|
|
|
54
53
|
} finally {
|
|
55
54
|
if (was_hydrating) {
|
|
56
55
|
set_hydrating(true);
|
|
57
|
-
set_hydrate_node(
|
|
56
|
+
set_hydrate_node(previous_hydrate_node);
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
}
|
|
@@ -426,58 +426,6 @@ export function track(v, get, set, b) {
|
|
|
426
426
|
return tracked(v, b, get, set);
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
-
/**
|
|
430
|
-
* @param {Record<string|symbol, any>} v
|
|
431
|
-
* @param {(symbol | string)[]} l
|
|
432
|
-
* @param {Block} b
|
|
433
|
-
* @returns {Tracked[]}
|
|
434
|
-
*/
|
|
435
|
-
export function track_split(v, l, b) {
|
|
436
|
-
var is_tracked = is_ripple_object(v);
|
|
437
|
-
|
|
438
|
-
if (is_tracked || typeof v !== 'object' || v === null || is_array(v)) {
|
|
439
|
-
throw new TypeError('Invalid value: expected a non-tracked object');
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/** @type {Tracked[]} */
|
|
443
|
-
var out = [];
|
|
444
|
-
/** @type {Record<string|symbol, any>} */
|
|
445
|
-
var rest = {};
|
|
446
|
-
/** @type {Record<PropertyKey, 1>} */
|
|
447
|
-
var done = {};
|
|
448
|
-
var props = Reflect.ownKeys(v);
|
|
449
|
-
|
|
450
|
-
for (let i = 0, key, t; i < l.length; i++) {
|
|
451
|
-
key = l[i];
|
|
452
|
-
|
|
453
|
-
if (props.includes(key)) {
|
|
454
|
-
if (is_ripple_object(v[key])) {
|
|
455
|
-
t = v[key];
|
|
456
|
-
} else {
|
|
457
|
-
t = tracked(undefined, b);
|
|
458
|
-
t = define_property(t, '__v', /** @type {PropertyDescriptor} */ (get_descriptor(v, key)));
|
|
459
|
-
}
|
|
460
|
-
} else {
|
|
461
|
-
t = tracked(undefined, b);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
out[i] = t;
|
|
465
|
-
done[key] = 1;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
for (let i = 0, key; i < props.length; i++) {
|
|
469
|
-
key = props[i];
|
|
470
|
-
if (done[key]) {
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
define_property(rest, key, /** @type {PropertyDescriptor} */ (get_descriptor(v, key)));
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
out.push(tracked(rest, b));
|
|
477
|
-
|
|
478
|
-
return out;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
429
|
/**
|
|
482
430
|
* @param {Tracked | Derived} tracked
|
|
483
431
|
* @returns {Dependency}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
@import { Component, Dependency, Derived, Tracked } from '#server';
|
|
3
|
-
@import { SSRComponent
|
|
3
|
+
@import { SSRComponent } from 'ripple/server';
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Readable } from 'stream';
|
|
@@ -231,12 +231,12 @@ class Output {
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
/** @type {render} */
|
|
234
|
+
/** @type {import('ripple/server').render} */
|
|
235
235
|
export async function render(component) {
|
|
236
236
|
const output = new Output(null, null);
|
|
237
237
|
let head = '';
|
|
238
238
|
let body = '';
|
|
239
|
-
let css = new Set();
|
|
239
|
+
let css = /** @type {Set<string>} */ (new Set());
|
|
240
240
|
|
|
241
241
|
// Reset dev-mode element tracking state at the start of each render
|
|
242
242
|
reset_element_state();
|
|
@@ -262,7 +262,7 @@ export async function render(component) {
|
|
|
262
262
|
return { head, body, css };
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
/** @type {renderToStream} */
|
|
265
|
+
/** @type {import('ripple/server').renderToStream} */
|
|
266
266
|
export function renderToStream(component) {
|
|
267
267
|
const stream = new Readable({
|
|
268
268
|
read() {},
|
|
@@ -702,6 +702,24 @@ function tracked(v, get, set) {
|
|
|
702
702
|
return /** @type {Tracked} */ (new TrackedValue(v, get || set ? { get, set } : empty_get_set));
|
|
703
703
|
}
|
|
704
704
|
|
|
705
|
+
/**
|
|
706
|
+
* @param {Record<string, unknown>} obj
|
|
707
|
+
* @param {string[]} exclude_keys
|
|
708
|
+
* @returns {Record<string, unknown>}
|
|
709
|
+
*/
|
|
710
|
+
export function exclude_from_object(obj, exclude_keys) {
|
|
711
|
+
/** @type {Record<string, unknown>} */
|
|
712
|
+
var new_obj = {};
|
|
713
|
+
|
|
714
|
+
for (const key of Object.keys(obj)) {
|
|
715
|
+
if (!exclude_keys.includes(key)) {
|
|
716
|
+
new_obj[key] = obj[key];
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return new_obj;
|
|
721
|
+
}
|
|
722
|
+
|
|
705
723
|
/**
|
|
706
724
|
* @param {any} v
|
|
707
725
|
* @param {(value: any) => any} [get]
|
|
@@ -722,57 +740,6 @@ export function track(v, get, set) {
|
|
|
722
740
|
return tracked(v, get, set);
|
|
723
741
|
}
|
|
724
742
|
|
|
725
|
-
/**
|
|
726
|
-
* @param {Record<string|symbol, any>} v
|
|
727
|
-
* @param {(symbol | string)[]} l
|
|
728
|
-
* @returns {Tracked[]}
|
|
729
|
-
*/
|
|
730
|
-
export function track_split(v, l) {
|
|
731
|
-
var is_tracked = is_ripple_object(v);
|
|
732
|
-
|
|
733
|
-
if (is_tracked || typeof v !== 'object' || v === null || is_array(v)) {
|
|
734
|
-
throw new TypeError('Invalid value: expected a non-tracked object');
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
/** @type {Tracked[]} */
|
|
738
|
-
var out = [];
|
|
739
|
-
/** @type {Record<string|symbol, any>} */
|
|
740
|
-
var rest = {};
|
|
741
|
-
/** @type {Record<PropertyKey, 1>} */
|
|
742
|
-
var done = {};
|
|
743
|
-
var props = Reflect.ownKeys(v);
|
|
744
|
-
|
|
745
|
-
for (let i = 0, key, t; i < l.length; i++) {
|
|
746
|
-
key = l[i];
|
|
747
|
-
|
|
748
|
-
if (props.includes(key)) {
|
|
749
|
-
if (is_ripple_object(v[key])) {
|
|
750
|
-
t = v[key];
|
|
751
|
-
} else {
|
|
752
|
-
t = tracked(undefined);
|
|
753
|
-
t = define_property(t, 'v', /** @type {PropertyDescriptor} */ (get_descriptor(v, key)));
|
|
754
|
-
}
|
|
755
|
-
} else {
|
|
756
|
-
t = tracked(undefined);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
out[i] = t;
|
|
760
|
-
done[key] = 1;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
for (let i = 0, key; i < props.length; i++) {
|
|
764
|
-
key = props[i];
|
|
765
|
-
if (done[key]) {
|
|
766
|
-
continue;
|
|
767
|
-
}
|
|
768
|
-
define_property(rest, key, /** @type {PropertyDescriptor} */ (get_descriptor(v, key)));
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
out.push(tracked(rest));
|
|
772
|
-
|
|
773
|
-
return out;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
743
|
/**
|
|
777
744
|
* @param {any} _
|
|
778
745
|
* @param {ConstructorParameters<typeof URL>} params
|
|
@@ -865,6 +832,15 @@ export function ripple_object(obj) {
|
|
|
865
832
|
return obj;
|
|
866
833
|
}
|
|
867
834
|
|
|
835
|
+
/**
|
|
836
|
+
* @template K, V
|
|
837
|
+
* @param {Iterable<readonly [K, V]>} [iterable]
|
|
838
|
+
* @returns {Map<K, V>}
|
|
839
|
+
*/
|
|
840
|
+
export function ripple_map(iterable) {
|
|
841
|
+
return new Map(iterable);
|
|
842
|
+
}
|
|
843
|
+
|
|
868
844
|
/**
|
|
869
845
|
* Returns the fallback value if the given value is undefined.
|
|
870
846
|
* @template T
|
|
@@ -89,6 +89,34 @@ describe('basic client > errors', () => {
|
|
|
89
89
|
);
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
it('should throw error for calling children as a function', () => {
|
|
93
|
+
const code = `
|
|
94
|
+
export component Layout({ children }) {
|
|
95
|
+
{children()}
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
expect(() => {
|
|
100
|
+
compile(code, 'test.ripple');
|
|
101
|
+
}).toThrow(
|
|
102
|
+
'`children` cannot be called like a regular function. Use element syntax instead, e.g. `<children />` or `<props.children />`.',
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should throw error for calling props.children as a function', () => {
|
|
107
|
+
const code = `
|
|
108
|
+
export component Layout(props) {
|
|
109
|
+
{props.children()}
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
expect(() => {
|
|
114
|
+
compile(code, 'test.ripple');
|
|
115
|
+
}).toThrow(
|
|
116
|
+
'`children` cannot be called like a regular function. Use element syntax instead, e.g. `<children />` or `<props.children />`.',
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
92
120
|
it('errors on mutating tracked value inside computed track() evaluation', () => {
|
|
93
121
|
component Basic() {
|
|
94
122
|
let &[count] = track(0);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PropsWithChildren, Tracked } from 'ripple';
|
|
2
|
-
import { effect, flushSync, track,
|
|
2
|
+
import { effect, flushSync, track, untrack } from 'ripple';
|
|
3
3
|
|
|
4
4
|
describe('basic client > reactivity', () => {
|
|
5
5
|
it('renders multiple reactive lexical blocks', () => {
|
|
@@ -384,162 +384,17 @@ describe('basic client > reactivity', () => {
|
|
|
384
384
|
expect(state.finalValue).toBe(5);
|
|
385
385
|
});
|
|
386
386
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
try {
|
|
393
|
-
const [a, b, rest] = trackSplit(null, ['a', 'b']);
|
|
394
|
-
} catch (e) {
|
|
395
|
-
message = (e as Error).message;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
<pre>{message}</pre>
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
render(App);
|
|
402
|
-
|
|
403
|
-
const pre = container.querySelectorAll('pre')[0];
|
|
404
|
-
expect(pre.textContent).toBe('Invalid value: expected a non-tracked object');
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
it('errors on invalid value as array for track with trackSplit', () => {
|
|
408
|
-
component App() {
|
|
409
|
-
let &[message] = track('');
|
|
410
|
-
|
|
411
|
-
try {
|
|
412
|
-
const [a, b, rest] = trackSplit([1, 2, 3], ['a', 'b']);
|
|
413
|
-
} catch (e) {
|
|
414
|
-
message = (e as Error).message;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
<pre>{message}</pre>
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
render(App);
|
|
421
|
-
|
|
422
|
-
const pre = container.querySelectorAll('pre')[0];
|
|
423
|
-
expect(pre.textContent).toBe('Invalid value: expected a non-tracked object');
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
it('errors on invalid value as tracked for track with trackSplit', () => {
|
|
427
|
-
component App() {
|
|
428
|
-
const t = track({ a: 1, b: 2, c: 3 });
|
|
429
|
-
let &[message] = track('');
|
|
430
|
-
|
|
431
|
-
try {
|
|
432
|
-
const [a, b, rest] = trackSplit(t, ['a', 'b']);
|
|
433
|
-
} catch (e) {
|
|
434
|
-
message = (e as Error).message;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
<pre>{message}</pre>
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
render(App);
|
|
441
|
-
|
|
442
|
-
const pre = container.querySelectorAll('pre')[0];
|
|
443
|
-
expect(pre.textContent).toBe('Invalid value: expected a non-tracked object');
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
it('returns undefined for non-existent props in track with trackSplit', () => {
|
|
447
|
-
component App() {
|
|
448
|
-
const [a, b, rest] = trackSplit({ a: 1, c: 1 }, ['a', 'b']);
|
|
449
|
-
|
|
450
|
-
<pre>{a.value}</pre>
|
|
451
|
-
<pre>{String(b.value)}</pre>
|
|
452
|
-
<pre>{rest.value.c}</pre>
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
render(App);
|
|
456
|
-
|
|
457
|
-
const preA = container.querySelectorAll('pre')[0];
|
|
458
|
-
const preB = container.querySelectorAll('pre')[1];
|
|
459
|
-
const preC = container.querySelectorAll('pre')[2];
|
|
460
|
-
|
|
461
|
-
expect(preA.textContent).toBe('1');
|
|
462
|
-
expect(preB.textContent).toBe('undefined');
|
|
463
|
-
expect(preC.textContent).toBe('1');
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
it('returns the same tracked object if plain track is called with a tracked object', () => {
|
|
467
|
-
component App() {
|
|
468
|
-
const t = track({ a: 1, b: 2, c: 3 });
|
|
469
|
-
const doublet = track(t);
|
|
470
|
-
|
|
471
|
-
<pre>{t === doublet}</pre>
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
render(App);
|
|
475
|
-
|
|
476
|
-
const pre = container.querySelectorAll('pre')[0];
|
|
477
|
-
expect(pre.textContent).toBe('true');
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
it('can retain reactivity for destructure rest via track trackSplit', () => {
|
|
481
|
-
let logs: string[] = [];
|
|
482
|
-
|
|
483
|
-
component App() {
|
|
484
|
-
let &[count] = track(0);
|
|
485
|
-
let &[name] = track('Click Me');
|
|
486
|
-
|
|
487
|
-
function buttonRef(el: HTMLButtonElement) {
|
|
488
|
-
logs.push('ref called');
|
|
489
|
-
return () => {
|
|
490
|
-
logs.push('cleanup ref');
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
<Child
|
|
495
|
-
class="my-button"
|
|
496
|
-
onClick={() => name === 'Click Me' ? name = 'Clicked' : name = 'Click Me'}
|
|
497
|
-
{count}
|
|
498
|
-
{ref buttonRef}
|
|
499
|
-
>
|
|
500
|
-
{name}
|
|
501
|
-
</Child>
|
|
502
|
-
|
|
503
|
-
<button onClick={() => count++}>{'Increment Count'}</button>
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
component Child(props: PropsWithChildren<{
|
|
507
|
-
count: Tracked<number>;
|
|
508
|
-
class: string;
|
|
509
|
-
onClick: () => void;
|
|
510
|
-
}>) {
|
|
511
|
-
const [children, count, rest] = trackSplit(props, ['children', 'count']);
|
|
512
|
-
|
|
513
|
-
if (count.value < 2) {
|
|
514
|
-
<button {...rest.value}>
|
|
515
|
-
<@children />
|
|
516
|
-
</button>
|
|
517
|
-
}
|
|
518
|
-
<pre>{count.value}</pre>
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
render(App);
|
|
522
|
-
flushSync();
|
|
523
|
-
|
|
524
|
-
const buttonClickMe = container.querySelectorAll('button')[0];
|
|
525
|
-
const buttonIncrement = container.querySelectorAll('button')[1];
|
|
526
|
-
const countPre = container.querySelector('pre');
|
|
527
|
-
|
|
528
|
-
expect(buttonClickMe.textContent).toBe('Click Me');
|
|
529
|
-
expect(countPre.textContent).toBe('0');
|
|
530
|
-
expect(logs).toEqual(['ref called']);
|
|
531
|
-
|
|
532
|
-
buttonClickMe.click();
|
|
533
|
-
buttonIncrement.click();
|
|
534
|
-
flushSync();
|
|
387
|
+
it('returns the same tracked object if plain track is called with a tracked object', () => {
|
|
388
|
+
component App() {
|
|
389
|
+
const t = track({ a: 1, b: 2, c: 3 });
|
|
390
|
+
const doublet = track(t);
|
|
535
391
|
|
|
536
|
-
|
|
537
|
-
|
|
392
|
+
<pre>{t === doublet}</pre>
|
|
393
|
+
}
|
|
538
394
|
|
|
539
|
-
|
|
540
|
-
flushSync();
|
|
395
|
+
render(App);
|
|
541
396
|
|
|
542
|
-
|
|
543
|
-
|
|
397
|
+
const pre = container.querySelectorAll('pre')[0];
|
|
398
|
+
expect(pre.textContent).toBe('true');
|
|
544
399
|
});
|
|
545
400
|
});
|
|
@@ -341,18 +341,6 @@ component App() {
|
|
|
341
341
|
});
|
|
342
342
|
|
|
343
343
|
it('keeps lazy destructuring as plain destructuring in to_ts output', () => {
|
|
344
|
-
const track_split_source = `
|
|
345
|
-
import { trackSplit } from 'ripple';
|
|
346
|
-
component App() {
|
|
347
|
-
const source = { a: 1, b: 2, c: 3 };
|
|
348
|
-
let &[a, b, rest] = trackSplit(source, ['a', 'b']);
|
|
349
|
-
const sum = a + b + rest.c;
|
|
350
|
-
}
|
|
351
|
-
`;
|
|
352
|
-
const track_split_result = compile_to_volar_mappings(track_split_source, 'test.ripple').code;
|
|
353
|
-
expect(track_split_result).toContain('let [a, b, rest] = trackSplit(source, [\'a\', \'b\']);');
|
|
354
|
-
expect(track_split_result).not.toContain('let lazy = trackSplit');
|
|
355
|
-
|
|
356
344
|
const track_source = `
|
|
357
345
|
import { track } from 'ripple';
|
|
358
346
|
component App() {
|
|
@@ -369,6 +357,37 @@ component App() {
|
|
|
369
357
|
expect(track_result).not.toContain('lazy0');
|
|
370
358
|
});
|
|
371
359
|
|
|
360
|
+
it('uses tracked fast path for nested lazy params typed as Tracked', () => {
|
|
361
|
+
const source = `
|
|
362
|
+
import type { Tracked } from 'ripple';
|
|
363
|
+
function use_nested({ value: &[count, tracked] }: { value: Tracked<number> }) {
|
|
364
|
+
count++;
|
|
365
|
+
return tracked;
|
|
366
|
+
}
|
|
367
|
+
`;
|
|
368
|
+
const { js } = compile(source, 'tracked-nested-lazy.ripple', { mode: 'client' });
|
|
369
|
+
|
|
370
|
+
// Nested lazy array should still use tracked tuple fast path from outer annotation.
|
|
371
|
+
expect(js.code).toContain('_$_.update(');
|
|
372
|
+
expect(js.code).not.toContain('[0]');
|
|
373
|
+
expect(js.code).not.toContain('[1]');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('uses tracked fast path for nested lazy params at tuple rest positions', () => {
|
|
377
|
+
const source = `
|
|
378
|
+
import type { Tracked } from 'ripple';
|
|
379
|
+
function use_tuple_rest({ value: [head, &[count, tracked]] }: { value: [number, ...Tracked<number>[]] }) {
|
|
380
|
+
count++;
|
|
381
|
+
return tracked;
|
|
382
|
+
}
|
|
383
|
+
`;
|
|
384
|
+
const { js } = compile(source, 'tracked-nested-lazy-tuple-rest.ripple', { mode: 'client' });
|
|
385
|
+
|
|
386
|
+
// Tuple rest element access should resolve to Tracked<number>, not Tracked<number>[].
|
|
387
|
+
expect(js.code).toContain('_$_.update(');
|
|
388
|
+
expect(js.code).not.toContain('[1]');
|
|
389
|
+
});
|
|
390
|
+
|
|
372
391
|
it('preserves generic type args in interface extends for Volar mappings', () => {
|
|
373
392
|
const source = `
|
|
374
393
|
interface PolymorphicProps<T extends keyof HTMLElementTagNameMap> {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Tracked, Props } from 'ripple';
|
|
2
|
-
import { effect, flushSync, track
|
|
2
|
+
import { effect, flushSync, track } from 'ripple';
|
|
3
3
|
|
|
4
4
|
describe('composite > props', () => {
|
|
5
5
|
it('correctly handles default prop values', () => {
|
|
@@ -106,9 +106,8 @@ describe('composite > props', () => {
|
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
it('correctly retains prop accessors and reactivity when using rest props', () => {
|
|
109
|
-
component Button(
|
|
110
|
-
|
|
111
|
-
<button {...rest.value}>
|
|
109
|
+
component Button(&{ children, ...rest }: Props) {
|
|
110
|
+
<button {...rest}>
|
|
112
111
|
<@children />
|
|
113
112
|
</button>
|
|
114
113
|
<style>
|
|
@@ -121,10 +120,9 @@ describe('composite > props', () => {
|
|
|
121
120
|
</style>
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
component Toggle(
|
|
125
|
-
const [pressed, rest] = trackSplit(props, ['pressed']);
|
|
123
|
+
component Toggle(&{ pressed, ...rest }: { pressed: Tracked<boolean> }) {
|
|
126
124
|
const onClick = () => (pressed.value = !pressed.value);
|
|
127
|
-
<Button {...rest
|
|
125
|
+
<Button {...rest} class={pressed.value ? 'on' : 'off'} {onClick}>{'button 1'}</Button>
|
|
128
126
|
<Button class={pressed.value ? 'on' : 'off'} {onClick}>{'button 2'}</Button>
|
|
129
127
|
}
|
|
130
128
|
|