From 3972c406d1fdc38407c67ce6771d467e494818a0 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 10 May 2026 01:57:05 +0200 Subject: [PATCH] test(activity): cover ChronikRow variant + kind branches Avatar with initials vs question-mark fallback, for-you marker visibility, data-variant matrix (simple/for-you/rollup/comment), count badge for rollup, comment preview rendering with fallback, document title link, default vs comment-deep-link href, time-range label for rollup with happenedAtUntil. 11 tests covering ~40 of ChronikRow's high-uncovered branches. Refs #496. Co-Authored-By: Claude Sonnet 4.6 --- .../lib/activity/ChronikRow.svelte.test.ts | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 frontend/src/lib/activity/ChronikRow.svelte.test.ts 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}/); + }); +});