import { describe, expect, it, vi, beforeEach } from 'vitest'; vi.mock('$lib/api.server', () => ({ createApiClient: vi.fn() })); import { load, actions } from './+page.server'; import { createApiClient } from '$lib/api.server'; beforeEach(() => vi.clearAllMocks()); function makeUrl(params: Record = {}) { const url = new URL('http://localhost/notifications'); for (const [key, value] of Object.entries(params)) { url.searchParams.set(key, value); } return url; } // ─── load ───────────────────────────────────────────────────────────────────── describe('notifications page load', () => { it('returns notifications and unreadCount from API response', async () => { const mockGet = vi.fn().mockResolvedValueOnce({ response: { ok: true }, data: { content: [ { id: 'n1', read: false }, { id: 'n2', read: true }, { id: 'n3', read: false } ], totalElements: 3, totalPages: 1, number: 0 } }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); const result = await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }); expect(result.notifications).toHaveLength(3); expect(result.unreadCount).toBe(2); }); it('passes type param to API when ?type=MENTION is in URL', async () => { const mockGet = vi.fn().mockResolvedValueOnce({ response: { ok: true }, data: { content: [], totalElements: 0, totalPages: 0, number: 0 } }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); await load({ url: makeUrl({ type: 'MENTION' }), fetch: vi.fn() as unknown as typeof fetch }); const queryParams = mockGet.mock.calls[0][1].params.query; expect(queryParams.type).toBe('MENTION'); }); it('passes read=false to API when ?read=false is in URL', async () => { const mockGet = vi.fn().mockResolvedValueOnce({ response: { ok: true }, data: { content: [], totalElements: 0, totalPages: 0, number: 0 } }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); await load({ url: makeUrl({ read: 'false' }), fetch: vi.fn() as unknown as typeof fetch }); const queryParams = mockGet.mock.calls[0][1].params.query; expect(queryParams.read).toBe(false); }); it('passes no filter params when no search params present', async () => { const mockGet = vi.fn().mockResolvedValueOnce({ response: { ok: true }, data: { content: [], totalElements: 0, totalPages: 0, number: 0 } }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }); const queryParams = mockGet.mock.calls[0][1].params.query; expect(queryParams.type).toBeUndefined(); expect(queryParams.read).toBeUndefined(); }); it('calls the API exactly once — no separate round-trip for unreadCount', async () => { const mockGet = vi.fn().mockResolvedValueOnce({ response: { ok: true }, data: { content: [], totalElements: 0, totalPages: 0, number: 0 } }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }); expect(mockGet).toHaveBeenCalledTimes(1); }); it('throws 401 error when API returns 401', async () => { const mockGet = vi.fn().mockResolvedValueOnce({ response: { ok: false, status: 401 }, data: null }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); await expect( load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }) ).rejects.toMatchObject({ status: 401 }); }); }); // ─── mark-all action ────────────────────────────────────────────────────────── describe('notifications mark-all action', () => { it('calls POST /api/notifications/read-all and redirects', async () => { const mockPost = vi.fn().mockResolvedValueOnce({ response: { ok: true } }); vi.mocked(createApiClient).mockReturnValue({ POST: mockPost } as ReturnType< typeof createApiClient >); const markAll = actions['mark-all'] as (ctx: { fetch: typeof fetch }) => Promise; await expect(markAll({ fetch: vi.fn() as unknown as typeof fetch })).rejects.toMatchObject({ location: '/notifications' }); expect(mockPost).toHaveBeenCalledTimes(1); }); });