// @vitest-environment jsdom import { cleanup, fireEvent, render, screen } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("@/lib/image-preview", () => ({ needsServerPreview: vi.fn(() => false), fetchDecodedPreview: vi.fn(() => Promise.resolve(null)), })); vi.mock("@/lib/analytics", () => ({ track: vi.fn(), })); vi.mock("@/lib/api", () => ({ formatHeaders: () => new Map(), parseApiError: () => "error", })); let mockToolId = "jpg-to-pdf"; vi.mock("react-router-dom", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useParams: () => ({ toolId: mockToolId }) }; }); import { ConversionPresetSettings } from "@/components/tools/conversion-preset-settings"; import { useFileStore } from "@/stores/file-store"; interface MockXhr { open: ReturnType; send: ReturnType; setRequestHeader: ReturnType; abort: ReturnType; upload: Record; status: number; responseText: string; timeout: number; } class MockEventSource { onmessage: ((event: MessageEvent) => void) | null = null; onerror: (() => void) | null = null; close = vi.fn(); constructor(readonly url: string) {} } function makeFile(name: string, type = "image/jpeg"): File { return new File([new ArrayBuffer(64)], name, { type }); } let xhrInstances: MockXhr[]; let fetchMock: ReturnType; beforeEach(() => { vi.stubGlobal("URL", { ...globalThis.URL, createObjectURL: vi.fn(() => "blob:fake-url"), revokeObjectURL: vi.fn(), }); useFileStore.getState().reset(); xhrInstances = []; vi.stubGlobal("EventSource", MockEventSource); vi.stubGlobal( "XMLHttpRequest", vi.fn(() => { const xhr: MockXhr = { status: 0, responseText: "", timeout: 0, upload: {}, open: vi.fn(), send: vi.fn(), setRequestHeader: vi.fn(), abort: vi.fn(), }; xhrInstances.push(xhr); return xhr; }), ); // Never resolves; these tests only assert the call was (or wasn't) made. fetchMock = vi.fn(() => new Promise(() => {})); vi.stubGlobal("fetch", fetchMock); }); afterEach(() => { cleanup(); vi.unstubAllGlobals(); }); /** * Issue #627: the jpg-to-pdf preset (and its image-to-pdf-group siblings) * combine every uploaded file into one PDF, the same as the base image-to-pdf * tool. Submitting 2+ files must stay on the single "attach all files" route, * never the generic per-file /batch endpoint (which 404s for these tools: * they are registered via registerImageToPdfRoute, not createToolRoute, so * they were never added to the batch-capable tool registry). */ describe("ConversionPresetSettings multi-file dispatch (issue #627)", () => { it("combines 2 files for jpg-to-pdf in one request instead of calling /batch", () => { mockToolId = "jpg-to-pdf"; useFileStore.getState().setFiles([makeFile("a.jpg"), makeFile("b.jpg")]); render(); fireEvent.click(screen.getByTestId("preset-submit")); expect(xhrInstances).toHaveLength(1); expect(xhrInstances[0].open).toHaveBeenCalledWith("POST", "/api/v1/tools/image/jpg-to-pdf"); expect(fetchMock).not.toHaveBeenCalled(); }); it("combines 2 files for every image-to-pdf-group preset, not just jpg-to-pdf", () => { mockToolId = "png-to-pdf"; useFileStore .getState() .setFiles([makeFile("a.png", "image/png"), makeFile("b.png", "image/png")]); render(); fireEvent.click(screen.getByTestId("preset-submit")); expect(xhrInstances).toHaveLength(1); expect(xhrInstances[0].open).toHaveBeenCalledWith("POST", "/api/v1/tools/image/png-to-pdf"); expect(fetchMock).not.toHaveBeenCalled(); }); it("still uses independent per-file batch processing for ordinary presets like jpg-to-png", () => { mockToolId = "jpg-to-png"; useFileStore.getState().setFiles([makeFile("a.jpg"), makeFile("b.jpg")]); render(); fireEvent.click(screen.getByTestId("preset-submit")); expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock.mock.calls[0][0]).toBe("/api/v1/tools/image/jpg-to-png/batch"); expect(xhrInstances).toHaveLength(0); }); it("uses the single-file request for jpg-to-pdf when only 1 file is selected", () => { mockToolId = "jpg-to-pdf"; useFileStore.getState().setFiles([makeFile("a.jpg")]); render(); fireEvent.click(screen.getByTestId("preset-submit")); expect(xhrInstances).toHaveLength(1); expect(xhrInstances[0].open).toHaveBeenCalledWith("POST", "/api/v1/tools/image/jpg-to-pdf"); expect(fetchMock).not.toHaveBeenCalled(); }); });