diff --git a/frontend/src/lib/activity/ChronikRow.svelte.test.ts b/frontend/src/lib/activity/ChronikRow.svelte.test.ts new file mode 100644 index 00000000..c62f4a1e --- /dev/null +++ b/frontend/src/lib/activity/ChronikRow.svelte.test.ts @@ -0,0 +1,117 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import ChronikRow from './ChronikRow.svelte'; + +afterEach(cleanup); + +const baseActor = { id: 'a1', name: 'Anna Schmidt', initials: 'AS', color: '#012851' }; + +const makeItem = (overrides: Record = {}) => ({ + id: 'i1', + kind: 'TEXT_SAVED' as string, + actor: baseActor as null | typeof baseActor, + documentId: 'd1', + documentTitle: 'Brief 1923', + count: 1, + happenedAt: '2026-04-15T10:00:00Z', + happenedAtUntil: null as string | null, + commentId: null as string | null, + commentPreview: null as string | null, + annotationId: null as string | null, + youMentioned: false, + ...overrides +}); + +describe('ChronikRow', () => { + it('renders the actor avatar with initials when actor is present', async () => { + render(ChronikRow, { props: { item: makeItem() } }); + + expect(document.body.textContent).toContain('AS'); + }); + + it('renders the question-mark fallback avatar when actor is null', async () => { + render(ChronikRow, { props: { item: makeItem({ actor: null }) } }); + + const fallback = document.querySelector('[data-testid="chronik-avatar-fallback"]'); + expect(fallback).not.toBeNull(); + }); + + it('renders the for-you marker when youMentioned is true', async () => { + render(ChronikRow, { props: { item: makeItem({ youMentioned: true }) } }); + + const marker = document.querySelector('[data-testid="chronik-foryou-marker"]'); + expect(marker).not.toBeNull(); + }); + + it('renders the for-you data-variant when youMentioned is true', async () => { + render(ChronikRow, { props: { item: makeItem({ youMentioned: true }) } }); + + const link = document.querySelector('a[data-variant]') as HTMLElement; + expect(link.getAttribute('data-variant')).toBe('for-you'); + }); + + it('renders the rollup variant when count > 1', async () => { + render(ChronikRow, { props: { item: makeItem({ count: 3 }) } }); + + const link = document.querySelector('a[data-variant]') as HTMLElement; + expect(link.getAttribute('data-variant')).toBe('rollup'); + const badge = document.querySelector('[data-testid="chronik-count-badge"]'); + expect(badge).not.toBeNull(); + }); + + it('renders the comment variant for COMMENT_ADDED kind', async () => { + render(ChronikRow, { + props: { item: makeItem({ kind: 'COMMENT_ADDED', commentPreview: 'Tolle Geschichte!' }) } + }); + + const link = document.querySelector('a[data-variant]') as HTMLElement; + expect(link.getAttribute('data-variant')).toBe('comment'); + const preview = document.querySelector('[data-testid="chronik-comment-preview"]'); + expect(preview?.textContent).toContain('Tolle Geschichte!'); + }); + + it('falls back to ellipsis comment preview when commentPreview is null', async () => { + render(ChronikRow, { props: { item: makeItem({ kind: 'COMMENT_ADDED' }) } }); + + const preview = document.querySelector('[data-testid="chronik-comment-preview"]'); + expect(preview?.textContent).toContain('…'); + }); + + it('renders the document title in a styled span', async () => { + render(ChronikRow, { props: { item: makeItem() } }); + + const title = document.querySelector('[data-testid="chronik-doc-title"]'); + expect(title?.textContent).toBe('Brief 1923'); + }); + + it('uses /documents/{id} as default href', async () => { + render(ChronikRow, { props: { item: makeItem() } }); + + const link = document.querySelector('a[data-variant]') as HTMLAnchorElement; + expect(link.href).toContain('/documents/d1'); + }); + + it('uses comment-deep-link href when commentId is set', async () => { + render(ChronikRow, { + props: { item: makeItem({ commentId: 'c1', kind: 'COMMENT_ADDED' }) } + }); + + const link = document.querySelector('a[data-variant]') as HTMLAnchorElement; + expect(link.href).toContain('c1'); + }); + + it('renders a time-range label when rollup has happenedAtUntil', async () => { + render(ChronikRow, { + props: { + item: makeItem({ + count: 5, + happenedAt: '2026-04-15T10:00:00Z', + happenedAtUntil: '2026-04-15T14:30:00Z' + }) + } + }); + + // Time range uses U+2013 between two HH:MM strings — check for any colon-bearing time + expect(document.body.textContent).toMatch(/\d{2}:\d{2}/); + }); +});