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 b35e16a409a6..302d48d03fe2 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -561,7 +561,7 @@ function infinite_loop_guard() { } } -/** @type {Effect[] | null} */ +/** @type {Set | null} */ export let eager_block_effects = null; /** @@ -578,7 +578,7 @@ function flush_queued_effects(effects) { var effect = effects[i++]; if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) { - eager_block_effects = []; + eager_block_effects = new Set(); update_effect(effect); @@ -601,15 +601,34 @@ 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 + if (eager_block_effects?.size > 0) { old_values.clear(); 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; + + // Run effects in order from ancestor to descendant, else we could run into nullpointers + /** @type {Effect[]} */ + const ordered_effects = [e]; + let ancestor = e.parent; + while (ancestor !== null) { + if (eager_block_effects.has(ancestor)) { + eager_block_effects.delete(ancestor); + ordered_effects.push(ancestor); + } + ancestor = ancestor.parent; + } + + for (let j = ordered_effects.length - 1; j >= 0; j--) { + const e = ordered_effects[j]; + // Skip eager effects that have already been unmounted + if ((e.f & (DESTROYED | INERT)) !== 0) continue; + update_effect(e); + } } - eager_block_effects = []; + eager_block_effects.clear(); } } } diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index cd0c28016dc5..c5dcff9cfbdd 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -336,7 +336,7 @@ function mark_reactions(signal, status) { } else if (not_dirty) { if ((flags & BLOCK_EFFECT) !== 0) { if (eager_block_effects !== null) { - eager_block_effects.push(/** @type {Effect} */ (reaction)); + eager_block_effects.add(/** @type {Effect} */ (reaction)); } } 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..4e8eec8b1670 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-else-effect/_config.js @@ -0,0 +1,20 @@ +import { test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + mode: ['client'], + async test({ target, assert, logs }) { + const button = target.querySelector('button'); + + button?.click(); + flushSync(); + button?.click(); + flushSync(); + button?.click(); + flushSync(); + button?.click(); + flushSync(); + + assert.deepEqual(logs, ['two', 'one', 'two', 'one', 'two']); + } +}); 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..91fd0442bdef --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-else-effect/main.svelte @@ -0,0 +1,18 @@ + + + + +{#if v === "one"} +
if1 matched! {console.log('one')}
+{:else if v === "two"} +
if2 matched! {console.log('two')}
+{:else} +
nothing matched {console.log('else')}
+{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/guard-if-nested/_config.js b/packages/svelte/tests/runtime-runes/samples/guard-if-nested/_config.js new file mode 100644 index 000000000000..881c1545ee9f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-if-nested/_config.js @@ -0,0 +1,13 @@ +import { test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + mode: ['client'], + async test({ target, assert }) { + const button = target.querySelector('button'); + + flushSync(() => button?.click()); + + assert.equal(target.textContent?.trim(), 'Trigger'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/guard-if-nested/main.svelte b/packages/svelte/tests/runtime-runes/samples/guard-if-nested/main.svelte new file mode 100644 index 000000000000..4514bd114e74 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-if-nested/main.svelte @@ -0,0 +1,18 @@ + + +{#if centerRow?.nested} + {#if centerRow?.nested?.optional != undefined && centerRow.nested.optional > 0} + op: {centerRow.nested.optional}
+ {:else} + req: {centerRow.nested.required}
+ {/if} +{/if} + + diff --git a/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/Component.svelte b/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/Component.svelte new file mode 100644 index 000000000000..b7322e75309f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/Component.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/_config.js b/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/_config.js new file mode 100644 index 000000000000..9706855fb436 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/_config.js @@ -0,0 +1,13 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + async test({ assert, target, logs }) { + const button = target.querySelector('button'); + + button?.click(); + flushSync(); + assert.deepEqual(logs, ['pre', 'running b', 'pre', 'pre']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/main.svelte b/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/main.svelte new file mode 100644 index 000000000000..4ebb13eca396 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/main.svelte @@ -0,0 +1,18 @@ + + +{#if p || !p} + {#if p} + + {/if} +{/if} + +