From f39d9e6f30f16b866896b50ecdc6f5ffbec16545 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 6 Apr 2026 19:43:54 +0200 Subject: [PATCH] =?UTF-8?q?feat(ui):=20two=20render=20states=20=E2=80=94?= =?UTF-8?q?=20hero=20vs=20results=20=E2=80=94=20with=20unified=20padding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hero state (no senderId): centred CorrespondenzHero with discovery headline, cross-link, large typeahead, recent persons. No person bar or filter controls shown. Results state (senderId set): full-width strips then content area with max-w-7xl responsive padding matching other overview pages. Removes focus delegation hack. Refs: #179 Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/routes/briefwechsel/+page.svelte | 137 ++++++++++-------- .../CorrespondenzFilterControls.svelte | 1 + .../CorrespondenzPersonBar.svelte | 5 +- .../routes/briefwechsel/page.svelte.spec.ts | 70 ++++++--- 4 files changed, 126 insertions(+), 87 deletions(-) diff --git a/frontend/src/routes/briefwechsel/+page.svelte b/frontend/src/routes/briefwechsel/+page.svelte index 3ac6308f..a280e90b 100644 --- a/frontend/src/routes/briefwechsel/+page.svelte +++ b/frontend/src/routes/briefwechsel/+page.svelte @@ -1,30 +1,26 @@ - -
- - - - - - - - {#if isSinglePerson} - +
+ +
+{:else} + +
+ - {/if} -
- -
- {#if !senderId} - - {:else if data.documents.length === 0} -
-

{m.conv_no_results_heading()}

-

{m.conv_no_results_text()}

-
- {:else} - - {/if} -
+ + {#if isSinglePerson} + + {/if} +
+ +
+ {#if data.documents.length === 0} +
+

{m.conv_no_results_heading()}

+

{m.conv_no_results_text()}

+
+ {:else} + + {/if} +
+{/if} diff --git a/frontend/src/routes/briefwechsel/CorrespondenzFilterControls.svelte b/frontend/src/routes/briefwechsel/CorrespondenzFilterControls.svelte index d26bd3bb..78313cda 100644 --- a/frontend/src/routes/briefwechsel/CorrespondenzFilterControls.svelte +++ b/frontend/src/routes/briefwechsel/CorrespondenzFilterControls.svelte @@ -27,6 +27,7 @@ let isActive = $derived(!!(fromDate || toDate || sortDir !== 'DESC'));
-
+
{ - it('shows the search heading when no person is selected', async () => { +describe('Briefwechsel page – hero state', () => { + it('shows the hero when no person is selected', async () => { render(Page, { data: baseData }); - await expect.element(page.getByText(/Korrespondenz durchsuchen/i)).toBeInTheDocument(); + await expect.element(page.getByTestId('conv-hero')).toBeInTheDocument(); }); - it('shows the empty-search button', async () => { + it('shows the discovery headline', async () => { render(Page, { data: baseData }); - await expect.element(page.getByTestId('conv-empty-search')).toBeInTheDocument(); + await expect.element(page.getByText(/Wessen Briefe möchten Sie lesen/i)).toBeInTheDocument(); + }); + + it('does not show the person bar in hero state', async () => { + render(Page, { data: baseData }); + await expect.element(page.getByTestId('conv-hero')).toBeInTheDocument(); + await expect.element(page.getByTestId('conv-person-bar')).not.toBeInTheDocument(); + }); + + it('does not show filter controls in hero state', async () => { + render(Page, { data: baseData }); + await expect.element(page.getByTestId('conv-hero')).toBeInTheDocument(); + await expect.element(page.getByTestId('conv-filter-controls')).not.toBeInTheDocument(); }); it('does not show the new document link when no person is selected', async () => { @@ -77,9 +89,29 @@ describe('Korrespondenz page – empty state', () => { }); }); +// ─── Results state (senderId set) ──────────────────────────────────────────── + +describe('Briefwechsel page – results state', () => { + it('does not show the hero when senderId is set', async () => { + render(Page, { data: withSender }); + await expect.element(page.getByTestId('conv-person-bar')).toBeInTheDocument(); + await expect.element(page.getByTestId('conv-hero')).not.toBeInTheDocument(); + }); + + it('shows the person bar when senderId is set', async () => { + render(Page, { data: withSender }); + await expect.element(page.getByTestId('conv-person-bar')).toBeInTheDocument(); + }); + + it('shows filter controls when senderId is set', async () => { + render(Page, { data: withSender }); + await expect.element(page.getByTestId('conv-filter-controls')).toBeInTheDocument(); + }); +}); + // ─── Recent persons chips ───────────────────────────────────────────────────── -describe('Korrespondenz page – recent persons', () => { +describe('Briefwechsel page – recent persons', () => { it('shows recent person chips from localStorage', async () => { localStorage.setItem( 'korrespondenz_recent_persons', @@ -93,15 +125,14 @@ describe('Korrespondenz page – 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(); + await expect.element(page.getByText(/Wessen Briefe möchten Sie lesen/i)).toBeInTheDocument(); localStorage.removeItem('korrespondenz_recent_persons'); }); }); // ─── Single-person hint bar ─────────────────────────────────────────────────── -describe('Korrespondenz page – single-person hint bar', () => { +describe('Briefwechsel 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(); @@ -120,13 +151,7 @@ describe('Korrespondenz page – single-person hint bar', () => { // ─── 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(); - }); - +describe('Briefwechsel page – filter strip Row 2 disabled state', () => { it('filter controls are not aria-disabled when senderId is set', async () => { render(Page, { data: withSender }); const strip = document.querySelector('[aria-disabled="false"]'); @@ -136,7 +161,7 @@ describe('Korrespondenz page – filter strip Row 2 disabled state', () => { // ─── Strip letter count ─────────────────────────────────────────────────────── -describe('Korrespondenz page – strip letter count', () => { +describe('Briefwechsel 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'); @@ -150,7 +175,7 @@ describe('Korrespondenz page – strip letter count', () => { // ─── No results ─────────────────────────────────────────────────────────────── -describe('Korrespondenz page – no results', () => { +describe('Briefwechsel 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(); @@ -159,12 +184,11 @@ describe('Korrespondenz page – no results', () => { // ─── Swap button ────────────────────────────────────────────────────────────── -describe('Korrespondenz page – swap button', () => { +describe('Briefwechsel 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/); }); @@ -187,7 +211,7 @@ describe('Korrespondenz page – swap button', () => { // ─── Year dividers ──────────────────────────────────────────────────────────── -describe('Korrespondenz page – year dividers', () => { +describe('Briefwechsel 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'); @@ -222,7 +246,7 @@ describe('Korrespondenz page – year dividers', () => { // ─── New document link ──────────────────────────────────────────────────────── -describe('Korrespondenz page – new document link', () => { +describe('Briefwechsel 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');