mirror of https://github.com/sveltejs/svelte
parent
a9aef844ab
commit
1ce167a7cb
@ -0,0 +1,36 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target, logs }) {
|
||||||
|
await tick();
|
||||||
|
const [x, y, shift] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
'<p>0</p><button>x</button><button>y</button><button>shift</button>'
|
||||||
|
);
|
||||||
|
assert.deepEqual(logs, []);
|
||||||
|
|
||||||
|
// write x — the batch is pending on its async expression
|
||||||
|
x.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(logs, []);
|
||||||
|
|
||||||
|
// an independent batch runs the effect, which 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 === 1 — the effect saw a
|
||||||
|
// stale value and must re-run with the real one
|
||||||
|
shift.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(logs, ['effect 0 1', 'effect 1 1']);
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
'<p>1</p><button>x</button><button>y</button><button>shift</button>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -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</button>
|
||||||
|
<button onclick={() => y++}>y</button>
|
||||||
|
<button onclick={() => deferred.shift()?.()}>shift</button>
|
||||||
@ -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,37 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target, logs }) {
|
||||||
|
await tick();
|
||||||
|
const [x, y, shift] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
assert.deepEqual(logs, ['effect _ 0']);
|
||||||
|
|
||||||
|
// write x — the batch is pending on its async expression
|
||||||
|
x.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(logs, ['effect _ 0']);
|
||||||
|
|
||||||
|
// the effect reads `x` through the pending batch's overlay
|
||||||
|
// (seeing the held-back value 0)
|
||||||
|
y.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(logs, ['effect _ 0', 'effect 0 1']);
|
||||||
|
|
||||||
|
// the effect re-runs and no longer depends on `x` at all
|
||||||
|
y.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(logs, ['effect _ 0', 'effect 0 1', 'effect _ 2']);
|
||||||
|
|
||||||
|
// the pending batch settles and commits x = 1 — the effect no longer
|
||||||
|
// depends on `x`, so it should not re-run
|
||||||
|
shift.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(logs, ['effect _ 0', 'effect 0 1', 'effect _ 2']);
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
'<p>1</p><button>x</button><button>y</button><button>shift</button>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
<script>
|
||||||
|
let x = $state(0);
|
||||||
|
let y = $state(0);
|
||||||
|
|
||||||
|
const deferred = [];
|
||||||
|
|
||||||
|
function delay(value) {
|
||||||
|
if (!value) return value;
|
||||||
|
return new Promise((resolve) => deferred.push(() => resolve(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (y === 1) {
|
||||||
|
console.log(`effect ${x} ${y}`);
|
||||||
|
} else {
|
||||||
|
console.log(`effect _ ${y}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>{await delay(x)}</p>
|
||||||
|
<button onclick={() => x++}>x</button>
|
||||||
|
<button onclick={() => y++}>y</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,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,47 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target, warnings }) {
|
||||||
|
await tick();
|
||||||
|
const [increment, forkButton, commit, resolve_sealed, resolve_latest] =
|
||||||
|
target.querySelectorAll('button');
|
||||||
|
const [p] = target.querySelectorAll('p');
|
||||||
|
|
||||||
|
// restart the merged batch often enough that it becomes sealed
|
||||||
|
// (MAX_ENTANGLED_RESTARTS) — subsequent work must wait behind it
|
||||||
|
for (let i = 0; i < 11; i += 1) {
|
||||||
|
increment.click();
|
||||||
|
await tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.htmlEqual(p.innerHTML, '0:0');
|
||||||
|
assert.equal(warnings.length, 0);
|
||||||
|
|
||||||
|
// fork writes count in its own world and runs the async expression
|
||||||
|
// with count = 111
|
||||||
|
forkButton.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(p.innerHTML, '0:0');
|
||||||
|
|
||||||
|
// committing the fork claims its validated effect, which is owned by
|
||||||
|
// the sealed batch — the commit has to wait behind it
|
||||||
|
commit.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(p.innerHTML, '0:0');
|
||||||
|
assert.isTrue(warnings.length === 1 && warnings[0].includes('Your app is stuck in a loop'));
|
||||||
|
|
||||||
|
// the sealed batch settles and commits its world (count = 11), then
|
||||||
|
// releases the waiting fork-commit batch, which is still pending on
|
||||||
|
// the fork's in-flight run (111)
|
||||||
|
resolve_sealed.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(p.innerHTML, '11:11');
|
||||||
|
|
||||||
|
// resolving the fork's run commits the fork-committed world — the
|
||||||
|
// two halves of the paragraph must not tear
|
||||||
|
resolve_latest.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(p.innerHTML, '111:111');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
<script>
|
||||||
|
import { fork } from 'svelte';
|
||||||
|
|
||||||
|
let count = $state(0);
|
||||||
|
let f;
|
||||||
|
|
||||||
|
const deferred = new Map();
|
||||||
|
|
||||||
|
function delay(value) {
|
||||||
|
if (value === 0) return value;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
deferred.set(value, () => resolve(value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>{count}:{await delay(count)}</p>
|
||||||
|
<button onclick={() => count++}>increment</button>
|
||||||
|
<button onclick={() => { f = fork(() => { count += 100; }); }}>fork</button>
|
||||||
|
<button onclick={() => f.commit()}>commit</button>
|
||||||
|
<button onclick={() => deferred.get(11)?.()}>resolve sealed</button>
|
||||||
|
<button onclick={() => deferred.get(count)?.()}>resolve latest</button>
|
||||||
Loading…
Reference in new issue