All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m20s
CI / OCR Service Tests (pull_request) Successful in 21s
CI / Backend Unit Tests (pull_request) Successful in 3m22s
CI / fail2ban Regex (pull_request) Successful in 46s
CI / Semgrep Security Scan (pull_request) Successful in 19s
CI / Compose Bucket Idempotency (pull_request) Successful in 58s
Dismiss (X) button and mark-all-read button now submit forms to /aktivitaeten?/dismiss-notification and /aktivitaeten?/mark-all-read respectively. Props renamed onMarkRead/onMarkAllRead → optimisticMarkRead/optimisticMarkAllRead. aktivitaeten/+page.svelte drops the now-deleted onMarkRead/onMarkAllRead wrapper functions and passes notificationStore.optimisticMarkRead/optimisticMarkAllRead directly to the box. Tests: $app/forms enhance mock added to both spec files so dismiss and mark-all assertions work synchronously against form-submit events. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
144 lines
4.3 KiB
TypeScript
144 lines
4.3 KiB
TypeScript
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';
|
|
|
|
vi.mock('$app/forms', () => ({
|
|
enhance(node: HTMLFormElement, submit?: (opts: { formData: FormData }) => unknown) {
|
|
const handler = (e: Event) => {
|
|
e.preventDefault();
|
|
submit?.({ formData: new FormData(node) } as never);
|
|
};
|
|
node.addEventListener('submit', handler);
|
|
return { destroy: () => node.removeEventListener('submit', handler) };
|
|
}
|
|
}));
|
|
|
|
afterEach(cleanup);
|
|
|
|
const mention = (overrides: Partial<NotificationItem> = {}): 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');
|
|
});
|
|
});
|