import { describe, it, expect, afterEach, vi } from "vitest"; import { render, screen, cleanup, waitFor } from "@testing-library/react"; import ProjectSubmissionsPage from "@/app/projects/[slug]/submissions/page"; // next/navigation 의 useRouter/useParams 를 목으로 대체해 // - URL 의 slug 파라미터에 따라 API 요청 경로가 달라지는지 // - 인증 실패 시 /login 으로 리다이렉트하는지 // 를 검증한다. export const pushMock = vi.fn(); export const useParamsMock = vi.fn(); vi.mock("next/navigation", () => { return { __esModule: true, useRouter: () => ({ push: pushMock }), useParams: () => useParamsMock(), }; }); afterEach(() => { cleanup(); vi.unstubAllGlobals(); vi.clearAllMocks(); useParamsMock.mockReset(); }); describe("ProjectSubmissionsPage - 폼 제출 내역", () => { it("/api/projects/[slug]/submissions 로부터 받은 제출 내역을 테이블로 렌더링해야 한다", async () => { useParamsMock.mockReturnValue({ slug: "test-project" }); const submissions = [ { id: "1", createdAt: "2025-01-01T12:00:00.000Z", projectSlug: "test-project", payload: { name: "홍길동", email: "user@example.com", phone: "010-1234-5678", birthdate: "1990-01-01", message: "안녕하세요", }, }, { id: "2", createdAt: "2025-01-02T08:30:00.000Z", projectSlug: "test-project", payload: { name: "김철수", email: "another@example.com", phone: "010-0000-0000", birthdate: "1995-05-05", message: "두 번째 문의입니다.", }, }, ]; const fetchMock = vi.fn().mockResolvedValue( new Response(JSON.stringify(submissions), { status: 200, headers: { "Content-Type": "application/json" }, }), ); vi.stubGlobal("fetch", fetchMock as any); render(); await waitFor(() => { expect(fetchMock).toHaveBeenCalledTimes(1); }); const [url, options] = fetchMock.mock.calls[0] as any; expect(url).toBe("/api/projects/test-project/submissions"); expect(options).toBeUndefined(); expect(await screen.findByText("폼 제출 내역")).toBeTruthy(); expect(screen.getByText("test-project")).toBeTruthy(); expect(screen.getByText("홍길동")).toBeTruthy(); expect(screen.getByText("user@example.com")).toBeTruthy(); expect(screen.getByText("010-1234-5678")).toBeTruthy(); expect(screen.getByText("1990-01-01")).toBeTruthy(); expect(screen.getByText("message: 안녕하세요")).toBeTruthy(); expect(screen.getByText("김철수")).toBeTruthy(); expect(screen.getByText("another@example.com")).toBeTruthy(); expect(screen.getByText("010-0000-0000")).toBeTruthy(); expect(screen.getByText("1995-05-05")).toBeTruthy(); expect(screen.getByText("message: 두 번째 문의입니다.")).toBeTruthy(); }); it("API 가 401 을 반환하면 로그인 페이지로 리다이렉트해야 한다", async () => { useParamsMock.mockReturnValue({ slug: "test-project" }); const fetchMock = vi.fn().mockResolvedValue( new Response( JSON.stringify({ message: "폼 제출 내역을 조회하려면 로그인이 필요합니다." }), { status: 401, headers: { "Content-Type": "application/json" }, }, ), ); vi.stubGlobal("fetch", fetchMock as any); render(); await waitFor(() => { expect(fetchMock).toHaveBeenCalledTimes(1); expect(pushMock).toHaveBeenCalledWith("/login"); }); }); it("API 가 404 를 반환하면 에러 메시지를 화면에 표시해야 한다", async () => { useParamsMock.mockReturnValue({ slug: "unknown-project" }); const fetchMock = vi.fn().mockResolvedValue( new Response(JSON.stringify({ message: "프로젝트를 찾을 수 없습니다." }), { status: 404, headers: { "Content-Type": "application/json" }, }), ); vi.stubGlobal("fetch", fetchMock as any); render(); const errorText = await screen.findByText( "프로젝트를 찾을 수 없거나 접근 권한이 없습니다. 프로젝트 소유자 계정으로 다시 로그인해 주세요.", ); expect(errorText).toBeTruthy(); }); });