import { describe, it, expect, afterEach, vi } from "vitest"; import { render, screen, fireEvent, cleanup, waitFor } from "@testing-library/react"; import { PublicPageRenderer } from "@/features/editor/components/PublicPageRenderer"; import type { Block } from "@/features/editor/state/editorStore"; // PublicPageRenderer 폼 제출 UX TDD // - 성공 응답 시 FormBlockProps 의 successMessage 를 success 스타일로 표시해야 한다. // - 실패 응답 시 FormBlockProps 의 errorMessage 를 error 스타일로 표시해야 한다. describe("PublicPageRenderer - 폼 제출 메시지", () => { afterEach(() => { cleanup(); // fetch 목 초기화 // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).fetch = undefined; }); it("성공 응답 시 FormBlock 의 successMessage 를 성공 메시지로 렌더해야 한다", async () => { const blocks: Block[] = [ { id: "form_success", type: "form", props: { kind: "contact", submitTarget: "internal", successMessage: "폼 성공 메시지 (config)", errorMessage: "폼 에러 메시지 (config)", fieldIds: ["field_name"], requiredFieldIds: ["field_name"], submitButtonId: "submit_btn", } as any, }, { id: "field_name", type: "formInput", props: { label: "이름", formFieldName: "name", required: true, } as any, }, { id: "submit_btn", type: "button", props: { label: "제출하기", href: "#", } as any, }, ]; const fetchMock = vi.fn(async () => new Response("ok", { status: 200, headers: { "Content-Type": "text/plain" }, }), ); // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).fetch = fetchMock; render(); const nameInput = screen.getByLabelText("이름") as HTMLInputElement; fireEvent.change(nameInput, { target: { value: "홍길동" } }); const submitButton = screen.getByRole("link", { name: "제출하기" }); expect(submitButton).toBeTruthy(); fireEvent.click(submitButton); await waitFor(() => { expect(fetchMock).toHaveBeenCalledTimes(1); }); await waitFor(() => { const msg = screen.getByText("폼 성공 메시지 (config)"); expect(msg).toBeTruthy(); }); }); it("실패 응답 시 FormBlock 의 errorMessage 를 에러 메시지로 렌더해야 한다", async () => { const blocks: Block[] = [ { id: "form_error", type: "form", props: { kind: "contact", submitTarget: "internal", successMessage: "폼 성공 메시지 (config)", errorMessage: "폼 에러 메시지 (config)", fieldIds: ["field_name"], requiredFieldIds: ["field_name"], submitButtonId: "submit_btn", } as any, }, { id: "field_name", type: "formInput", props: { label: "이름", formFieldName: "name", required: true, } as any, }, { id: "submit_btn", type: "button", props: { label: "제출하기", href: "#", } as any, }, ]; const fetchMock = vi.fn(async () => new Response("error", { status: 500, headers: { "Content-Type": "text/plain" }, }), ); // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).fetch = fetchMock; render(); const nameInput = screen.getByLabelText("이름") as HTMLInputElement; fireEvent.change(nameInput, { target: { value: "홍길동" } }); const submitButton = screen.getByRole("link", { name: "제출하기" }); expect(submitButton).toBeTruthy(); fireEvent.click(submitButton); await waitFor(() => { expect(fetchMock).toHaveBeenCalledTimes(1); }); await waitFor(() => { const errorMsg = screen.getByText("폼 에러 메시지 (config)"); expect(errorMsg).toBeTruthy(); }); }); it("필수 필드가 비어 있고 errorMessage 가 없으면 기본 문구에 필수 항목 라벨을 포함해야 한다", async () => { const blocks: Block[] = [ { id: "form_required_default_msg", type: "form", props: { kind: "contact", submitTarget: "internal", successMessage: "폼 성공 메시지 (config)", fieldIds: ["field_email"], requiredFieldIds: ["field_email"], submitButtonId: "submit_btn", } as any, }, { id: "field_email", type: "formInput", props: { label: "이메일", formFieldName: "email", required: true, } as any, }, { id: "submit_btn", type: "button", props: { label: "제출하기", href: "#", } as any, }, ]; const fetchMock = vi.fn(async () => new Response("ok", { status: 200, headers: { "Content-Type": "text/plain" }, }), ); // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).fetch = fetchMock; render(); const submitButton = screen.getByRole("link", { name: "제출하기" }); expect(submitButton).toBeTruthy(); fireEvent.click(submitButton); await waitFor(() => { expect(fetchMock).toHaveBeenCalledTimes(0); }); await waitFor(() => { const errorMsg = screen.getByText(/다음 필수 항목을 입력해 주세요:\s*-\s*이메일/); expect(errorMsg).toBeTruthy(); }); }); it("필수 필드가 비어 있으면 요청을 보내지 않고 필수 항목 안내 메시지를 표시해야 한다 (errorMessage 설정 시)", async () => { const blocks: Block[] = [ { id: "form_required", type: "form", props: { kind: "contact", submitTarget: "internal", successMessage: "폼 성공 메시지 (config)", errorMessage: "폼 에러 메시지 (config)", fieldIds: ["field_name"], requiredFieldIds: ["field_name"], submitButtonId: "submit_btn", } as any, }, { id: "field_name", type: "formInput", props: { label: "이름", formFieldName: "name", required: true, } as any, }, { id: "submit_btn", type: "button", props: { label: "제출하기", href: "#", } as any, }, ]; const fetchMock = vi.fn(async () => new Response("ok", { status: 200, headers: { "Content-Type": "text/plain" }, }), ); // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).fetch = fetchMock; render(); const submitButton = screen.getByRole("link", { name: "제출하기" }); expect(submitButton).toBeTruthy(); // 이름 필드를 비운 채로 제출한다. fireEvent.click(submitButton); await waitFor(() => { expect(fetchMock).toHaveBeenCalledTimes(0); }); await waitFor(() => { const errorMsg = screen.getByText(/다음 필수 항목을 입력해 주세요:\s*-\s*이름/); expect(errorMsg).toBeTruthy(); }); }); });