import "dotenv/config"; import { describe, it, expect, vi } from "vitest"; import JSZip from "jszip"; import type { Block, ProjectConfig } from "@/features/editor/state/editorStore"; const BASE_URL = "http://localhost"; // 정적 Export + main.js + /api/forms/submit 를 묶어서 검증하는 통합 테스트. // - /api/export 로 ZIP 을 생성해서 index.html / main.js 를 추출한다. // - jsdom DOM 에 index.html 을 주입하고, main.js 스크립트를 실제로 실행한다. // - form.pb-form-controller 에서 submit 이벤트를 발생시키면 // main.js 가 submit 을 가로채고 /api/forms/submit 로 fetch 를 호출해야 한다. describe("정적 Export 폼 컨트롤러 통합", () => { it("Export된 index.html + main.js 에서 submit 시 /api/forms/submit 로 fetch 를 호출해야 한다", async () => { const blocks: Block[] = [ { id: "form_1", type: "form", props: { kind: "contact", submitTarget: "internal", fieldIds: ["input_name"], submitButtonId: null, formWidthMode: "full", }, } as any, { id: "input_name", type: "formInput", props: { label: "이름", formFieldName: "name", required: true, }, } as any, ]; const projectConfig: ProjectConfig = { title: "폼 통합 테스트", slug: "form-integration-test", canvasPreset: "full", }; const payload = { blocks, projectConfig }; const { POST: handleExport } = await import("@/app/api/export/route"); const res = await handleExport( new Request(`${BASE_URL}/api/export`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }), ); expect(res.status).toBe(200); const arrayBuffer = await res.arrayBuffer(); const zip = await JSZip.loadAsync(arrayBuffer); const indexEntry = zip.file("index.html"); const jsEntry = zip.file("main.js"); expect(indexEntry).toBeTruthy(); expect(jsEntry).toBeTruthy(); const html = await indexEntry!.async("string"); const mainJs = await jsEntry!.async("string"); // jsdom DOM 구성: export 된 HTML 을 그대로 주입한다. document.documentElement.innerHTML = html; const form = document.querySelector("form.pb-form-controller") as HTMLFormElement | null; expect(form).not.toBeNull(); // name 필드에 값 채우기 (FormData 에 실제 값이 들어가도록) const nameInput = document.querySelector('input[name="name"]') as HTMLInputElement | null; if (nameInput) { nameInput.value = "Alice"; } // fetch 를 목킹하여 main.js 의 submit 가로채기 동작을 검증한다. const fetchMock = vi.fn(async () => new Response(JSON.stringify({ ok: true, message: "ok" }), { status: 200, headers: { "Content-Type": "application/json" }, }), ); (globalThis as any).fetch = fetchMock; // main.js 실행 (IIFE 코드이므로 평가만 하면 즉시 pbInitFormControllers 가 동작한다.) // eslint-disable-next-line no-new-func new Function(mainJs)(); // submit 이벤트 발생: main.js 가 이를 가로채고 fetch 를 호출해야 한다. const submitEvent = new Event("submit", { bubbles: true, cancelable: true }); form!.dispatchEvent(submitEvent); expect(fetchMock).toHaveBeenCalledTimes(1); const [url, options] = fetchMock.mock.calls[0] as unknown as [any, RequestInit]; expect(url).toBe("/api/forms/submit"); expect(options.method).toBe("POST"); expect(options.body).toBeInstanceOf(FormData); const body = options.body as FormData; expect(body.get("__projectSlug")).toBe(projectConfig.slug); }); });