diff --git a/frontend/src/lib/activity/DashboardActivityFeed.svelte.test.ts b/frontend/src/lib/activity/DashboardActivityFeed.svelte.test.ts new file mode 100644 index 00000000..a0e7fbfc --- /dev/null +++ b/frontend/src/lib/activity/DashboardActivityFeed.svelte.test.ts @@ -0,0 +1,161 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import DashboardActivityFeed from './DashboardActivityFeed.svelte'; +import type { components } from '$lib/generated/api'; + +type ActivityFeedItemDTO = components['schemas']['ActivityFeedItemDTO']; + +afterEach(cleanup); + +const baseItem = (overrides: Partial = {}): ActivityFeedItemDTO => + ({ + kind: 'TEXT_SAVED', + documentId: 'doc-1', + documentTitle: 'Brief 1899', + actor: { + id: 'u-1', + name: 'Anna Schmidt', + initials: 'AS', + color: '#336699' + }, + count: 1, + happenedAt: '2026-04-14T14:02:00Z', + happenedAtUntil: null, + youMentioned: false, + ...overrides + }) as ActivityFeedItemDTO; + +describe('DashboardActivityFeed', () => { + it('renders the feed caption and show-all link', async () => { + render(DashboardActivityFeed, { props: { feed: [] } }); + + await expect.element(page.getByText('Kommentare & Aktivität')).toBeVisible(); + const link = document.querySelector('a[href="/aktivitaeten"]'); + expect(link).not.toBeNull(); + }); + + it('renders nothing in the list when the feed is empty', async () => { + render(DashboardActivityFeed, { props: { feed: [] } }); + + const lists = document.querySelectorAll('ul'); + expect(lists.length).toBe(0); + }); + + it('renders one row per feed item with the actor initials', async () => { + render(DashboardActivityFeed, { + props: { + feed: [baseItem(), baseItem({ documentId: 'doc-2', documentTitle: 'Brief 1900' })] + } + }); + + const items = document.querySelectorAll('li'); + expect(items.length).toBe(2); + expect(document.body.textContent).toContain('AS'); + }); + + it('renders the question-mark badge when no actor is set', async () => { + render(DashboardActivityFeed, { + props: { feed: [baseItem({ actor: null as unknown as undefined })] } + }); + + const li = document.querySelector('li'); + expect(li?.textContent).toContain('?'); + }); + + it('renders the rollup count badge when count > 1', async () => { + render(DashboardActivityFeed, { + props: { feed: [baseItem({ count: 5 })] } + }); + + const badge = document.querySelector('[data-testid="feed-rollup-count"]'); + expect(badge?.textContent?.trim()).toBe('5'); + }); + + it('omits the rollup count badge when count is 1', async () => { + render(DashboardActivityFeed, { props: { feed: [baseItem({ count: 1 })] } }); + + const badge = document.querySelector('[data-testid="feed-rollup-count"]'); + expect(badge).toBeNull(); + }); + + it('renders the "für dich" badge when youMentioned is true', async () => { + render(DashboardActivityFeed, { + props: { feed: [baseItem({ youMentioned: true })] } + }); + + await expect.element(page.getByText(/für dich/i)).toBeVisible(); + }); + + it('maps the kind enum to a localized verb (TEXT_SAVED)', async () => { + render(DashboardActivityFeed, { + props: { feed: [baseItem({ kind: 'TEXT_SAVED' as ActivityFeedItemDTO['kind'] })] } + }); + + expect(document.body.textContent).toContain('hat Text gespeichert in'); + }); + + it('maps the kind enum to a localized verb (FILE_UPLOADED)', async () => { + render(DashboardActivityFeed, { + props: { feed: [baseItem({ kind: 'FILE_UPLOADED' as ActivityFeedItemDTO['kind'] })] } + }); + + expect(document.body.textContent).toContain('hat eine Datei hochgeladen'); + }); + + it('falls back to the raw kind when no verb is mapped', async () => { + render(DashboardActivityFeed, { + props: { + feed: [baseItem({ kind: 'UNKNOWN_KIND' as unknown as ActivityFeedItemDTO['kind'] })] + } + }); + + expect(document.body.textContent).toContain('UNKNOWN_KIND'); + }); + + it('renders a rollup time range when happenedAtUntil is set and count > 1', async () => { + render(DashboardActivityFeed, { + props: { + feed: [ + baseItem({ + happenedAt: '2026-04-14T14:02:00Z', + happenedAtUntil: '2026-04-14T14:32:00Z', + count: 3 + }) + ] + } + }); + + // "14:02–14:32" appears (with the en-dash) + expect(document.body.textContent).toMatch(/\d{2}:\d{2}–\d{2}:\d{2}/); + }); + + it('uses the actor initials as the fallback name when name is null', async () => { + render(DashboardActivityFeed, { + props: { + feed: [ + baseItem({ + actor: { + id: 'u-2', + name: null as unknown as undefined, + initials: 'XR', + color: '#000' + } + }) + ] + } + }); + + const strong = document.querySelector('strong'); + expect(strong?.textContent).toBe('XR'); + }); + + it('builds the document detail href from documentId', async () => { + render(DashboardActivityFeed, { + props: { feed: [baseItem({ documentId: 'doc-xyz', documentTitle: 'Brief 1901' })] } + }); + + const link = document.querySelector('a[href="/documents/doc-xyz"]'); + expect(link).not.toBeNull(); + }); +});