diff --git a/.changeset/vast-hornets-draw.md b/.changeset/vast-hornets-draw.md new file mode 100644 index 0000000000..256439e9da --- /dev/null +++ b/.changeset/vast-hornets-draw.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: increment signal versions when discarding forks diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 134ebe53a2..d82f965541 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -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(); diff --git a/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-1/_config.js b/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-1/_config.js new file mode 100644 index 0000000000..fb50ceb9b1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-1/_config.js @@ -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, + ` + + + +
0
+ ` + ); + + flushSync(() => increment.click()); + + assert.htmlEqual( + target.innerHTML, + ` + + + +2
+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-1/main.svelte b/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-1/main.svelte new file mode 100644 index 0000000000..08ebaba25a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-1/main.svelte @@ -0,0 +1,23 @@ + + + + + + + + +{#if show} +{derived}
+{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-2/_config.js b/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-2/_config.js new file mode 100644 index 0000000000..21ab140fc0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-2/_config.js @@ -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, + ` + + + +0
+ ` + ); + + flushSync(() => increment.click()); + + assert.htmlEqual( + target.innerHTML, + ` + + + +2
+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-2/main.svelte new file mode 100644 index 0000000000..08ebaba25a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-2/main.svelte @@ -0,0 +1,23 @@ + + + + + + + + +{#if show} +{derived}
+{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-3/_config.js b/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-3/_config.js new file mode 100644 index 0000000000..4827aa99f3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-3/_config.js @@ -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, + ` + + + +2
+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-3/main.svelte new file mode 100644 index 0000000000..08ebaba25a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/fork-derived-uncached-3/main.svelte @@ -0,0 +1,23 @@ + + + + + + + + +{#if show} +{derived}
+{/if} +