fix: increment signal versions when discarding forks (#17577)

* fix: always update an UNINITIALIZED derived on read

Co-authored-by: David Roizenman <hmnd@users.noreply.github.com>

* obsolete comments

* rename test

* add tests, fix

* revert

* Update .changeset/vast-hornets-draw.md

---------

Co-authored-by: David Roizenman <hmnd@users.noreply.github.com>
pull/17579/head
Rich Harris 1 month ago committed by GitHub
parent 37cd40d2e5
commit ece2e83eb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: increment signal versions when discarding forks

@ -972,6 +972,13 @@ export function fork(fn) {
await settled;
},
discard: () => {
// cause any MAYBE_DIRTY deriveds to update
// if they depend on things thath changed
// inside the discarded fork
for (var source of batch.current.keys()) {
source.wv = increment_write_version();
}
if (!committed && batches.has(batch)) {
batches.delete(batch);
batch.discard();

@ -0,0 +1,40 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
skip_no_async: true,
async test({ assert, target }) {
const [fork, toggle, increment] = target.querySelectorAll('button');
// derived is first evaluated in block effect, then discarded
flushSync(() => fork.click());
// should not throw "Cannot convert a Symbol value to a string" due to cached UNINITIALIZED from first fork
flushSync(() => fork.click());
// should not reflect the temporary change to `clicks` inside the fork
flushSync(() => toggle.click());
assert.htmlEqual(
target.innerHTML,
`
<button>fork</button>
<button>toggle</button>
<button>clicks: 0</button>
<p>0</p>
`
);
flushSync(() => increment.click());
assert.htmlEqual(
target.innerHTML,
`
<button>fork</button>
<button>toggle</button>
<button>clicks: 1</button>
<p>2</p>
`
);
}
});

@ -0,0 +1,23 @@
<script>
import { fork } from "svelte";
let clicks = $state(0);
let show = $state(false);
const derived = $derived(clicks * 2);
</script>
<button onclick={() => {
fork(() => {
clicks += 1;
show = true;
}).discard();
}}>fork</button>
<button onclick={() => show = !show}>toggle</button>
<button onclick={() => clicks++}>clicks: {clicks}</button>
{#if show}
<p>{derived}</p>
{/if}

@ -0,0 +1,38 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
skip_no_async: true,
async test({ assert, target }) {
const [fork, toggle, increment] = target.querySelectorAll('button');
// derived is first evaluated in block effect, then discarded
flushSync(() => fork.click());
// should not reflect the temporary change to `clicks` inside the fork
// or throw "Cannot convert a Symbol value to a string" due to cached UNINITIALIZED
flushSync(() => toggle.click());
assert.htmlEqual(
target.innerHTML,
`
<button>fork</button>
<button>toggle</button>
<button>clicks: 0</button>
<p>0</p>
`
);
flushSync(() => increment.click());
assert.htmlEqual(
target.innerHTML,
`
<button>fork</button>
<button>toggle</button>
<button>clicks: 1</button>
<p>2</p>
`
);
}
});

@ -0,0 +1,23 @@
<script>
import { fork } from "svelte";
let clicks = $state(0);
let show = $state(false);
const derived = $derived(clicks * 2);
</script>
<button onclick={() => {
fork(() => {
clicks += 1;
show = true;
}).discard();
}}>fork</button>
<button onclick={() => show = !show}>toggle</button>
<button onclick={() => clicks++}>clicks: {clicks}</button>
{#if show}
<p>{derived}</p>
{/if}

@ -0,0 +1,32 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
skip_no_async: true,
async test({ assert, target }) {
const [fork, toggle, increment] = target.querySelectorAll('button');
// initialize derived by showing it
flushSync(() => toggle.click());
flushSync(() => toggle.click());
// increment clicks
flushSync(() => increment.click());
// update derived, but without writing to `derived.v`
flushSync(() => fork.click());
// show derived
flushSync(() => toggle.click());
assert.htmlEqual(
target.innerHTML,
`
<button>fork</button>
<button>toggle</button>
<button>clicks: 1</button>
<p>2</p>
`
);
}
});

@ -0,0 +1,23 @@
<script>
import { fork } from "svelte";
let clicks = $state(0);
let show = $state(false);
const derived = $derived(clicks * 2);
</script>
<button onclick={() => {
fork(() => {
clicks += 1;
show = true;
}).discard();
}}>fork</button>
<button onclick={() => show = !show}>toggle</button>
<button onclick={() => clicks++}>clicks: {clicks}</button>
{#if show}
<p>{derived}</p>
{/if}
Loading…
Cancel
Save