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