ripple 0.3.56 → 0.3.58
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 +38 -0
- package/package.json +4 -4
- package/src/runtime/internal/client/blocks.js +3 -3
- package/src/runtime/internal/client/events.js +5 -5
- package/src/runtime/internal/client/render.js +2 -5
- package/src/runtime/internal/server/index.js +7 -4
- package/tests/client/basic/basic.hmr.test.tsrx +78 -0
- package/tests/server/lazy-destructuring.test.tsrx +310 -2
- package/tests/utils/runtime-imports.test.js +38 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# ripple
|
|
2
2
|
|
|
3
|
+
## 0.3.58
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1130](https://github.com/Ripple-TS/ripple/pull/1130)
|
|
8
|
+
[`0a5f39b`](https://github.com/Ripple-TS/ripple/commit/0a5f39b6e13807dfd3dc1228f40d7bb02b933373)
|
|
9
|
+
Thanks [@leonidaz](https://github.com/leonidaz)! - Fix client cleanup for
|
|
10
|
+
HMR-wrapped roots that do not own their DOM range directly.
|
|
11
|
+
|
|
12
|
+
- Updated dependencies
|
|
13
|
+
[[`b54fdfc`](https://github.com/Ripple-TS/ripple/commit/b54fdfc3ebfea29ac613307b76732c5bf5f49ab5),
|
|
14
|
+
[`0a5f39b`](https://github.com/Ripple-TS/ripple/commit/0a5f39b6e13807dfd3dc1228f40d7bb02b933373),
|
|
15
|
+
[`165703c`](https://github.com/Ripple-TS/ripple/commit/165703c588b52f3dc0d26c06187f21700d448693)]:
|
|
16
|
+
- @tsrx/core@0.1.8
|
|
17
|
+
- ripple@0.3.58
|
|
18
|
+
- @tsrx/ripple@0.1.8
|
|
19
|
+
|
|
20
|
+
## 0.3.57
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- [#1126](https://github.com/Ripple-TS/ripple/pull/1126)
|
|
25
|
+
[`2b1f746`](https://github.com/Ripple-TS/ripple/commit/2b1f7469ab31713140a5baf912a19fa8eedb9234)
|
|
26
|
+
Thanks [@leonidaz](https://github.com/leonidaz)! - Keep runtime helper imports
|
|
27
|
+
on namespaced runtime subpaths so production app bundles do not pull in
|
|
28
|
+
compiler-only modules.
|
|
29
|
+
|
|
30
|
+
- [#1123](https://github.com/Ripple-TS/ripple/pull/1123)
|
|
31
|
+
[`e4a04dd`](https://github.com/Ripple-TS/ripple/commit/e4a04ddb4bbc8e21a9c7c2c65b179d764b72e4fb)
|
|
32
|
+
Thanks [@leonidaz](https://github.com/leonidaz)! - Nested lazy destructuring
|
|
33
|
+
support for all tsrx targets. Ripple already fully supported it.
|
|
34
|
+
- Updated dependencies
|
|
35
|
+
[[`2b1f746`](https://github.com/Ripple-TS/ripple/commit/2b1f7469ab31713140a5baf912a19fa8eedb9234),
|
|
36
|
+
[`e4a04dd`](https://github.com/Ripple-TS/ripple/commit/e4a04ddb4bbc8e21a9c7c2c65b179d764b72e4fb)]:
|
|
37
|
+
- @tsrx/core@0.1.7
|
|
38
|
+
- ripple@0.3.57
|
|
39
|
+
- @tsrx/ripple@0.1.7
|
|
40
|
+
|
|
3
41
|
## 0.3.56
|
|
4
42
|
|
|
5
43
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Ripple is an elegant TypeScript UI framework",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.58",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
"esm-env": "^1.2.2",
|
|
77
77
|
"@types/estree": "^1.0.8",
|
|
78
78
|
"@types/estree-jsx": "^1.0.5",
|
|
79
|
-
"@tsrx/core": "0.1.
|
|
80
|
-
"@tsrx/ripple": "0.1.
|
|
79
|
+
"@tsrx/core": "0.1.8",
|
|
80
|
+
"@tsrx/ripple": "0.1.8"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@types/node": "^24.3.0",
|
|
@@ -87,6 +87,6 @@
|
|
|
87
87
|
"vscode-languageserver-types": "^3.17.5"
|
|
88
88
|
},
|
|
89
89
|
"peerDependencies": {
|
|
90
|
-
"ripple": "0.3.
|
|
90
|
+
"ripple": "0.3.58"
|
|
91
91
|
}
|
|
92
92
|
}
|
|
@@ -188,7 +188,7 @@ export function root(fn, compat) {
|
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
return block(ROOT_BLOCK, target_fn, { compat }, create_component_ctx());
|
|
191
|
+
return block(ROOT_BLOCK, target_fn, { compat, start: null, end: null }, create_component_ctx());
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
/**
|
|
@@ -274,7 +274,7 @@ export function destroy_block_children(parent, remove_dom = false) {
|
|
|
274
274
|
var block = parent.first;
|
|
275
275
|
parent.first = parent.last = null;
|
|
276
276
|
|
|
277
|
-
if ((parent.f & CONTAINS_TEARDOWN) !== 0) {
|
|
277
|
+
if (remove_dom || (parent.f & CONTAINS_TEARDOWN) !== 0) {
|
|
278
278
|
while (block !== null) {
|
|
279
279
|
var next = block.next;
|
|
280
280
|
destroy_block(block, remove_dom);
|
|
@@ -456,7 +456,7 @@ export function destroy_block(block, remove_dom = true) {
|
|
|
456
456
|
(f & HEAD_BLOCK) !== 0
|
|
457
457
|
) {
|
|
458
458
|
var s = block.s;
|
|
459
|
-
if (s !== null) {
|
|
459
|
+
if (s !== null && s.start !== null) {
|
|
460
460
|
remove_block_dom(s.start, s.end);
|
|
461
461
|
removed = true;
|
|
462
462
|
}
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* @typedef {EventTarget & Record<string, any>} DelegatedEventTarget
|
|
4
4
|
*/
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from '@tsrx/core';
|
|
6
|
+
event_name_from_capture,
|
|
7
|
+
is_capture_event,
|
|
8
|
+
is_non_delegated,
|
|
9
|
+
is_passive_event,
|
|
10
|
+
} from '@tsrx/core/runtime/events';
|
|
11
11
|
import {
|
|
12
12
|
active_block,
|
|
13
13
|
active_reaction,
|
|
@@ -10,13 +10,10 @@ import {
|
|
|
10
10
|
get_prototype_of,
|
|
11
11
|
} from '@tsrx/core/runtime/language-helpers';
|
|
12
12
|
import { event } from './events.js';
|
|
13
|
-
import {
|
|
14
|
-
getAttributeEventName as get_attribute_event_name,
|
|
15
|
-
isEventAttribute as is_event_attribute,
|
|
16
|
-
} from '@tsrx/core';
|
|
13
|
+
import { get_attribute_event_name, is_event_attribute } from '@tsrx/core/runtime/events';
|
|
17
14
|
import { get } from './runtime.js';
|
|
18
15
|
import { clsx } from 'clsx';
|
|
19
|
-
import {
|
|
16
|
+
import { normalize_css_property_name } from '@tsrx/core/runtime/html';
|
|
20
17
|
|
|
21
18
|
/**
|
|
22
19
|
* @param {Text} text
|
|
@@ -29,10 +29,13 @@ import {
|
|
|
29
29
|
import { DEV } from 'esm-env';
|
|
30
30
|
import { is_ripple_object } from '../client/utils.js';
|
|
31
31
|
import { array_slice } from '@tsrx/core/runtime/language-helpers';
|
|
32
|
-
import {
|
|
33
|
-
|
|
32
|
+
import {
|
|
33
|
+
escape,
|
|
34
|
+
escape_script,
|
|
35
|
+
is_boolean_attribute,
|
|
36
|
+
normalize_css_property_name,
|
|
37
|
+
} from '@tsrx/core/runtime/html';
|
|
34
38
|
import { clsx } from 'clsx';
|
|
35
|
-
import { normalizeCssPropertyName as normalize_css_property_name } from '@tsrx/core';
|
|
36
39
|
import { create_ref_prop } from '@tsrx/core/runtime/ref';
|
|
37
40
|
import { BLOCK_CLOSE, BLOCK_OPEN } from '../../../constants.js';
|
|
38
41
|
import { is_tsrx_element, normalize_children, tsrx_element } from '../../element.js';
|
|
@@ -53,7 +56,7 @@ import { COMPONENT_BLOCK, TRY_BLOCK } from './constants.js';
|
|
|
53
56
|
|
|
54
57
|
export { escape };
|
|
55
58
|
export { register_component_css as register_css } from './css-registry.js';
|
|
56
|
-
export {
|
|
59
|
+
export { simple_hash, strong_hash } from '@tsrx/core/runtime/hash';
|
|
57
60
|
export { context } from './context.js';
|
|
58
61
|
export { try_block, component_block, regular_block } from './blocks.js';
|
|
59
62
|
export { array_slice };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HMR } from '../../../src/runtime/internal/client/constants.js';
|
|
2
2
|
import { hmr } from '../../../src/runtime/internal/client/hmr.js';
|
|
3
|
+
import { effect, flushSync, mount } from 'ripple';
|
|
3
4
|
|
|
4
5
|
describe('basic client > hmr', () => {
|
|
5
6
|
it('handles updates before first render', () => {
|
|
@@ -16,4 +17,81 @@ describe('basic client > hmr', () => {
|
|
|
16
17
|
expect(wrapper[HMR].current).toBeUndefined();
|
|
17
18
|
expect(wrapper[HMR].fn).toBe(updated_component);
|
|
18
19
|
});
|
|
20
|
+
|
|
21
|
+
it('removes DOM when an HMR-wrapped root is unmounted', () => {
|
|
22
|
+
component App() {
|
|
23
|
+
<div class="hmr-root">{'hmr root'}</div>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const cleanup = mount(hmr(App), { target: container });
|
|
27
|
+
flushSync();
|
|
28
|
+
|
|
29
|
+
expect(container.querySelector('.hmr-root')?.textContent).toBe('hmr root');
|
|
30
|
+
|
|
31
|
+
cleanup();
|
|
32
|
+
|
|
33
|
+
expect(container.querySelector('.hmr-root')).toBeNull();
|
|
34
|
+
expect(container.innerHTML).toBe('');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('runs child teardowns when an HMR-wrapped root is unmounted', () => {
|
|
38
|
+
let teardown_count = 0;
|
|
39
|
+
|
|
40
|
+
component Child() {
|
|
41
|
+
effect(() => {
|
|
42
|
+
return () => {
|
|
43
|
+
teardown_count++;
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
<span class="hmr-child">{'child'}</span>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
component App() {
|
|
51
|
+
<Child />
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const cleanup = mount(hmr(App), { target: container });
|
|
55
|
+
flushSync();
|
|
56
|
+
|
|
57
|
+
expect(container.querySelector('.hmr-child')?.textContent).toBe('child');
|
|
58
|
+
expect(teardown_count).toBe(0);
|
|
59
|
+
|
|
60
|
+
cleanup();
|
|
61
|
+
|
|
62
|
+
expect(container.querySelector('.hmr-child')).toBeNull();
|
|
63
|
+
expect(teardown_count).toBe(1);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('does not double-remove child DOM when the root owns the DOM range', () => {
|
|
67
|
+
let remove_count = 0;
|
|
68
|
+
const original_remove = Element.prototype.remove;
|
|
69
|
+
|
|
70
|
+
Element.prototype.remove = function () {
|
|
71
|
+
if (this.classList.contains('owned-root')) {
|
|
72
|
+
remove_count++;
|
|
73
|
+
}
|
|
74
|
+
return original_remove.call(this);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
component App() {
|
|
79
|
+
<div class="owned-root">
|
|
80
|
+
<span>{'child'}</span>
|
|
81
|
+
</div>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const cleanup = mount(App, { target: container });
|
|
85
|
+
flushSync();
|
|
86
|
+
|
|
87
|
+
expect(container.querySelector('.owned-root')).not.toBeNull();
|
|
88
|
+
|
|
89
|
+
cleanup();
|
|
90
|
+
|
|
91
|
+
expect(container.querySelector('.owned-root')).toBeNull();
|
|
92
|
+
expect(remove_count).toBe(1);
|
|
93
|
+
} finally {
|
|
94
|
+
Element.prototype.remove = original_remove;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
19
97
|
});
|
|
@@ -140,7 +140,11 @@ describe('lazy destructuring', () => {
|
|
|
140
140
|
it(
|
|
141
141
|
'preserves lazy getter/setter behavior for RestElement nested destructuring in non-lazy component params',
|
|
142
142
|
async () => {
|
|
143
|
-
component Inner({
|
|
143
|
+
component Inner({
|
|
144
|
+
values: [head, ...&{ 0: first_rest, length: rest_length }],
|
|
145
|
+
}: {
|
|
146
|
+
values: number[];
|
|
147
|
+
}) {
|
|
144
148
|
const before = `${first_rest}-${rest_length}`;
|
|
145
149
|
rest_length = 0;
|
|
146
150
|
<pre>{`${head}-${before}-${first_rest}-${rest_length}`}</pre>
|
|
@@ -159,7 +163,11 @@ describe('lazy destructuring', () => {
|
|
|
159
163
|
'preserves lazy getter/setter behavior for RestElement nested destructuring in non-lazy function params',
|
|
160
164
|
async () => {
|
|
161
165
|
component Test() {
|
|
162
|
-
function getInfo({
|
|
166
|
+
function getInfo({
|
|
167
|
+
values: [head, ...&{ 0: first_rest, length: rest_length }],
|
|
168
|
+
}: {
|
|
169
|
+
values: number[];
|
|
170
|
+
}) {
|
|
163
171
|
const before = `${first_rest}-${rest_length}`;
|
|
164
172
|
rest_length = 0;
|
|
165
173
|
return `${head}-${before}-${first_rest}-${rest_length}`;
|
|
@@ -228,4 +236,304 @@ describe('lazy destructuring', () => {
|
|
|
228
236
|
const { body } = await render(Test);
|
|
229
237
|
expect(body).toBeHtml('<pre>10-20</pre>');
|
|
230
238
|
});
|
|
239
|
+
|
|
240
|
+
describe('nested lazy destructuring', () => {
|
|
241
|
+
it('preserves nested lazy object access inside lazy object as component params', async () => {
|
|
242
|
+
let inner_value = 7;
|
|
243
|
+
|
|
244
|
+
component Inner(&{ outer: &{ inner } }: { outer: { inner: number } }) {
|
|
245
|
+
const before = inner;
|
|
246
|
+
inner_value = 8;
|
|
247
|
+
<pre>{`${before}-${inner}`}</pre>
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
component Test() {
|
|
251
|
+
const outer = {
|
|
252
|
+
get inner() {
|
|
253
|
+
return inner_value;
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
<Inner {outer} />
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const { body } = await render(Test);
|
|
260
|
+
expect(body).toBeHtml('<pre>7-8</pre>');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('preserves nested lazy array access inside regular object as component params', async () => {
|
|
264
|
+
let first_value = 3;
|
|
265
|
+
let second_value = 4;
|
|
266
|
+
|
|
267
|
+
component Inner({ pair: &[first, second] }: { pair: [number, number] }) {
|
|
268
|
+
const before = `${first}-${second}`;
|
|
269
|
+
first_value = 5;
|
|
270
|
+
second_value = 6;
|
|
271
|
+
<pre>{`${before}-${first}-${second}`}</pre>
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
component Test() {
|
|
275
|
+
const pair = [0, 0] as [number, number];
|
|
276
|
+
Object.defineProperty(pair, 0, { get: () => first_value });
|
|
277
|
+
Object.defineProperty(pair, 1, { get: () => second_value });
|
|
278
|
+
<Inner {pair} />
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const { body } = await render(Test);
|
|
282
|
+
expect(body).toBeHtml('<pre>3-4-5-6</pre>');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('preserves nested lazy object access inside lazy array as function params', async () => {
|
|
286
|
+
component Test() {
|
|
287
|
+
let name_value = 'Alice';
|
|
288
|
+
function getName(&[&{ name }]: [{ name: string }]) {
|
|
289
|
+
const before = name;
|
|
290
|
+
name_value = 'Bob';
|
|
291
|
+
return `${before}-${name}`;
|
|
292
|
+
}
|
|
293
|
+
const user = {
|
|
294
|
+
get name() {
|
|
295
|
+
return name_value;
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
<pre>{getName([user])}</pre>
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const { body } = await render(Test);
|
|
302
|
+
expect(body).toBeHtml('<pre>Alice-Bob</pre>');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('preserves nested lazy array access inside lazy array as function params', async () => {
|
|
306
|
+
component Test() {
|
|
307
|
+
let first_value = 5;
|
|
308
|
+
let second_value = 6;
|
|
309
|
+
function getValues(&[&[a, b]]: [[number, number]]) {
|
|
310
|
+
const before = `${a}-${b}`;
|
|
311
|
+
first_value = 7;
|
|
312
|
+
second_value = 8;
|
|
313
|
+
return `${before}-${a}-${b}`;
|
|
314
|
+
}
|
|
315
|
+
const pair = [0, 0] as [number, number];
|
|
316
|
+
Object.defineProperty(pair, 0, { get: () => first_value });
|
|
317
|
+
Object.defineProperty(pair, 1, { get: () => second_value });
|
|
318
|
+
<pre>{getValues([pair])}</pre>
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const { body } = await render(Test);
|
|
322
|
+
expect(body).toBeHtml('<pre>5-6-7-8</pre>');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('preserves three-level lazy object access as component params', async () => {
|
|
326
|
+
let c_value = 42;
|
|
327
|
+
|
|
328
|
+
component Inner(&{ a: &{ b: &{ c } } }: { a: { b: { c: number } } }) {
|
|
329
|
+
const before = c;
|
|
330
|
+
c_value = 43;
|
|
331
|
+
<pre>{`${before}-${c}`}</pre>
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
component Test() {
|
|
335
|
+
const a = {
|
|
336
|
+
b: {
|
|
337
|
+
get c() {
|
|
338
|
+
return c_value;
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
<Inner {a} />
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const { body } = await render(Test);
|
|
346
|
+
expect(body).toBeHtml('<pre>42-43</pre>');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('preserves nested lazy object access inside lazy object as function params', async () => {
|
|
350
|
+
component Test() {
|
|
351
|
+
let inner_value = 11;
|
|
352
|
+
function getValue(&{ outer: &{ inner } }: { outer: { inner: number } }) {
|
|
353
|
+
const before = inner;
|
|
354
|
+
inner_value = 12;
|
|
355
|
+
return `${before}-${inner}`;
|
|
356
|
+
}
|
|
357
|
+
const outer = {
|
|
358
|
+
get inner() {
|
|
359
|
+
return inner_value;
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
<pre>{getValue({ outer })}</pre>
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const { body } = await render(Test);
|
|
366
|
+
expect(body).toBeHtml('<pre>11-12</pre>');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it(
|
|
370
|
+
'supports nested lazy array inside lazy object as function params with writeback',
|
|
371
|
+
async () => {
|
|
372
|
+
component Test() {
|
|
373
|
+
const obj = { pair: [1, 2] as [number, number] };
|
|
374
|
+
function bump(&{ pair: &[first, second] }: { pair: [number, number] }) {
|
|
375
|
+
first = first + 10;
|
|
376
|
+
second = second + 20;
|
|
377
|
+
}
|
|
378
|
+
bump(obj);
|
|
379
|
+
<pre>{`${obj.pair[0]}-${obj.pair[1]}`}</pre>
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const { body } = await render(Test);
|
|
383
|
+
expect(body).toBeHtml('<pre>11-22</pre>');
|
|
384
|
+
},
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
it('preserves three-level lazy object access as function params', async () => {
|
|
388
|
+
component Test() {
|
|
389
|
+
let c_value = 99;
|
|
390
|
+
function getValue(&{ a: &{ b: &{ c } } }: { a: { b: { c: number } } }) {
|
|
391
|
+
const before = c;
|
|
392
|
+
c_value = 100;
|
|
393
|
+
return `${before}-${c}`;
|
|
394
|
+
}
|
|
395
|
+
const a = {
|
|
396
|
+
b: {
|
|
397
|
+
get c() {
|
|
398
|
+
return c_value;
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
<pre>{getValue({ a })}</pre>
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const { body } = await render(Test);
|
|
406
|
+
expect(body).toBeHtml('<pre>99-100</pre>');
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('preserves nested lazy object access inside lazy object in const declaration', async () => {
|
|
410
|
+
component Test() {
|
|
411
|
+
let inner_value = 5;
|
|
412
|
+
const data = {
|
|
413
|
+
outer: {
|
|
414
|
+
get inner() {
|
|
415
|
+
return inner_value;
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
const &{ outer: &{ inner } } = data;
|
|
420
|
+
const before = inner;
|
|
421
|
+
inner_value = 6;
|
|
422
|
+
<pre>{`${before}-${inner}`}</pre>
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const { body } = await render(Test);
|
|
426
|
+
expect(body).toBeHtml('<pre>5-6</pre>');
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('supports nested lazy object inside lazy object in let with writeback', async () => {
|
|
430
|
+
component Test() {
|
|
431
|
+
const data = { outer: { inner: 5 } };
|
|
432
|
+
let &{ outer: &{ inner } } = data;
|
|
433
|
+
inner = 50;
|
|
434
|
+
<pre>{data.outer.inner}</pre>
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const { body } = await render(Test);
|
|
438
|
+
expect(body).toBeHtml('<pre>50</pre>');
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('supports nested lazy array inside lazy object in let with writeback', async () => {
|
|
442
|
+
component Test() {
|
|
443
|
+
const data = { pair: [1, 2] as [number, number] };
|
|
444
|
+
let &{ pair: &[first, second] } = data;
|
|
445
|
+
first = 100;
|
|
446
|
+
second = 200;
|
|
447
|
+
<pre>{`${data.pair[0]}-${data.pair[1]}`}</pre>
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const { body } = await render(Test);
|
|
451
|
+
expect(body).toBeHtml('<pre>100-200</pre>');
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it('supports three-level lazy object nesting in let with writeback', async () => {
|
|
455
|
+
component Test() {
|
|
456
|
+
const data = { a: { b: { c: 1 } } };
|
|
457
|
+
let &{ a: &{ b: &{ c } } } = data;
|
|
458
|
+
c = 999;
|
|
459
|
+
<pre>{data.a.b.c}</pre>
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const { body } = await render(Test);
|
|
463
|
+
expect(body).toBeHtml('<pre>999</pre>');
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('supports compound assignment on deeply nested lazy bindings', async () => {
|
|
467
|
+
component Test() {
|
|
468
|
+
const data = { a: { b: { c: 5 } } };
|
|
469
|
+
let &{ a: &{ b: &{ c } } } = data;
|
|
470
|
+
c += 10;
|
|
471
|
+
c *= 2;
|
|
472
|
+
<pre>{data.a.b.c}</pre>
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const { body } = await render(Test);
|
|
476
|
+
expect(body).toBeHtml('<pre>30</pre>');
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('preserves default values inside nested lazy destructuring', async () => {
|
|
480
|
+
component Test() {
|
|
481
|
+
let inner_value: number | undefined;
|
|
482
|
+
const data: { outer: { inner?: number } } = {
|
|
483
|
+
outer: {
|
|
484
|
+
get inner() {
|
|
485
|
+
return inner_value;
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
const &{ outer: &{ inner = 42 } } = data;
|
|
490
|
+
const before = inner;
|
|
491
|
+
inner_value = 43;
|
|
492
|
+
<pre>{`${before}-${inner}`}</pre>
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const { body } = await render(Test);
|
|
496
|
+
expect(body).toBeHtml('<pre>42-43</pre>');
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('preserves multiple sibling nested lazy destructures', async () => {
|
|
500
|
+
component Test() {
|
|
501
|
+
let x_value = 1;
|
|
502
|
+
let y_value = 2;
|
|
503
|
+
const data = {
|
|
504
|
+
a: {
|
|
505
|
+
get x() {
|
|
506
|
+
return x_value;
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
b: {
|
|
510
|
+
get y() {
|
|
511
|
+
return y_value;
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
};
|
|
515
|
+
const &{ a: &{ x }, b: &{ y } } = data;
|
|
516
|
+
const before = `${x}-${y}`;
|
|
517
|
+
x_value = 3;
|
|
518
|
+
y_value = 4;
|
|
519
|
+
<pre>{`${before}-${x}-${y}`}</pre>
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const { body } = await render(Test);
|
|
523
|
+
expect(body).toBeHtml('<pre>1-2-3-4</pre>');
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('supports multiple sibling nested lazy destructures with writeback', async () => {
|
|
527
|
+
component Test() {
|
|
528
|
+
const data = { a: { x: 1 }, b: { y: 2 } };
|
|
529
|
+
let &{ a: &{ x }, b: &{ y } } = data;
|
|
530
|
+
x = 11;
|
|
531
|
+
y = 22;
|
|
532
|
+
<pre>{`${data.a.x}-${data.b.y}`}</pre>
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const { body } = await render(Test);
|
|
536
|
+
expect(body).toBeHtml('<pre>11-22</pre>');
|
|
537
|
+
});
|
|
538
|
+
});
|
|
231
539
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
const runtime_dir = new URL('../../src/runtime/', import.meta.url);
|
|
6
|
+
|
|
7
|
+
/** @param {string} dir */
|
|
8
|
+
async function get_js_files(dir) {
|
|
9
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
10
|
+
const files = [];
|
|
11
|
+
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
const path = join(dir, entry.name);
|
|
14
|
+
if (entry.isDirectory()) {
|
|
15
|
+
files.push(...(await get_js_files(path)));
|
|
16
|
+
} else if (entry.isFile() && entry.name.endsWith('.js')) {
|
|
17
|
+
files.push(path);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return files;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('runtime imports', () => {
|
|
25
|
+
it('does not import the @tsrx/core compiler barrel', async () => {
|
|
26
|
+
const files = await get_js_files(runtime_dir.pathname);
|
|
27
|
+
const barrel_imports = [];
|
|
28
|
+
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
const source = await readFile(file, 'utf8');
|
|
31
|
+
if (/from\s+['"]@tsrx\/core['"]/.test(source)) {
|
|
32
|
+
barrel_imports.push(file);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
expect(barrel_imports).toEqual([]);
|
|
37
|
+
});
|
|
38
|
+
});
|