mirror of https://github.com/sveltejs/svelte
entangle-batches-3
parent
2f4d50c5c6
commit
f8276d5fbf
@ -0,0 +1,41 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = `
|
||||
<button>fork</button>
|
||||
<button>update</button>
|
||||
<button>resolve</button>
|
||||
<button>discard</button>
|
||||
`;
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, instance }) {
|
||||
const [fork_button, update, resolve, discard] = target.querySelectorAll('button');
|
||||
|
||||
fork_button.click();
|
||||
await tick();
|
||||
assert.equal(instance.get_calls(), 1);
|
||||
assert.htmlEqual(target.innerHTML, buttons);
|
||||
|
||||
// Transfer an invalidation into the fork while its async work is pending.
|
||||
update.click();
|
||||
await tick();
|
||||
assert.equal(instance.get_calls(), 1); // can also be 2 at this point already, would also be ok
|
||||
|
||||
try {
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.equal(instance.get_calls(), 2);
|
||||
|
||||
// Completing the replacement must not replay the same invalidation.
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.equal(instance.get_calls(), 2);
|
||||
} finally {
|
||||
discard.click();
|
||||
await tick();
|
||||
}
|
||||
|
||||
assert.htmlEqual(target.innerHTML, buttons);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,42 @@
|
||||
<script>
|
||||
import { fork } from 'svelte';
|
||||
|
||||
let sharedState = $state(0);
|
||||
let show = $state(false);
|
||||
let f;
|
||||
let calls = 0;
|
||||
|
||||
const resolvers = [];
|
||||
|
||||
// Only evaluated in the fork, with a fresh object on each evaluation.
|
||||
const searchParams = $derived({ value: sharedState });
|
||||
|
||||
export function get_calls() {
|
||||
return calls;
|
||||
}
|
||||
|
||||
function load(value) {
|
||||
calls += 1;
|
||||
return new Promise((resolve) => resolvers.push(() => resolve(value)));
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
onclick={() => {
|
||||
f = fork(() => {
|
||||
sharedState = 1;
|
||||
show = true;
|
||||
});
|
||||
}}
|
||||
>fork</button
|
||||
>
|
||||
<button onclick={() => sharedState++}>update</button>
|
||||
<button onclick={() => resolvers.shift()?.()}>resolve</button>
|
||||
<button onclick={() => f?.discard()}>discard</button>
|
||||
|
||||
{#if show}
|
||||
<svelte:boundary>
|
||||
{#snippet pending()}loading{/snippet}
|
||||
<p>{await load(searchParams.value)}</p>
|
||||
</svelte:boundary>
|
||||
{/if}
|
||||
@ -0,0 +1,35 @@
|
||||
import { flushSync, tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = `
|
||||
<button>preload</button>
|
||||
<button>increment</button>
|
||||
<button>commit</button>
|
||||
<button>merge</button>
|
||||
<button>resolve</button>
|
||||
`;
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, logs }) {
|
||||
const [preload, increment, commit, merge, resolve] = target.querySelectorAll('button');
|
||||
|
||||
preload.click();
|
||||
flushSync(() => increment.click());
|
||||
assert.deepEqual(logs, [0]);
|
||||
|
||||
commit.click();
|
||||
assert.deepEqual(logs, [0, 1]);
|
||||
|
||||
flushSync(() => merge.click());
|
||||
await tick();
|
||||
assert.deepEqual(logs, [0, 1]);
|
||||
assert.htmlEqual(target.innerHTML, buttons);
|
||||
|
||||
// Resolve the obsolete request for 0, then the existing request for 1.
|
||||
resolve.click();
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, [0, 1]);
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>1</p>`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
<script>
|
||||
import { fork } from 'svelte';
|
||||
|
||||
let show = $state(false);
|
||||
let count = $state(0);
|
||||
let gate = $state(1);
|
||||
let f;
|
||||
|
||||
const deferred = [];
|
||||
|
||||
function load(value) {
|
||||
console.log(value);
|
||||
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => (f = fork(() => (show = true)))}>preload</button>
|
||||
<button onclick={() => count++}>increment</button>
|
||||
<button onclick={() => f.commit()}>commit</button>
|
||||
<button onclick={() => gate++}>merge</button>
|
||||
<button onclick={() => deferred.shift()?.()}>resolve</button>
|
||||
|
||||
{#if show && gate > 0}
|
||||
<p>{await load(count)}</p>
|
||||
{/if}
|
||||
@ -0,0 +1,45 @@
|
||||
import { flushSync, tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = `
|
||||
<button>preload</button>
|
||||
<button>increment</button>
|
||||
<button>commit</button>
|
||||
<button>reveal</button>
|
||||
<button>discard</button>
|
||||
<button>preload second</button>
|
||||
<button>commit second</button>
|
||||
<button>reset</button>
|
||||
`;
|
||||
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
const [preload, increment, commit, reveal, discard, preload_second, commit_second, reset] =
|
||||
target.querySelectorAll('button');
|
||||
|
||||
for (const mode of ['commit', 'reveal', 'second-fork']) {
|
||||
preload.click();
|
||||
flushSync(() => increment.click());
|
||||
assert.htmlEqual(target.innerHTML, buttons);
|
||||
|
||||
if (mode === 'commit') {
|
||||
commit.click();
|
||||
await tick();
|
||||
} else if (mode === 'reveal') {
|
||||
flushSync(() => reveal.click());
|
||||
discard.click();
|
||||
} else {
|
||||
preload_second.click();
|
||||
discard.click();
|
||||
commit_second.click();
|
||||
await tick();
|
||||
}
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>1 2</p>`);
|
||||
flushSync(() => increment.click());
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>2 4</p>`);
|
||||
flushSync(() => reset.click());
|
||||
assert.htmlEqual(target.innerHTML, buttons);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
<script>
|
||||
import { fork } from 'svelte';
|
||||
|
||||
let show = $state(false);
|
||||
let count = $state(0);
|
||||
let doubled = $derived(count * 2);
|
||||
let f;
|
||||
let other;
|
||||
</script>
|
||||
|
||||
<button onclick={() => (f = fork(() => (show = true)))}>preload</button>
|
||||
<button onclick={() => count++}>increment</button>
|
||||
<button onclick={() => f.commit()}>commit</button>
|
||||
<button onclick={() => (show = true)}>reveal</button>
|
||||
<button onclick={() => f.discard()}>discard</button>
|
||||
<button onclick={() => (other = fork(() => (show = true)))}>preload second</button>
|
||||
<button onclick={() => other.commit()}>commit second</button>
|
||||
<button onclick={() => { show = false; count = 0; }}>reset</button>
|
||||
|
||||
{#if show}
|
||||
<p>{count} {doubled}</p>
|
||||
{/if}
|
||||
@ -0,0 +1,32 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = `
|
||||
<button>preload</button>
|
||||
<button>reveal</button>
|
||||
<button>hide</button>
|
||||
<button>commit</button>
|
||||
`;
|
||||
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
const [preload, reveal, hide, commit] = target.querySelectorAll('button');
|
||||
|
||||
preload.click();
|
||||
reveal.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`${buttons}<b>0</b><p>constant</p><p>keyed</p><p>boundary</p>`
|
||||
);
|
||||
|
||||
hide.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<b>0</b>`);
|
||||
|
||||
// The remaining fork write must not resurrect its obsolete branch selection.
|
||||
commit.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<b>1</b>`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
<script>
|
||||
import { fork } from 'svelte';
|
||||
|
||||
let show = $state(false);
|
||||
let other = $state(0);
|
||||
let f;
|
||||
</script>
|
||||
|
||||
<button onclick={() => (f = fork(() => { show = true; other = 1; }))}>preload</button>
|
||||
<button onclick={() => (show = true)}>reveal</button>
|
||||
<button onclick={() => (show = false)}>hide</button>
|
||||
<button onclick={() => f.commit()}>commit</button>
|
||||
|
||||
<b>{other}</b>
|
||||
|
||||
{#if show}
|
||||
{#if true}<p>constant</p>{/if}
|
||||
{#key 1}<p>keyed</p>{/key}
|
||||
<svelte:boundary onerror={console.error}>
|
||||
<p>boundary</p>
|
||||
</svelte:boundary>
|
||||
{/if}
|
||||
@ -0,0 +1,8 @@
|
||||
<script>
|
||||
let { total, navigating } = $props();
|
||||
const pages = $derived(Math.ceil(total / 28));
|
||||
const pending = $derived(navigating && pages > 1);
|
||||
</script>
|
||||
|
||||
{#if pending}pending{/if}
|
||||
<p>{pages}</p>
|
||||
@ -0,0 +1,73 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = `
|
||||
<button>preload</button>
|
||||
<button>reveal outer</button>
|
||||
<button>reveal and navigate</button>
|
||||
<button>navigate</button>
|
||||
<button>resolve</button>
|
||||
<button>commit</button>
|
||||
<button>discard</button>
|
||||
<button>reset</button>
|
||||
`;
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, logs }) {
|
||||
const [preload, reveal, reveal_and_navigate, navigate, resolve, commit, discard, reset] =
|
||||
target.querySelectorAll('button');
|
||||
|
||||
for (const mode of ['separate', 'together', 'pending']) {
|
||||
for (const finish of [commit, discard]) {
|
||||
preload.click();
|
||||
|
||||
if (mode !== 'pending') {
|
||||
resolve.click();
|
||||
await tick();
|
||||
}
|
||||
|
||||
assert.htmlEqual(target.innerHTML, buttons);
|
||||
assert.deepEqual(logs, ['load']);
|
||||
|
||||
if (mode === 'together') {
|
||||
reveal_and_navigate.click();
|
||||
} else {
|
||||
reveal.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<section></section>`);
|
||||
navigate.click();
|
||||
}
|
||||
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<section></section>`);
|
||||
assert.deepEqual(logs, ['load']);
|
||||
|
||||
if (mode === 'pending') {
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<section></section>`);
|
||||
}
|
||||
|
||||
finish.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`${buttons}<section>${finish === commit ? 'pending <p>2</p>' : ''}</section>`
|
||||
);
|
||||
assert.deepEqual(logs, ['load']);
|
||||
|
||||
if (finish === commit) {
|
||||
navigate.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<section><p>2</p></section>`);
|
||||
assert.deepEqual(logs, ['load']);
|
||||
}
|
||||
|
||||
reset.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, buttons);
|
||||
logs.length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,36 @@
|
||||
<script>
|
||||
import { fork } from 'svelte';
|
||||
import Child from './Child.svelte';
|
||||
|
||||
let outer = $state(false);
|
||||
let inner = $state(false);
|
||||
let navigating = $state(false);
|
||||
let f;
|
||||
|
||||
const deferred = [];
|
||||
|
||||
function load() {
|
||||
console.log('load');
|
||||
return new Promise((resolve) => deferred.push(() => resolve(42)));
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => (f = fork(() => { outer = true; inner = true; }))}>preload</button>
|
||||
<button onclick={() => (outer = true)}>reveal outer</button>
|
||||
<button onclick={() => { outer = true; navigating = true; }}>reveal and navigate</button>
|
||||
<button onclick={() => (navigating = !navigating)}>navigate</button>
|
||||
<button onclick={() => deferred.shift()?.()}>resolve</button>
|
||||
<button onclick={() => f.commit()}>commit</button>
|
||||
<button onclick={() => f.discard()}>discard</button>
|
||||
<button onclick={() => { outer = false; inner = false; navigating = false; }}>reset</button>
|
||||
|
||||
{#if outer}
|
||||
<section>
|
||||
{#if inner}
|
||||
<svelte:boundary onerror={(error) => console.log(error.message)}>
|
||||
{#snippet pending()}loading{/snippet}
|
||||
<Child total={await load()} {navigating} />
|
||||
</svelte:boundary>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
@ -0,0 +1,8 @@
|
||||
<script>
|
||||
let { total, navigating } = $props();
|
||||
const pages = $derived(Math.ceil(total / 28));
|
||||
const pending = $derived(navigating && pages > 1);
|
||||
</script>
|
||||
|
||||
{#if pending}pending{/if}
|
||||
<p>{pages}</p>
|
||||
@ -0,0 +1,50 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = `
|
||||
<button>preload</button>
|
||||
<button>navigate</button>
|
||||
<button>commit</button>
|
||||
<button>discard</button>
|
||||
`;
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, logs }) {
|
||||
const [preload, navigate, commit, discard] = target.querySelectorAll('button');
|
||||
|
||||
preload.click();
|
||||
// Let the async child resolve, without committing its fork.
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
assert.htmlEqual(target.innerHTML, buttons);
|
||||
assert.deepEqual(logs, []);
|
||||
|
||||
// A real-world update must not evaluate the speculative child in the real world.
|
||||
navigate.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, buttons);
|
||||
assert.deepEqual(logs, []);
|
||||
discard.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, buttons);
|
||||
assert.deepEqual(logs, []);
|
||||
|
||||
navigate.click();
|
||||
await tick();
|
||||
preload.click();
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
navigate.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, buttons);
|
||||
assert.deepEqual(logs, []);
|
||||
|
||||
commit.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}pending <p>2</p>`);
|
||||
assert.deepEqual(logs, []);
|
||||
|
||||
navigate.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>2</p>`);
|
||||
assert.deepEqual(logs, []);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
<script>
|
||||
import { fork } from 'svelte';
|
||||
import Child from './Child.svelte';
|
||||
|
||||
let show = $state(false);
|
||||
let navigating = $state(false);
|
||||
let f;
|
||||
</script>
|
||||
|
||||
<button onclick={() => (f = fork(() => (show = true)))}>preload</button>
|
||||
<button onclick={() => (navigating = !navigating)}>navigate</button>
|
||||
<button onclick={() => f.commit()}>commit</button>
|
||||
<button onclick={() => f.discard()}>discard</button>
|
||||
|
||||
{#if show}
|
||||
<svelte:boundary onerror={(error) => console.log(error.message)}>
|
||||
{#snippet pending()}loading{/snippet}
|
||||
<Child total={await Promise.resolve(42)} {navigating} />
|
||||
</svelte:boundary>
|
||||
{/if}
|
||||
Loading…
Reference in new issue