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