import { beforeEach, describe, expect, it, vi } from "vitest"; import { getUnreadCount, hydrateNotifications, listNotifications } from "#lib/api/notifications.ts"; import type { Auth } from "#lib/auth.svelte.ts"; import { createFixtureNotifications, createNotifications } from "#lib/notifications.svelte.ts"; import { notifications } from "#lib/components/notifications/mock.ts"; import type { NotificationSummary } from "#lib/components/notifications/types.ts"; vi.mock("#lib/api/notifications.ts", () => ({ getUnreadCount: vi.fn(), hydrateNotifications: vi.fn(async () => []), listNotifications: vi.fn(async () => ({ notifications: [] })), markAllNotificationsRead: vi.fn(), markEntityNotificationsRead: vi.fn(), updateNotificationSeen: vi.fn() })); const rows: NotificationSummary[] = notifications.map((notification, index) => ({ ...notification, uri: `at://mock/notif/${index}`, read: index % 2 === 1 })); const unreadRow = rows.find((row) => !row.read)!; const readRow = rows.find((row) => row.read)!; const baseline = rows.filter((row) => !row.read).length; const readOf = ( service: { overlay(rows: NotificationSummary[]): NotificationSummary[] }, uri: string ) => service.overlay(rows).find((row) => row.uri === uri)!.read; describe("notification read state", () => { it("moves the count with the row it marked", async () => { const service = createFixtureNotifications(rows); expect(service.unread).toBe(baseline); await service.setRead(unreadRow.uri, true); expect(service.unread).toBe(baseline - 1); expect(readOf(service, unreadRow.uri)).toBe(true); await service.setRead(unreadRow.uri, false); expect(service.unread).toBe(baseline); expect(readOf(service, unreadRow.uri)).toBe(false); }); it("ignores a mark that changes nothing", async () => { const service = createFixtureNotifications(rows); await service.setRead(unreadRow.uri, true); await service.setRead(unreadRow.uri, true); await service.setRead(readRow.uri, true); expect(service.unread).toBe(baseline - 1); }); it("empties the count and every known row on mark all read", async () => { const service = createFixtureNotifications(rows); await service.markAllRead(); expect(service.unread).toBe(0); expect(service.overlay(rows).every((row) => row.read)).toBe(true); }); }); describe("list", () => { const alice = "did:plc:alice"; const auth = { currentDid: alice, agent: { sub: alice } } as unknown as Auth; const mockedCount = vi.mocked(getUnreadCount); const create = () => createNotifications(auth, "https://notifs.example", "https://bobbin.example"); beforeEach(() => { vi.clearAllMocks(); mockedCount.mockResolvedValue({ count: 3 }); }); it("asks for the unread count once when two lists load together", async () => { const service = create(); await Promise.all([service.list({ read: "unread", limit: 5 }), service.list({ limit: 5 })]); expect(mockedCount).toHaveBeenCalledTimes(1); expect(listNotifications).toHaveBeenCalledTimes(2); expect(hydrateNotifications).toHaveBeenCalledTimes(2); expect(service.unread).toBe(3); }); it("asks again once the earlier count has landed", async () => { const service = create(); await service.list(); mockedCount.mockResolvedValue({ count: 1 }); await service.list(); expect(mockedCount).toHaveBeenCalledTimes(2); expect(service.unread).toBe(1); }); });