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