mirror of https://github.com/sveltejs/svelte
test: add async tests that already pass on main, unskip async-overlap-multiple-2 (#18981)
These tests from #18880 already pass on `main`. Moving them out of that PR makes it clearer what behaviour is actually changing over therepull/18176/merge
parent
6480a9737f
commit
d9acd2da90
@ -0,0 +1,33 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = '<button>x</button><button>show</button><button>shift</button>';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, logs }) {
|
||||
await tick();
|
||||
const [x, show, shift] = target.querySelectorAll('button');
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `<p>true</p><p>1</p>${buttons}`);
|
||||
assert.deepEqual(logs, []);
|
||||
|
||||
// write x — the batch is pending on its async expression, and claims
|
||||
// the `positive` derived (marked through x)
|
||||
x.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, []);
|
||||
|
||||
// an independent batch runs the effect, which reads the claimed derived
|
||||
// through the pending batch's overlay (x = 1, so `positive` is true)
|
||||
show.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['positive true']);
|
||||
|
||||
// the pending batch settles and commits x = 2 — `positive` recomputes
|
||||
// to the same value (true), so the effect should not re-run
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['positive true']);
|
||||
assert.htmlEqual(target.innerHTML, `<p>true</p><p>2</p>${buttons}`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,26 @@
|
||||
<script>
|
||||
let x = $state(1);
|
||||
let show = $state(false);
|
||||
let pend = false;
|
||||
|
||||
const deferred = [];
|
||||
|
||||
let positive = $derived(x > 0);
|
||||
|
||||
function delay(value) {
|
||||
if (!pend) return value;
|
||||
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (show) {
|
||||
console.log(`positive ${positive}`);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<p>{positive}</p>
|
||||
<p>{await delay(x)}</p>
|
||||
<button onclick={() => { pend = true; x += 1; }}>x</button>
|
||||
<button onclick={() => (show = true)}>show</button>
|
||||
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||
@ -0,0 +1,33 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, logs }) {
|
||||
await tick();
|
||||
const [revert, y, shift] = target.querySelectorAll('button');
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
'<p>0</p><button>revert</button><button>y</button><button>shift</button>'
|
||||
);
|
||||
assert.deepEqual(logs, []);
|
||||
|
||||
// write x and revert it within the same batch — the batch is pending
|
||||
// (its async expression re-runs), with previous === current for `x`
|
||||
revert.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, []);
|
||||
|
||||
// an independent batch runs the effect, which now reads `x` through
|
||||
// the pending batch's overlay (seeing the held-back value 0)
|
||||
y.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['effect 0 1']);
|
||||
|
||||
// the pending batch settles and commits x === 0, i.e. exactly the value
|
||||
// the effect already saw — it should not re-run
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['effect 0 1']);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,23 @@
|
||||
<script>
|
||||
let x = $state(0);
|
||||
let y = $state(0);
|
||||
let pend = false;
|
||||
|
||||
const deferred = [];
|
||||
|
||||
function delay(value) {
|
||||
if (!pend) return value;
|
||||
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (y > 0) {
|
||||
console.log(`effect ${x} ${y}`);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<p>{await delay(x)}</p>
|
||||
<button onclick={() => { pend = true; x += 1; x -= 1; }}>revert</button>
|
||||
<button onclick={() => y++}>y</button>
|
||||
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||
@ -0,0 +1,29 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
mode: ['client'],
|
||||
async test({ assert, target, logs }) {
|
||||
await tick();
|
||||
const [update, resolve_a, resolve_b] = target.querySelectorAll('button');
|
||||
assert.deepEqual(logs, [0]);
|
||||
|
||||
update.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, [0]);
|
||||
|
||||
// The new if block evaluates parity while the text effect is still deferred.
|
||||
resolve_b.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, [0, 2]);
|
||||
|
||||
// The batch must forget its earlier deferred DIRTY entry for parity.
|
||||
resolve_a.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, [0, 2]);
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
'<button>update</button><button>resolve a</button><button>resolve b</button><p>2:0</p>'
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
<script>
|
||||
let count = $state(0);
|
||||
const parity = $derived.by(() => {
|
||||
console.log(count);
|
||||
return count % 2;
|
||||
});
|
||||
const pending = new Map();
|
||||
|
||||
function delay(name, value) {
|
||||
if (value === 0) return value;
|
||||
return new Promise(resolve => pending.set(name, () => resolve(value)));
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => count += 2}>update</button>
|
||||
<button onclick={() => pending.get('a')?.()}>resolve a</button>
|
||||
<button onclick={() => pending.get('b')?.()}>resolve b</button>
|
||||
|
||||
<p>{await delay('a', count)}:{parity}</p>
|
||||
{#if await delay('b', count)}
|
||||
{#if parity}<p>odd</p>{/if}
|
||||
{/if}
|
||||
@ -0,0 +1,36 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, logs }) {
|
||||
await tick();
|
||||
const [a, b, resolve, read] = target.querySelectorAll('button');
|
||||
const [p] = target.querySelectorAll('p');
|
||||
assert.htmlEqual(p.innerHTML, '0 0 0');
|
||||
|
||||
// A waits for its async expression, so its updated input is not yet rendered.
|
||||
a.click();
|
||||
await tick();
|
||||
assert.htmlEqual(p.innerHTML, '0 0 0');
|
||||
|
||||
// B commits with A's old input. Its derived values must remain batch-local.
|
||||
b.click();
|
||||
await tick();
|
||||
assert.htmlEqual(p.innerHTML, '0 1 2');
|
||||
|
||||
// Outside reactivity, both deriveds should reflect the real inputs, without
|
||||
// changing the DOM until A resolves.
|
||||
read.click();
|
||||
await tick();
|
||||
assert.htmlEqual(p.innerHTML, '0 1 2');
|
||||
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.htmlEqual(p.innerHTML, '1 2 4');
|
||||
read.click();
|
||||
assert.deepEqual(logs, [
|
||||
{ a: 1, b: 1, c: 2, doubled: 4 },
|
||||
{ a: 1, b: 1, c: 2, doubled: 4 }
|
||||
]);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,18 @@
|
||||
<script>
|
||||
let a = $state(0);
|
||||
let b = $state(0);
|
||||
const c = $derived(a + b);
|
||||
const doubled = $derived(c * 2);
|
||||
const queued = [];
|
||||
|
||||
function delay(value) {
|
||||
if (!value) return value;
|
||||
return new Promise((resolve) => queued.push(() => resolve(value)));
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>{await delay(a)} {c} {doubled}</p>
|
||||
<button onclick={() => a++}>a</button>
|
||||
<button onclick={() => b++}>b</button>
|
||||
<button onclick={() => queued.shift()?.()}>resolve</button>
|
||||
<button onclick={() => console.log({ a, b, c, doubled })}>read</button>
|
||||
@ -0,0 +1,38 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, logs }) {
|
||||
await tick();
|
||||
|
||||
const [increment_a, increment_a_b, shift_second, shift] = target.querySelectorAll('button');
|
||||
const [p] = target.querySelectorAll('p');
|
||||
|
||||
increment_a.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['a_d: 1']);
|
||||
logs.length = 0;
|
||||
|
||||
increment_a_b.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['a_d: 2', 'c: 1']);
|
||||
logs.length = 0;
|
||||
|
||||
shift_second.click(); // a_d of second batch
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['c: 3']);
|
||||
logs.length = 0;
|
||||
|
||||
shift.click(); // a_d of first batch - should not rerun second batch
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['c: 1']);
|
||||
logs.length = 0;
|
||||
|
||||
shift.click();
|
||||
await tick();
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(p.innerHTML, '3');
|
||||
assert.deepEqual(logs, []);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,21 @@
|
||||
<script>
|
||||
let a = $state(0);
|
||||
let b = $state(0);
|
||||
let a_d = $derived(await delay(a, 'a_d'));
|
||||
let c = $derived(await delay(a_d + b, 'c'));
|
||||
|
||||
function delay(value, source) {
|
||||
if (!value) return value;
|
||||
console.log(source + ': ' + value);
|
||||
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||
}
|
||||
|
||||
let deferred = [];
|
||||
</script>
|
||||
|
||||
<button onclick={() => a++}>increment a</button>
|
||||
<button onclick={() => {a++; b++}}>increment a & b</button>
|
||||
<button onclick={() => deferred.splice(1,1)[0]?.()}>shift second</button>
|
||||
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||
|
||||
<p>{c}</p>
|
||||
@ -0,0 +1,16 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
await tick();
|
||||
const [increment] = target.querySelectorAll('button');
|
||||
|
||||
assert.htmlEqual(target.innerHTML, '<button>increment</button><p>0</p>');
|
||||
|
||||
increment.click();
|
||||
await tick();
|
||||
|
||||
assert.htmlEqual(target.innerHTML, '<button>increment</button><p>2</p>');
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,14 @@
|
||||
<script>
|
||||
let count = $state(0);
|
||||
let runs = 0;
|
||||
|
||||
function delay(value, eager_value) {
|
||||
if (runs++ > 10) throw new Error('infinite loop detected')
|
||||
if (value === 0) return 0;
|
||||
return Promise.resolve(value + eager_value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => count += 1}>increment</button>
|
||||
|
||||
<p>{await delay(count, $state.eager(count))}</p>
|
||||
@ -0,0 +1,36 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = '<button>count</button><button>eager</button><button>shift</button>';
|
||||
|
||||
export default test({
|
||||
// running more than once per bump
|
||||
async test({ assert, target, logs }) {
|
||||
await tick();
|
||||
const [count, eager, shift] = target.querySelectorAll('button');
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `<p>-1</p>${buttons}`);
|
||||
assert.deepEqual(logs, []);
|
||||
|
||||
// count++ creates a pending batch; the inner block (containing the
|
||||
// $state.eager expression) is created inside it and belongs to it.
|
||||
// it does not depend on `count`
|
||||
count.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['inner 0']);
|
||||
|
||||
// an eager version bump re-runs the inner block in the eager batch's
|
||||
// world — since the block doesn't depend on the pending batch's
|
||||
// changes, it sees exactly the same values the owner's world would
|
||||
eager.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['inner 0', 'inner 1']);
|
||||
|
||||
// the pending batch settles — nothing the inner block sees has
|
||||
// changed since its eager run, so it should not be re-run
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['inner 0', 'inner 1']);
|
||||
assert.htmlEqual(target.innerHTML, `<p>0</p><span>1</span>${buttons}`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,26 @@
|
||||
<script>
|
||||
let count = $state(-1);
|
||||
let eag = $state(0);
|
||||
|
||||
const deferred = [];
|
||||
|
||||
function delay(value) {
|
||||
if (value < 0) return value;
|
||||
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||
}
|
||||
|
||||
function log(value) {
|
||||
console.log(`inner ${value}`);
|
||||
return value >= 0;
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>{await delay(count)}</p>
|
||||
{#if count >= 0}
|
||||
{#if log($state.eager(eag))}
|
||||
<span>{eag}</span>
|
||||
{/if}
|
||||
{/if}
|
||||
<button onclick={() => count++}>count</button>
|
||||
<button onclick={() => eag++}>eager</button>
|
||||
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||
@ -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}
|
||||
@ -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,30 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
await tick();
|
||||
const [a_b, b, resolve] = target.querySelectorAll('button');
|
||||
|
||||
a_b.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
'<button>a_b 0_0</button> <button>b 0</button> <button>resolve</button> 0'
|
||||
);
|
||||
|
||||
b.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
'<button>a_b 0_0</button> <button>b 0</button> <button>resolve</button> 0'
|
||||
);
|
||||
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
'<button>a_b 1_2</button> <button>b 2</button> <button>resolve</button> 1'
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,18 @@
|
||||
<script>
|
||||
let a = $state(0);
|
||||
let b = $state(0);
|
||||
let deferreds = [];
|
||||
const value = () => (`${a}_${b}`);
|
||||
|
||||
function push(value) {
|
||||
if (!value) return value;
|
||||
return new Promise((resolve) => {
|
||||
deferreds.push(() => resolve(value));
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => {a++;b++}}>a_b {value()}</button>
|
||||
<button onclick={() => (b++)}>b {b}</button>
|
||||
<button onclick={() => (deferreds.shift()?.())}>resolve</button>
|
||||
{await push(a)}
|
||||
@ -0,0 +1,31 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = '<button>a</button><button>b</button><button>shift</button>';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
await tick();
|
||||
const [a, b, shift] = target.querySelectorAll('button');
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `<p>0</p><p>0</p>${buttons}`);
|
||||
|
||||
// batch A writes `a` and pends on its async expression
|
||||
a.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `<p>0</p><p>0</p>${buttons}`);
|
||||
|
||||
// batch B writes `b` — it only shares the `{add(a, b)}` template expression
|
||||
// (a leaf) with batch A, so the two are independent: B commits right away,
|
||||
// rendering its own view of the world (in which `a` is still 0)
|
||||
b.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `<p>0</p><p>1</p>${buttons}`);
|
||||
|
||||
// the async expression settles — A commits and brings the shared
|
||||
// template expression up to date
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `<p>1</p><p>2</p>${buttons}`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,23 @@
|
||||
<script>
|
||||
let a = $state(0);
|
||||
let b = $state(0);
|
||||
const c = $derived(add(a, b))
|
||||
let pend = false;
|
||||
|
||||
const deferred = [];
|
||||
|
||||
function delay(value) {
|
||||
if (!pend) return value;
|
||||
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||
}
|
||||
|
||||
function add(x, y) {
|
||||
return x + y;
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>{await delay(a)}</p>
|
||||
<p>{c}</p>
|
||||
<button onclick={() => { pend = true; a++; }}>a</button>
|
||||
<button onclick={() => b++}>b</button>
|
||||
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||
@ -0,0 +1,38 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = '<button>z</button><button>w</button><button>shift</button>';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, logs }) {
|
||||
await tick();
|
||||
const [z, w, shift] = target.querySelectorAll('button');
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `<p>true</p><p>0</p>${buttons}`);
|
||||
assert.deepEqual(logs, ['eval true']);
|
||||
|
||||
// with no other batch pending, the equality cut-off works: `c`
|
||||
// recomputes to the same value, the template expression is not
|
||||
// re-evaluated
|
||||
z.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['eval true']);
|
||||
|
||||
// write w — the batch is pending on its async expression
|
||||
w.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['eval true']);
|
||||
|
||||
// z++ recomputes `c` to the same value again — the template expression
|
||||
// should still not be re-evaluated, even though another batch is pending
|
||||
z.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['eval true']);
|
||||
|
||||
// the pending batch settles — nothing `c` depends on changed
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['eval true']);
|
||||
assert.htmlEqual(target.innerHTML, `<p>true</p><p>1</p>${buttons}`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
<script>
|
||||
let z = $state(1);
|
||||
let w = $state(0);
|
||||
let pend = false;
|
||||
|
||||
const deferred = [];
|
||||
|
||||
let c = $derived(z > 0);
|
||||
|
||||
function delay(value) {
|
||||
if (!pend) return value;
|
||||
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||
}
|
||||
|
||||
function log(value) {
|
||||
console.log(`eval ${value}`);
|
||||
return value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>{log(c)}</p>
|
||||
<p>{await delay(w)}</p>
|
||||
<button onclick={() => z++}>z</button>
|
||||
<button onclick={() => { pend = true; w += 1; }}>w</button>
|
||||
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||
@ -0,0 +1,36 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = '<button>b</button><button>z</button><button>shift</button>';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, logs }) {
|
||||
await tick();
|
||||
const [b, z, shift] = target.querySelectorAll('button');
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `<p>0</p><p>a</p>${buttons}`);
|
||||
assert.deepEqual(logs, ['fetch a']);
|
||||
|
||||
// write x = 'b' — the batch is pending on its async expression
|
||||
b.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['fetch a', 'fetch b']);
|
||||
|
||||
// an independent batch runs the effect, which writes x = 'b' — the
|
||||
// real value is already 'b', so this is a no-op: the async expression
|
||||
// must not re-run (no needless refetch), and the batch must not be
|
||||
// entangled with the pending one (z commits immediately)
|
||||
z.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['fetch a', 'fetch b']);
|
||||
assert.htmlEqual(target.innerHTML, `<p>1</p><p>a</p>${buttons}`);
|
||||
|
||||
// the pending batch settles
|
||||
shift.click();
|
||||
await tick();
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['fetch a', 'fetch b']);
|
||||
assert.htmlEqual(target.innerHTML, `<p>1</p><p>b</p>${buttons}`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
<script>
|
||||
let x = $state('a');
|
||||
let z = $state(0);
|
||||
let pend = false;
|
||||
|
||||
const deferred = [];
|
||||
|
||||
function delay(value) {
|
||||
console.log(`fetch ${value}`);
|
||||
if (!pend) return value;
|
||||
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (z > 0) {
|
||||
x = 'b';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<p>{z}</p>
|
||||
<p>{await delay(x)}</p>
|
||||
<button onclick={() => { pend = true; x = 'b'; }}>b</button>
|
||||
<button onclick={() => z++}>z</button>
|
||||
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||
@ -0,0 +1,33 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
const buttons = '<button>b</button><button>reset</button><button>shift</button>';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, logs }) {
|
||||
await tick();
|
||||
const [b, reset, shift] = target.querySelectorAll('button');
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `<p>0</p><p>a</p>${buttons}`);
|
||||
assert.deepEqual(logs, ['fetch a']);
|
||||
|
||||
// write x = 'b' — the batch is pending on its async expression
|
||||
b.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['fetch a', 'fetch b']);
|
||||
|
||||
// an independent batch runs the effect, which resets x = 'a'. The real
|
||||
// (pending) value is 'b', so this is a genuine change and must not be
|
||||
// swallowed just because the effect's world still shows 'a'
|
||||
reset.click();
|
||||
await tick();
|
||||
assert.deepEqual(logs, ['fetch a', 'fetch b', 'fetch a']);
|
||||
|
||||
// resolve all in-flight runs — the reset must win
|
||||
shift.click();
|
||||
await tick();
|
||||
shift.click();
|
||||
await tick();
|
||||
assert.htmlEqual(target.innerHTML, `<p>1</p><p>a</p>${buttons}`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
<script>
|
||||
let x = $state('a');
|
||||
let z = $state(0);
|
||||
let pend = false;
|
||||
|
||||
const deferred = [];
|
||||
|
||||
function delay(value) {
|
||||
console.log(`fetch ${value}`);
|
||||
if (!pend) return value;
|
||||
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (z > 0) {
|
||||
x = 'a';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<p>{z}</p>
|
||||
<p>{await delay(x)}</p>
|
||||
<button onclick={() => { pend = true; x = 'b'; }}>b</button>
|
||||
<button onclick={() => z++}>reset</button>
|
||||
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||
Loading…
Reference in new issue