import { describe, it, expect, vi, afterEach } from 'vitest'; import { cleanup, render } from 'vitest-browser-svelte'; import { page } from 'vitest/browser'; import ChronikFuerDichBox from './ChronikFuerDichBox.svelte'; import type { NotificationItem } from '$lib/notification/notifications'; const mockFormResult = vi.hoisted(() => ({ type: 'success' as string })); vi.mock('$app/forms', () => ({ enhance( node: HTMLFormElement, submit?: (opts: { formData: FormData; }) => (opts: { result: { type: string; data?: Record }; update: () => Promise; }) => Promise ) { const handler = async (e: Event) => { e.preventDefault(); const cb = submit?.({ formData: new FormData(node) } as never); if (typeof cb === 'function') { await ( cb as (o: { result: typeof mockFormResult; update: () => Promise }) => Promise )({ result: mockFormResult, update: async () => {} }); } }; node.addEventListener('submit', handler); return { destroy: () => node.removeEventListener('submit', handler) }; } })); afterEach(() => { cleanup(); mockFormResult.type = 'success'; }); const mention = (overrides: Partial = {}): NotificationItem => ({ id: 'n-1', type: 'MENTION', documentId: 'doc-1', referenceId: 'ref-1', annotationId: null, read: false, createdAt: new Date().toISOString(), actorName: 'Anna', documentTitle: 'Brief 1899', ...overrides }); describe('ChronikFuerDichBox', () => { it('renders the inbox-zero state when there are no unread', async () => { render(ChronikFuerDichBox, { props: { unread: [], optimisticMarkRead: () => {}, optimisticMarkAllRead: () => {} } }); await expect.element(page.getByText(/keine neuen erwähnungen/i)).toBeVisible(); const link = document.querySelector('a[href="/aktivitaeten?filter=fuer-dich"]'); expect(link).not.toBeNull(); }); it('renders the count badge with the unread count', async () => { render(ChronikFuerDichBox, { props: { unread: [mention(), mention({ id: 'n-2' }), mention({ id: 'n-3' })], optimisticMarkRead: () => {}, optimisticMarkAllRead: () => {} } }); const badge = document.querySelector('[data-testid="chronik-fuerdich-count"]'); expect(badge?.textContent).toContain('3'); }); it('uses the @ glyph for MENTION and ↩ for REPLY', async () => { render(ChronikFuerDichBox, { props: { unread: [mention({ id: 'n-m', type: 'MENTION' }), mention({ id: 'n-r', type: 'REPLY' })], optimisticMarkRead: () => {}, optimisticMarkAllRead: () => {} } }); const items = document.querySelectorAll('ul[role="list"] li'); expect(items.length).toBe(2); expect(items[0].textContent).toContain('@'); expect(items[1].textContent).toContain('↩'); }); it('renders MENTION verb text from paraglide messages', async () => { render(ChronikFuerDichBox, { props: { unread: [mention({ actorName: 'Bertha' })], optimisticMarkRead: () => {}, optimisticMarkAllRead: () => {} } }); await expect .element(page.getByText(/bertha hat dich in einem kommentar erwähnt/i)) .toBeVisible(); }); it('renders REPLY verb text from paraglide messages', async () => { render(ChronikFuerDichBox, { props: { unread: [mention({ type: 'REPLY', actorName: 'Carl' })], optimisticMarkRead: () => {}, optimisticMarkAllRead: () => {} } }); await expect .element(page.getByText(/carl hat auf deinen kommentar geantwortet/i)) .toBeVisible(); }); it('calls optimisticMarkRead with the notification id when its dismiss button is clicked', async () => { const optimisticMarkRead = vi.fn(); const item = mention({ id: 'n-7' }); render(ChronikFuerDichBox, { props: { unread: [item], optimisticMarkRead, optimisticMarkAllRead: () => {} } }); const dismiss = document.querySelector( '[data-testid="chronik-fuerdich-dismiss"]' ) as HTMLElement; dismiss.click(); expect(optimisticMarkRead).toHaveBeenCalledWith('n-7'); }); it('calls optimisticMarkAllRead when the mark-all-read button is clicked', async () => { const optimisticMarkAllRead = vi.fn(); render(ChronikFuerDichBox, { props: { unread: [mention()], optimisticMarkRead: () => {}, optimisticMarkAllRead } }); const btn = document.querySelector('[data-testid="chronik-mark-all-read"]') as HTMLElement; btn.click(); expect(optimisticMarkAllRead).toHaveBeenCalledOnce(); }); it('builds a deep-link href to the comment for each notification', async () => { render(ChronikFuerDichBox, { props: { unread: [mention({ documentId: 'doc-x', referenceId: 'ref-y', annotationId: null })], optimisticMarkRead: () => {}, optimisticMarkAllRead: () => {} } }); const link = document.querySelector('ul[role="list"] li a') as HTMLAnchorElement; expect(link.getAttribute('href')).toContain('doc-x'); }); it('shows an accessible error banner when the dismiss action returns a failure', async () => { mockFormResult.type = 'failure'; render(ChronikFuerDichBox, { props: { unread: [mention({ id: 'err-1' })], optimisticMarkRead: () => {}, optimisticMarkAllRead: () => {} } }); const dismiss = document.querySelector( '[data-testid="chronik-fuerdich-dismiss"]' ) as HTMLElement; dismiss.click(); // Allow microtask queue to flush await new Promise((r) => setTimeout(r, 0)); const alert = document.querySelector('[role="alert"]'); expect(alert).not.toBeNull(); }); });