test(notification-bell): cover handleMarkRead annotationId and commentId-only paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
82
frontend/src/lib/components/NotificationBell.svelte.spec.ts
Normal file
82
frontend/src/lib/components/NotificationBell.svelte.spec.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { afterEach, describe, it, expect, vi } from 'vitest';
|
||||||
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
|
import type { NotificationItem } from '$lib/utils/notifications';
|
||||||
|
import NotificationBell from './NotificationBell.svelte';
|
||||||
|
|
||||||
|
const gotoMock = vi.hoisted(() => vi.fn());
|
||||||
|
vi.mock('$app/navigation', () => ({ goto: gotoMock, beforeNavigate: vi.fn() }));
|
||||||
|
|
||||||
|
const mockMarkRead = vi.hoisted(() => vi.fn().mockResolvedValue(undefined));
|
||||||
|
const mockNotificationList = vi.hoisted((): { value: NotificationItem[] } => ({ value: [] }));
|
||||||
|
|
||||||
|
vi.mock('$lib/stores/notifications.svelte', () => ({
|
||||||
|
notificationStore: {
|
||||||
|
get notifications() {
|
||||||
|
return mockNotificationList.value;
|
||||||
|
},
|
||||||
|
get unreadCount() {
|
||||||
|
return mockNotificationList.value.length;
|
||||||
|
},
|
||||||
|
markRead: mockMarkRead,
|
||||||
|
fetchNotifications: vi.fn().mockResolvedValue(undefined),
|
||||||
|
init: vi.fn(),
|
||||||
|
destroy: vi.fn(),
|
||||||
|
markAllRead: vi.fn()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
gotoMock.mockClear();
|
||||||
|
mockMarkRead.mockClear();
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
async function openDropdownAndClickFirstNotification() {
|
||||||
|
const bellButton = document.querySelector<HTMLButtonElement>('button[aria-haspopup="true"]')!;
|
||||||
|
bellButton.click();
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(document.querySelector('[role="dialog"]')).not.toBeNull();
|
||||||
|
});
|
||||||
|
const notifButton = document.querySelector<HTMLButtonElement>('[role="list"] button')!;
|
||||||
|
notifButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('NotificationBell', () => {
|
||||||
|
it('handleMarkRead navigates to URL including annotationId when notification has annotationId', async () => {
|
||||||
|
mockNotificationList.value = [makeNotification({ annotationId: 'annot-1' })];
|
||||||
|
render(NotificationBell);
|
||||||
|
|
||||||
|
await openDropdownAndClickFirstNotification();
|
||||||
|
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(gotoMock).toHaveBeenCalledWith(
|
||||||
|
'/documents/doc-1?commentId=ref-1&annotationId=annot-1'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handleMarkRead navigates to commentId-only URL when annotationId is absent', async () => {
|
||||||
|
mockNotificationList.value = [makeNotification({ annotationId: null })];
|
||||||
|
render(NotificationBell);
|
||||||
|
|
||||||
|
await openDropdownAndClickFirstNotification();
|
||||||
|
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(gotoMock).toHaveBeenCalledWith('/documents/doc-1?commentId=ref-1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user