import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { MemoryRouter } from "react-router-dom"; import { ApiError, api } from "../api"; import { usePollingResource } from "../hooks/usePollingResource"; import { VisitorTakeTicketForm } from "./VisitorTakeTicketPage"; vi.mock("../hooks/usePollingResource", () => ({ usePollingResource: vi.fn() })); const mockedUsePollingResource = vi.mocked(usePollingResource); beforeEach(() => { mockedUsePollingResource.mockReturnValue({ data: { projects: [{ id: "project-a", name: "云岭漂流", status: "RUNNING", visitor_notice: "请在入口附近等候。", min_party_size: 1, max_party_size: 8 }], }, loading: false, refreshing: false, error: null, offline: false, lastClientSuccessAt: new Date().toISOString(), refresh: vi.fn(), resourceKey: "visitor-public-projects", }); }); describe("VisitorTakeTicketForm", () => { it("允许清空默认同行人数后输入新值", () => { render(); const partySizeInput = screen.getByRole("spinbutton", { name: /同行人数/ }); expect(partySizeInput).toHaveValue(1); fireEvent.change(partySizeInput, { target: { value: "" } }); expect(partySizeInput).toHaveValue(null); fireEvent.change(partySizeInput, { target: { value: "5" } }); expect(partySizeInput).toHaveValue(5); }); it("creates a ticket from the visitor form and offers the private status page", async () => { const createTicket = vi.spyOn(api, "publicCreateTicket").mockResolvedValueOnce({ ticket: { id: "ticket-1", ticket_number: "00021", status: "WAITING", phone_last4: "8000", party_size: 3 }, public_token: "visitor-token", public_url: "/visitor/visitor-token", revision: 4, }); try { render(); expect(screen.queryByText(/一个号码对应您填写的同行人数/)).not.toBeInTheDocument(); expect(screen.getByLabelText("排队项目")).toHaveValue("project-a"); fireEvent.change(screen.getByLabelText("手机号"), { target: { value: "13800138000" } }); fireEvent.change(screen.getByLabelText("姓氏(选填)"), { target: { value: "李" } }); fireEvent.change(screen.getByRole("spinbutton", { name: /同行人数/ }), { target: { value: "3" } }); fireEvent.click(screen.getByRole("button", { name: "创建排队号码" })); await waitFor(() => expect(screen.getByRole("status")).toHaveTextContent("00021")); expect(createTicket).toHaveBeenCalledWith("project-a", { phone: "13800138000", party_size: 3, last_name: "李", honorific: "", allow_duplicate: false, }, expect.any(String)); expect(screen.getByRole("link", { name: "查看排队状态" })).toHaveAttribute("href", "/visitor/visitor-token"); } finally { createTicket.mockRestore(); } }); it("requires explicit confirmation before creating another active ticket for the same phone", async () => { const createTicket = vi.spyOn(api, "publicCreateTicket") .mockRejectedValueOnce(new ApiError("该手机号已有活动号码,请确认后继续", 409, undefined, "DUPLICATE_PHONE")) .mockResolvedValueOnce({ ticket: { id: "ticket-2", ticket_number: "00022", status: "WAITING", phone_last4: "8000", party_size: 1 }, public_token: "visitor-token-2", revision: 5, }); try { render(); fireEvent.change(screen.getByLabelText("手机号"), { target: { value: "13800138000" } }); fireEvent.click(screen.getByRole("button", { name: "创建排队号码" })); await waitFor(() => expect(screen.getByText("该手机号已有活动号码")).toBeVisible()); expect(screen.getByRole("button", { name: "确认继续取号" })).toBeVisible(); fireEvent.click(screen.getByRole("button", { name: "确认继续取号" })); await waitFor(() => expect(screen.getByRole("status")).toHaveTextContent("00022")); expect(createTicket).toHaveBeenLastCalledWith("project-a", { phone: "13800138000", party_size: 1, last_name: "", honorific: "", allow_duplicate: true, }, expect.any(String)); } finally { createTicket.mockRestore(); } }); it("shows the Retry-After wait without clearing the visitor form", async () => { const createTicket = vi.spyOn(api, "publicCreateTicket").mockRejectedValueOnce( new ApiError("取号次数过多,请稍后再试", 429, undefined, "PUBLIC_TICKET_RATE_LIMITED", 17), ); try { render(); const phoneInput = screen.getByLabelText("手机号"); fireEvent.change(phoneInput, { target: { value: "13800138000" } }); fireEvent.click(screen.getByRole("button", { name: "创建排队号码" })); await waitFor(() => expect(screen.getByText(/17 秒后重试/)).toBeVisible()); expect(phoneInput).toHaveValue("13800138000"); expect(screen.getByRole("button", { name: /请等待 17 秒/ })).toBeDisabled(); } finally { createTicket.mockRestore(); } }); });