NotificationDropdown now wraps each row in a <form action="/aktivitaeten?/dismiss-notification"> and the mark-all control in <form action="/aktivitaeten?/mark-all-read">, wired via use:enhance for optimistic UI. Props renamed onMarkRead/onMarkAllRead → optimisticMarkRead/optimisticMarkAllRead to match the simplified store API. NotificationBell passes the store helpers directly; handleMarkRead is removed. Test mocks updated: $app/forms enhance mock fires SubmitFunction synchronously on form submit so callback assertions work without a real HTTP round-trip. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
import { afterEach, describe, it, expect, vi } from 'vitest';
|
|
import { cleanup, render } from 'vitest-browser-svelte';
|
|
import type { NotificationItem } from '$lib/notification/notifications';
|
|
import NotificationBell from './NotificationBell.svelte';
|
|
|
|
vi.mock('$app/navigation', () => ({ goto: vi.fn(), beforeNavigate: vi.fn() }));
|
|
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) };
|
|
}
|
|
}));
|
|
|
|
const mockNotificationList = vi.hoisted((): { value: NotificationItem[] } => ({ value: [] }));
|
|
|
|
vi.mock('$lib/notification/notifications.svelte', () => ({
|
|
notificationStore: {
|
|
get notifications() {
|
|
return mockNotificationList.value;
|
|
},
|
|
get unreadCount() {
|
|
return mockNotificationList.value.length;
|
|
},
|
|
optimisticMarkRead: vi.fn(),
|
|
optimisticMarkAllRead: vi.fn(),
|
|
fetchNotifications: vi.fn().mockResolvedValue(undefined),
|
|
init: vi.fn(),
|
|
destroy: vi.fn()
|
|
}
|
|
}));
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.clearAllMocks();
|
|
mockNotificationList.value = [];
|
|
});
|
|
|
|
const makeNotification = (overrides: Partial<NotificationItem> = {}): NotificationItem => ({
|
|
id: 'n1',
|
|
type: 'REPLY',
|
|
documentId: 'doc-1',
|
|
referenceId: 'ref-1',
|
|
annotationId: null,
|
|
read: false,
|
|
createdAt: '2026-04-21T10:00:00Z',
|
|
actorName: 'Anna',
|
|
documentTitle: 'Test Doc',
|
|
...overrides
|
|
});
|
|
|
|
describe('NotificationBell — cursor and tooltip', () => {
|
|
it('bell button has cursor-pointer class', async () => {
|
|
render(NotificationBell);
|
|
const btn = document.querySelector<HTMLButtonElement>('button[aria-haspopup="true"]')!;
|
|
expect(btn.classList.contains('cursor-pointer')).toBe(true);
|
|
});
|
|
|
|
it('bell button title equals aria-label when unreadCount is 0', async () => {
|
|
mockNotificationList.value = [];
|
|
render(NotificationBell);
|
|
const btn = document.querySelector<HTMLButtonElement>('button[aria-haspopup="true"]')!;
|
|
expect(btn.getAttribute('title')).toBe('Benachrichtigungen');
|
|
expect(btn.getAttribute('aria-label')).toBe(btn.getAttribute('title'));
|
|
});
|
|
|
|
it('bell button title equals aria-label when unreadCount is 3', async () => {
|
|
mockNotificationList.value = [
|
|
makeNotification({ id: 'n1' }),
|
|
makeNotification({ id: 'n2' }),
|
|
makeNotification({ id: 'n3' })
|
|
];
|
|
render(NotificationBell);
|
|
const btn = document.querySelector<HTMLButtonElement>('button[aria-haspopup="true"]')!;
|
|
expect(btn.getAttribute('title')).toBe('3 ungelesene Benachrichtigungen');
|
|
expect(btn.getAttribute('aria-label')).toBe(btn.getAttribute('title'));
|
|
});
|
|
});
|