mirror of https://github.com/sveltejs/svelte
parent
072b97d426
commit
b96a3dad45
@ -0,0 +1,43 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
const buttons =
|
||||||
|
'<button>fork</button><button>y</button><button>shift</button><button>commit</button>';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target }) {
|
||||||
|
await tick();
|
||||||
|
const [forkButton, y, shift, commit] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, `<p>0</p>${buttons}`);
|
||||||
|
|
||||||
|
// speculative world: x becomes 1, async expression runs with x + y = 1
|
||||||
|
forkButton.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(target.innerHTML, `<p>0</p>${buttons}`);
|
||||||
|
|
||||||
|
// real world: y becomes 1, async expression runs with x + y = 1
|
||||||
|
// (computed with the pre-fork x = 0) — this run supersedes the
|
||||||
|
// fork's validation of the effect
|
||||||
|
y.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(target.innerHTML, `<p>0</p>${buttons}`);
|
||||||
|
|
||||||
|
// commit the fork while the real batch is still pending — x = 1 is
|
||||||
|
// written, and the async expression must eventually re-run with the
|
||||||
|
// committed value, because its in-flight run used x = 0
|
||||||
|
commit.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(target.innerHTML, `<p>0</p>${buttons}`);
|
||||||
|
|
||||||
|
// resolve all in-flight runs (superseded ones are no-ops)
|
||||||
|
for (let i = 0; i < 4; i += 1) {
|
||||||
|
shift.click();
|
||||||
|
await tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// x = 1, y = 1 — anything else means the effect resolved with a value
|
||||||
|
// computed from stale inputs and was never re-run
|
||||||
|
assert.htmlEqual(target.innerHTML, `<p>2</p>${buttons}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import { fork } from 'svelte';
|
||||||
|
|
||||||
|
let x = $state(0);
|
||||||
|
let y = $state(0);
|
||||||
|
let f;
|
||||||
|
|
||||||
|
const deferred = [];
|
||||||
|
|
||||||
|
function delay(value) {
|
||||||
|
if (!value) return value;
|
||||||
|
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>{await delay(x + y)}</p>
|
||||||
|
<button onclick={() => { f = fork(() => x++); }}>fork</button>
|
||||||
|
<button onclick={() => y++}>y</button>
|
||||||
|
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||||
|
<button onclick={() => f.commit()}>commit</button>
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target, logs }) {
|
||||||
|
await tick();
|
||||||
|
const [forkButton, real, shift, discard] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
assert.deepEqual(logs, ['b 0']);
|
||||||
|
logs.length = 0;
|
||||||
|
|
||||||
|
// speculative world: fork writes b and c, runs the async expression
|
||||||
|
forkButton.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(logs, ['b 1']);
|
||||||
|
|
||||||
|
// real world: b++ re-runs the async expression for real
|
||||||
|
real.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(logs, ['b 1', 'b 1']);
|
||||||
|
|
||||||
|
// resolve the fork's in-flight run — the fork is still speculative,
|
||||||
|
// nothing should be committed or re-run
|
||||||
|
shift.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(logs, ['b 1', 'b 1']);
|
||||||
|
|
||||||
|
// resolve the real run — the real batch commits b = 1. The fork's world
|
||||||
|
// value of `b` is also 1 (its own write, now also committed), so the
|
||||||
|
// fork's async expression sees unchanged inputs and should not re-run
|
||||||
|
shift.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
'<p>1 0</p><button>fork</button><button>real</button><button>shift</button><button>discard</button>'
|
||||||
|
);
|
||||||
|
assert.deepEqual(logs, ['b 1', 'b 1']);
|
||||||
|
|
||||||
|
discard.click();
|
||||||
|
await tick();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import { fork } from 'svelte';
|
||||||
|
|
||||||
|
let b = $state(0);
|
||||||
|
let c = $state(0);
|
||||||
|
let f;
|
||||||
|
|
||||||
|
const deferred = [];
|
||||||
|
|
||||||
|
function delay(_, value) {
|
||||||
|
if (!value) return value;
|
||||||
|
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>{await delay(console.log(`b ${b}`), b)} {c}</p>
|
||||||
|
<button onclick={() => { f = fork(() => { b += 1; c += 1; }); }}>fork</button>
|
||||||
|
<button onclick={() => b++}>real</button>
|
||||||
|
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||||
|
<button onclick={() => f.discard()}>discard</button>
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
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 shares the `{add(a, b)}` template expression
|
||||||
|
// with batch A, so the two describe the same world and are merged
|
||||||
|
b.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(target.innerHTML, `<p>0</p><p>0</p>${buttons}`);
|
||||||
|
|
||||||
|
// the async expression settles — the merged batch commits both writes
|
||||||
|
shift.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(target.innerHTML, `<p>1</p><p>2</p>${buttons}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<script>
|
||||||
|
let a = $state(0);
|
||||||
|
let b = $state(0);
|
||||||
|
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>{add(a, b)}</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>
|
||||||
Loading…
Reference in new issue