Files
Mangalord/frontend/e2e/unsaved-guard.spec.ts
MechaCat02 a47b6895c2 feat: warn before leaving a form with unsaved changes
Drag-dropping page images then clicking a nav link silently discarded the
whole form. Add a reusable guardUnsavedChanges helper (SvelteKit beforeNavigate
confirm + native beforeunload) and wire it into the manga upload, manga edit,
and chapter upload forms. The dirty check returns false while submitting so the
post-save redirect isn't prompted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:16:40 +02:00

60 lines
2.1 KiB
TypeScript

import { test, expect, type Page } from './fixtures';
// Leaving a form with unsaved work (e.g. a half-filled upload) must prompt so a
// misclicked nav link doesn't silently discard everything.
const DESKTOP = { width: 1280, height: 720 } as const;
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: 'uploader', 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: '{}' })
);
await page.route('**/api/v1/genres', (r) =>
r.fulfill({ status: 200, contentType: 'application/json', body: '[]' })
);
await page.route('**/api/v1/me/bookmarks*', (r) =>
r.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [], page: { limit: 50, offset: 0, total: 0 } })
})
);
}
test('upload form prompts before navigating away with unsaved changes', async ({ page }) => {
await authed(page);
await page.setViewportSize(DESKTOP);
await page.goto('/upload');
// Make the form dirty.
await page.getByLabel(/Title/).fill('WIP Manga');
// Cancel the confirm → navigation is blocked, we stay put with our input.
let dialogSeen = false;
page.once('dialog', (d) => {
dialogSeen = true;
d.dismiss();
});
await page.getByRole('link', { name: 'Bookmarks' }).click();
await expect.poll(() => dialogSeen).toBe(true);
await expect(page).toHaveURL(/\/upload/);
await expect(page.getByLabel(/Title/)).toHaveValue('WIP Manga');
});