Skip to content

Commit 86eb09b

Browse files
authored
chore: even more async tests (#18990)
extracting some tests from #18983 — these pass on `main`, `pt-1`, `pt-2` and `pt-3`. The others in that PR don't pass on `main`, so will be dealt with separately
1 parent 4d2ad8a commit 86eb09b

8 files changed

Lines changed: 245 additions & 0 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
const buttons =
5+
'<button>x</button><button>s</button><button>resolve a</button><button>resolve b</button>';
6+
7+
export default test({
8+
mode: ['client'],
9+
async test({ assert, target }) {
10+
await tick();
11+
const [x, s, resolve_a, resolve_b] = target.querySelectorAll('button');
12+
assert.htmlEqual(target.innerHTML, `${buttons}<p>0:5</p><p>5</p>`);
13+
14+
// batch A: `t` becomes truthy (pending)
15+
x.click();
16+
await tick();
17+
18+
// batch B: `d` becomes 0 (pending)
19+
s.click();
20+
await tick();
21+
assert.htmlEqual(target.innerHTML, `${buttons}<p>0:5</p><p>5</p>`);
22+
23+
// In A's world, `d` is recomputed to 0. That's the real value, but not the value
24+
// A sees for it (the one from before B's change), so A has to record it
25+
resolve_a.click();
26+
await tick();
27+
assert.htmlEqual(target.innerHTML, `${buttons}<p>1:0</p><p>5</p>`);
28+
29+
resolve_b.click();
30+
await tick();
31+
assert.htmlEqual(target.innerHTML, `${buttons}<p>1:0</p><p>0</p>`);
32+
}
33+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script>
2+
let x = $state(0);
3+
let s = $state(5);
4+
5+
const pending = new Map();
6+
7+
function delay(name, value, initial) {
8+
if (value === initial) return value;
9+
return new Promise((resolve) => pending.set(name, () => resolve(value)));
10+
}
11+
12+
const t = $derived(await delay('a', x, 0));
13+
const d = $derived(t ? 0 : s);
14+
</script>
15+
16+
<button onclick={() => x++}>x</button>
17+
<button onclick={() => (s = 0)}>s</button>
18+
<button onclick={() => pending.get('a')?.()}>resolve a</button>
19+
<button onclick={() => pending.get('b')?.()}>resolve b</button>
20+
21+
{#if d >= 0}
22+
<p>{t}:{d}</p>
23+
{/if}
24+
25+
<p>{await delay('b', s, 5)}</p>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
const buttons = '<button>x</button><button>t</button><button>shift</button>';
5+
6+
// While batch A (x) commits, a pre effect writes to `s`. That marks `label` (via `big`) as
7+
// maybe dirty, but it doesn't actually change. `label` must still show the value from A's
8+
// point of view, not the one from the later, still-pending batch B (t).
9+
export default test({
10+
async test({ assert, target }) {
11+
await tick();
12+
const [x, t, shift] = target.querySelectorAll('button');
13+
14+
// A
15+
x.click();
16+
await tick();
17+
18+
// B
19+
t.click();
20+
await tick();
21+
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p><p>0</p><p>0 false-0</p>`);
22+
23+
// A commits, B is still pending
24+
shift.click();
25+
await tick();
26+
assert.htmlEqual(target.innerHTML, `${buttons}<p>1</p><p>0</p><p>1 false-0</p>`);
27+
28+
// B commits
29+
shift.click();
30+
await tick();
31+
assert.htmlEqual(target.innerHTML, `${buttons}<p>1</p><p>1</p><p>1 false-1</p>`);
32+
}
33+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script>
2+
import { untrack } from 'svelte';
3+
4+
let x = $state(0);
5+
let t = $state(0);
6+
let s = $state(0);
7+
8+
// `s` changes, but `big` doesn't
9+
let big = $derived(s > 100);
10+
let label = $derived(`${big}-${t}`);
11+
12+
const queue = [];
13+
function push(v) {
14+
if (!v) return v;
15+
return new Promise((r) => queue.push(() => r(v)));
16+
}
17+
18+
$effect.pre(() => {
19+
if (x) untrack(() => s++);
20+
});
21+
</script>
22+
23+
<button onclick={() => x++}>x</button>
24+
<button onclick={() => t++}>t</button>
25+
<button onclick={() => queue.shift()()}>shift</button>
26+
27+
<p>{await push(x)}</p>
28+
<p>{await push(t)}</p>
29+
<!-- evaluates `label` during traversal of B -->
30+
{#key label}{/key}
31+
32+
<p>{x} {label}</p>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
const buttons =
5+
'<button>u</button><button>fork</button><button>cond</button><button>commit</button><button>shift</button>';
6+
7+
// The template effect is deferred by the fork (it depends on `s` via `big`), but before the
8+
// fork is committed, it re-runs in the real world and starts reading `s` directly.
9+
// Committing the fork (while another batch is pending) must still update it.
10+
export default test({
11+
async test({ assert, target }) {
12+
await tick();
13+
const [u, f, cond, commit, shift] = target.querySelectorAll('button');
14+
15+
// unrelated pending batch
16+
u.click();
17+
await tick();
18+
19+
f.click();
20+
await tick();
21+
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p><p>false</p>`);
22+
23+
cond.click();
24+
await tick();
25+
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p><p>0</p>`);
26+
27+
commit.click();
28+
await tick();
29+
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p><p>1</p>`);
30+
31+
shift.click();
32+
await tick();
33+
assert.htmlEqual(target.innerHTML, `${buttons}<p>1</p><p>1</p>`);
34+
}
35+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script>
2+
import { fork } from 'svelte';
3+
4+
let s = $state(0);
5+
let cond = $state(false);
6+
let u = $state(0);
7+
let big = $derived(s > 100);
8+
9+
const queue = [];
10+
function push(v) {
11+
if (!v) return v;
12+
return new Promise((r) => queue.push(() => r(v)));
13+
}
14+
15+
let f;
16+
</script>
17+
18+
<button onclick={() => u++}>u</button>
19+
<button onclick={() => (f = fork(() => s++))}>fork</button>
20+
<button onclick={() => (cond = true)}>cond</button>
21+
<button onclick={() => f.commit()}>commit</button>
22+
<button onclick={() => queue.shift()()}>shift</button>
23+
24+
<p>{await push(u)}</p>
25+
<p>{cond ? s : big}</p>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
const buttons = '<button>show</button><button>items</button><button>shift</button>';
5+
/** @param {number} offset */
6+
const list = (offset) => Array.from({ length: 2000 }, (_, i) => `${i + offset},`).join('');
7+
8+
// When the branch is created in B1, the each block reads all 2000 items, each of which B2 holds
9+
// a newer value of. The each block must be scheduled to re-run in B2 once, not once per stale
10+
// value — otherwise B2 is flushed 2000 times (each flush being O(size of B2)), which takes seconds
11+
export default test({
12+
mode: ['client'],
13+
async test({ assert, target }) {
14+
await tick();
15+
const [show, items, shift] = target.querySelectorAll('button');
16+
17+
// B1: show = true -> pending
18+
show.click();
19+
await tick();
20+
21+
// B2: increment all items -> pending
22+
items.click();
23+
await tick();
24+
25+
// B1 resolves -> the branch is created, reading 2000 values that are stale relative to B2
26+
const start = performance.now();
27+
shift.click();
28+
await tick();
29+
const duration = performance.now() - start;
30+
31+
assert.ok(duration < 1000, `took ${duration}ms`);
32+
assert.htmlEqual(target.innerHTML, `${buttons}<p>0</p><div>${list(0)}</div>`);
33+
34+
// B2 resolves
35+
shift.click();
36+
await tick();
37+
assert.htmlEqual(target.innerHTML, `${buttons}<p>1</p><div>${list(1)}</div>`);
38+
}
39+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script>
2+
let show = $state(false);
3+
let items = $state(Array.from({ length: 2000 }, (_, i) => i));
4+
5+
const deferred = [];
6+
7+
function delay(v) {
8+
if (!v) return v;
9+
return new Promise((resolve) => deferred.push(() => resolve(v)));
10+
}
11+
</script>
12+
13+
<button onclick={() => (show = true)}>show</button>
14+
<button onclick={() => items.forEach((_, i) => items[i]++)}>items</button>
15+
<button onclick={() => deferred.shift()?.()}>shift</button>
16+
17+
<p>{await delay(items[0])}</p>
18+
19+
{#if await delay(show)}
20+
<div>
21+
{#each items as item}{item},{/each}
22+
</div>
23+
{/if}

0 commit comments

Comments
 (0)