diff --git a/.changeset/sixty-glasses-try.md b/.changeset/sixty-glasses-try.md new file mode 100644 index 000000000000..77db4a5cc57d --- /dev/null +++ b/.changeset/sixty-glasses-try.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: avoid other batches running with queued root effects of main batch diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 84308ef3ed61..45e27fad3992 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -166,6 +166,8 @@ export class Batch { for (const root of root_effects) { this.#traverse_effect_tree(root, target); + // Note: #traverse_effect_tree runs block effects eagerly, which can schedule effects, + // which means queued_root_effects now may be filled again. } if (!this.is_fork) { @@ -392,6 +394,10 @@ export class Batch { // Re-run async/block effects that depend on distinct values changed in both batches const others = [...batch.current.keys()].filter((s) => !this.current.has(s)); if (others.length > 0) { + // Avoid running queued root effects on the wrong branch + var prev_queued_root_effects = queued_root_effects; + queued_root_effects = []; + /** @type {Set} */ const marked = new Set(); /** @type {Map} */ @@ -410,9 +416,10 @@ export class Batch { // TODO do we need to do anything with `target`? defer block effects? - queued_root_effects = []; batch.deactivate(); } + + queued_root_effects = prev_queued_root_effects; } } diff --git a/packages/svelte/tests/runtime-runes/samples/async-block-effect-queueing/A.svelte b/packages/svelte/tests/runtime-runes/samples/async-block-effect-queueing/A.svelte new file mode 100644 index 000000000000..7971deff5f15 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-block-effect-queueing/A.svelte @@ -0,0 +1,15 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/async-block-effect-queueing/B.svelte b/packages/svelte/tests/runtime-runes/samples/async-block-effect-queueing/B.svelte new file mode 100644 index 000000000000..7371f47a6f8b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-block-effect-queueing/B.svelte @@ -0,0 +1 @@ +B \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/async-block-effect-queueing/_config.js b/packages/svelte/tests/runtime-runes/samples/async-block-effect-queueing/_config.js new file mode 100644 index 000000000000..789cdfaa020a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-block-effect-queueing/_config.js @@ -0,0 +1,63 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [fork, commit, toggle] = target.querySelectorAll('button'); + + fork.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + + ` + ); + + toggle.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + + ` + ); + + toggle.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + + ` + ); + + toggle.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + + ` + ); + + commit.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + B + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-block-effect-queueing/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-block-effect-queueing/main.svelte new file mode 100644 index 000000000000..7342a37f05c9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-block-effect-queueing/main.svelte @@ -0,0 +1,24 @@ + + + + + + +{#if open} + +{:else} + +{/if}