mirror of https://github.com/sveltejs/svelte
fix: prevent flushed effects from running again (#17787)
We never cleared the list of (maybe)dirty_effects on the assumption that once a batch has run them it's complete. But that's not the case when a boundary has a pending snippet, in which case the pending snippet shows up, so `blocking_pending` is already 0 and effects are flushed. That can lead to effects being run unnecessarily, even leading to infinite loops. So we clear them. This is safe because any additional effects would either be scheduled by the boundary (which keeps track of the offscreen effects created while the pending snippet is shown, and schedules them once the pending snippet goes away) or by unskipping skipped branches (which reschedules the effects inside it) Fixes #17717 After creating the test I noticed it fails when run together with other tests, but not alone, which lead me to discover that we're missing an `unset_context`. I also added clearing of `#skipped_branches` just to be safe.pull/17786/head
parent
f6e8b1d11e
commit
1b3e660519
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: prevent flushed effects from running again
|
||||
@ -0,0 +1,82 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
html: `
|
||||
<button>a</button>
|
||||
<button>b</button>
|
||||
<button>resolve a</button>
|
||||
<button>resolve b</button>
|
||||
<p>pending a</p>
|
||||
`,
|
||||
async test({ assert, target }) {
|
||||
const [a, b, resolve_a, resolve_b] = target.querySelectorAll('button');
|
||||
|
||||
resolve_a.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<button>a</button>
|
||||
<button>b</button>
|
||||
<button>resolve a</button>
|
||||
<button>resolve b</button>
|
||||
<p>page a</p>
|
||||
`
|
||||
);
|
||||
|
||||
b.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<button>a</button>
|
||||
<button>b</button>
|
||||
<button>resolve a</button>
|
||||
<button>resolve b</button>
|
||||
<p>pending b</p>
|
||||
`
|
||||
);
|
||||
|
||||
a.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<button>a</button>
|
||||
<button>b</button>
|
||||
<button>resolve a</button>
|
||||
<button>resolve b</button>
|
||||
<p>pending a</p>
|
||||
`
|
||||
);
|
||||
|
||||
resolve_b.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<button>a</button>
|
||||
<button>b</button>
|
||||
<button>resolve a</button>
|
||||
<button>resolve b</button>
|
||||
<p>pending a</p>
|
||||
`
|
||||
);
|
||||
|
||||
resolve_a.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<button>a</button>
|
||||
<button>b</button>
|
||||
<button>resolve a</button>
|
||||
<button>resolve b</button>
|
||||
<p>page a</p>
|
||||
`
|
||||
);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,52 @@
|
||||
<script>
|
||||
let page = $state('a');
|
||||
|
||||
/** @type {Array<() => void>} */
|
||||
const a = [];
|
||||
/** @type {Array<() => void>} */
|
||||
const b = [];
|
||||
|
||||
function gate(next) {
|
||||
const deferred = Promise.withResolvers();
|
||||
|
||||
if (next === 'a') a.push(deferred.resolve);
|
||||
else b.push(deferred.resolve);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function nav(next) {
|
||||
page = next;
|
||||
}
|
||||
|
||||
const to_render = $derived(page === 'a' ? snippet_a : snippet_b);
|
||||
</script>
|
||||
|
||||
<button onclick={() => nav('a')}>a</button>
|
||||
<button onclick={() => nav('b')}>b</button>
|
||||
<button onclick={() => a.shift()?.()}>resolve a</button>
|
||||
<button onclick={() => b.shift()?.()}>resolve b</button>
|
||||
|
||||
{#snippet snippet_a()}
|
||||
<svelte:boundary>
|
||||
{@const _a = await gate('a')}
|
||||
<p>page a</p>
|
||||
|
||||
{#snippet pending()}
|
||||
<p>pending a</p>
|
||||
{/snippet}
|
||||
</svelte:boundary>
|
||||
{/snippet}
|
||||
|
||||
{#snippet snippet_b()}
|
||||
<svelte:boundary>
|
||||
{@const _b = await gate('b')}
|
||||
<p>page b</p>
|
||||
|
||||
{#snippet pending()}
|
||||
<p>pending b</p>
|
||||
{/snippet}
|
||||
</svelte:boundary>
|
||||
{/snippet}
|
||||
|
||||
{@render to_render()}
|
||||
Loading…
Reference in new issue