Files
XQKqueue/web/src/pages/VisitorTakeTicketPage.test.tsx
2026-07-16 11:40:34 +08:00

104 lines
4.4 KiB
TypeScript

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(<MemoryRouter><VisitorTakeTicketForm /></MemoryRouter>);
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(<MemoryRouter><VisitorTakeTicketForm /></MemoryRouter>);
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(<MemoryRouter><VisitorTakeTicketForm /></MemoryRouter>);
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();
}
});
});