feat: root error boundary and network-error normalisation
A load() that rethrew (e.g. the profile overview) fell through to SvelteKit's bare default error page, and a connection-refused fetch rejected as a raw TypeError that blanked the view. Wrap fetch network failures in the client as ApiError(status 0, network_error) so callers handle them uniformly, and add a root +error.svelte with a friendly message plus Try again / Go home actions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
42
frontend/e2e/error-boundary.spec.ts
Normal file
42
frontend/e2e/error-boundary.spec.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { test, expect, type Page } from './fixtures';
|
||||
|
||||
// The root +error.svelte catches load() errors that a page rethrows rather than
|
||||
// capturing in-band (e.g. the profile overview). Without it these fell through
|
||||
// to SvelteKit's bare default error page — or blanked entirely on a raw
|
||||
// TypeError. Drive the profile loader into a failure and assert the boundary.
|
||||
|
||||
async function authed(page: Page) {
|
||||
await page.route('**/api/v1/auth/config', (r) =>
|
||||
r.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ self_register_enabled: true, private_mode: false })
|
||||
})
|
||||
);
|
||||
await page.route('**/api/v1/auth/me', (r) =>
|
||||
r.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
user: { id: 'u1', username: 'reader', created_at: '2026-01-01T00:00:00Z', is_admin: false }
|
||||
})
|
||||
})
|
||||
);
|
||||
await page.route('**/api/v1/auth/me/preferences', (r) =>
|
||||
r.fulfill({ status: 200, contentType: 'application/json', body: '{}' })
|
||||
);
|
||||
}
|
||||
|
||||
test('profile: a network failure renders the error boundary, not a blank page', async ({
|
||||
page
|
||||
}) => {
|
||||
await authed(page);
|
||||
// Profile's load rethrows non-401 errors; a raw abort is the TypeError case
|
||||
// the client now normalises to an ApiError so it reaches the boundary.
|
||||
await page.route('**/api/v1/me/bookmarks*', (r) => r.abort());
|
||||
await page.route('**/api/v1/me/collections*', (r) => r.abort());
|
||||
|
||||
await page.goto('/profile');
|
||||
await expect(page.getByTestId('error-boundary')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Try again' })).toBeVisible();
|
||||
});
|
||||
Reference in New Issue
Block a user