test: cover enrich/done and documents/bulk-edit page branches

enrich/done: heading, body, both CTA links.

documents/bulk-edit: empty-store onMount redirect to /documents,
loading spinner during in-flight fetch, error banner on backend error
code, error banner on fetch rejection. Mocks fetch via vi.spyOn so the
async branches are exercised without a real backend.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-09 21:19:37 +02:00
parent 2e9ad7e0be
commit cd14f9cd6c
2 changed files with 116 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
const gotoSpy = vi.fn();
vi.mock('$app/navigation', () => ({
beforeNavigate: () => {},
afterNavigate: () => {},
goto: gotoSpy,
invalidate: vi.fn(),
invalidateAll: vi.fn(),
preloadCode: vi.fn(),
preloadData: vi.fn(),
pushState: vi.fn(),
replaceState: vi.fn(),
disableScrollHandling: vi.fn(),
onNavigate: () => () => {}
}));
const { bulkSelectionStore } = await import('$lib/document/bulkSelection.svelte');
const { default: BulkEditPage } = await import('./+page.svelte');
afterEach(() => {
cleanup();
bulkSelectionStore.clear();
gotoSpy.mockClear();
});
describe('documents/bulk-edit page', () => {
it('redirects to /documents when no documents are selected', async () => {
render(BulkEditPage, { props: {} });
// onMount runs immediately — give it a tick
await new Promise((r) => setTimeout(r, 50));
expect(gotoSpy).toHaveBeenCalledWith('/documents');
});
it('shows the loading spinner while fetching batch metadata', async () => {
bulkSelectionStore.toggle('d1');
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(() => new Promise(() => {}));
try {
render(BulkEditPage, { props: {} });
await expect.element(page.getByRole('status')).toBeVisible();
await expect.element(page.getByText('Dokumente werden geladen…')).toBeVisible();
} finally {
fetchSpy.mockRestore();
}
});
it('shows the error banner when the fetch fails with a backend error code', async () => {
bulkSelectionStore.toggle('d1');
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(JSON.stringify({ code: 'INTERNAL_ERROR' }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
})
);
try {
render(BulkEditPage, { props: {} });
await expect.element(page.getByRole('alert')).toBeVisible();
} finally {
fetchSpy.mockRestore();
}
});
it('shows the error banner when fetch throws an unexpected error', async () => {
bulkSelectionStore.toggle('d1');
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('Network down'));
try {
render(BulkEditPage, { props: {} });
await expect.element(page.getByRole('alert')).toBeVisible();
} finally {
fetchSpy.mockRestore();
}
});
});

View File

@@ -0,0 +1,36 @@
import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import EnrichDonePage from './+page.svelte';
afterEach(cleanup);
describe('enrich/done page', () => {
it('renders the success heading', async () => {
render(EnrichDonePage, { props: {} });
await expect.element(page.getByRole('heading', { name: /alles erledigt/i })).toBeVisible();
});
it('renders the body message', async () => {
render(EnrichDonePage, { props: {} });
await expect.element(page.getByText('Alle Dokumente wurden bearbeitet.')).toBeVisible();
});
it('links the primary CTA to the home page', async () => {
render(EnrichDonePage, { props: {} });
await expect
.element(page.getByRole('link', { name: /zurück zur übersicht/i }))
.toHaveAttribute('href', '/');
});
it('links the secondary CTA back to /enrich', async () => {
render(EnrichDonePage, { props: {} });
await expect
.element(page.getByRole('link', { name: /zurück zur liste/i }))
.toHaveAttribute('href', '/enrich');
});
});