diff --git a/packages/svelte/tests/runtime-runes/samples/async-branch-commit-order/_config.js b/packages/svelte/tests/runtime-runes/samples/async-branch-commit-order/_config.js new file mode 100644 index 0000000000..11c3bce995 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-branch-commit-order/_config.js @@ -0,0 +1,27 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +const buttons = ''; + +export default test({ + async test({ assert, target }) { + await tick(); + const [a, b, shift] = target.querySelectorAll('button'); + + // batch A waits on its key; batch B registers its key right away + a.click(); + await tick(); + b.click(); + await tick(); + + // the older batch commits first + shift.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `
0
1
${buttons}`); + + // the newer batch commits with its own branch + shift.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `1
B
${buttons}`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-branch-commit-order/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-branch-commit-order/main.svelte new file mode 100644 index 0000000000..d0b1a57053 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-branch-commit-order/main.svelte @@ -0,0 +1,24 @@ + + +{await delay(b)}
+ +{#key k} +{k}
+{/key} + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-assign-keeps-maybe-dirty/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived-assign-keeps-maybe-dirty/_config.js new file mode 100644 index 0000000000..ae9ef996f6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-assign-keeps-maybe-dirty/_config.js @@ -0,0 +1,39 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +const buttons = + ''; + +export default test({ + mode: ['client'], + async test({ assert, target, logs }) { + await tick(); + const [s, u, d, resolve_a, resolve_b, resolve_c] = target.querySelectorAll('button'); + + // batch A: `d` becomes 10, but is not evaluated yet (the `` is deferred) + s.click(); + await tick(); + + // batch B + u.click(); + await tick(); + + // batch C overrides `d` while A and B are pending. This must not mark `d` as + // clean, because the other batches haven't seen its new value (10) yet + d.click(); + await tick(); + + // B sees A's change (but not C's), so `d` is 10 in its world + resolve_b.click(); + await tick(); + assert.equal(logs[0], 'probe 10'); + + resolve_a.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}1
0
1010`); + + resolve_c.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}1
1
9999`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-assign-keeps-maybe-dirty/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-assign-keeps-maybe-dirty/main.svelte new file mode 100644 index 0000000000..08c00a4911 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-assign-keeps-maybe-dirty/main.svelte @@ -0,0 +1,42 @@ + + + + + + + + + +{await delay('a', s)}
+{await delay('c', w)}
+ +{#if show} + {d} +{/if} + +{#if await delay('b', u)} + {await probe(d)} +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-equal-real-value-hidden/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived-equal-real-value-hidden/_config.js new file mode 100644 index 0000000000..d319831f2e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-equal-real-value-hidden/_config.js @@ -0,0 +1,33 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +const buttons = + ''; + +export default test({ + mode: ['client'], + async test({ assert, target }) { + await tick(); + const [x, s, resolve_a, resolve_b] = target.querySelectorAll('button'); + assert.htmlEqual(target.innerHTML, `${buttons}0:5
5
`); + + // batch A: `t` becomes truthy (pending) + x.click(); + await tick(); + + // batch B: `d` becomes 0 (pending) + s.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}0:5
5
`); + + // In A's world, `d` is recomputed to 0. That's the real value, but not the value + // A sees for it (the one from before B's change), so A has to record it + resolve_a.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}1:0
5
`); + + resolve_b.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}1:0
0
`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-equal-real-value-hidden/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-equal-real-value-hidden/main.svelte new file mode 100644 index 0000000000..9fe34a5e12 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-equal-real-value-hidden/main.svelte @@ -0,0 +1,25 @@ + + + + + + + +{#if d >= 0} +{t}:{d}
+{/if} + +{await delay('b', s, 5)}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-previous-effect-batch/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived-previous-effect-batch/_config.js new file mode 100644 index 0000000000..8f021032e0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-previous-effect-batch/_config.js @@ -0,0 +1,33 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +// Batch B (x) commits while an earlier batch A (y) is still pending, but B stays around because +// of the async work in the new boundary. While B flushes its effects, an effect creates a new batch C, +// and `d` is computed for the first time afterwards (so it's captured in both B and C). When A +// commits later, it sees B's previous values, which must be consistent (`d` must be `x * 2`). +export default test({ + async test({ assert, target, logs }) { + await tick(); + const [y, x, shift] = target.querySelectorAll('button'); + assert.deepEqual(logs, [0, 0, 0]); + + // A + y.click(); + await tick(); + + // B + x.click(); + await tick(); + assert.deepEqual(logs, [0, 0, 0, 0, 1, 2]); + + // A commits + shift.click(); + await tick(); + + for (let i = 0; i < logs.length; i += 3) { + assert.equal(logs[i + 2], logs[i + 1] * 2, `torn read: ${logs.slice(i, i + 3)}`); + } + + assert.deepEqual(logs.slice(-3), [1, 1, 2]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-previous-effect-batch/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-previous-effect-batch/main.svelte new file mode 100644 index 0000000000..5cd0876884 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-previous-effect-batch/main.svelte @@ -0,0 +1,38 @@ + + + + + + +{y ? await push(y) : y}
+ +{#if x} +{await push(x)}
+ {#snippet pending()} +loading
+ {/snippet} +page: 1
result: 1/a
`); + + // filter = 'b' -> eager batch re-runs load(1, 'b'), pending + filter.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}page: 1
result: 1/a
`); + + // resolve load(1, 'b') -> the eager batch commits on its own + pop.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}page: 1
result: 1/b
`); + + // resolve load(2, 'a') -> B1 commits (showing B1's own result is fine, as is + // waiting for load(2, 'b'), as long as the result matches the page) + shift.click(); + await tick(); + const [p1, p2] = target.querySelectorAll('p'); + assert.equal(p1.textContent, 'page: 2'); + assert.match(p2.textContent ?? '', /^result: 2\//); + + // load(2, 'b') resolves + shift.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}page: 2
result: 2/b
`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-eager-batch-no-merge/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-eager-batch-no-merge/main.svelte new file mode 100644 index 0000000000..008fabe2e5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-eager-batch-no-merge/main.svelte @@ -0,0 +1,20 @@ + + + + + + + +page: {page}
+result: {await load(page, $state.eager(filter))}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-resume-no-rerun/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-resume-no-rerun/Child.svelte new file mode 100644 index 0000000000..6f48eaf129 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-resume-no-rerun/Child.svelte @@ -0,0 +1,9 @@ + + +child
diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-resume-no-rerun/_config.js b/packages/svelte/tests/runtime-runes/samples/async-effect-resume-no-rerun/_config.js new file mode 100644 index 0000000000..4fd60a98ce --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-resume-no-rerun/_config.js @@ -0,0 +1,37 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + async test({ assert, target, logs, raf }) { + await tick(); + const [x, s, toggle, shift] = target.querySelectorAll('button'); + assert.deepEqual(logs, ['effect false']); + + // keep an earlier batch pending, so that the next one commits while time travelling + x.click(); + await tick(); + + // `big` stays `false`, so the effect must not re-run + s.click(); + await tick(); + assert.deepEqual(logs, ['effect false']); + + shift.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + '1
child
{await push(x)}
+ +{#if show} +0
0
0 false-0
`); + + // A commits, B is still pending + shift.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}1
0
1 false-0
`); + + // B commits + shift.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}1
1
1 false-1
`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-write-keeps-batch-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-write-keeps-batch-derived/main.svelte new file mode 100644 index 0000000000..1791827eae --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-write-keeps-batch-derived/main.svelte @@ -0,0 +1,32 @@ + + + + + + +{await push(x)}
+{await push(t)}
+ +{#key label}{/key} + +{x} {label}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-new-reader/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-commit-new-reader/_config.js new file mode 100644 index 0000000000..a9e860bec7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-fork-commit-new-reader/_config.js @@ -0,0 +1,35 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +const buttons = + ''; + +// The template effect is deferred by the fork (it depends on `s` via `big`), but before the +// fork is committed, it re-runs in the real world and starts reading `s` directly. +// Committing the fork (while another batch is pending) must still update it. +export default test({ + async test({ assert, target }) { + await tick(); + const [u, f, cond, commit, shift] = target.querySelectorAll('button'); + + // unrelated pending batch + u.click(); + await tick(); + + f.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}0
false
`); + + cond.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}0
0
`); + + commit.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}0
1
`); + + shift.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `${buttons}1
1
`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-commit-new-reader/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-commit-new-reader/main.svelte new file mode 100644 index 0000000000..a52d6b4dfd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-fork-commit-new-reader/main.svelte @@ -0,0 +1,25 @@ + + + + + + + + +{await push(u)}
+{cond ? s : big}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-merge-blocking-pending/_config.js b/packages/svelte/tests/runtime-runes/samples/async-merge-blocking-pending/_config.js new file mode 100644 index 0000000000..9cd8eba0b3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-merge-blocking-pending/_config.js @@ -0,0 +1,34 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +const buttons = ''; + +export default test({ + async test({ assert, target }) { + await tick(); + const [x, toggle, pop] = target.querySelectorAll('button'); + + // X is deferred on its own await + x.click(); + await tick(); + // Y is deferred on the await inside the new branch + toggle.click(); + await tick(); + // Z hides that branch again, merges into Y, which merges into X + toggle.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `n = 0
x = 0
${buttons}`); + + // the branch's await resolves while it is hidden + pop.click(); + await tick(); + // W shows the branch again and merges into X + toggle.click(); + await tick(); + + // nothing is pending any more, so X commits + pop.click(); + await tick(); + assert.htmlEqual(target.innerHTML, `n = 4
x = 1
cond: e
${buttons}`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-merge-blocking-pending/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-merge-blocking-pending/main.svelte new file mode 100644 index 0000000000..d4cbd8c5b6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-merge-blocking-pending/main.svelte @@ -0,0 +1,29 @@ + + +n = {n}
+x = {await delay(x)}
+ {#if cond} +cond: {await delay('e')}
+ {/if} + + {#snippet pending()} +loading
+ {/snippet} +a: 0
` + ); + // the removed items must be destroyed, not kept around for the discarded batch Y + assert.deepEqual(logs, ['destroy 1', 'destroy 2', 'destroy 3']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-merge-discard-after-error/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-merge-discard-after-error/main.svelte new file mode 100644 index 0000000000..ea1bfd8594 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-merge-discard-after-error/main.svelte @@ -0,0 +1,39 @@ + + + + + + + + +a: {a}
+ {/if} + + + {#each list.slice(0, a + 10) as item (item)} +loading...
+ {/snippet} +0
1
{await delay(items[0])}
+ +{#if await delay(show)} +