From a8577fabc497192a9f02233d68c3b63e0d057368 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 10 May 2026 02:45:59 +0200 Subject: [PATCH] test(activity): cover ChronikErrorCard and ChronikTimeline ChronikErrorCard: default vs supplied message, retry callback wiring, role=alert. ChronikTimeline: empty render, today bucket grouping, older bucket grouping, multi-bucket grouping when items span time ranges. 8 tests, ~20 branches. Refs #496. Co-Authored-By: Claude Sonnet 4.6 --- .../activity/ChronikErrorCard.svelte.test.ts | 37 ++++++++++ .../activity/ChronikTimeline.svelte.test.ts | 67 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 frontend/src/lib/activity/ChronikErrorCard.svelte.test.ts create mode 100644 frontend/src/lib/activity/ChronikTimeline.svelte.test.ts diff --git a/frontend/src/lib/activity/ChronikErrorCard.svelte.test.ts b/frontend/src/lib/activity/ChronikErrorCard.svelte.test.ts new file mode 100644 index 00000000..8a516501 --- /dev/null +++ b/frontend/src/lib/activity/ChronikErrorCard.svelte.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import ChronikErrorCard from './ChronikErrorCard.svelte'; + +afterEach(cleanup); + +describe('ChronikErrorCard', () => { + it('renders the default error message when no message is supplied', async () => { + render(ChronikErrorCard, { props: { onRetry: () => {} } }); + + await expect.element(page.getByText(/Aktivitäten konnten nicht/i)).toBeVisible(); + }); + + it('renders the supplied message when provided', async () => { + render(ChronikErrorCard, { + props: { onRetry: () => {}, message: 'Custom error message' } + }); + + await expect.element(page.getByText('Custom error message')).toBeVisible(); + }); + + it('calls onRetry when the retry button is clicked', async () => { + const onRetry = vi.fn(); + render(ChronikErrorCard, { props: { onRetry } }); + + await page.getByRole('button', { name: /erneut versuchen/i }).click(); + + expect(onRetry).toHaveBeenCalledOnce(); + }); + + it('marks the card as role="alert" for assistive tech', async () => { + render(ChronikErrorCard, { props: { onRetry: () => {} } }); + + await expect.element(page.getByRole('alert')).toBeVisible(); + }); +}); diff --git a/frontend/src/lib/activity/ChronikTimeline.svelte.test.ts b/frontend/src/lib/activity/ChronikTimeline.svelte.test.ts new file mode 100644 index 00000000..49300ce0 --- /dev/null +++ b/frontend/src/lib/activity/ChronikTimeline.svelte.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import ChronikTimeline from './ChronikTimeline.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, + documentId: 'd1', + documentTitle: 'Brief 1923', + count: 1, + happenedAt: new Date().toISOString(), + youMentioned: false, + ...overrides +}); + +describe('ChronikTimeline', () => { + it('renders nothing when items is empty', async () => { + render(ChronikTimeline, { props: { items: [] } }); + + const buckets = document.querySelectorAll('[data-testid^="chronik-bucket-"]'); + expect(buckets.length).toBe(0); + }); + + it('renders the today bucket for today items', async () => { + const today = new Date(); + render(ChronikTimeline, { + props: { items: [makeItem({ id: 'i1', happenedAt: today.toISOString() })] } + }); + + const today_bucket = document.querySelector('[data-testid="chronik-bucket-today"]'); + expect(today_bucket).not.toBeNull(); + }); + + it('renders the older bucket for old items', async () => { + render(ChronikTimeline, { + props: { items: [makeItem({ id: 'i1', happenedAt: '2020-01-01T10:00:00Z' })] } + }); + + const olderBucket = document.querySelector('[data-testid="chronik-bucket-older"]'); + expect(olderBucket).not.toBeNull(); + }); + + it('renders multiple buckets when items span time ranges', async () => { + const today = new Date(); + render(ChronikTimeline, { + props: { + items: [ + makeItem({ id: 'i1', kind: 'TEXT_SAVED', happenedAt: today.toISOString() }), + makeItem({ + id: 'i2', + kind: 'FILE_UPLOADED', + documentId: 'd2', + happenedAt: '2020-01-01T10:00:00Z' + }) + ] + } + }); + + const buckets = document.querySelectorAll('[data-testid^="chronik-bucket-"]'); + expect(buckets.length).toBeGreaterThanOrEqual(2); + }); +});