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
|
@@ -166,18 +166,18 @@ describe('for statements', () => {
|
|
|
166
166
|
|
|
167
167
|
it('correctly handles keyed for...of loops', () => {
|
|
168
168
|
function App() @{
|
|
169
|
-
|
|
169
|
+
const items = track([
|
|
170
170
|
{ id: 1, text: 'Item 1' },
|
|
171
171
|
{ id: 2, text: 'Item 2' },
|
|
172
172
|
{ id: 3, text: 'Item 3' },
|
|
173
173
|
]);
|
|
174
174
|
<>
|
|
175
|
-
@for (let item of items; index i; key item.id) {
|
|
175
|
+
@for (let item of items.value; index i; key item.id) {
|
|
176
176
|
<div>{i + ':' + item.text}</div>
|
|
177
177
|
}
|
|
178
178
|
<button
|
|
179
179
|
onClick={() => {
|
|
180
|
-
items = items.toReversed();
|
|
180
|
+
items.value = items.value.toReversed();
|
|
181
181
|
}}
|
|
182
182
|
>{'Reverse'}</button>
|
|
183
183
|
</>
|
|
@@ -195,22 +195,63 @@ describe('for statements', () => {
|
|
|
195
195
|
expect(container).toMatchSnapshot();
|
|
196
196
|
});
|
|
197
197
|
|
|
198
|
+
it('keyed by identity re-reads a plain array mutated in place', () => {
|
|
199
|
+
const list = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
|
200
|
+
let bump = () => {};
|
|
201
|
+
|
|
202
|
+
function App() @{
|
|
203
|
+
const version = track(0);
|
|
204
|
+
bump = () => {
|
|
205
|
+
version.value = version.value + 1;
|
|
206
|
+
};
|
|
207
|
+
<ul>
|
|
208
|
+
@for (const item of (version.value, list); key item) {
|
|
209
|
+
<li>{item.id}</li>
|
|
210
|
+
}
|
|
211
|
+
</ul>
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
render(App);
|
|
215
|
+
const texts = () => Array.from(container.querySelectorAll('li'), (el) => el.textContent);
|
|
216
|
+
expect(texts()).toEqual(['1', '2', '3']);
|
|
217
|
+
|
|
218
|
+
list.push({ id: 4 });
|
|
219
|
+
bump();
|
|
220
|
+
flushSync();
|
|
221
|
+
expect(texts()).toEqual(['1', '2', '3', '4']);
|
|
222
|
+
|
|
223
|
+
list.splice(0, 1);
|
|
224
|
+
bump();
|
|
225
|
+
flushSync();
|
|
226
|
+
expect(texts()).toEqual(['2', '3', '4']);
|
|
227
|
+
|
|
228
|
+
list.reverse();
|
|
229
|
+
bump();
|
|
230
|
+
flushSync();
|
|
231
|
+
expect(texts()).toEqual(['4', '3', '2']);
|
|
232
|
+
|
|
233
|
+
list.splice(1, 1, { id: 5 });
|
|
234
|
+
bump();
|
|
235
|
+
flushSync();
|
|
236
|
+
expect(texts()).toEqual(['4', '5', '2']);
|
|
237
|
+
});
|
|
238
|
+
|
|
198
239
|
it('keyed for over derived updates sibling text nodes', () => {
|
|
199
240
|
function App() @{
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
() => Array.from({ length: count }).map((_, id) => ({ id, label: `Item ${id}` })),
|
|
241
|
+
const count = track(0);
|
|
242
|
+
const items = track(
|
|
243
|
+
() => Array.from({ length: count.value }).map((_, id) => ({ id, label: `Item ${id}` })),
|
|
203
244
|
);
|
|
204
245
|
<>
|
|
205
246
|
<button
|
|
206
247
|
onClick={() => {
|
|
207
|
-
count++;
|
|
248
|
+
count.value++;
|
|
208
249
|
}}
|
|
209
250
|
>{'Add'}</button>
|
|
210
|
-
@for (const item of items; key item.id) {
|
|
251
|
+
@for (const item of items.value; key item.id) {
|
|
211
252
|
<div class="item">{item.label}</div>
|
|
212
253
|
}
|
|
213
|
-
<p class="count">{count}</p>
|
|
254
|
+
<p class="count">{count.value}</p>
|
|
214
255
|
</>
|
|
215
256
|
}
|
|
216
257
|
|
|
@@ -236,18 +277,18 @@ describe('for statements', () => {
|
|
|
236
277
|
|
|
237
278
|
it('keyed for inserts multiple new items in the middle in correct order', () => {
|
|
238
279
|
function App() @{
|
|
239
|
-
|
|
280
|
+
const items = track([
|
|
240
281
|
{ id: 'a', text: 'A' },
|
|
241
282
|
{ id: 'c', text: 'C' },
|
|
242
283
|
{ id: 'd', text: 'D' },
|
|
243
284
|
]);
|
|
244
285
|
<>
|
|
245
|
-
@for (const item of items; key item.id) {
|
|
286
|
+
@for (const item of items.value; key item.id) {
|
|
246
287
|
<div class="item">{item.text}</div>
|
|
247
288
|
}
|
|
248
289
|
<button
|
|
249
290
|
onClick={() => {
|
|
250
|
-
items = [
|
|
291
|
+
items.value = [
|
|
251
292
|
{ id: 'a', text: 'A' },
|
|
252
293
|
{ id: 'b1', text: 'B1' },
|
|
253
294
|
{ id: 'b2', text: 'B2' },
|
|
@@ -279,14 +320,14 @@ describe('for statements', () => {
|
|
|
279
320
|
const d = { text: 'D' };
|
|
280
321
|
|
|
281
322
|
function App() @{
|
|
282
|
-
|
|
323
|
+
const items = track([a, c, d]);
|
|
283
324
|
<>
|
|
284
|
-
@for (const item of items) {
|
|
325
|
+
@for (const item of items.value) {
|
|
285
326
|
<div class="item">{item.text}</div>
|
|
286
327
|
}
|
|
287
328
|
<button
|
|
288
329
|
onClick={() => {
|
|
289
|
-
items = [a, { text: 'B1' }, { text: 'B2' }, c, d];
|
|
330
|
+
items.value = [a, { text: 'B1' }, { text: 'B2' }, c, d];
|
|
290
331
|
}}
|
|
291
332
|
>{'Insert'}</button>
|
|
292
333
|
</>
|
|
@@ -308,16 +349,16 @@ describe('for statements', () => {
|
|
|
308
349
|
|
|
309
350
|
it('keyed for with 32+ items: full reversal updates values via Map path', () => {
|
|
310
351
|
function App() @{
|
|
311
|
-
|
|
352
|
+
const items = track(Array.from({ length: 40 }, (_, i) => ({ id: i, text: `Item ${i}` })));
|
|
312
353
|
<>
|
|
313
354
|
<div>
|
|
314
|
-
@for (let item of items; index idx; key item.id) {
|
|
355
|
+
@for (let item of items.value; index idx; key item.id) {
|
|
315
356
|
<span class="item">{idx + ':' + item.text}</span>
|
|
316
357
|
}
|
|
317
358
|
</div>
|
|
318
359
|
<button
|
|
319
360
|
onClick={() => {
|
|
320
|
-
items = items.toReversed();
|
|
361
|
+
items.value = items.value.toReversed();
|
|
321
362
|
}}
|
|
322
363
|
>{'Reverse'}</button>
|
|
323
364
|
</>
|
|
@@ -339,25 +380,25 @@ describe('for statements', () => {
|
|
|
339
380
|
|
|
340
381
|
it('handles updating with new objects with same key', () => {
|
|
341
382
|
function App() @{
|
|
342
|
-
|
|
383
|
+
const items = track([
|
|
343
384
|
{ id: 1, text: 'Item 1' },
|
|
344
385
|
{ id: 2, text: 'Item 2' },
|
|
345
386
|
{ id: 3, text: 'Item 3' },
|
|
346
387
|
]);
|
|
347
388
|
<>
|
|
348
|
-
@for (let item of items; index i; key item.id) {
|
|
389
|
+
@for (let item of items.value; index i; key item.id) {
|
|
349
390
|
<div>{i + ':' + item.text}</div>
|
|
350
391
|
}
|
|
351
392
|
<button
|
|
352
393
|
onClick={() => {
|
|
353
|
-
items[0].id = 3;
|
|
354
|
-
items[1].id = 2;
|
|
355
|
-
items[2].id = 1;
|
|
356
|
-
|
|
357
|
-
items = [
|
|
358
|
-
{ ...items[0], text: 'Item 1!' },
|
|
359
|
-
{ ...items[1], text: 'Item 2!' },
|
|
360
|
-
{ ...items[2], text: 'Item 3!' },
|
|
394
|
+
items.value[0].id = 3;
|
|
395
|
+
items.value[1].id = 2;
|
|
396
|
+
items.value[2].id = 1;
|
|
397
|
+
|
|
398
|
+
items.value = [
|
|
399
|
+
{ ...items.value[0], text: 'Item 1!' },
|
|
400
|
+
{ ...items.value[1], text: 'Item 2!' },
|
|
401
|
+
{ ...items.value[2], text: 'Item 3!' },
|
|
361
402
|
];
|
|
362
403
|
}}
|
|
363
404
|
>{'Reverse'}</button>
|
|
@@ -378,16 +419,16 @@ describe('for statements', () => {
|
|
|
378
419
|
const objects = Array.from({ length: 50 }, (_, i) => ({ id: i, text: `Obj ${i}` }));
|
|
379
420
|
|
|
380
421
|
function App() @{
|
|
381
|
-
|
|
422
|
+
const items = track(objects.slice());
|
|
382
423
|
<>
|
|
383
424
|
<div>
|
|
384
|
-
@for (const item of items) {
|
|
425
|
+
@for (const item of items.value) {
|
|
385
426
|
<span class="item">{item.text}</span>
|
|
386
427
|
}
|
|
387
428
|
</div>
|
|
388
429
|
<button
|
|
389
430
|
onClick={() => {
|
|
390
|
-
items = objects.slice(15).reverse();
|
|
431
|
+
items.value = objects.slice(15).reverse();
|
|
391
432
|
}}
|
|
392
433
|
>{'Trim and reverse'}</button>
|
|
393
434
|
</>
|
|
@@ -406,4 +447,264 @@ describe('for statements', () => {
|
|
|
406
447
|
|
|
407
448
|
expect(getTexts()).toEqual(objects.slice(15).reverse().map((o) => o.text));
|
|
408
449
|
});
|
|
450
|
+
|
|
451
|
+
it('renders the index and the key from a function that returns its template', () => {
|
|
452
|
+
function Plain({ items }) {
|
|
453
|
+
return <ul>
|
|
454
|
+
@for (const item of items) {
|
|
455
|
+
<li class="plain">{item.text}</li>
|
|
456
|
+
}
|
|
457
|
+
</ul>;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function Indexed({ items }) {
|
|
461
|
+
return <ul>
|
|
462
|
+
@for (const item of items; index i) {
|
|
463
|
+
<li class="indexed">{i + ': ' + item.text}</li>
|
|
464
|
+
}
|
|
465
|
+
</ul>;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function Keyed({ items }) {
|
|
469
|
+
return <ul>
|
|
470
|
+
@for (const item of items; key item.id) {
|
|
471
|
+
<li class="keyed">{item.text}</li>
|
|
472
|
+
}
|
|
473
|
+
</ul>;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function IndexedAndKeyed({ items }) {
|
|
477
|
+
return <ul>
|
|
478
|
+
@for (const item of items; index i; key item.id) {
|
|
479
|
+
<li class="indexed-keyed">{i + ': ' + item.text}</li>
|
|
480
|
+
}
|
|
481
|
+
</ul>;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const Arrow = ({ items }) => <ul>
|
|
485
|
+
@for (const item of items; index i) {
|
|
486
|
+
<li class="arrow">{i + ': ' + item.text}</li>
|
|
487
|
+
}
|
|
488
|
+
</ul>;
|
|
489
|
+
|
|
490
|
+
function App() @{
|
|
491
|
+
const items = [
|
|
492
|
+
{ id: 1, text: 'a' },
|
|
493
|
+
{ id: 2, text: 'b' },
|
|
494
|
+
];
|
|
495
|
+
<>
|
|
496
|
+
<Plain {items} />
|
|
497
|
+
<Indexed {items} />
|
|
498
|
+
<Keyed {items} />
|
|
499
|
+
<IndexedAndKeyed {items} />
|
|
500
|
+
<Arrow {items} />
|
|
501
|
+
</>
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
render(App);
|
|
505
|
+
|
|
506
|
+
const texts = (selector) => Array.from(container.querySelectorAll(selector)).map(
|
|
507
|
+
(el) => el.textContent,
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
expect(texts('.plain')).toEqual(['a', 'b']);
|
|
511
|
+
expect(texts('.indexed')).toEqual(['0: a', '1: b']);
|
|
512
|
+
expect(texts('.keyed')).toEqual(['a', 'b']);
|
|
513
|
+
expect(texts('.indexed-keyed')).toEqual(['0: a', '1: b']);
|
|
514
|
+
expect(texts('.arrow')).toEqual(['0: a', '1: b']);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('renders the index and the key from a template held in a variable', () => {
|
|
518
|
+
function Indexed({ items }) {
|
|
519
|
+
const list = <ul>
|
|
520
|
+
@for (const item of items; index i) {
|
|
521
|
+
<li class="indexed">{i + ': ' + item.text}</li>
|
|
522
|
+
}
|
|
523
|
+
</ul>;
|
|
524
|
+
return list;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function Keyed({ items }) {
|
|
528
|
+
return [
|
|
529
|
+
<ul>
|
|
530
|
+
@for (const item of items; key item.id) {
|
|
531
|
+
<li class="keyed">{item.text}</li>
|
|
532
|
+
}
|
|
533
|
+
</ul>,
|
|
534
|
+
];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function App() @{
|
|
538
|
+
const items = [
|
|
539
|
+
{ id: 1, text: 'a' },
|
|
540
|
+
{ id: 2, text: 'b' },
|
|
541
|
+
];
|
|
542
|
+
<>
|
|
543
|
+
<Indexed {items} />
|
|
544
|
+
<Keyed {items} />
|
|
545
|
+
</>
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
render(App);
|
|
549
|
+
|
|
550
|
+
const texts = (selector) => Array.from(container.querySelectorAll(selector)).map(
|
|
551
|
+
(el) => el.textContent,
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
expect(texts('.indexed')).toEqual(['0: a', '1: b']);
|
|
555
|
+
expect(texts('.keyed')).toEqual(['a', 'b']);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('renders a bare `@for` held in a variable through an expression', () => {
|
|
559
|
+
function Returned({ items }) {
|
|
560
|
+
const yo = @for (const item of items; index i; key item.id) {
|
|
561
|
+
<li class="returned">{i + ': ' + item.text}</li>
|
|
562
|
+
};
|
|
563
|
+
return <ul>{yo}</ul>;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function Held({ items }) {
|
|
567
|
+
const yo = @for (const item of items; index i) {
|
|
568
|
+
<li class="held">{i + ': ' + item.text}</li>
|
|
569
|
+
};
|
|
570
|
+
const list = <ul>{yo}</ul>;
|
|
571
|
+
return list;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function App() @{
|
|
575
|
+
const items = [
|
|
576
|
+
{ id: 1, text: 'a' },
|
|
577
|
+
{ id: 2, text: 'b' },
|
|
578
|
+
];
|
|
579
|
+
<>
|
|
580
|
+
<Returned {items} />
|
|
581
|
+
<Held {items} />
|
|
582
|
+
</>
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
render(App);
|
|
586
|
+
|
|
587
|
+
const texts = (selector) => Array.from(container.querySelectorAll(selector)).map(
|
|
588
|
+
(el) => el.textContent,
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
expect(texts('.returned')).toEqual(['0: a', '1: b']);
|
|
592
|
+
expect(texts('.held')).toEqual(['0: a', '1: b']);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('reads rest and default pattern names off the per-key item as it changes', () => {
|
|
596
|
+
type Row = { id: number; name: string; tags?: string[] };
|
|
597
|
+
let replace: (rows: Row[]) => void = () => {};
|
|
598
|
+
|
|
599
|
+
function App() @{
|
|
600
|
+
const rows = track<Row[]>([
|
|
601
|
+
{ id: 1, name: 'one', tags: ['a', 'b'] },
|
|
602
|
+
]);
|
|
603
|
+
replace = (next) => {
|
|
604
|
+
rows.value = next;
|
|
605
|
+
};
|
|
606
|
+
<ul>
|
|
607
|
+
@for (const { id, ...rest } of rows.value; key id) {
|
|
608
|
+
<li>{rest.name}</li>
|
|
609
|
+
}
|
|
610
|
+
@for (const [head, second = 'none', ...others] of rows.value.map(
|
|
611
|
+
(row) => row.tags ?? [],
|
|
612
|
+
); key head) {
|
|
613
|
+
<li>{head + ':' + second + ':' + others.length}</li>
|
|
614
|
+
}
|
|
615
|
+
</ul>
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
render(App);
|
|
619
|
+
const items = () => [...container.querySelectorAll('li')].map((li) => li.textContent);
|
|
620
|
+
expect(items()).toEqual(['one', 'a:b:0']);
|
|
621
|
+
|
|
622
|
+
// A new object under the same key updates the existing block in place.
|
|
623
|
+
replace([
|
|
624
|
+
{ id: 1, name: 'uno', tags: ['a'] },
|
|
625
|
+
]);
|
|
626
|
+
flushSync();
|
|
627
|
+
expect(items()).toEqual(['uno', 'a:none:0']);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('destructures a keyed item once per change, so defaults run once', () => {
|
|
631
|
+
type Row = { id: number; label?: string };
|
|
632
|
+
let replace: (rows: Row[]) => void = () => {};
|
|
633
|
+
let defaults = 0;
|
|
634
|
+
const next = () => `default ${++defaults}`;
|
|
635
|
+
|
|
636
|
+
function App() @{
|
|
637
|
+
const rows = track<Row[]>([{ id: 1 }]);
|
|
638
|
+
replace = (next) => {
|
|
639
|
+
rows.value = next;
|
|
640
|
+
};
|
|
641
|
+
<ul>
|
|
642
|
+
@for (const { id, label = next() } of rows.value; key id) {
|
|
643
|
+
<li>{label + '/' + label + '/' + id}</li>
|
|
644
|
+
}
|
|
645
|
+
</ul>
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
render(App);
|
|
649
|
+
const items = () => [...container.querySelectorAll('li')].map((li) => li.textContent);
|
|
650
|
+
// Two reads of `label`, one evaluation of its default.
|
|
651
|
+
expect(items()).toEqual(['default 1/default 1/1']);
|
|
652
|
+
expect(defaults).toBe(1);
|
|
653
|
+
|
|
654
|
+
replace([{ id: 1 }]);
|
|
655
|
+
flushSync();
|
|
656
|
+
expect(items()).toEqual(['default 2/default 2/1']);
|
|
657
|
+
expect(defaults).toBe(2);
|
|
658
|
+
|
|
659
|
+
replace([
|
|
660
|
+
{ id: 1, label: 'given' },
|
|
661
|
+
]);
|
|
662
|
+
flushSync();
|
|
663
|
+
expect(items()).toEqual(['given/given/1']);
|
|
664
|
+
expect(defaults).toBe(2);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it('reads rest pattern names inside a hoisted branch of a keyed loop', () => {
|
|
668
|
+
type Row = { id: number; name: string };
|
|
669
|
+
let replace: (rows: Row[]) => void = () => {};
|
|
670
|
+
let toggle: () => void = () => {};
|
|
671
|
+
|
|
672
|
+
function App() @{
|
|
673
|
+
const rows = track<Row[]>([
|
|
674
|
+
{ id: 1, name: 'one' },
|
|
675
|
+
]);
|
|
676
|
+
const show = track(true);
|
|
677
|
+
replace = (next) => {
|
|
678
|
+
rows.value = next;
|
|
679
|
+
};
|
|
680
|
+
toggle = () => {
|
|
681
|
+
show.value = !show.value;
|
|
682
|
+
};
|
|
683
|
+
<ul>
|
|
684
|
+
@for (const { id, ...rest } of rows.value; key id) {
|
|
685
|
+
@if (show.value) {
|
|
686
|
+
<li>{rest.name}</li>
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
</ul>
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
render(App);
|
|
693
|
+
const items = () => [...container.querySelectorAll('li')].map((li) => li.textContent);
|
|
694
|
+
expect(items()).toEqual(['one']);
|
|
695
|
+
|
|
696
|
+
replace([
|
|
697
|
+
{ id: 1, name: 'uno' },
|
|
698
|
+
]);
|
|
699
|
+
flushSync();
|
|
700
|
+
expect(items()).toEqual(['uno']);
|
|
701
|
+
|
|
702
|
+
toggle();
|
|
703
|
+
flushSync();
|
|
704
|
+
expect(items()).toEqual([]);
|
|
705
|
+
|
|
706
|
+
toggle();
|
|
707
|
+
flushSync();
|
|
708
|
+
expect(items()).toEqual(['uno']);
|
|
709
|
+
});
|
|
409
710
|
});
|
|
@@ -31,18 +31,18 @@ describe('head elements', () => {
|
|
|
31
31
|
|
|
32
32
|
it('renders reactive title element', () => {
|
|
33
33
|
function App() @{
|
|
34
|
-
|
|
34
|
+
const title = track('Initial Title');
|
|
35
35
|
<>
|
|
36
36
|
<head>
|
|
37
|
-
<title>{title}</title>
|
|
37
|
+
<title>{title.value}</title>
|
|
38
38
|
</head>
|
|
39
39
|
<div>
|
|
40
40
|
<button
|
|
41
41
|
onClick={() => {
|
|
42
|
-
title = 'Updated Title';
|
|
42
|
+
title.value = 'Updated Title';
|
|
43
43
|
}}
|
|
44
44
|
>{'Update Title'}</button>
|
|
45
|
-
<span>{title}</span>
|
|
45
|
+
<span>{title.value}</span>
|
|
46
46
|
</div>
|
|
47
47
|
</>
|
|
48
48
|
}
|
|
@@ -62,15 +62,15 @@ describe('head elements', () => {
|
|
|
62
62
|
|
|
63
63
|
it('renders title with template literal', () => {
|
|
64
64
|
function App() @{
|
|
65
|
-
|
|
65
|
+
const name = track('World');
|
|
66
66
|
<>
|
|
67
67
|
<head>
|
|
68
|
-
<title>{`Hello ${name}!`}</title>
|
|
68
|
+
<title>{`Hello ${name.value}!`}</title>
|
|
69
69
|
</head>
|
|
70
70
|
<div>
|
|
71
71
|
<button
|
|
72
72
|
onClick={() => {
|
|
73
|
-
name = 'Ripple';
|
|
73
|
+
name.value = 'Ripple';
|
|
74
74
|
}}
|
|
75
75
|
>{'Change Name'}</button>
|
|
76
76
|
</div>
|
|
@@ -90,19 +90,19 @@ describe('head elements', () => {
|
|
|
90
90
|
|
|
91
91
|
it('renders title with computed value', () => {
|
|
92
92
|
function App() @{
|
|
93
|
-
|
|
93
|
+
const count = track(0);
|
|
94
94
|
let prefix = 'Count: ';
|
|
95
95
|
<>
|
|
96
96
|
<head>
|
|
97
|
-
<title>{prefix + count}</title>
|
|
97
|
+
<title>{prefix + count.value}</title>
|
|
98
98
|
</head>
|
|
99
99
|
<div>
|
|
100
100
|
<button
|
|
101
101
|
onClick={() => {
|
|
102
|
-
count++;
|
|
102
|
+
count.value++;
|
|
103
103
|
}}
|
|
104
104
|
>{'Increment'}</button>
|
|
105
|
-
<span>{count}</span>
|
|
105
|
+
<span>{count.value}</span>
|
|
106
106
|
</div>
|
|
107
107
|
</>
|
|
108
108
|
}
|
|
@@ -121,15 +121,15 @@ describe('head elements', () => {
|
|
|
121
121
|
|
|
122
122
|
it('handles multiple title updates', () => {
|
|
123
123
|
function App() @{
|
|
124
|
-
|
|
124
|
+
const step = track(1);
|
|
125
125
|
<>
|
|
126
126
|
<head>
|
|
127
|
-
<title>{`Step ${step} of 3`}</title>
|
|
127
|
+
<title>{`Step ${step.value} of 3`}</title>
|
|
128
128
|
</head>
|
|
129
129
|
<div>
|
|
130
130
|
<button
|
|
131
131
|
onClick={() => {
|
|
132
|
-
step = step % 3 + 1;
|
|
132
|
+
step.value = step.value % 3 + 1;
|
|
133
133
|
}}
|
|
134
134
|
>{'Next Step'}</button>
|
|
135
135
|
</div>
|
|
@@ -244,23 +244,23 @@ describe('head elements', () => {
|
|
|
244
244
|
|
|
245
245
|
it('renders title with conditional content', () => {
|
|
246
246
|
function App() @{
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
const showPrefix = track(true);
|
|
248
|
+
const title = track('Main Page');
|
|
249
249
|
<>
|
|
250
250
|
<head>
|
|
251
251
|
<title>
|
|
252
|
-
{showPrefix ? 'App - ' + title : title}
|
|
252
|
+
{showPrefix.value ? 'App - ' + title.value : title.value}
|
|
253
253
|
</title>
|
|
254
254
|
</head>
|
|
255
255
|
<div>
|
|
256
256
|
<button
|
|
257
257
|
onClick={() => {
|
|
258
|
-
showPrefix = !showPrefix;
|
|
258
|
+
showPrefix.value = !showPrefix.value;
|
|
259
259
|
}}
|
|
260
260
|
>{'Toggle Prefix'}</button>
|
|
261
261
|
<button
|
|
262
262
|
onClick={() => {
|
|
263
|
-
title = title === 'Main Page' ? 'Settings' : 'Main Page';
|
|
263
|
+
title.value = title.value === 'Main Page' ? 'Settings' : 'Main Page';
|
|
264
264
|
}}
|
|
265
265
|
>{'Change Page'}</button>
|
|
266
266
|
</div>
|
|
@@ -16,12 +16,14 @@ describe('Fragment innerHTML', () => {
|
|
|
16
16
|
|
|
17
17
|
it('renders spread innerHTML as content instead of an attribute', () => {
|
|
18
18
|
function App() @{
|
|
19
|
-
|
|
19
|
+
const attrs = track<{ innerHTML?: string }>({
|
|
20
20
|
innerHTML: '<span>Spread HTML</span>',
|
|
21
21
|
});
|
|
22
22
|
<>
|
|
23
|
-
<code {...attrs} />
|
|
24
|
-
<button
|
|
23
|
+
<code {...attrs.value} />
|
|
24
|
+
<button
|
|
25
|
+
onClick={() => (attrs.value = { innerHTML: '<em>Updated HTML</em>' })}
|
|
26
|
+
>{'Update'}</button>
|
|
25
27
|
</>
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -64,12 +66,12 @@ describe('Fragment innerHTML', () => {
|
|
|
64
66
|
|
|
65
67
|
it('renders dynamic innerHTML fragments', () => {
|
|
66
68
|
function App() @{
|
|
67
|
-
|
|
69
|
+
const str = track('<div>Test</div>');
|
|
68
70
|
<>
|
|
69
|
-
<Fragment innerHTML={str} />
|
|
71
|
+
<Fragment innerHTML={str.value} />
|
|
70
72
|
<button
|
|
71
73
|
onClick={() => {
|
|
72
|
-
str = '<div>Updated</div>';
|
|
74
|
+
str.value = '<div>Updated</div>';
|
|
73
75
|
}}
|
|
74
76
|
>{'Update'}</button>
|
|
75
77
|
</>
|
|
@@ -99,10 +101,10 @@ describe('Fragment innerHTML', () => {
|
|
|
99
101
|
|
|
100
102
|
it('updates dynamic Fragment innerHTML', () => {
|
|
101
103
|
function App() @{
|
|
102
|
-
|
|
104
|
+
const html = track('<span>First</span>');
|
|
103
105
|
<>
|
|
104
|
-
<Fragment innerHTML={html} />
|
|
105
|
-
<button onClick={() => (html = '<em>Second</em>')}>{'Update'}</button>
|
|
106
|
+
<Fragment innerHTML={html.value} />
|
|
107
|
+
<button onClick={() => (html.value = '<em>Second</em>')}>{'Update'}</button>
|
|
106
108
|
</>
|
|
107
109
|
}
|
|
108
110
|
|