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'); }); it('tag click does not navigate to the document detail page', async () => { const item = makeItem({ document: { ...makeItem().document, tags: [{ id: 't2', name: 'Familie', color: null, parentId: null }] } }); render(DocumentRow, { item }); const before = window.location.href; await page.getByRole('button', { name: 'Familie' }).click(); expect(window.location.href).toBe(before); }); }); // ─── 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(); }); });