mirror of https://github.com/sveltejs/svelte
parent
7005aae212
commit
b08aeee3cf
@ -0,0 +1,38 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
const buttons = `
|
||||||
|
<button>fork x = 1</button>
|
||||||
|
<button>update y and discard</button>
|
||||||
|
<button>commit y fork and discard</button>
|
||||||
|
<button>resolve requests</button>
|
||||||
|
<button>reset</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['client'],
|
||||||
|
async test({ assert, target, logs }) {
|
||||||
|
await tick();
|
||||||
|
const [speculate, update, commit, resolve, reset] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
for (const action of [update, commit]) {
|
||||||
|
speculate.click();
|
||||||
|
await tick();
|
||||||
|
logs.length = 0;
|
||||||
|
|
||||||
|
// Discard before the queued fork revalidation runs. It must not restart
|
||||||
|
// the async effect and abort the real world's request, whether the write
|
||||||
|
// came from a normal update or another fork's commit.
|
||||||
|
action.click();
|
||||||
|
await tick();
|
||||||
|
resolve.click();
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, `${buttons}<p>0/1</p>`);
|
||||||
|
assert.deepEqual(logs, ['0/1']);
|
||||||
|
|
||||||
|
reset.click();
|
||||||
|
await tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
<script>
|
||||||
|
import { fork, getAbortSignal } from 'svelte';
|
||||||
|
|
||||||
|
let x = $state(0);
|
||||||
|
let y = $state(0);
|
||||||
|
let f;
|
||||||
|
const resolvers = [];
|
||||||
|
|
||||||
|
function load(x, y) {
|
||||||
|
console.log(`${x}/${y}`);
|
||||||
|
const signal = getAbortSignal();
|
||||||
|
if (y === 0) return `${x}/${y}`;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
resolvers.push(() => resolve(`${x}/${y}`));
|
||||||
|
signal.addEventListener('abort', () => reject(signal.reason));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function speculate() {
|
||||||
|
f = fork(() => { x = 1; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_and_discard(use_fork) {
|
||||||
|
if (use_fork) {
|
||||||
|
fork(() => { y = 1; }).commit();
|
||||||
|
} else {
|
||||||
|
y = 1;
|
||||||
|
}
|
||||||
|
f.discard();
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish() {
|
||||||
|
for (const resolve of resolvers.splice(0)) resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={speculate}>fork x = 1</button>
|
||||||
|
<button onclick={() => update_and_discard(false)}>update y and discard</button>
|
||||||
|
<button onclick={() => update_and_discard(true)}>commit y fork and discard</button>
|
||||||
|
<button onclick={finish}>resolve requests</button>
|
||||||
|
<button onclick={reset}>reset</button>
|
||||||
|
|
||||||
|
<p>{await load(x, y)}</p>
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
const buttons = `
|
||||||
|
<button>fork x = y = 1</button>
|
||||||
|
<button>set x = y = 1</button>
|
||||||
|
<button>set z = 1</button>
|
||||||
|
<button>commit fork</button>
|
||||||
|
<button>discard fork</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['client'],
|
||||||
|
async test({ assert, target, logs }) {
|
||||||
|
await tick();
|
||||||
|
const [speculate, catch_up, update, commit, discard] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
speculate.click();
|
||||||
|
await tick();
|
||||||
|
catch_up.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(target.innerHTML, `${buttons}<p>2</p>`);
|
||||||
|
logs.length = 0;
|
||||||
|
|
||||||
|
// All speculative writes are obsolete. Only the real world should react.
|
||||||
|
update.click();
|
||||||
|
await tick();
|
||||||
|
try {
|
||||||
|
assert.htmlEqual(target.innerHTML, `${buttons}<p>3</p>`);
|
||||||
|
assert.deepEqual(logs, ['1/1/1']);
|
||||||
|
|
||||||
|
// Committing an automatically discarded fork should be a no-op and not throw,
|
||||||
|
// as the user cannot really know that something got automatically discarded.
|
||||||
|
commit.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(target.innerHTML, `${buttons}<p>3</p>`);
|
||||||
|
assert.deepEqual(logs, ['1/1/1', 'committed']);
|
||||||
|
} finally {
|
||||||
|
discard.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
<script>
|
||||||
|
import { fork } from 'svelte';
|
||||||
|
|
||||||
|
let x = $state(0);
|
||||||
|
let y = $state(0);
|
||||||
|
let z = $state(0);
|
||||||
|
let f;
|
||||||
|
|
||||||
|
function load(x, y, z) {
|
||||||
|
console.log(`${x}/${y}/${z}`);
|
||||||
|
return x + y + z;
|
||||||
|
}
|
||||||
|
|
||||||
|
function speculate() {
|
||||||
|
f = fork(() => { x = 1; y = 1; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function catch_up() {
|
||||||
|
x = 1;
|
||||||
|
y = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
z = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function discard() {
|
||||||
|
f.discard();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function commit() {
|
||||||
|
await f.commit();
|
||||||
|
console.log('committed');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={speculate}>fork x = y = 1</button>
|
||||||
|
<button onclick={catch_up}>set x = y = 1</button>
|
||||||
|
<button onclick={update}>set z = 1</button>
|
||||||
|
<button onclick={commit}>commit fork</button>
|
||||||
|
<button onclick={discard}>discard fork</button>
|
||||||
|
|
||||||
|
<p>{await load(x, y, z)}</p>
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
const buttons = `
|
||||||
|
<button>start A: x = 1</button>
|
||||||
|
<button>start B: show branch</button>
|
||||||
|
<button>resolve A</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['client'],
|
||||||
|
async test({ assert, target }) {
|
||||||
|
await tick();
|
||||||
|
const [start_a, start_b, resolve] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
start_a.click();
|
||||||
|
await tick();
|
||||||
|
start_b.click();
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
// B selected the empty branch before merging into A. A's async result
|
||||||
|
// now selects the content, which B's old commit callback must not remove.
|
||||||
|
// The two blocks register A's and B's callbacks in opposite orders.
|
||||||
|
resolve.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`${buttons}<p>1/1</p><strong>ready</strong><em>also ready</em>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
<script>
|
||||||
|
let x = $state(0);
|
||||||
|
let show = $state(false);
|
||||||
|
let done;
|
||||||
|
|
||||||
|
function delay(value) {
|
||||||
|
if (value === 0) return 0;
|
||||||
|
return new Promise(resolve => { done = () => resolve(value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = $derived(await delay(x));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => x = 1}>start A: x = 1</button>
|
||||||
|
<button onclick={() => show = true}>start B: show branch</button>
|
||||||
|
<button onclick={() => done()}>resolve A</button>
|
||||||
|
|
||||||
|
<p>{x}/{result}</p>
|
||||||
|
{#if show && x > 0 && result > 0}
|
||||||
|
<strong>ready</strong>
|
||||||
|
{/if}
|
||||||
|
{#if x > 0 && show && result > 0}
|
||||||
|
<em>also ready</em>
|
||||||
|
{/if}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<script>
|
||||||
|
let { delay } = $props();
|
||||||
|
let result = $derived(await delay());
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<strong>{result}</strong>
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
const buttons = `
|
||||||
|
<button>start A: x = 1</button>
|
||||||
|
<button>start B: show boundary</button>
|
||||||
|
<button>resolve A</button>
|
||||||
|
<button>resolve boundary</button>
|
||||||
|
<button>reset</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['client'],
|
||||||
|
async test({ assert, target, logs }) {
|
||||||
|
await tick();
|
||||||
|
const [start_a, start_b, resolve_a, resolve_b, reset] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
for (const boundary_first of [false, true]) {
|
||||||
|
logs.length = 0;
|
||||||
|
start_a.click();
|
||||||
|
await tick();
|
||||||
|
start_b.click();
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
// B's pending boundary must transfer effects to A regardless of whether
|
||||||
|
// it resolves before or after A renders.
|
||||||
|
if (boundary_first) {
|
||||||
|
resolve_b.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p>`);
|
||||||
|
assert.deepEqual(logs, []);
|
||||||
|
resolve_a.click();
|
||||||
|
} else {
|
||||||
|
resolve_a.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(target.innerHTML, `${buttons}<p>1</p><span>pending</span>`);
|
||||||
|
resolve_b.click();
|
||||||
|
}
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(target.innerHTML, `${buttons}<p>1</p><strong>ready</strong>`);
|
||||||
|
assert.deepEqual(logs, ['ready']);
|
||||||
|
|
||||||
|
reset.click();
|
||||||
|
await tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
<script>
|
||||||
|
import Child from './Child.svelte';
|
||||||
|
|
||||||
|
let x = $state(0);
|
||||||
|
let show = $state(false);
|
||||||
|
let resolve_a;
|
||||||
|
let resolve_b;
|
||||||
|
|
||||||
|
function delay_a(value) {
|
||||||
|
if (value === 0) return 0;
|
||||||
|
return new Promise(resolve => { resolve_a = () => resolve(value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
function delay_b() {
|
||||||
|
return new Promise(resolve => { resolve_b = () => resolve('ready'); });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => x = 1}>start A: x = 1</button>
|
||||||
|
<button onclick={() => show = true}>start B: show boundary</button>
|
||||||
|
<button onclick={() => resolve_a()}>resolve A</button>
|
||||||
|
<button onclick={() => resolve_b()}>resolve boundary</button>
|
||||||
|
<button onclick={() => { show = false; x = 0; }}>reset</button>
|
||||||
|
|
||||||
|
<p>{await delay_a(x)}</p>
|
||||||
|
{#if show && x > 0}
|
||||||
|
<svelte:boundary>
|
||||||
|
{#snippet pending()}<span>pending</span>{/snippet}
|
||||||
|
<Child delay={delay_b} />
|
||||||
|
</svelte:boundary>
|
||||||
|
{/if}
|
||||||
Loading…
Reference in new issue