From 2ce95f2542af11542b9ed7c7c380996db5dc7ca3 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 29 Mar 2026 00:38:45 +0100 Subject: [PATCH] feat(#145): add DashboardMentions widget component Shows unread mention notifications as a dashboard widget. Renders nothing when the mentions list is empty. Co-Authored-By: Claude Sonnet 4.6 --- .../lib/components/DashboardMentions.svelte | 38 ++++++++++++ .../DashboardMentions.svelte.spec.ts | 58 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 frontend/src/lib/components/DashboardMentions.svelte create mode 100644 frontend/src/lib/components/DashboardMentions.svelte.spec.ts diff --git a/frontend/src/lib/components/DashboardMentions.svelte b/frontend/src/lib/components/DashboardMentions.svelte new file mode 100644 index 00000000..6a0098d6 --- /dev/null +++ b/frontend/src/lib/components/DashboardMentions.svelte @@ -0,0 +1,38 @@ + + +{#if mentions.length > 0} +
+

+ Erwähnungen +

+ {#each mentions as mention (mention.id)} +
+ {#if mention.documentId} + + {mention.actorName ?? ''} + + {:else} + {mention.actorName ?? ''} + {/if} +
+ {/each} +
+{/if} diff --git a/frontend/src/lib/components/DashboardMentions.svelte.spec.ts b/frontend/src/lib/components/DashboardMentions.svelte.spec.ts new file mode 100644 index 00000000..53a871c6 --- /dev/null +++ b/frontend/src/lib/components/DashboardMentions.svelte.spec.ts @@ -0,0 +1,58 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; + +import DashboardMentions from './DashboardMentions.svelte'; + +afterEach(cleanup); + +type NotificationDTO = { + id: string; + type: 'REPLY' | 'MENTION'; + documentId?: string; + read: boolean; + createdAt: string; + actorName?: string; +}; + +function makeMention(overrides: Partial = {}): NotificationDTO { + return { + id: 'notif-1', + type: 'MENTION', + documentId: 'doc-abc', + read: false, + createdAt: '2026-01-15T10:00:00Z', + actorName: 'Anna Schmidt', + ...overrides + }; +} + +describe('DashboardMentions', () => { + it('renders nothing when mentions list is empty', async () => { + render(DashboardMentions, { mentions: [] }); + const widget = page.getByTestId('dashboard-mentions'); + await expect.element(widget).not.toBeInTheDocument(); + }); + + it('shows a heading when mentions are present', async () => { + render(DashboardMentions, { mentions: [makeMention()] }); + const widget = page.getByTestId('dashboard-mentions'); + await expect.element(widget).toBeInTheDocument(); + }); + + it('renders one row per mention with link to document', async () => { + const mentions = [ + makeMention({ id: 'n1', documentId: 'doc-1', actorName: 'Anna' }), + makeMention({ id: 'n2', documentId: 'doc-2', actorName: 'Bob' }) + ]; + render(DashboardMentions, { mentions }); + const links = page.getByRole('link'); + await expect.element(links.nth(0)).toHaveAttribute('href', '/documents/doc-1'); + await expect.element(links.nth(1)).toHaveAttribute('href', '/documents/doc-2'); + }); + + it('shows actor name in each row', async () => { + render(DashboardMentions, { mentions: [makeMention({ actorName: 'Maria Müller' })] }); + await expect.element(page.getByText('Maria Müller')).toBeInTheDocument(); + }); +});