mirror of https://github.com/sveltejs/svelte
parent
d822c50b75
commit
08abbd3c96
@ -0,0 +1,39 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons =
|
||||
'<button>b = 1</button><button>b = 2, c = 1</button><button>resolve latest pos</button><button>resolve oldest pos</button><button>resolve all</button>';
|
||||
|
||||
// A batch's async result equals the real value, which a later (still pending) batch wrote. The batch
|
||||
// itself sees that later batch's previous value though, so the result changes its world and has to show
|
||||
export default test({
|
||||
mode: ['client'],
|
||||
async test({ assert, target }) {
|
||||
const [b1, b2c1, resolve_latest, resolve_oldest, resolve_all] =
|
||||
target.querySelectorAll('button');
|
||||
|
||||
resolve_all.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>zero</p><span>0</span><i>c0</i>`);
|
||||
|
||||
b1.click();
|
||||
await tick();
|
||||
b2c1.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>zero</p><span>0</span><i>c0</i>`);
|
||||
|
||||
// the later batch's result becomes the real value, but that batch still waits for c1
|
||||
resolve_latest.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>zero</p><span>0</span><i>c0</i>`);
|
||||
|
||||
// the earlier batch's equal result completes it
|
||||
resolve_oldest.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>pos</p><span>1</span><i>c0</i>`);
|
||||
|
||||
resolve_all.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>pos</p><span>2</span><i>c1</i>`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,34 @@
|
||||
<script>
|
||||
let b = $state(0);
|
||||
let c = $state(0);
|
||||
|
||||
/** @type {Map<string, Array<() => void>>} */
|
||||
const pending = new Map();
|
||||
|
||||
/** @param {string} value */
|
||||
function f(value) {
|
||||
return new Promise((fulfil) => {
|
||||
if (!pending.has(value)) pending.set(value, []);
|
||||
pending.get(value)?.push(() => fulfil(value));
|
||||
});
|
||||
}
|
||||
|
||||
function resolve_all() {
|
||||
for (const list of pending.values()) {
|
||||
for (const fn of list.splice(0)) fn();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => (b = 1)}>b = 1</button>
|
||||
<button onclick={() => ((b = 2), (c = 1))}>b = 2, c = 1</button>
|
||||
<button onclick={() => pending.get('pos')?.pop()?.()}>resolve latest pos</button>
|
||||
<button onclick={() => pending.get('pos')?.shift()?.()}>resolve oldest pos</button>
|
||||
<button onclick={resolve_all}>resolve all</button>
|
||||
|
||||
<svelte:boundary>
|
||||
<p>{await f(b > 0 ? 'pos' : 'zero')}</p>
|
||||
<span>{b}</span>
|
||||
<i>{await f('c' + c)}</i>
|
||||
{#snippet pending()}{/snippet}
|
||||
</svelte:boundary>
|
||||
@ -0,0 +1,47 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons =
|
||||
'<button>b = 2</button><button>b = 0</button><button>resolve k3</button><button>resolve k0</button><button>resolve all</button>';
|
||||
|
||||
// A batch's async result that equals the current real value (but not the value the batch sees,
|
||||
// because an earlier pending batch has a different one in flight) must not be dropped, or the
|
||||
// earlier batch's result wins although the later batch reverted the input
|
||||
export default test({
|
||||
mode: ['client'],
|
||||
async test({ assert, target }) {
|
||||
const [b2, b0, resolve_k3, resolve_k0, resolve_all] = target.querySelectorAll('button');
|
||||
|
||||
resolve_all.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<b>k0</b><b>k1</b>`);
|
||||
|
||||
// batch 1 requests k2/k3
|
||||
b2.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<b>k0</b><b>k1</b>`);
|
||||
|
||||
// batch 2 requests k0/k1, which equal the values currently shown
|
||||
b0.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<b>k0</b><b>k1</b>`);
|
||||
|
||||
resolve_k3.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<b>k0</b><b>k1</b>`);
|
||||
|
||||
// batch 2's first result equals the real value
|
||||
resolve_k0.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<b>k0</b><b>k1</b>`);
|
||||
|
||||
resolve_all.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<b>k0</b><b>k1</b>`);
|
||||
|
||||
// nothing left over
|
||||
resolve_all.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<b>k0</b><b>k1</b>`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,36 @@
|
||||
<script>
|
||||
let b = $state(0);
|
||||
|
||||
/** @type {Map<string, Array<() => void>>} */
|
||||
const pending = new Map();
|
||||
|
||||
/** @param {string} value */
|
||||
function delay(value) {
|
||||
return new Promise((fulfil) => {
|
||||
if (!pending.has(value)) pending.set(value, []);
|
||||
pending.get(value)?.push(() => fulfil(value));
|
||||
});
|
||||
}
|
||||
|
||||
/** @param {string} value */
|
||||
function resolve(value) {
|
||||
pending.get(value)?.shift()?.();
|
||||
}
|
||||
|
||||
function resolve_all() {
|
||||
for (const list of pending.values()) {
|
||||
for (const fn of list.splice(0)) fn();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => (b = 2)}>b = 2</button>
|
||||
<button onclick={() => (b = 0)}>b = 0</button>
|
||||
<button onclick={() => resolve('k3')}>resolve k3</button>
|
||||
<button onclick={() => resolve('k0')}>resolve k0</button>
|
||||
<button onclick={resolve_all}>resolve all</button>
|
||||
|
||||
<svelte:boundary>
|
||||
<b>{await delay('k' + (0 + b))}</b><b>{await delay('k' + (1 + b))}</b>
|
||||
{#snippet pending()}{/snippet}
|
||||
</svelte:boundary>
|
||||
@ -0,0 +1,50 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons =
|
||||
'<button>fork</button><button>a = 2</button><button>shift</button><button>commit</button>';
|
||||
|
||||
// Like `async-fork-equal-result-dropped`, but a block depends on the async expression: the fork's
|
||||
// re-run result equals the real value, but differs from the fork's outdated one, so the block has to
|
||||
// update in the fork (its effects are considered up to date with the fork once it is committed)
|
||||
export default test({
|
||||
mode: ['client'],
|
||||
async test({ assert, target }) {
|
||||
const [fork, a2, shift, commit] = target.querySelectorAll('button');
|
||||
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>other</p><span>0|0</span>`);
|
||||
|
||||
// the fork's result is 1
|
||||
fork.click();
|
||||
await tick();
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>other</p><span>0|0</span>`);
|
||||
|
||||
// the real world overtakes the fork's `a`, the real batch and the fork both run the expression with 2
|
||||
a2.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>other</p><span>0|0</span>`);
|
||||
|
||||
// the real batch's result
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>other</p><span>2|0</span>`);
|
||||
|
||||
// the fork's result, which equals the real value
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>other</p><span>2|0</span>`);
|
||||
|
||||
commit.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>other</p><span>2|1</span>`);
|
||||
|
||||
// nothing left over
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>other</p><span>2|1</span>`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,28 @@
|
||||
<script>
|
||||
import { fork } from 'svelte';
|
||||
|
||||
let a = $state(0);
|
||||
let b = $state(0);
|
||||
|
||||
/** @type {ReturnType<typeof fork> | null} */
|
||||
let f = null;
|
||||
|
||||
/** @type {Array<() => void>} */
|
||||
const deferred = [];
|
||||
|
||||
/** @param {number} value */
|
||||
function delay(value) {
|
||||
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => (f = fork(() => { a = 1; b = 1; }))}>fork</button>
|
||||
<button onclick={() => (a = 2)}>a = 2</button>
|
||||
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||
<button onclick={() => f?.commit()}>commit</button>
|
||||
|
||||
<svelte:boundary>
|
||||
{#if (await delay(a)) === 1}<p>one</p>{:else}<p>other</p>{/if}
|
||||
<span>{a}|{b}</span>
|
||||
{#snippet pending()}{/snippet}
|
||||
</svelte:boundary>
|
||||
@ -0,0 +1,49 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons =
|
||||
'<button>fork</button><button>a = 2</button><button>shift</button><button>commit</button>';
|
||||
|
||||
// A fork re-runs an async expression (because the real world changed its input) and the result equals
|
||||
// the real value. The fork must not keep (and commit) its outdated result from before
|
||||
export default test({
|
||||
mode: ['client'],
|
||||
async test({ assert, target }) {
|
||||
const [fork, a2, shift, commit] = target.querySelectorAll('button');
|
||||
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p><span>0|0</span>`);
|
||||
|
||||
// the fork's result is 1
|
||||
fork.click();
|
||||
await tick();
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p><span>0|0</span>`);
|
||||
|
||||
// the real world overtakes the fork's `a`, the real batch and the fork both run the expression with 2
|
||||
a2.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p><span>0|0</span>`);
|
||||
|
||||
// the real batch's result
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>2</p><span>2|0</span>`);
|
||||
|
||||
// the fork's result, which equals the real value
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>2</p><span>2|0</span>`);
|
||||
|
||||
commit.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>2</p><span>2|1</span>`);
|
||||
|
||||
// nothing left over
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>2</p><span>2|1</span>`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,28 @@
|
||||
<script>
|
||||
import { fork } from 'svelte';
|
||||
|
||||
let a = $state(0);
|
||||
let b = $state(0);
|
||||
|
||||
/** @type {ReturnType<typeof fork> | null} */
|
||||
let f = null;
|
||||
|
||||
/** @type {Array<() => void>} */
|
||||
const deferred = [];
|
||||
|
||||
/** @param {number} value */
|
||||
function delay(value) {
|
||||
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => (f = fork(() => { a = 1; b = 1; }))}>fork</button>
|
||||
<button onclick={() => (a = 2)}>a = 2</button>
|
||||
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||
<button onclick={() => f?.commit()}>commit</button>
|
||||
|
||||
<svelte:boundary>
|
||||
<p>{await delay(a)}</p>
|
||||
<span>{a}|{b}</span>
|
||||
{#snippet pending()}{/snippet}
|
||||
</svelte:boundary>
|
||||
@ -0,0 +1,47 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
// A fork whose only remaining values equal the real ones is obsolete. Here, the forks re-run the
|
||||
// each block in their own view after `list` changed, writing the (unchanged) item values, which
|
||||
// they hold. Once the real world has overtaken fork A's only change (`show = true`), these must not
|
||||
// keep it alive: otherwise it would be committed as a real batch instead of being discarded, and
|
||||
// its `{#await}` would resolve with a stale `b` (from fork B, committed later) and win over the
|
||||
// real result.
|
||||
export default test({
|
||||
mode: ['client'],
|
||||
async test({ assert, target }) {
|
||||
await tick();
|
||||
const [fork_a, fork_b, write_list, write_real, commit_a, commit_b, resolve_all] =
|
||||
target.querySelectorAll('button');
|
||||
const buttons = [...target.querySelectorAll('button')].map((b) => b.outerHTML).join('');
|
||||
|
||||
const settle = async () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await tick();
|
||||
resolve_all.click();
|
||||
}
|
||||
await tick();
|
||||
};
|
||||
|
||||
await settle();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>p0</p><s>0</s><s>1</s><q>aw0</q>`);
|
||||
|
||||
fork_a.click();
|
||||
fork_b.click();
|
||||
|
||||
// same items, but a new array, so the forks re-run the each block
|
||||
write_list.click();
|
||||
await tick();
|
||||
|
||||
// overtakes everything fork A changed (and `b` in fork B)
|
||||
write_real.click();
|
||||
commit_a.click();
|
||||
commit_b.click();
|
||||
|
||||
await settle();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`${buttons}<p>p1</p><section>s2</section><s>0</s><s>1</s><q>aw2</q>`
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,46 @@
|
||||
<script>
|
||||
import { fork } from 'svelte';
|
||||
|
||||
let a = $state(0);
|
||||
let b = $state(0);
|
||||
let show = $state(false);
|
||||
let list = $state([0, 1]);
|
||||
|
||||
let fork_a;
|
||||
let fork_b;
|
||||
|
||||
const queued = [];
|
||||
let initial = true;
|
||||
|
||||
// values are available right away during the initial render, after that only once resolved
|
||||
function delay(value) {
|
||||
if (initial) return value;
|
||||
return new Promise((resolve) => queued.push(() => resolve(value)));
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
initial = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<button onclick={() => (fork_a = fork(() => (show = true)))}>fork a</button>
|
||||
<button onclick={() => (fork_b = fork(() => { b = 2; a = 1; }))}>fork b</button>
|
||||
<button onclick={() => (list = [0, 1])}>list = [0, 1]</button>
|
||||
<button onclick={() => { b = 2; show = true; }}>b = 2, show = true</button>
|
||||
<button onclick={() => fork_a.commit()}>commit a</button>
|
||||
<button onclick={() => fork_b.commit()}>commit b</button>
|
||||
<button onclick={() => { for (const resolve of queued.splice(0)) resolve(); }}>resolve all</button>
|
||||
|
||||
<svelte:boundary>
|
||||
<p>{await delay(`p${a}`)}</p>
|
||||
|
||||
{#if show}
|
||||
<section>{await delay(`s${b}`)}</section>
|
||||
{/if}
|
||||
|
||||
{#each list as x}
|
||||
<s>{x}</s>
|
||||
{/each}
|
||||
|
||||
{#await delay(`aw${b}`) then v}<q>{v}</q>{/await}
|
||||
</svelte:boundary>
|
||||
Loading…
Reference in new issue