mirror of https://github.com/sveltejs/svelte
parent
7018c09247
commit
18219ad1ff
@ -0,0 +1,70 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
/** @param {number} n */
|
||||||
|
const html = (n) => `<p>c</p><section>${n}|${n}</section>`;
|
||||||
|
|
||||||
|
// Branches with async expressions (with and without dependencies) that fork `a` created are revealed
|
||||||
|
// by another batch (a real one, or fork `b`), before or after `a`'s results are in. The revealing batch
|
||||||
|
// has to run the async expressions itself, since `a`'s results only exist in `a`. When `a` is discarded
|
||||||
|
// the other batch's results are shown; when it's committed, its own results are shown (nothing is fetched
|
||||||
|
// again), even though the other batch's results come in later.
|
||||||
|
export default test({
|
||||||
|
mode: ['client'],
|
||||||
|
async test({ assert, target, instance }) {
|
||||||
|
const [fork_a, show, fork_b, reset, resolve_all, discard_a, commit_a, commit_b] =
|
||||||
|
target.querySelectorAll('button');
|
||||||
|
const [output] = target.querySelectorAll('main');
|
||||||
|
|
||||||
|
const settle = async () => {
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await tick();
|
||||||
|
resolve_all.click();
|
||||||
|
}
|
||||||
|
await tick();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const revealer of ['real', 'fork']) {
|
||||||
|
for (const finish of ['discard', 'commit']) {
|
||||||
|
for (const resolved of [true, false]) {
|
||||||
|
const mode = `${revealer} reveals, a ${finish}ed, a's results ${resolved ? 'resolved' : 'pending'}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fork_a.click();
|
||||||
|
if (resolved) {
|
||||||
|
resolve_all.click();
|
||||||
|
await tick();
|
||||||
|
}
|
||||||
|
assert.deepEqual(instance.log.splice(0), ['cc', 'n1']);
|
||||||
|
|
||||||
|
if (revealer === 'real') show.click();
|
||||||
|
else fork_b.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(output.innerHTML, '');
|
||||||
|
assert.deepEqual(instance.log.splice(0), ['cc', 'n0']);
|
||||||
|
|
||||||
|
if (finish === 'discard') discard_a.click();
|
||||||
|
else commit_a.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(
|
||||||
|
output.innerHTML,
|
||||||
|
finish === 'commit' ? html(1) : revealer === 'real' ? html(0) : ''
|
||||||
|
);
|
||||||
|
|
||||||
|
commit_b.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html(finish === 'commit' ? 1 : 0));
|
||||||
|
assert.deepEqual(instance.log, []);
|
||||||
|
|
||||||
|
reset.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, '');
|
||||||
|
} catch (e) {
|
||||||
|
/** @type {Error} */ (e).message = `${mode}: ${/** @type {Error} */ (e).message}`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
<script>
|
||||||
|
import { fork } from 'svelte';
|
||||||
|
|
||||||
|
let show = $state(false);
|
||||||
|
let n = $state(0);
|
||||||
|
export const log = [];
|
||||||
|
const registry = [];
|
||||||
|
let a;
|
||||||
|
let b;
|
||||||
|
|
||||||
|
function f(tag, value) {
|
||||||
|
log.push(`${tag}${value}`);
|
||||||
|
return new Promise((resolve) => registry.push(() => resolve(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function set(values) {
|
||||||
|
if ('show' in values) show = values.show;
|
||||||
|
if ('n' in values) n = values.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve_all() {
|
||||||
|
for (const r of registry.splice(0)) r();
|
||||||
|
}
|
||||||
|
|
||||||
|
function discard_a() {
|
||||||
|
a?.discard();
|
||||||
|
a = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function commit_a() {
|
||||||
|
a?.commit();
|
||||||
|
a = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function commit_b() {
|
||||||
|
b?.commit();
|
||||||
|
b = undefined;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => (a = fork(() => set({ show: true, n: 1 })))}>fork a</button>
|
||||||
|
<button onclick={() => set({ show: true })}>show</button>
|
||||||
|
<button onclick={() => (b = fork(() => set({ show: true })))}>fork b</button>
|
||||||
|
<button onclick={() => set({ show: false, n: 0 })}>reset</button>
|
||||||
|
<button onclick={resolve_all}>resolve all</button>
|
||||||
|
<button onclick={discard_a}>discard a</button>
|
||||||
|
<button onclick={commit_a}>commit a</button>
|
||||||
|
<button onclick={commit_b}>commit b</button>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{#if show}<p>{await f('c', 'c')}</p>{/if}
|
||||||
|
{#if show}<section>{n}|{await f('n', n)}</section>{/if}
|
||||||
|
</main>
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['client'],
|
||||||
|
async test({ assert, target }) {
|
||||||
|
const [fork_a, fork_b, resolve_one, resolve_all, discard_a, commit_b] =
|
||||||
|
target.querySelectorAll('button');
|
||||||
|
const [output] = target.querySelectorAll('main');
|
||||||
|
|
||||||
|
fork_a.click();
|
||||||
|
await tick();
|
||||||
|
resolve_one.click();
|
||||||
|
await tick();
|
||||||
|
fork_b.click();
|
||||||
|
await tick();
|
||||||
|
resolve_one.click();
|
||||||
|
await tick();
|
||||||
|
discard_a.click();
|
||||||
|
await tick();
|
||||||
|
resolve_one.click();
|
||||||
|
await tick();
|
||||||
|
commit_b.click();
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
resolve_all.click();
|
||||||
|
await tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.htmlEqual(output.innerHTML, '<section>s</section>');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
<script>
|
||||||
|
import { fork } from 'svelte';
|
||||||
|
|
||||||
|
let show = $state(false);
|
||||||
|
const registry = [];
|
||||||
|
let a;
|
||||||
|
let b;
|
||||||
|
|
||||||
|
function f(value) {
|
||||||
|
return new Promise((resolve) => registry.push(() => resolve(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve_all() { for (const r of registry.splice(0)) r(); }
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => (a = fork(() => (show = true)))}>fork a</button>
|
||||||
|
<button onclick={() => (b = fork(() => (show = true)))}>fork b</button>
|
||||||
|
<button onclick={() => registry.shift()?.()}>resolve one</button>
|
||||||
|
<button onclick={resolve_all}>resolve all</button>
|
||||||
|
<button onclick={() => a?.discard()}>discard a</button>
|
||||||
|
<button onclick={() => b?.commit()}>commit b</button>
|
||||||
|
|
||||||
|
<main>{#if show}<section>{await f('s')}</section>{/if}</main>
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
let { f, n } = $props();
|
||||||
|
let x = $derived(await f(n));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>{n}|{x}</p>
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
// A component with a top-level `await` inside a branch that a fork created is revealed by a real batch
|
||||||
|
export default test({
|
||||||
|
mode: ['client'],
|
||||||
|
async test({ assert, target, instance }) {
|
||||||
|
const [fork_a, show, reset, resolve_all, discard, commit] = target.querySelectorAll('button');
|
||||||
|
const [output] = target.querySelectorAll('main');
|
||||||
|
|
||||||
|
const settle = async () => {
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await tick();
|
||||||
|
resolve_all.click();
|
||||||
|
}
|
||||||
|
await tick();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const finish of ['discard', 'commit']) {
|
||||||
|
for (const resolved of [true, false]) {
|
||||||
|
const mode = `${finish}, ${resolved ? 'resolved' : 'pending'}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fork_a.click();
|
||||||
|
await tick();
|
||||||
|
if (resolved) {
|
||||||
|
resolve_all.click();
|
||||||
|
await tick();
|
||||||
|
}
|
||||||
|
assert.deepEqual(instance.log.splice(0), [1]);
|
||||||
|
|
||||||
|
show.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(instance.log.splice(0), [0]);
|
||||||
|
|
||||||
|
if (finish === 'discard') discard.click();
|
||||||
|
else commit.click();
|
||||||
|
await settle();
|
||||||
|
const n = finish === 'commit' ? 1 : 0;
|
||||||
|
assert.htmlEqual(output.innerHTML, `<p>${n}|${n}</p>`);
|
||||||
|
assert.deepEqual(instance.log, []);
|
||||||
|
|
||||||
|
reset.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, '');
|
||||||
|
} catch (e) {
|
||||||
|
/** @type {Error} */ (e).message = `${mode}: ${/** @type {Error} */ (e).message}`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
<script>
|
||||||
|
import { fork } from 'svelte';
|
||||||
|
import Child from './Child.svelte';
|
||||||
|
|
||||||
|
let show = $state(false);
|
||||||
|
let n = $state(0);
|
||||||
|
export const log = [];
|
||||||
|
const registry = [];
|
||||||
|
let a;
|
||||||
|
|
||||||
|
function f(value) {
|
||||||
|
log.push(value);
|
||||||
|
return new Promise((resolve) => registry.push(() => resolve(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function set(values) {
|
||||||
|
if ('show' in values) show = values.show;
|
||||||
|
if ('n' in values) n = values.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve_all() {
|
||||||
|
for (const r of registry.splice(0)) r();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => (a = fork(() => set({ show: true, n: 1 })))}>fork</button>
|
||||||
|
<button onclick={() => set({ show: true })}>show</button>
|
||||||
|
<button onclick={() => set({ show: false, n: 0 })}>reset</button>
|
||||||
|
<button onclick={resolve_all}>resolve all</button>
|
||||||
|
<button onclick={() => a?.discard()}>discard</button>
|
||||||
|
<button onclick={() => a?.commit()}>commit</button>
|
||||||
|
|
||||||
|
<main>{#if show}<Child {f} {n} />{/if}</main>
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
// The fallback of an each block, created by a fork, is adopted by a real batch: it has to run the
|
||||||
|
// fallback's async expression itself, since the fork's result only exists in the fork
|
||||||
|
export default test({
|
||||||
|
mode: ['client'],
|
||||||
|
async test({ assert, target, instance }) {
|
||||||
|
const [fork_empty, empty, reset, resolve_all, discard, commit] =
|
||||||
|
target.querySelectorAll('button');
|
||||||
|
const [output] = target.querySelectorAll('main');
|
||||||
|
|
||||||
|
const settle = async () => {
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await tick();
|
||||||
|
resolve_all.click();
|
||||||
|
}
|
||||||
|
await tick();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const finish of ['discard', 'commit']) {
|
||||||
|
for (const resolved of [true, false]) {
|
||||||
|
const mode = `${finish}, ${resolved ? 'resolved' : 'pending'}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fork_empty.click();
|
||||||
|
if (resolved) {
|
||||||
|
resolve_all.click();
|
||||||
|
await tick();
|
||||||
|
}
|
||||||
|
assert.deepEqual(instance.log.splice(0), ['empty']);
|
||||||
|
|
||||||
|
empty.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(instance.log.splice(0), ['empty']);
|
||||||
|
|
||||||
|
if (finish === 'discard') discard.click();
|
||||||
|
else commit.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, '<p>empty</p>');
|
||||||
|
assert.deepEqual(instance.log, []);
|
||||||
|
|
||||||
|
reset.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, '<b>0</b>');
|
||||||
|
} catch (e) {
|
||||||
|
/** @type {Error} */ (e).message = `${mode}: ${/** @type {Error} */ (e).message}`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
<script>
|
||||||
|
import { fork } from 'svelte';
|
||||||
|
|
||||||
|
let list = $state([0]);
|
||||||
|
export const log = [];
|
||||||
|
const registry = [];
|
||||||
|
let current_fork;
|
||||||
|
|
||||||
|
function f(value) {
|
||||||
|
log.push(value);
|
||||||
|
return new Promise((resolve) => registry.push(() => resolve(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve_all() {
|
||||||
|
for (const r of registry.splice(0)) r();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => (current_fork = fork(() => (list = [])))}>fork empty</button>
|
||||||
|
<button onclick={() => (list = [])}>empty</button>
|
||||||
|
<button onclick={() => (list = [0])}>reset</button>
|
||||||
|
<button onclick={resolve_all}>resolve all</button>
|
||||||
|
<button onclick={() => current_fork?.discard()}>discard</button>
|
||||||
|
<button onclick={() => current_fork?.commit()}>commit</button>
|
||||||
|
|
||||||
|
<main>{#each list as x (x)}<b>{x}</b>{:else}<p>{await f('empty')}</p>{/each}</main>
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
/** @param {number[]} list */
|
||||||
|
const html = (list) =>
|
||||||
|
list.map((x) => `<b>${x}|${x}|c</b>`).join('') + list.map((x) => `<s>${x}|${x}</s>`).join('');
|
||||||
|
|
||||||
|
// Items of keyed and unkeyed each blocks that were created by a fork (and whose async expressions
|
||||||
|
// therefore only have results in that fork) are adopted by a real batch. The real batch has to run
|
||||||
|
// their async expressions itself — with dependencies (unkeyed `x`) or without (keyed `x` is a plain
|
||||||
|
// value, `'c'` is a constant) — whether the fork is discarded or committed afterwards. When the
|
||||||
|
// fork is committed, its own results are used: nothing is fetched again.
|
||||||
|
export default test({
|
||||||
|
mode: ['client'],
|
||||||
|
async test({ assert, target, instance }) {
|
||||||
|
const [
|
||||||
|
reset_list,
|
||||||
|
fork_list,
|
||||||
|
real_list,
|
||||||
|
fork_append_2,
|
||||||
|
append_2,
|
||||||
|
append_5,
|
||||||
|
resolve_all,
|
||||||
|
discard,
|
||||||
|
commit
|
||||||
|
] = target.querySelectorAll('button');
|
||||||
|
const [output] = target.querySelectorAll('main');
|
||||||
|
|
||||||
|
const settle = async () => {
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await tick();
|
||||||
|
resolve_all.click();
|
||||||
|
}
|
||||||
|
await tick();
|
||||||
|
};
|
||||||
|
|
||||||
|
const reset = async () => {
|
||||||
|
reset_list.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0, 1]));
|
||||||
|
instance.log.length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0, 1]));
|
||||||
|
instance.log.length = 0;
|
||||||
|
|
||||||
|
for (const finish of ['discard', 'commit']) {
|
||||||
|
try {
|
||||||
|
// the fork creates items, the real batch reuses some of them (with equal and different values)
|
||||||
|
fork_list.click();
|
||||||
|
assert.deepEqual(
|
||||||
|
instance.log.sort(),
|
||||||
|
['cc', 'cc', 'k3', 'k2', 'u0', 'u1', 'u2', 'u3'].sort()
|
||||||
|
);
|
||||||
|
instance.log.length = 0;
|
||||||
|
|
||||||
|
real_list.click();
|
||||||
|
if (finish === 'discard') discard.click();
|
||||||
|
else commit.click();
|
||||||
|
await settle();
|
||||||
|
// the real write came last, so it wins either way
|
||||||
|
assert.htmlEqual(output.innerHTML, html([3, 0, 1]));
|
||||||
|
await reset();
|
||||||
|
|
||||||
|
// the fork's results are already in when the real batch adopts its items with the same values
|
||||||
|
fork_append_2.click();
|
||||||
|
resolve_all.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0, 1]));
|
||||||
|
instance.log.length = 0;
|
||||||
|
|
||||||
|
append_2.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0, 1, 2]));
|
||||||
|
// the fork's results only exist in the fork, so the real batch has to fetch them itself
|
||||||
|
assert.deepEqual(instance.log.sort(), ['cc', 'k2', 'u2']);
|
||||||
|
instance.log.length = 0;
|
||||||
|
|
||||||
|
if (finish === 'discard') discard.click();
|
||||||
|
else commit.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0, 1, 2]));
|
||||||
|
assert.deepEqual(instance.log, []);
|
||||||
|
await reset();
|
||||||
|
|
||||||
|
// the fork is finished while the real batch that adopted its items is still pending
|
||||||
|
fork_append_2.click();
|
||||||
|
instance.log.length = 0;
|
||||||
|
append_2.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(instance.log.sort(), ['cc', 'k2', 'u2']);
|
||||||
|
instance.log.length = 0;
|
||||||
|
|
||||||
|
if (finish === 'discard') discard.click();
|
||||||
|
else commit.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0, 1, 2]));
|
||||||
|
assert.deepEqual(instance.log, []);
|
||||||
|
await reset();
|
||||||
|
|
||||||
|
// a real batch adopts an item with a different value after the fork's results are in
|
||||||
|
// (TODO also commit the fork here, once the fork re-running the unkeyed item's expression
|
||||||
|
// with the real world's new value no longer keeps the fork's outdated result)
|
||||||
|
if (finish === 'discard') {
|
||||||
|
fork_append_2.click();
|
||||||
|
resolve_all.click();
|
||||||
|
await tick();
|
||||||
|
instance.log.length = 0;
|
||||||
|
|
||||||
|
append_5.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0, 1, 5]));
|
||||||
|
// keyed: a new item, unkeyed: the adopted item's input changed (and the fork revalidates it)
|
||||||
|
assert.deepEqual(instance.log.sort(), ['cc', 'k5', 'u5', 'u5']);
|
||||||
|
|
||||||
|
discard.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0, 1, 5]));
|
||||||
|
await reset();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
/** @type {Error} */ (e).message = `${finish}: ${/** @type {Error} */ (e).message}`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
<script>
|
||||||
|
import { fork } from 'svelte';
|
||||||
|
|
||||||
|
let list = $state([0, 1]);
|
||||||
|
export const log = [];
|
||||||
|
const registry = [];
|
||||||
|
let current_fork;
|
||||||
|
|
||||||
|
function f(tag, value) {
|
||||||
|
log.push(`${tag}${value}`);
|
||||||
|
return new Promise((resolve) => registry.push(() => resolve(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve_all() {
|
||||||
|
for (const r of registry.splice(0)) r();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => (list = [0, 1])}>reset</button>
|
||||||
|
<button onclick={() => (current_fork = fork(() => (list = [3, 2, 1, 0])))}>fork list</button>
|
||||||
|
<button onclick={() => (list = [3, 0, 1])}>real list</button>
|
||||||
|
<button onclick={() => (current_fork = fork(() => (list = [0, 1, 2])))}>fork append 2</button>
|
||||||
|
<button onclick={() => (list = [0, 1, 2])}>append 2</button>
|
||||||
|
<button onclick={() => (list = [0, 1, 5])}>append 5</button>
|
||||||
|
<button onclick={resolve_all}>resolve all</button>
|
||||||
|
<button onclick={() => current_fork?.discard()}>discard</button>
|
||||||
|
<button onclick={() => current_fork?.commit()}>commit</button>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<svelte:boundary>
|
||||||
|
{#each list as x (x)}<b>{x}|{await f('k', x)}|{await f('c', 'c')}</b>{/each}
|
||||||
|
{#each list as x}<s>{x}|{await f('u', x)}</s>{/each}
|
||||||
|
{#snippet pending()}{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
|
</main>
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['client'],
|
||||||
|
async test({ assert, target }) {
|
||||||
|
const [fork_list, real_list, resolve_all, discard] = target.querySelectorAll('button');
|
||||||
|
const [output] = target.querySelectorAll('main');
|
||||||
|
|
||||||
|
resolve_all.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(output.innerHTML, '<s>0|0</s><s>1|1</s>');
|
||||||
|
|
||||||
|
fork_list.click();
|
||||||
|
real_list.click();
|
||||||
|
discard.click();
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await tick();
|
||||||
|
resolve_all.click();
|
||||||
|
}
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(output.innerHTML, '<s>3|3</s><s>0|0</s><s>1|1</s>');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
<script>
|
||||||
|
import { fork } from 'svelte';
|
||||||
|
|
||||||
|
let list = $state([0, 1]);
|
||||||
|
const registry = [];
|
||||||
|
let current_fork;
|
||||||
|
|
||||||
|
function f(value) {
|
||||||
|
return new Promise((resolve) => registry.push(() => resolve(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve_all() { for (const r of registry.splice(0)) r(); }
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => (current_fork = fork(() => (list = [3, 2, 1, 0])))}>fork list</button>
|
||||||
|
<button onclick={() => (list = [3, 0, 1])}>real list</button>
|
||||||
|
<button onclick={resolve_all}>resolve all</button>
|
||||||
|
<button onclick={() => current_fork?.discard()}>discard</button>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<svelte:boundary>
|
||||||
|
{#each list as x}<s>{x}|{await f(x)}</s>{/each}
|
||||||
|
{#snippet pending()}{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
|
</main>
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} list
|
||||||
|
* @param {boolean} show
|
||||||
|
*/
|
||||||
|
const html = (list, show) =>
|
||||||
|
list.map((x) => `<div>${x}|${x}${show ? `<p>${x}</p>` : ''}</div>`).join('');
|
||||||
|
|
||||||
|
// A fork creates an each item, and branches inside of it (and inside an existing item) that contain
|
||||||
|
// async expressions. A real batch adopts the item first and the branches later, or both at once.
|
||||||
|
export default test({
|
||||||
|
mode: ['client'],
|
||||||
|
async test({ assert, target, instance }) {
|
||||||
|
const [fork_a, list_and_show, list, show, reset, resolve_all, discard, commit] =
|
||||||
|
target.querySelectorAll('button');
|
||||||
|
const [output] = target.querySelectorAll('main');
|
||||||
|
|
||||||
|
const settle = async () => {
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await tick();
|
||||||
|
resolve_all.click();
|
||||||
|
}
|
||||||
|
await tick();
|
||||||
|
};
|
||||||
|
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0], false));
|
||||||
|
instance.log.length = 0;
|
||||||
|
|
||||||
|
for (const together of [false, true]) {
|
||||||
|
for (const finish of ['discard', 'commit']) {
|
||||||
|
const mode = `${together ? 'together' : 'separately'}, ${finish}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fork_a.click();
|
||||||
|
resolve_all.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(instance.log.splice(0).sort(), ['p0', 'p1', 'x1']);
|
||||||
|
|
||||||
|
if (!together) {
|
||||||
|
// the item is adopted, the branch inside it stays the fork's
|
||||||
|
list.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0, 1], false));
|
||||||
|
assert.deepEqual(instance.log.splice(0), ['x1']);
|
||||||
|
|
||||||
|
show.click();
|
||||||
|
} else {
|
||||||
|
list_and_show.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0, 1], true));
|
||||||
|
assert.deepEqual(
|
||||||
|
instance.log.splice(0).sort(),
|
||||||
|
together ? ['p0', 'p1', 'x1'] : ['p0', 'p1']
|
||||||
|
);
|
||||||
|
|
||||||
|
if (finish === 'discard') discard.click();
|
||||||
|
else commit.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0, 1], true));
|
||||||
|
assert.deepEqual(instance.log, []);
|
||||||
|
|
||||||
|
reset.click();
|
||||||
|
await settle();
|
||||||
|
assert.htmlEqual(output.innerHTML, html([0], false));
|
||||||
|
instance.log.length = 0;
|
||||||
|
} catch (e) {
|
||||||
|
/** @type {Error} */ (e).message = `${mode}: ${/** @type {Error} */ (e).message}`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
<script>
|
||||||
|
import { fork } from 'svelte';
|
||||||
|
|
||||||
|
let list = $state([0]);
|
||||||
|
let show = $state(false);
|
||||||
|
export const log = [];
|
||||||
|
const registry = [];
|
||||||
|
let a;
|
||||||
|
|
||||||
|
function f(tag, value) {
|
||||||
|
log.push(`${tag}${value}`);
|
||||||
|
return new Promise((resolve) => registry.push(() => resolve(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function set(values) {
|
||||||
|
if ('list' in values) list = values.list;
|
||||||
|
if ('show' in values) show = values.show;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve_all() {
|
||||||
|
for (const r of registry.splice(0)) r();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => (a = fork(() => set({ list: [0, 1], show: true })))}>fork</button>
|
||||||
|
<button onclick={() => set({ list: [0, 1], show: true })}>list and show</button>
|
||||||
|
<button onclick={() => set({ list: [0, 1] })}>list</button>
|
||||||
|
<button onclick={() => set({ show: true })}>show</button>
|
||||||
|
<button onclick={() => set({ list: [0], show: false })}>reset</button>
|
||||||
|
<button onclick={resolve_all}>resolve all</button>
|
||||||
|
<button onclick={() => a?.discard()}>discard</button>
|
||||||
|
<button onclick={() => a?.commit()}>commit</button>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<svelte:boundary>
|
||||||
|
{#each list as x (x)}
|
||||||
|
<div>{x}|{await f('x', x)}{#if show}<p>{await f('p', x)}</p>{/if}</div>
|
||||||
|
{/each}
|
||||||
|
{#snippet pending()}{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
|
</main>
|
||||||
Loading…
Reference in new issue