From 3707d34c626b92959a6b5a0e084f262953b24700 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 20 Apr 2026 00:23:08 +0200 Subject: [PATCH] =?UTF-8?q?test(docs):=20add=20DocumentRow=20unit=20tests?= =?UTF-8?q?=20=E2=80=94=20title,=20snippet,=20tags,=20sender,=20progress?= =?UTF-8?q?=20ring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../lib/components/DocumentRow.svelte.spec.ts | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 frontend/src/lib/components/DocumentRow.svelte.spec.ts diff --git a/frontend/src/lib/components/DocumentRow.svelte.spec.ts b/frontend/src/lib/components/DocumentRow.svelte.spec.ts new file mode 100644 index 00000000..0f85fb94 --- /dev/null +++ b/frontend/src/lib/components/DocumentRow.svelte.spec.ts @@ -0,0 +1,166 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import DocumentRow from './DocumentRow.svelte'; +import type { components } from '$lib/generated/api'; + +vi.mock('$app/navigation', () => ({ goto: vi.fn() })); + +afterEach(() => cleanup()); + +type DocumentSearchItem = components['schemas']['DocumentSearchItem']; + +function makeItem(overrides: Partial = {}): DocumentSearchItem { + return { + document: { + id: '1', + title: 'Testbrief', + originalFilename: 'testbrief.pdf', + status: 'UPLOADED', + documentDate: '2024-03-15', + sender: null, + receivers: [], + tags: [], + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + metadataComplete: false, + scriptType: 'UNKNOWN' + }, + matchData: { + titleOffsets: [], + senderMatched: false, + matchedReceiverIds: [], + matchedTagIds: [], + snippetOffsets: [], + summaryOffsets: [] + }, + completionPercentage: 0, + contributors: [], + ...overrides + }; +} + +// ─── Title ──────────────────────────────────────────────────────────────────── + +describe('DocumentRow – title', () => { + it('renders document title', async () => { + render(DocumentRow, { item: makeItem() }); + await expect.element(page.getByRole('heading', { name: 'Testbrief' })).toBeInTheDocument(); + }); + + it('falls back to originalFilename when title is null', async () => { + const item = makeItem({ document: { ...makeItem().document, title: null } }); + render(DocumentRow, { item }); + await expect.element(page.getByRole('heading', { name: 'testbrief.pdf' })).toBeInTheDocument(); + }); + + it('renders a mark element for highlighted title offsets', async () => { + const item = makeItem({ + document: { ...makeItem().document, title: 'Brief an Anna' }, + matchData: { + titleOffsets: [{ start: 0, length: 5 }], + senderMatched: false, + matchedReceiverIds: [], + matchedTagIds: [], + snippetOffsets: [], + summaryOffsets: [] + } + }); + render(DocumentRow, { item }); + const mark = page.getByRole('mark'); + await expect.element(mark).toBeInTheDocument(); + await expect.element(mark).toHaveTextContent('Brief'); + }); +}); + +// ─── Snippet ────────────────────────────────────────────────────────────────── + +describe('DocumentRow – snippet', () => { + it('shows transcription snippet when present', async () => { + const item = makeItem({ + matchData: { + transcriptionSnippet: 'Er schrieb einen langen Brief', + titleOffsets: [], + senderMatched: false, + matchedReceiverIds: [], + matchedTagIds: [], + snippetOffsets: [], + summaryOffsets: [] + } + }); + render(DocumentRow, { item }); + await expect.element(page.getByText('Er schrieb einen langen Brief')).toBeInTheDocument(); + }); + + it('does not render snippet section when no snippet', async () => { + render(DocumentRow, { item: makeItem() }); + await expect.element(page.getByTestId('search-snippet')).not.toBeInTheDocument(); + }); +}); + +// ─── Sender / receivers ─────────────────────────────────────────────────────── + +describe('DocumentRow – sender', () => { + it('shows sender display name', async () => { + const item = makeItem({ + document: { + ...makeItem().document, + sender: { id: 's1', displayName: 'Großmutter Maria' } + } + }); + render(DocumentRow, { item }); + await expect.element(page.getByText('Großmutter Maria').first()).toBeInTheDocument(); + }); + + it('shows unknown fallback when sender is null', async () => { + render(DocumentRow, { item: makeItem() }); + const unknownElements = page.getByText('Unbekannt'); + await expect.element(unknownElements.first()).toBeInTheDocument(); + }); +}); + +// ─── Tags ───────────────────────────────────────────────────────────────────── + +describe('DocumentRow – tags', () => { + it('renders tag buttons', async () => { + const item = makeItem({ + document: { + ...makeItem().document, + tags: [{ id: 't1', name: 'Familie', color: null, parentId: null }] + } + }); + render(DocumentRow, { item }); + await expect.element(page.getByRole('button', { name: 'Familie' })).toBeInTheDocument(); + }); + + it('navigates to /documents?tag=… on tag click', async () => { + const { goto } = await import('$app/navigation'); + const item = makeItem({ + document: { + ...makeItem().document, + tags: [{ id: 't1', name: 'Urlaub & Reise', color: null, parentId: null }] + } + }); + render(DocumentRow, { item }); + await page.getByRole('button', { name: 'Urlaub & Reise' }).click(); + expect(goto).toHaveBeenCalledWith('/documents?tag=Urlaub%20%26%20Reise'); + }); +}); + +// ─── ProgressRing & ContributorStack ───────────────────────────────────────── + +describe('DocumentRow – progress ring and contributors', () => { + it('renders the completion percentage label', async () => { + const item = makeItem({ completionPercentage: 42 }); + render(DocumentRow, { item }); + await expect.element(page.getByText('42%').first()).toBeInTheDocument(); + }); + + it('renders contributor initials when contributors present', async () => { + const item = makeItem({ + contributors: [{ initials: 'AR', color: '#4a90e2', name: 'Anna Raddatz' }] + }); + render(DocumentRow, { item }); + await expect.element(page.getByText('AR').first()).toBeInTheDocument(); + }); +});