diff --git a/frontend/src/routes/korrespondenz/CorrespondenzEmptyState.svelte b/frontend/src/routes/korrespondenz/CorrespondenzEmptyState.svelte index 0ff49bbf..9dae76ee 100644 --- a/frontend/src/routes/korrespondenz/CorrespondenzEmptyState.svelte +++ b/frontend/src/routes/korrespondenz/CorrespondenzEmptyState.svelte @@ -4,8 +4,7 @@ import { conv_empty_search_placeholder, conv_empty_recent_label } from '$lib/mes interface RecentPerson { id: string; - firstName: string; - lastName: string; + name: string; } interface Props { @@ -110,9 +109,9 @@ onMount(() => { class="flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[#002850] text-[10px] text-white" aria-hidden="true" > - {person.firstName[0]}{person.lastName[0]} + {person.name.charAt(0).toUpperCase()} - {person.firstName} + {person.name} {/each} diff --git a/frontend/src/routes/korrespondenz/page.svelte.spec.ts b/frontend/src/routes/korrespondenz/page.svelte.spec.ts index 85f18e63..b4e4ca7e 100644 --- a/frontend/src/routes/korrespondenz/page.svelte.spec.ts +++ b/frontend/src/routes/korrespondenz/page.svelte.spec.ts @@ -18,8 +18,15 @@ const baseData = { filters: { senderId: '', receiverId: '', from: '', to: '', dir: 'DESC' as const } }; +const withSender = { + ...baseData, + initialValues: { senderName: 'Hans Müller', receiverName: '' }, + filters: { ...baseData.filters, senderId: 'p1' } +}; + const withPersons = { ...baseData, + initialValues: { senderName: 'Hans Müller', receiverName: 'Anna Schmidt' }, filters: { ...baseData.filters, senderId: 'p1', receiverId: 'p2' } }; @@ -45,41 +52,120 @@ const withDocs = { documents: [makeDoc()] }; -// ─── Empty state ────────────────────────────────────────────────────────────── +// ─── Empty state (no senderId) ──────────────────────────────────────────────── -describe('Conversations page – empty state', () => { - it('shows the "select two persons" prompt when no persons are selected', async () => { +describe('Korrespondenz page – empty state', () => { + it('shows the search heading when no person is selected', async () => { render(Page, { data: baseData }); - await expect.element(page.getByText(/Wählen Sie zwei Personen aus/i)).toBeInTheDocument(); + await expect.element(page.getByText(/Korrespondenz durchsuchen/i)).toBeInTheDocument(); }); - it('hides the swap button when no persons are selected', async () => { + it('shows the empty-search button', async () => { render(Page, { data: baseData }); - // Button is always in the DOM (holds grid column width on desktop) but made invisible - await expect.element(page.getByTestId('conv-swap-btn')).toHaveClass('invisible'); + await expect.element(page.getByTestId('conv-empty-search')).toBeInTheDocument(); }); - it('does not show the new document link when no persons are selected', async () => { + it('does not show the new document link when no person is selected', async () => { render(Page, { data: baseData }); await expect.element(page.getByTestId('conv-new-doc-link')).not.toBeInTheDocument(); }); + + it('does not show a year divider when no person is selected', async () => { + render(Page, { data: baseData }); + await expect.element(page.getByTestId('year-divider')).not.toBeInTheDocument(); + }); +}); + +// ─── Recent persons chips ───────────────────────────────────────────────────── + +describe('Korrespondenz page – recent persons', () => { + it('shows recent person chips from localStorage', async () => { + localStorage.setItem( + 'korrespondenz_recent_persons', + JSON.stringify([{ id: 'r1', name: 'Clara Braun' }]) + ); + render(Page, { data: baseData }); + await expect.element(page.getByText('Clara Braun')).toBeInTheDocument(); + localStorage.removeItem('korrespondenz_recent_persons'); + }); + + it('does not crash when localStorage contains corrupt JSON', async () => { + localStorage.setItem('korrespondenz_recent_persons', '}{not valid json'); + render(Page, { data: baseData }); + // Empty state heading is still shown — no chip list crash + await expect.element(page.getByText(/Korrespondenz durchsuchen/i)).toBeInTheDocument(); + localStorage.removeItem('korrespondenz_recent_persons'); + }); +}); + +// ─── Single-person hint bar ─────────────────────────────────────────────────── + +describe('Korrespondenz page – single-person hint bar', () => { + it('shows hint bar when only senderId is set', async () => { + render(Page, { data: withSender }); + await expect.element(page.getByText(/Alle Briefe von Hans Müller/i)).toBeInTheDocument(); + }); + + it('does not show hint bar when both persons are set', async () => { + render(Page, { data: { ...withPersons, documents: [makeDoc()] } }); + await expect.element(page.getByText(/Alle Briefe von Hans Müller/i)).not.toBeInTheDocument(); + }); + + it('does not show hint bar when no person is set', async () => { + render(Page, { data: baseData }); + await expect.element(page.getByText(/Alle Briefe von/i)).not.toBeInTheDocument(); + }); +}); + +// ─── Filter controls disabled state ────────────────────────────────────────── + +describe('Korrespondenz page – filter strip Row 2 disabled state', () => { + it('renders filter controls with aria-disabled when no senderId', async () => { + render(Page, { data: baseData }); + const strip = document.querySelector('[aria-disabled="true"]'); + expect(strip).not.toBeNull(); + }); +}); + +// ─── Strip letter count ─────────────────────────────────────────────────────── + +describe('Korrespondenz page – strip letter count', () => { + it('shows 0 Briefe when senderId is set but no documents', async () => { + render(Page, { data: withSender }); + await expect.element(page.getByTestId('conv-strip-count')).toHaveTextContent('0 Briefe'); + }); + + it('shows correct count when documents are loaded', async () => { + render(Page, { data: { ...withPersons, documents: [makeDoc()] } }); + await expect.element(page.getByTestId('conv-strip-count')).toHaveTextContent('1 Briefe'); + }); }); // ─── No results ─────────────────────────────────────────────────────────────── -describe('Conversations page – no results', () => { - it('shows "no documents found" when both persons are selected but there are no documents', async () => { - render(Page, { data: withPersons }); +describe('Korrespondenz page – no results', () => { + it('shows "no documents found" when a person is selected but there are no documents', async () => { + render(Page, { data: withSender }); await expect.element(page.getByText(/Keine Dokumente gefunden/i)).toBeInTheDocument(); }); }); // ─── Swap button ────────────────────────────────────────────────────────────── -describe('Conversations page – swap button', () => { - it('shows the swap button when both persons are selected', async () => { +describe('Korrespondenz page – swap button', () => { + it('swap button is invisible when only one person is set', async () => { + render(Page, { data: withSender }); + const btn = document.querySelector('[data-testid="conv-swap-btn"]'); + expect(btn).not.toBeNull(); + // opacity-0 is applied via class when swapVisible is false + expect(btn!.className).toMatch(/opacity-0/); + }); + + it('swap button is visible when both persons are set', async () => { render(Page, { data: withPersons }); - await expect.element(page.getByTestId('conv-swap-btn')).not.toHaveClass('invisible'); + const btn = document.querySelector('[data-testid="conv-swap-btn"]'); + expect(btn).not.toBeNull(); + expect(btn!.className).not.toMatch(/opacity-0/); }); it('calls goto with swapped sender and receiver when clicked', async () => { @@ -92,28 +178,9 @@ describe('Conversations page – swap button', () => { }); }); -// ─── Summary ────────────────────────────────────────────────────────────────── - -describe('Conversations page – summary', () => { - it('shows document count and year range when documents are loaded', async () => { - const data = { - ...withPersons, - documents: [ - makeDoc({ documentDate: '1923-04-12' }), - makeDoc({ id: 'd2', documentDate: '1965-08-03' }) - ] - }; - render(Page, { data }); - const summary = page.getByTestId('conv-summary'); - await expect.element(summary).toHaveTextContent('2'); - await expect.element(summary).toHaveTextContent('1923'); - await expect.element(summary).toHaveTextContent('1965'); - }); -}); - // ─── Year dividers ──────────────────────────────────────────────────────────── -describe('Conversations page – year dividers', () => { +describe('Korrespondenz page – year dividers', () => { it('renders a year divider for the first document', async () => { render(Page, { data: withDocs }); await expect.element(page.getByTestId('year-divider').first()).toHaveTextContent('1923'); @@ -141,7 +208,6 @@ describe('Conversations page – year dividers', () => { ] }; render(Page, { data }); - // Only one divider for 1923; 1965 divider should not appear await expect.element(page.getByTestId('year-divider').first()).toHaveTextContent('1923'); await expect.element(page.getByTestId('year-divider').nth(1)).not.toBeInTheDocument(); }); @@ -149,12 +215,21 @@ describe('Conversations page – year dividers', () => { // ─── New document link ──────────────────────────────────────────────────────── -describe('Conversations page – new document link', () => { - it('shows the link with correct href for a write user', async () => { +describe('Korrespondenz page – new document link', () => { + it('shows the link with correct href for a write user (bilateral)', async () => { render(Page, { data: { ...withDocs, canWrite: true } }); const link = page.getByTestId('conv-new-doc-link'); await expect.element(link).toBeInTheDocument(); - await expect.element(link).toHaveAttribute('href', '/documents/new?senderId=p1&receiverId=p2'); + await expect.element(link).toHaveAttribute('href', expect.stringContaining('senderId=p1')); + await expect.element(link).toHaveAttribute('href', expect.stringContaining('receiverId=p2')); + }); + + it('shows the link with correct href for single-person mode', async () => { + render(Page, { data: { ...withSender, documents: [makeDoc()], canWrite: true } }); + const link = page.getByTestId('conv-new-doc-link'); + await expect.element(link).toBeInTheDocument(); + await expect.element(link).toHaveAttribute('href', expect.stringContaining('senderId=p1')); + await expect.element(link).not.toHaveAttribute('href', expect.stringContaining('receiverId')); }); it('hides the link for a read-only user', async () => {