mirror of https://github.com/sveltejs/svelte
parent
33e3540bb9
commit
ab75873a57
@ -0,0 +1,21 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = `<button>fork</button><button>show</button><button>commit</button>`;
|
||||
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
const [fork, show, commit] = target.querySelectorAll('button');
|
||||
|
||||
fork.click();
|
||||
await tick();
|
||||
|
||||
show.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p>`);
|
||||
|
||||
commit.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<span>two</span><p>2</p>`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,17 @@
|
||||
<script>
|
||||
import { fork } from 'svelte';
|
||||
|
||||
let s1 = $state(0);
|
||||
let show = $state(false);
|
||||
let d2 = $derived(s1 * 2);
|
||||
let f;
|
||||
</script>
|
||||
|
||||
<button onclick={() => (f = fork(() => (s1 = 1)))}>fork</button>
|
||||
<button onclick={() => (show = true)}>show</button>
|
||||
<button onclick={() => f.commit()}>commit</button>
|
||||
|
||||
{#if d2 === 2}<span>two</span>{/if}
|
||||
{#if show}
|
||||
<p>{d2}</p>
|
||||
{/if}
|
||||
@ -0,0 +1,8 @@
|
||||
<script>
|
||||
import { untrack } from 'svelte';
|
||||
let { a, b, c, slow, fast } = $props();
|
||||
</script>
|
||||
|
||||
<p>{await slow(a)}</p>
|
||||
<p>{await slow(b)}</p>
|
||||
<p>{await fast(`${c}:${untrack(() => `${a}${b}`)}`)}</p>
|
||||
@ -0,0 +1,43 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = `
|
||||
<button>a</button>
|
||||
<button>b</button>
|
||||
<button>c</button>
|
||||
<button>resolve</button>
|
||||
<button>resolve last</button>
|
||||
`;
|
||||
|
||||
// B3 reads values held by both B1 and B2 and therefore depends on both. When B3 merges
|
||||
// into B2 (the nearest earlier related batch), B2 must inherit B3's dependency on B1,
|
||||
// otherwise B2+B3 commit before B1 and leak B1's uncommitted value into the DOM
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
const [a, b, c, resolve, resolve_last] = target.querySelectorAll('button');
|
||||
|
||||
resolve.click();
|
||||
await tick();
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p><p>0</p><p>0:00</p>`);
|
||||
|
||||
a.click(); // B1: slow(a=1) pending
|
||||
await tick();
|
||||
b.click(); // B2: slow(b=1) pending
|
||||
await tick();
|
||||
c.click(); // B3: reads a=1 (held by B1) and b=1 (held by B2) -> depends on both, merges into B2
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p><p>0</p><p>0:00</p>`);
|
||||
|
||||
// B2 finishes first: it must still wait for B1
|
||||
resolve_last.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p><p>0</p><p>0:00</p>`);
|
||||
|
||||
// B1 finishes: everything commits together
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>1</p><p>1</p><p>1:11</p>`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
<script>
|
||||
import Child from './Child.svelte';
|
||||
let a = $state(0);
|
||||
let b = $state(0);
|
||||
let c = $state(0);
|
||||
|
||||
const resolvers = [];
|
||||
function slow(v) {
|
||||
return new Promise((r) => resolvers.push(() => r(v)));
|
||||
}
|
||||
function fast(v) {
|
||||
return Promise.resolve(v);
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => a++}>a</button>
|
||||
<button onclick={() => b++}>b</button>
|
||||
<button onclick={() => c++}>c</button>
|
||||
<button onclick={() => resolvers.shift()?.()}>resolve</button>
|
||||
<button onclick={() => resolvers.pop()?.()}>resolve last</button>
|
||||
|
||||
<svelte:boundary>
|
||||
{#snippet pending()}loading{/snippet}
|
||||
<Child {a} {b} {c} {slow} {fast} />
|
||||
</svelte:boundary>
|
||||
@ -0,0 +1,40 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = `
|
||||
<button>flip</button>
|
||||
<button>a</button>
|
||||
<button>b</button>
|
||||
<button>resolve</button>
|
||||
`;
|
||||
|
||||
// A reaction that reads its existing dependencies in a different order than last time
|
||||
// is not a "new reader" of those dependencies — it must keep seeing its own batch's view,
|
||||
// not the latest (uncommitted) value of a later pending batch
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
const [flip, a, , resolve] = target.querySelectorAll('button');
|
||||
|
||||
resolve.click();
|
||||
await tick();
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>a0b0</p><p>true</p><p>a0</p>`);
|
||||
|
||||
flip.click(); // B1: cond = false, slow(cond) pending
|
||||
await tick();
|
||||
a.click(); // B2: a = 'a1', slow(a) pending
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>a0b0</p><p>true</p><p>a0</p>`);
|
||||
|
||||
// B1 commits while B2 is still pending. The flipped branch reads `b` then `a` (reordered
|
||||
// deps) — `a` must still resolve to B1's view (a0), not B2's uncommitted a1
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>b0a0</p><p>false</p><p>a0</p>`);
|
||||
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>b0a1</p><p>false</p><p>a1</p>`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
<script>
|
||||
let cond = $state(true);
|
||||
let a = $state('a0');
|
||||
let b = $state('b0');
|
||||
|
||||
const resolvers = [];
|
||||
function slow(v) {
|
||||
return new Promise((r) => resolvers.push(() => r(v)));
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => (cond = !cond)}>flip</button>
|
||||
<button onclick={() => (a = "a1")}>a</button>
|
||||
<button onclick={() => (b = "b1")}>b</button>
|
||||
<button onclick={() => resolvers.shift()?.()}>resolve</button>
|
||||
|
||||
<svelte:boundary>
|
||||
{#snippet pending()}loading{/snippet}
|
||||
<p>{cond ? a + b : b + a}</p>
|
||||
<p>{await slow(cond)}</p>
|
||||
<p>{await slow(a)}</p>
|
||||
</svelte:boundary>
|
||||
@ -0,0 +1,8 @@
|
||||
<script>
|
||||
let { a, b, c, slow, track } = $props();
|
||||
let sa = $derived(await slow(a));
|
||||
</script>
|
||||
|
||||
<p>{sa}</p>
|
||||
<p>{await slow(b + c)}</p>
|
||||
<p>{await track(sa, b, c)}</p>
|
||||
@ -0,0 +1,44 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = `<button>a</button><button>bc</button><button>resolve</button>`;
|
||||
|
||||
// An effect that runs in an earlier batch and reads _several_ values that a later batch
|
||||
// holds newer versions of must only be re-run once in that later batch, not once per value
|
||||
export default test({
|
||||
async test({ assert, target, instance }) {
|
||||
const [a, bc, resolve] = target.querySelectorAll('button');
|
||||
|
||||
resolve.click();
|
||||
await tick();
|
||||
resolve.click();
|
||||
await tick();
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.deepEqual(instance.get_calls(), ['0/0/0']);
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p><p>0</p><p>0/0/0</p>`);
|
||||
|
||||
// B1: a++ -> `sa` pending
|
||||
a.click();
|
||||
await tick();
|
||||
assert.deepEqual(instance.get_calls(), ['0/0/0']);
|
||||
|
||||
// B2: b++, c++ -> `slow(b + c)` pending, `track` re-runs with B2's view
|
||||
bc.click();
|
||||
await tick();
|
||||
assert.deepEqual(instance.get_calls(), ['0/0/0', '0/1/1']);
|
||||
|
||||
// resolve `sa` in B1: `track` re-runs in B1 (reading B1's stale view of b/c) and,
|
||||
// because it read two values held by B2, exactly once more in B2
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.deepEqual(instance.get_calls(), ['0/0/0', '0/1/1', '1/0/0', '1/1/1']);
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
resolve.click();
|
||||
await tick();
|
||||
}
|
||||
assert.deepEqual(instance.get_calls(), ['0/0/0', '0/1/1', '1/0/0', '1/1/1']);
|
||||
assert.htmlEqual(target.innerHTML, `${buttons}<p>1</p><p>2</p><p>1/1/1</p>`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,30 @@
|
||||
<script>
|
||||
import Child from './Child.svelte';
|
||||
let a = $state(0);
|
||||
let b = $state(0);
|
||||
let c = $state(0);
|
||||
let calls = [];
|
||||
|
||||
const resolvers = [];
|
||||
function slow(v) {
|
||||
return new Promise((r) => resolvers.push(() => r(v)));
|
||||
}
|
||||
|
||||
export function get_calls() {
|
||||
return calls;
|
||||
}
|
||||
|
||||
function track(...args) {
|
||||
calls.push(args.join('/'));
|
||||
return slow(args.join('/'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => a++}>a</button>
|
||||
<button onclick={() => { b++; c++; }}>bc</button>
|
||||
<button onclick={() => resolvers.shift()?.()}>resolve</button>
|
||||
|
||||
<svelte:boundary>
|
||||
{#snippet pending()}loading{/snippet}
|
||||
<Child {a} {b} {c} {slow} {track} />
|
||||
</svelte:boundary>
|
||||
Loading…
Reference in new issue