import { beforeEach, describe, expect, it, vi } from 'vitest'; import { copyTemplate, createTemplate, createTemplateGroup, deleteTemplate, deleteTemplateGroup, getTemplate, getTemplateGroups, getTemplateItems, getTemplates, replaceTemplate, type ReplaceTemplateRequest } from './templates'; function jsonResponse(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { headers: { 'Content-Type': 'application/json' }, status }); } function noContent(): Response { return new Response(null, { status: 204 }); } const sampleReplace: ReplaceTemplateRequest = { name: 'Weekdays', items: [{ blockId: 10, startTime: '06:00:00' }] }; describe('templates api client', () => { beforeEach(() => { window.localStorage.clear(); vi.restoreAllMocks(); }); it('getTemplateGroups fetches all template groups', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([{ id: 1, name: 'Prime', templateCount: 2 }])); await expect(getTemplateGroups()).resolves.toHaveLength(1); expect(fetchMock).toHaveBeenCalledWith('/api/v1/templates/groups', expect.objectContaining({ method: 'GET' })); }); it('createTemplateGroup POSTs the name', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 3, name: 'Prime', templateCount: 0 }, 201)); await createTemplateGroup({ name: 'Prime' }); const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe('/api/v1/templates/groups'); expect(init).toMatchObject({ method: 'POST' }); expect(JSON.parse(String(init?.body)).name).toBe('Prime'); }); it('deleteTemplateGroup DELETEs by id', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContent()); await expect(deleteTemplateGroup(3)).resolves.toBeUndefined(); expect(fetchMock).toHaveBeenCalledWith('/api/v1/templates/groups/3', expect.objectContaining({ method: 'DELETE' })); }); it('getTemplates fetches all templates', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([])); await getTemplates(); expect(fetchMock).toHaveBeenCalledWith('/api/v1/templates', expect.objectContaining({ method: 'GET' })); }); it('getTemplate fetches a template by id', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 4 })); await getTemplate(4); expect(fetchMock).toHaveBeenCalledWith('/api/v1/templates/4', expect.objectContaining({ method: 'GET' })); }); it('createTemplate POSTs group id and name', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 8 }, 201)); await createTemplate({ templateGroupId: 2, name: 'Weekdays' }); const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe('/api/v1/templates'); expect(init).toMatchObject({ method: 'POST' }); expect(JSON.parse(String(init?.body))).toMatchObject({ templateGroupId: 2, name: 'Weekdays' }); }); it('deleteTemplate DELETEs by id', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContent()); await deleteTemplate(4); expect(fetchMock).toHaveBeenCalledWith('/api/v1/templates/4', expect.objectContaining({ method: 'DELETE' })); }); it('getTemplateItems fetches items for a template', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([])); await getTemplateItems(4); expect(fetchMock).toHaveBeenCalledWith('/api/v1/templates/4/items', expect.objectContaining({ method: 'GET' })); }); it('replaceTemplate PUTs the full template body', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 4, items: [] })); await replaceTemplate(4, sampleReplace); const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe('/api/v1/templates/4'); expect(init).toMatchObject({ method: 'PUT' }); const body = JSON.parse(String(init?.body)); expect(body.name).toBe('Weekdays'); expect(body.items).toHaveLength(1); expect(body.items[0]).toMatchObject({ blockId: 10, startTime: '06:00:00' }); }); it('copyTemplate POSTs the target group and name to the copy route', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 9 }, 201)); await copyTemplate(4, { templateGroupId: 3, name: 'Weekdays Copy' }); const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe('/api/v1/templates/4/copy'); expect(init).toMatchObject({ method: 'POST' }); expect(JSON.parse(String(init?.body))).toMatchObject({ templateGroupId: 3, name: 'Weekdays Copy' }); }); });