import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { getPresets, traceImage, simplifyVector } from '../engine'; // ---------- helpers ---------- function mockFetchOk(body: unknown) { return vi.fn().mockResolvedValue({ ok: true, status: 200, json: () => Promise.resolve(body), text: () => Promise.resolve(JSON.stringify(body)), }); } function mockFetchFail(status: number, statusText: string) { return vi.fn().mockResolvedValue({ ok: false, status, statusText, text: () => Promise.resolve(statusText), }); } // ---------- setup ---------- const originalFetch = globalThis.fetch; beforeEach(() => { // reset to a no-op fetch so tests that forget to mock still fail clearly globalThis.fetch = vi.fn().mockRejectedValue(new Error('fetch not mocked')); }); afterEach(() => { globalThis.fetch = originalFetch; }); // ---------- getPresets ---------- describe('getPresets', () => { it('calls GET /engine/presets and returns parsed JSON', async () => { const payload = { presets: { sign: { name: 'sign' } } }; globalThis.fetch = mockFetchOk(payload); const result = await getPresets(); expect(globalThis.fetch).toHaveBeenCalledOnce(); const [url, opts] = (globalThis.fetch as ReturnType).mock.calls[0]; expect(url).toBe('/engine/presets'); expect(opts?.signal).toBeUndefined(); expect(result).toEqual(payload); }); it('passes AbortSignal when provided', async () => { globalThis.fetch = mockFetchOk({ presets: {} }); const controller = new AbortController(); await getPresets(controller.signal); const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]; expect(opts.signal).toBe(controller.signal); }); it('throws on non-ok response', async () => { globalThis.fetch = mockFetchFail(500, 'Internal Server Error'); await expect(getPresets()).rejects.toThrow(/failed.*500/i); }); }); // ---------- traceImage ---------- describe('traceImage', () => { it('sends FormData with file, preset, output_format, and JSON params', async () => { globalThis.fetch = mockFetchOk({ output: '', format: 'svg', metadata: {} }); const file = new File(['pixels'], 'test.png', { type: 'image/png' }); const params = { epsilon: 2.5, turdsize: 10 }; await traceImage(file, 'sign', params); expect(globalThis.fetch).toHaveBeenCalledOnce(); const [url, opts] = (globalThis.fetch as ReturnType).mock.calls[0]; expect(url).toBe('/engine/trace'); expect(opts.method).toBe('POST'); const body: FormData = opts.body; expect(body).toBeInstanceOf(FormData); expect(body.get('file')).toBeInstanceOf(File); expect(body.get('preset')).toBe('sign'); expect(body.get('output_format')).toBe('svg'); expect(body.get('params')).toBe(JSON.stringify(params)); }); it('passes AbortSignal when provided', async () => { globalThis.fetch = mockFetchOk({ output: '', format: 'svg', metadata: {} }); const controller = new AbortController(); const file = new File(['px'], 'img.png', { type: 'image/png' }); await traceImage(file, 'sign', {}, controller.signal); const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]; expect(opts.signal).toBe(controller.signal); }); it('throws with detail on non-ok response', async () => { globalThis.fetch = mockFetchFail(422, 'Unprocessable Entity'); const file = new File(['px'], 'bad.png', { type: 'image/png' }); await expect(traceImage(file, 'sign', {})).rejects.toThrow(/failed.*422/i); }); }); // ---------- simplifyVector ---------- describe('simplifyVector', () => { it('sends FormData with file, epsilon, and output_format', async () => { globalThis.fetch = mockFetchOk({ output: '', format: 'svg', metadata: {} }); const file = new File([''], 'input.svg', { type: 'image/svg+xml' }); await simplifyVector(file, 3.0); const [url, opts] = (globalThis.fetch as ReturnType).mock.calls[0]; expect(url).toBe('/engine/simplify'); expect(opts.method).toBe('POST'); const body: FormData = opts.body; expect(body.get('file')).toBeInstanceOf(File); expect(body.get('epsilon')).toBe('3'); expect(body.get('output_format')).toBe('svg'); }); it('passes AbortSignal when provided', async () => { globalThis.fetch = mockFetchOk({ output: '', format: 'svg', metadata: {} }); const controller = new AbortController(); const file = new File([''], 'input.svg', { type: 'image/svg+xml' }); await simplifyVector(file, 1.0, controller.signal); const [, opts] = (globalThis.fetch as ReturnType).mock.calls[0]; expect(opts.signal).toBe(controller.signal); }); it('throws on non-ok response', async () => { globalThis.fetch = mockFetchFail(400, 'Bad Request'); const file = new File([''], 'input.svg', { type: 'image/svg+xml' }); await expect(simplifyVector(file, 1.0)).rejects.toThrow(/failed.*400/i); }); });