mirror of https://github.com/sveltejs/svelte
fix: robustify reset calls in error boundaries (#16171)
Fixes #16134 * Add a warning when the misuse of `reset` in an `error:boundary` causes an error to be thrown when flushing the boundary content * Prevent uncaught errors to make test fails when they are expected and are fired during template effects flush * reset should just be a noop after the first call * correctly handle errors inside boundary during reset * handle errors in the correct boundary --------- Co-authored-by: Rich Harris <rich.harris@vercel.com>pull/16418/head
parent
b8b662a1ad
commit
9e1e7139fb
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: handle error in correct boundary after reset
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: make `<svelte:boundary>` reset function a noop after the first call
|
@ -0,0 +1,15 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
test({ assert, target }) {
|
||||
const btn = target.querySelector('button');
|
||||
|
||||
btn?.click();
|
||||
|
||||
assert.throws(flushSync, 'svelte_boundary_reset_onerror');
|
||||
|
||||
// boundary content empty; only button remains
|
||||
assert.htmlEqual(target.innerHTML, `<button>trigger throw</button>`);
|
||||
}
|
||||
});
|
@ -0,0 +1,17 @@
|
||||
<script>
|
||||
let must_throw = $state(false);
|
||||
|
||||
function throw_error() {
|
||||
throw new Error("error on template render");
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:boundary onerror={(_, reset) => reset()}>
|
||||
{must_throw ? throw_error() : 'normal content'}
|
||||
|
||||
{#snippet failed()}
|
||||
<div>err</div>
|
||||
{/snippet}
|
||||
</svelte:boundary>
|
||||
|
||||
<button onclick={() => must_throw = true}>trigger throw</button>
|
@ -0,0 +1,28 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
html: `
|
||||
normal content
|
||||
<button>toggle</button>
|
||||
`,
|
||||
|
||||
async test({ assert, target, warnings }) {
|
||||
const [btn] = target.querySelectorAll('button');
|
||||
|
||||
flushSync(() => btn.click());
|
||||
assert.htmlEqual(target.innerHTML, `<div>err</div><button>toggle</button>`);
|
||||
assert.deepEqual(warnings, []);
|
||||
|
||||
flushSync(() => btn.click());
|
||||
assert.htmlEqual(target.innerHTML, `normal content <button>toggle</button>`);
|
||||
assert.deepEqual(warnings, []);
|
||||
|
||||
flushSync(() => btn.click());
|
||||
assert.htmlEqual(target.innerHTML, `<div>err</div><button>toggle</button>`);
|
||||
|
||||
assert.deepEqual(warnings, [
|
||||
'A `<svelte:boundary>` `reset` function only resets the boundary the first time it is called'
|
||||
]);
|
||||
}
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
<script>
|
||||
let must_throw = $state(false);
|
||||
let reset = $state(null);
|
||||
|
||||
function throw_error() {
|
||||
throw new Error("error on template render");
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:boundary onerror={console.error}>
|
||||
<svelte:boundary onerror={(_, fn) => (reset = fn)}>
|
||||
{must_throw ? throw_error() : 'normal content'}
|
||||
|
||||
{#snippet failed()}
|
||||
<div>err</div>
|
||||
{/snippet}
|
||||
</svelte:boundary>
|
||||
</svelte:boundary>
|
||||
|
||||
<button
|
||||
onclick={() => {
|
||||
must_throw = !must_throw;
|
||||
if (reset) reset();
|
||||
}}>
|
||||
toggle
|
||||
</button>
|
@ -0,0 +1,27 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
test({ assert, target, warnings }) {
|
||||
const [toggle] = target.querySelectorAll('button');
|
||||
|
||||
flushSync(() => toggle.click());
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`<button>toggle</button><p>yikes!</p><button>reset</button>`
|
||||
);
|
||||
|
||||
const [, reset] = target.querySelectorAll('button');
|
||||
flushSync(() => reset.click());
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`<button>toggle</button><p>yikes!</p><button>reset</button>`
|
||||
);
|
||||
|
||||
flushSync(() => toggle.click());
|
||||
|
||||
const [, reset2] = target.querySelectorAll('button');
|
||||
flushSync(() => reset2.click());
|
||||
assert.htmlEqual(target.innerHTML, `<button>toggle</button><p>hello!</p>`);
|
||||
}
|
||||
});
|
@ -0,0 +1,20 @@
|
||||
<script>
|
||||
let must_throw = $state(false);
|
||||
|
||||
function throw_error() {
|
||||
throw new Error('yikes!');
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => must_throw = !must_throw}>toggle</button>
|
||||
|
||||
<svelte:boundary>
|
||||
<p>{must_throw ? throw_error() : 'hello!'}</p>
|
||||
|
||||
{#snippet failed(error, reset)}
|
||||
<p>{error.message}</p>
|
||||
<button onclick={reset}>reset</button>
|
||||
{/snippet}
|
||||
</svelte:boundary>
|
||||
|
||||
|
Loading…
Reference in new issue