From 3fe52d1e9ff1d345bf5bb8cd4c28fdf2800041ad Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 9 May 2026 21:02:30 +0200 Subject: [PATCH] test: cover admin/groups/new and enrich/+ page branches admin/groups/new: heading, both permission group renderings (4 standard + 4 administrative checkboxes), form-error banner branch, cancel link href, submit button form-attribute wiring, name input requiredness. Mocks $app/navigation so beforeNavigate doesn't crash the test runner. enrich/+: heading, empty placeholder vs populated count + start CTA, start CTA href derived from documents[0].id, per-row title rendering, bulk-select checkbox gated on canWrite. 16 tests across two files. Refs #496. Co-Authored-By: Claude Sonnet 4.6 --- .../admin/groups/new/page.svelte.test.ts | 85 +++++++++++++ .../src/routes/enrich/page.svelte.test.ts | 113 ++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 frontend/src/routes/admin/groups/new/page.svelte.test.ts create mode 100644 frontend/src/routes/enrich/page.svelte.test.ts diff --git a/frontend/src/routes/admin/groups/new/page.svelte.test.ts b/frontend/src/routes/admin/groups/new/page.svelte.test.ts new file mode 100644 index 00000000..e9834bd0 --- /dev/null +++ b/frontend/src/routes/admin/groups/new/page.svelte.test.ts @@ -0,0 +1,85 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; + +vi.mock('$app/navigation', () => ({ + beforeNavigate: () => {}, + afterNavigate: () => {}, + goto: vi.fn(), + invalidate: vi.fn(), + invalidateAll: vi.fn(), + preloadCode: vi.fn(), + preloadData: vi.fn(), + pushState: vi.fn(), + replaceState: vi.fn(), + disableScrollHandling: vi.fn(), + onNavigate: () => () => {} +})); + +const { default: AdminGroupNewPage } = await import('./+page.svelte'); + +afterEach(cleanup); + +describe('admin/groups/new page', () => { + it('renders the page heading', async () => { + render(AdminGroupNewPage, { props: { form: undefined } }); + + await expect.element(page.getByRole('heading', { name: /neue gruppe anlegen/i })).toBeVisible(); + }); + + it('renders all four standard permission checkboxes', async () => { + render(AdminGroupNewPage, { props: { form: undefined } }); + + const standardPerms = ['READ_ALL', 'ANNOTATE_ALL', 'WRITE_ALL', 'BLOG_WRITE']; + for (const perm of standardPerms) { + const checkbox = document.querySelector(`input[name="permissions"][value="${perm}"]`); + expect(checkbox).not.toBeNull(); + } + }); + + it('renders all four administrative permission checkboxes', async () => { + render(AdminGroupNewPage, { props: { form: undefined } }); + + const adminPerms = ['ADMIN', 'ADMIN_USER', 'ADMIN_TAG', 'ADMIN_PERMISSION']; + for (const perm of adminPerms) { + const checkbox = document.querySelector(`input[name="permissions"][value="${perm}"]`); + expect(checkbox).not.toBeNull(); + } + }); + + it('shows the form error banner when form.error is set', async () => { + render(AdminGroupNewPage, { props: { form: { error: 'Name is required' } } }); + + await expect.element(page.getByText('Name is required')).toBeVisible(); + }); + + it('does not show the form error banner when form is undefined', async () => { + render(AdminGroupNewPage, { props: { form: undefined } }); + + await expect.element(page.getByText(/^Name is required$/)).not.toBeInTheDocument(); + }); + + it('renders cancel link to /admin/groups', async () => { + render(AdminGroupNewPage, { props: { form: undefined } }); + + const cancelLink = document.querySelector('a[href="/admin/groups"]'); + expect(cancelLink).not.toBeNull(); + }); + + it('renders the submit button labelled "Erstellen" tied to the form via the form attribute', async () => { + render(AdminGroupNewPage, { props: { form: undefined } }); + + const submit = (await page + .getByRole('button', { name: /erstellen/i }) + .element()) as HTMLButtonElement; + expect(submit.getAttribute('form')).toBe('new-group-form'); + }); + + it('renders the name input with placeholder text', async () => { + render(AdminGroupNewPage, { props: { form: undefined } }); + + const nameInput = document.querySelector('input[name="name"]') as HTMLInputElement; + expect(nameInput.placeholder).not.toBe(''); + expect(nameInput.required).toBe(true); + }); +}); diff --git a/frontend/src/routes/enrich/page.svelte.test.ts b/frontend/src/routes/enrich/page.svelte.test.ts new file mode 100644 index 00000000..e49fc51b --- /dev/null +++ b/frontend/src/routes/enrich/page.svelte.test.ts @@ -0,0 +1,113 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import EnrichListPage from './+page.svelte'; + +afterEach(cleanup); + +const baseData = (overrides: Record = {}) => ({ + documents: [] as { id: string; title: string }[], + canWrite: false, + ...overrides +}); + +describe('enrich/+ page', () => { + it('renders the page heading', async () => { + render(EnrichListPage, { props: { data: baseData() } }); + + await expect + .element(page.getByRole('heading', { name: /dokumente ohne metadaten/i })) + .toBeVisible(); + }); + + it('renders the empty placeholder when there are no documents', async () => { + render(EnrichListPage, { props: { data: baseData() } }); + + await expect.element(page.getByText('Alle Dokumente vollständig')).toBeVisible(); + }); + + it('hides the document count and start CTA when the list is empty', async () => { + render(EnrichListPage, { props: { data: baseData() } }); + + await expect + .element(page.getByRole('link', { name: /überprüfung starten/i })) + .not.toBeInTheDocument(); + }); + + it('renders the count when documents are present', async () => { + render(EnrichListPage, { + props: { + data: baseData({ + documents: [ + { id: 'd1', title: 'Brief 1' }, + { id: 'd2', title: 'Brief 2' }, + { id: 'd3', title: 'Brief 3' } + ] + }) + } + }); + + await expect.element(page.getByText(/3\s+Dokumente/)).toBeVisible(); + }); + + it('points the start CTA to the first document', async () => { + render(EnrichListPage, { + props: { + data: baseData({ + documents: [ + { id: 'first-doc', title: 'Brief 1' }, + { id: 'd2', title: 'Brief 2' } + ] + }) + } + }); + + await expect + .element(page.getByRole('link', { name: /überprüfung starten/i })) + .toHaveAttribute('href', '/enrich/first-doc'); + }); + + it('renders one row per document with title visible', async () => { + render(EnrichListPage, { + props: { + data: baseData({ + documents: [ + { id: 'd1', title: 'Tagebucheintrag 1923' }, + { id: 'd2', title: 'Postkarte aus Berlin' } + ] + }) + } + }); + + await expect.element(page.getByText('Tagebucheintrag 1923')).toBeVisible(); + await expect.element(page.getByText('Postkarte aus Berlin')).toBeVisible(); + }); + + it('renders the bulk-select checkbox per row when canWrite is true', async () => { + render(EnrichListPage, { + props: { + data: baseData({ + canWrite: true, + documents: [{ id: 'd1', title: 'Brief 1' }] + }) + } + }); + + const checkbox = document.querySelector('input[type="checkbox"]'); + expect(checkbox).not.toBeNull(); + }); + + it('omits the bulk-select checkbox when canWrite is false', async () => { + render(EnrichListPage, { + props: { + data: baseData({ + canWrite: false, + documents: [{ id: 'd1', title: 'Brief 1' }] + }) + } + }); + + const checkbox = document.querySelector('li input[type="checkbox"]'); + expect(checkbox).toBeNull(); + }); +});