From ae6355d206333928bbecc474c2aa2165eea67f63 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 8 May 2026 16:56:52 +0200 Subject: [PATCH] =?UTF-8?q?refactor(dashboard):=20ReaderPersonChips=20?= =?UTF-8?q?=E2=86=92=20grid=20layout=20with=20mint-pill=20doc=20count=20(T?= =?UTF-8?q?DD,=20#483)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../shared/dashboard/ReaderPersonChips.svelte | 25 ++++---- .../ReaderPersonChips.svelte.spec.ts | 59 ++++++++++++++++--- 2 files changed, 63 insertions(+), 21 deletions(-) diff --git a/frontend/src/lib/shared/dashboard/ReaderPersonChips.svelte b/frontend/src/lib/shared/dashboard/ReaderPersonChips.svelte index f78a9b6a..e91a4310 100644 --- a/frontend/src/lib/shared/dashboard/ReaderPersonChips.svelte +++ b/frontend/src/lib/shared/dashboard/ReaderPersonChips.svelte @@ -27,37 +27,34 @@ interface Props { const { persons }: Props = $props(); -
-

- {m.dashboard_reader_person_chips_heading()} -

+
{#if persons.length === 0}

{m.dashboard_reader_no_persons()}

{/if} - +
diff --git a/frontend/src/lib/shared/dashboard/ReaderPersonChips.svelte.spec.ts b/frontend/src/lib/shared/dashboard/ReaderPersonChips.svelte.spec.ts index 4aebb735..0ae40474 100644 --- a/frontend/src/lib/shared/dashboard/ReaderPersonChips.svelte.spec.ts +++ b/frontend/src/lib/shared/dashboard/ReaderPersonChips.svelte.spec.ts @@ -32,7 +32,7 @@ const person2: PersonSummaryDTO = { }; describe('ReaderPersonChips', () => { - it('renders a chip for each person with correct href', async () => { + it('renders a card for each person with correct href', async () => { render(ReaderPersonChips, { persons: [person1, person2] }); const link1 = page.getByRole('link', { name: /Anna Müller/ }); await expect @@ -44,12 +44,44 @@ describe('ReaderPersonChips', () => { .toHaveAttribute('href', '/persons/aaaaaaaa-0000-0000-0000-000000000002'); }); - it('shows document count in each chip', async () => { + it('person card has min-h-[44px] touch target', async () => { render(ReaderPersonChips, { persons: [person1] }); - const chip = page.getByRole('link', { name: /Anna Müller/ }); - await expect.element(chip).toBeInTheDocument(); - const text = ((await chip.element()) as HTMLElement).textContent; - expect(text).toContain('23'); + const link = page.getByRole('link', { name: /Anna Müller/ }); + const cls = ((await link.element()) as HTMLElement).className; + expect(cls).toMatch(/min-h-\[44px\]/); + }); + + it('doc count renders as mint pill with bg-mint-soft', async () => { + render(ReaderPersonChips, { persons: [person1] }); + const link = page.getByRole('link', { name: /Anna Müller/ }); + const el = (await link.element()) as HTMLElement; + const pill = el.querySelector('[class*="bg-mint-soft"]'); + expect(pill).not.toBeNull(); + expect(pill!.textContent).toContain('23'); + }); + + it('doc count pill has rounded-full class', async () => { + render(ReaderPersonChips, { persons: [person1] }); + const link = page.getByRole('link', { name: /Anna Müller/ }); + const el = (await link.element()) as HTMLElement; + const pill = el.querySelector('[class*="rounded-full"]'); + expect(pill).not.toBeNull(); + }); + + it('person grid uses grid layout', async () => { + render(ReaderPersonChips, { persons: [person1, person2] }); + const section = page.getByRole('region'); + const el = (await section.element()) as HTMLElement; + const grid = el.querySelector('[class*="grid"]'); + expect(grid).not.toBeNull(); + }); + + it('wrapper is a section with aria-label', async () => { + render(ReaderPersonChips, { persons: [person1] }); + const section = page.getByRole('region'); + await expect.element(section).toBeInTheDocument(); + const label = ((await section.element()) as HTMLElement).getAttribute('aria-label'); + expect(label).toBeTruthy(); }); it('renders an "Alle Personen" link to /persons', async () => { @@ -58,6 +90,13 @@ describe('ReaderPersonChips', () => { await expect.element(allLink).toHaveAttribute('href', '/persons'); }); + it('"Alle Personen" link has text-link-quiet class', async () => { + render(ReaderPersonChips, { persons: [person1] }); + const allLink = page.getByRole('link', { name: /Alle Personen/i }); + const cls = ((await allLink.element()) as HTMLElement).className; + expect(cls).toMatch(/text-link-quiet/); + }); + it('exposes a focus-visible ring on the "Alle Personen" link', async () => { render(ReaderPersonChips, { persons: [person1] }); const allLink = page.getByRole('link', { name: /Alle Personen/i }); @@ -73,7 +112,13 @@ describe('ReaderPersonChips', () => { expect(cls).toMatch(/min-h-\[44px\]/); }); - it('renders empty state without chips when persons array is empty', async () => { + it('does not render h2 heading', async () => { + render(ReaderPersonChips, { persons: [person1] }); + const heading = page.getByRole('heading', { level: 2 }); + await expect.element(heading).not.toBeInTheDocument(); + }); + + it('renders empty state without person cards when persons array is empty', async () => { render(ReaderPersonChips, { persons: [] }); const chips = page.getByRole('link', { name: /Müller|Schmidt/ }); await expect.element(chips).not.toBeInTheDocument();