import { beforeEach, describe, expect, it, vi } from 'vitest'; import { createDecoTemplate, createDecoTemplateGroup, deleteDecoTemplate, deleteDecoTemplateGroup, getDecoTemplate, getDecoTemplateGroups, getDecoTemplateItems, getDecoTemplates, replaceDecoTemplate, type ReplaceDecoTemplateRequest } from './decoTemplates'; 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: ReplaceDecoTemplateRequest = { name: 'Weekdays', items: [{ decoId: 10, startTime: '06:00:00', endTime: '07:00:00' }] }; describe('decoTemplates api client', () => { beforeEach(() => { window.localStorage.clear(); vi.restoreAllMocks(); }); it('getDecoTemplateGroups fetches all deco template groups', async () => { const fetchMock = vi .spyOn(window, 'fetch') .mockResolvedValue(jsonResponse([{ id: 1, name: 'Weekday', decoTemplateCount: 2 }])); await expect(getDecoTemplateGroups()).resolves.toHaveLength(1); expect(fetchMock).toHaveBeenCalledWith( '/api/deco-templates/groups', expect.objectContaining({ method: 'GET' }) ); }); it('createDecoTemplateGroup POSTs the name', async () => { const fetchMock = vi .spyOn(window, 'fetch') .mockResolvedValue(jsonResponse({ id: 3, name: 'Weekday', decoTemplateCount: 0 }, 201)); await createDecoTemplateGroup({ name: 'Weekday' }); const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe('/api/deco-templates/groups'); expect(init).toMatchObject({ method: 'POST' }); expect(JSON.parse(String(init?.body)).name).toBe('Weekday'); }); it('deleteDecoTemplateGroup DELETEs by id', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContent()); await expect(deleteDecoTemplateGroup(3)).resolves.toBeUndefined(); expect(fetchMock).toHaveBeenCalledWith( '/api/deco-templates/groups/3', expect.objectContaining({ method: 'DELETE' }) ); }); it('getDecoTemplates fetches all deco templates', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([])); await getDecoTemplates(); expect(fetchMock).toHaveBeenCalledWith('/api/deco-templates', expect.objectContaining({ method: 'GET' })); }); it('getDecoTemplate fetches a deco template by id', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 4 })); await getDecoTemplate(4); expect(fetchMock).toHaveBeenCalledWith('/api/deco-templates/4', expect.objectContaining({ method: 'GET' })); }); it('createDecoTemplate POSTs group id and name', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 8 }, 201)); await createDecoTemplate({ decoTemplateGroupId: 2, name: 'Weekdays' }); const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe('/api/deco-templates'); expect(init).toMatchObject({ method: 'POST' }); expect(JSON.parse(String(init?.body))).toMatchObject({ decoTemplateGroupId: 2, name: 'Weekdays' }); }); it('deleteDecoTemplate DELETEs by id', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContent()); await deleteDecoTemplate(4); expect(fetchMock).toHaveBeenCalledWith('/api/deco-templates/4', expect.objectContaining({ method: 'DELETE' })); }); it('getDecoTemplateItems fetches items for a deco template', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([])); await getDecoTemplateItems(4); expect(fetchMock).toHaveBeenCalledWith( '/api/deco-templates/4/items', expect.objectContaining({ method: 'GET' }) ); }); it('replaceDecoTemplate PUTs the full deco template body', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 4, items: [] })); await replaceDecoTemplate(4, sampleReplace); const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe('/api/deco-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({ decoId: 10, startTime: '06:00:00', endTime: '07:00:00' }); }); });