diff --git a/.changeset/witty-seas-learn.md b/.changeset/witty-seas-learn.md new file mode 100644 index 000000000000..aa94c7c35f36 --- /dev/null +++ b/.changeset/witty-seas-learn.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure guards (eg. if, each, key) run before their contents diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index fd2a6d9f5dcf..c78d2b4b3ef9 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -601,15 +601,35 @@ function flush_queued_effects(effects) { // If update_effect() has a flushSync() in it, we may have flushed another flush_queued_effects(), // which already handled this logic and did set eager_block_effects to null. if (eager_block_effects?.length > 0) { - // TODO this feels incorrect! it gets the tests passing old_values.clear(); + /** @type {Effect[][]} */ + const depth_buckets = []; + for (const e of eager_block_effects) { - update_effect(e); + // Skip eager effects that have already been unmounted + if ((e.f & (DESTROYED | INERT)) !== 0) continue; + + let depth = 0; + let ancestor = e.parent; + while (ancestor !== null) { + depth++; + ancestor = ancestor.parent; + } + + (depth_buckets[depth] ??= []).push(e); } - eager_block_effects = []; + for (const effects of depth_buckets) { + if (effects) { + for (const e of effects) { + update_effect(e); + } + } + } } + + eager_block_effects = []; } } diff --git a/packages/svelte/tests/runtime-runes/samples/guard-else-effect/_config.js b/packages/svelte/tests/runtime-runes/samples/guard-else-effect/_config.js new file mode 100644 index 000000000000..c8a8e1078e07 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-else-effect/_config.js @@ -0,0 +1,22 @@ +import { expect, vi } from 'vitest'; +import { test } from '../../test'; +import { flushSync } from 'svelte'; + +const trackBranch = vi.fn(); + +export default test({ + mode: ['client'], + props: { trackBranch: trackBranch }, + async test({ target }) { + const button = target.querySelector('button'); + + flushSync(() => button?.click()); + flushSync(() => button?.click()); + flushSync(() => button?.click()); + flushSync(() => button?.click()); + + expect(trackBranch).toHaveBeenCalledWith('one'); + expect(trackBranch).toHaveBeenCalledWith('two'); + expect(trackBranch).not.toHaveBeenCalledWith('else'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/guard-else-effect/main.svelte b/packages/svelte/tests/runtime-runes/samples/guard-else-effect/main.svelte new file mode 100644 index 000000000000..3f75004decab --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-else-effect/main.svelte @@ -0,0 +1,25 @@ + + + + + + +{#if v === "one"} +