224 lines
13 KiB
TypeScript
224 lines
13 KiB
TypeScript
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||
import { describe, expect, it, vi } from "vitest";
|
||
import { MemoryRouter } from "react-router-dom";
|
||
import { api } from "../api";
|
||
|
||
vi.mock("../components/AppShell", () => ({ AppShell: ({ children }: { children: React.ReactNode }) => <div>{children}</div> }));
|
||
vi.mock("../hooks/usePollingResource", () => ({
|
||
usePollingResource: (_loader: unknown, options: { intervalMs: number }) => ({
|
||
data: options.intervalMs === 30_000 ? {
|
||
users: [
|
||
{ id: "user-1", username: "staff01", role: "STAFF", active: true, project_ids: ["project-1"] },
|
||
{ id: "super-1", username: "xqkwljtadmin", role: "ADMIN", active: true, protected: true, project_ids: [] },
|
||
],
|
||
} : {
|
||
summary: { running_projects: 1, waiting_count: 1, anomaly_projects: 0, offline_devices: 0 },
|
||
projects: [{
|
||
id: "project-1",
|
||
code: "EAST-RIDE",
|
||
name: "东门观光车",
|
||
timezone: "Asia/Shanghai",
|
||
ticket_prefix: "A",
|
||
status: "RUNNING",
|
||
batch_size: 5,
|
||
call_batch_size: 5,
|
||
grace_period_minutes: 5,
|
||
visitor_notice: "请在入口附近等候。",
|
||
eta: { interval_per_number_seconds: 120 },
|
||
waiting_count: 1,
|
||
current_batch: {
|
||
id: "batch-1",
|
||
batch_number: 4,
|
||
status: "ACTIVE",
|
||
tickets: [
|
||
{ id: "ticket-2", ticket_number: "00013", phone: "13800138000", status: "CALLED" },
|
||
{ id: "ticket-3", ticket_number: "00014", phone: "13800138001", status: "CALLED" },
|
||
],
|
||
},
|
||
estimated_wait: { available: true, lower_minutes: 5, upper_minutes: 15 },
|
||
device_status: { label: "模拟设备执行成功" },
|
||
}],
|
||
active_tickets: [{
|
||
id: "ticket-1",
|
||
project_id: "project-1",
|
||
project_name: "东门观光车",
|
||
ticket_number: "00012",
|
||
phone: "13800138000",
|
||
last_name: "张",
|
||
honorific: "先生",
|
||
status: "WAITING",
|
||
created_at: "2026-07-10T09:00:00Z",
|
||
}],
|
||
},
|
||
loading: false,
|
||
refreshing: false,
|
||
error: null,
|
||
offline: false,
|
||
lastClientSuccessAt: new Date().toISOString(),
|
||
refresh: vi.fn(),
|
||
}),
|
||
}));
|
||
|
||
import { AdminPage } from "./AdminPage";
|
||
|
||
describe("AdminPage active tickets", () => {
|
||
it("保留核心排队数据并删除辅助概览模块", () => {
|
||
render(<MemoryRouter initialEntries={["/admin"]}><AdminPage /></MemoryRouter>);
|
||
|
||
expect(screen.getByRole("list", { name: "项目队列" })).toHaveTextContent("东门观光车");
|
||
expect(screen.getByRole("list", { name: "项目队列" })).toHaveTextContent("00013 至 00014");
|
||
expect(screen.getByText("运行项目").parentElement).toHaveTextContent("1");
|
||
expect(screen.getByText("总等待号码").parentElement).toHaveTextContent("1");
|
||
expect(screen.queryByText("最近操作")).not.toBeInTheDocument();
|
||
expect(screen.queryByText("异常与提醒")).not.toBeInTheDocument();
|
||
expect(screen.queryByText("最新叫号")).not.toBeInTheDocument();
|
||
expect(screen.queryByText("异常项目")).not.toBeInTheDocument();
|
||
expect(screen.queryByText("离线设备")).not.toBeInTheDocument();
|
||
expect(screen.queryByRole("group", { name: "数据视图" })).not.toBeInTheDocument();
|
||
expect(screen.queryByText("演示视图")).not.toBeInTheDocument();
|
||
expect(screen.queryByText("实时数据")).not.toBeInTheDocument();
|
||
});
|
||
|
||
it("在项目管理中列出项目并提供创建与维护入口", () => {
|
||
render(<MemoryRouter initialEntries={["/admin/projects"]}><AdminPage /></MemoryRouter>);
|
||
|
||
expect(screen.getByRole("heading", { name: "项目管理" })).toBeVisible();
|
||
expect(screen.getByRole("table", { name: "项目列表" })).toHaveTextContent("东门观光车");
|
||
expect(screen.getByRole("link", { name: "创建项目" })).toHaveAttribute("href", "/admin/projects/new");
|
||
expect(screen.getByRole("link", { name: "维护" })).toHaveAttribute("href", "/admin/projects/project-1");
|
||
expect(screen.queryByRole("form", { name: "规则配置" })).not.toBeInTheDocument();
|
||
});
|
||
|
||
it("将上线超级管理员显示为不可编辑", () => {
|
||
render(<MemoryRouter initialEntries={["/admin/accounts"]}><AdminPage /></MemoryRouter>);
|
||
|
||
const row = screen.getByText("xqkwljtadmin").closest('[role="row"]');
|
||
expect(row).toHaveTextContent("超级管理员");
|
||
expect(row).toHaveTextContent("不可编辑");
|
||
expect(row?.querySelector('a[href="/admin/accounts/super-1"]')).toBeNull();
|
||
});
|
||
|
||
it("在同一表单中维护项目全部字段", () => {
|
||
render(<MemoryRouter initialEntries={["/admin/projects/project-1"]}><AdminPage /></MemoryRouter>);
|
||
|
||
expect(screen.getByRole("heading", { name: "东门观光车" })).toBeVisible();
|
||
expect(screen.queryByRole("heading", { name: "维护项目" })).not.toBeInTheDocument();
|
||
expect(screen.getByRole("form", { name: "维护项目" })).toBeVisible();
|
||
expect(screen.getByRole("form", { name: "维护项目" })).toHaveTextContent("项目名称");
|
||
expect(screen.getByRole("form", { name: "维护项目" })).toHaveTextContent("项目状态");
|
||
expect(screen.queryByText("基础信息")).not.toBeInTheDocument();
|
||
expect(screen.queryByText("规则配置")).not.toBeInTheDocument();
|
||
expect(screen.getByRole("combobox", { name: "票号格式" })).toHaveDisplayValue("00000");
|
||
expect(screen.queryByRole("textbox", { name: "时区" })).not.toBeInTheDocument();
|
||
expect(screen.queryByRole("textbox", { name: "票号前缀" })).not.toBeInTheDocument();
|
||
expect(screen.getByRole("spinbutton", { name: "每次叫号数量" })).toHaveValue(5);
|
||
expect(screen.getByRole("spinbutton", { name: "单个号码预计间隔时间(秒)" })).toHaveValue(120);
|
||
expect(screen.queryByRole("combobox", { name: "预计等待时间方式" })).not.toBeInTheDocument();
|
||
expect(screen.getByRole("textbox", { name: "游客官方提示" })).toHaveValue("请在入口附近等候。");
|
||
expect(screen.getByRole("button", { name: "保存项目" })).toBeVisible();
|
||
});
|
||
|
||
it("创建项目与维护项目使用相同字段", () => {
|
||
render(<MemoryRouter initialEntries={["/admin/projects/new"]}><AdminPage /></MemoryRouter>);
|
||
expect(screen.getByRole("form", { name: "创建项目" })).toBeVisible();
|
||
expect(screen.getByRole("textbox", { name: "项目名称" })).toBeVisible();
|
||
expect(screen.getByRole("textbox", { name: "项目编码" })).toBeVisible();
|
||
expect(screen.getByRole("combobox", { name: "票号格式" })).toHaveDisplayValue("00000");
|
||
expect(screen.getByRole("combobox", { name: "项目状态" })).toHaveDisplayValue("未开放");
|
||
expect(screen.getByRole("spinbutton", { name: "每次叫号数量" })).toHaveValue(1);
|
||
expect(screen.getByRole("spinbutton", { name: "单个号码预计间隔时间(秒)" })).toHaveValue(60);
|
||
expect(screen.getByRole("textbox", { name: "游客官方提示" })).toHaveValue("请您在景区附近等候,注意听从工作人员指引。");
|
||
expect(screen.queryByRole("textbox", { name: "时区" })).not.toBeInTheDocument();
|
||
expect(screen.getByRole("button", { name: "创建项目" })).toBeVisible();
|
||
});
|
||
|
||
it("创建项目提交完整的默认配置", async () => {
|
||
const createdProject = { id: "project-2", code: "NEW-RIDE", name: "新项目", status: "NOT_OPEN", batch_size: 1, timezone: "Asia/Shanghai", ticket_prefix: "A" };
|
||
const createProject = vi.spyOn(api, "createProject").mockResolvedValue({ project: createdProject });
|
||
const updateProjectSettings = vi.spyOn(api, "updateProjectSettings").mockResolvedValue({ project: createdProject });
|
||
try {
|
||
render(<MemoryRouter initialEntries={["/admin/projects/new"]}><AdminPage /></MemoryRouter>);
|
||
fireEvent.change(screen.getByRole("textbox", { name: "项目名称" }), { target: { value: "新项目" } });
|
||
fireEvent.change(screen.getByRole("textbox", { name: "项目编码" }), { target: { value: "new-ride" } });
|
||
fireEvent.submit(screen.getByRole("form", { name: "创建项目" }));
|
||
|
||
await waitFor(() => expect(createProject).toHaveBeenCalledTimes(1));
|
||
expect(createProject).toHaveBeenCalledWith({ name: "新项目", code: "NEW-RIDE", timezone: "Asia/Shanghai", ticket_prefix: "A" });
|
||
expect(updateProjectSettings).toHaveBeenCalledWith("project-2", {
|
||
status: "NOT_OPEN",
|
||
call_batch_size: 1,
|
||
grace_period_minutes: 5,
|
||
eta_interval_seconds: 60,
|
||
visitor_notice: "请您在景区附近等候,注意听从工作人员指引。",
|
||
});
|
||
} finally {
|
||
vi.restoreAllMocks();
|
||
}
|
||
});
|
||
|
||
it("keeps the screen center free of contact details", () => {
|
||
render(<MemoryRouter initialEntries={["/admin/display"]}><AdminPage /></MemoryRouter>);
|
||
|
||
expect(screen.getAllByRole("button", { name: "全屏展示" }).length).toBeGreaterThan(0);
|
||
expect(screen.getByRole("region", { name: "项目实时监控墙" })).toBeVisible();
|
||
expect(screen.getByText("00013 至 00014")).toBeVisible();
|
||
expect(screen.queryByText("00013、00014")).not.toBeInTheDocument();
|
||
expect(screen.queryByText("13800138000")).not.toBeInTheDocument();
|
||
expect(screen.queryByText("全项目实时监控")).not.toBeInTheDocument();
|
||
expect(screen.queryByText("当前批次")).not.toBeInTheDocument();
|
||
expect(screen.queryByText("设备状态")).not.toBeInTheDocument();
|
||
expect(screen.queryByText("大屏中心只展示公开票号与运行状态,不展示游客联系方式。")).not.toBeInTheDocument();
|
||
expect(screen.getByLabelText(/当前时间/)).toBeInTheDocument();
|
||
expect(screen.queryByText("当前项目")).not.toBeInTheDocument();
|
||
expect(screen.getByRole("heading", { name: "东门观光车" })).toBeVisible();
|
||
expect(screen.getByRole("heading", { name: "最新取号码:00015号" })).toBeInTheDocument();
|
||
expect(screen.getByLabelText("总计等待:1 个号码")).toBeInTheDocument();
|
||
expect(screen.getByText("预计等待时间")).toBeVisible();
|
||
expect(screen.queryByText("后续号段预计")).not.toBeInTheDocument();
|
||
expect(screen.queryByText(/\d+ \/ \d+/)).not.toBeInTheDocument();
|
||
expect(screen.queryByText("运行中")).not.toBeInTheDocument();
|
||
});
|
||
|
||
it("先展示账号列表,不在列表页展示创建表单", () => {
|
||
render(<MemoryRouter initialEntries={["/admin/accounts"]}><AdminPage /></MemoryRouter>);
|
||
|
||
expect(screen.getByRole("heading", { name: "账号管理" })).toBeVisible();
|
||
expect(screen.getByRole("table", { name: "账号列表" })).toBeVisible();
|
||
expect(screen.getByText("staff01")).toBeVisible();
|
||
expect(screen.getByRole("link", { name: "创建账号" })).toHaveAttribute("href", "/admin/accounts/new");
|
||
expect(screen.getByRole("link", { name: "维护" })).toHaveAttribute("href", "/admin/accounts/user-1");
|
||
expect(screen.queryByRole("combobox", { name: "角色" })).not.toBeInTheDocument();
|
||
expect(screen.queryByRole("form", { name: "创建账号" })).not.toBeInTheDocument();
|
||
});
|
||
|
||
it("点击创建后由独立路由维护新账号", () => {
|
||
render(<MemoryRouter initialEntries={["/admin/accounts/new"]}><AdminPage /></MemoryRouter>);
|
||
|
||
expect(screen.getByRole("form", { name: "创建账号" })).toBeVisible();
|
||
expect(screen.getByRole("textbox", { name: "账号" })).toBeVisible();
|
||
expect(screen.getByLabelText("初始密码")).toBeVisible();
|
||
expect(screen.getByRole("link", { name: "取消" })).toHaveAttribute("href", "/admin/accounts");
|
||
expect(screen.queryByText("staff01")).not.toBeInTheDocument();
|
||
});
|
||
|
||
it("账号维护控件只在独立维护页展示", () => {
|
||
render(<MemoryRouter initialEntries={["/admin/accounts/user-1"]}><AdminPage /></MemoryRouter>);
|
||
|
||
expect(screen.getByRole("heading", { name: "维护账号" })).toBeVisible();
|
||
expect(screen.getByRole("radiogroup", { name: "角色" })).toBeVisible();
|
||
expect(screen.getByRole("radio", { name: "员工" })).toBeChecked();
|
||
expect(screen.getByRole("radio", { name: "管理员" })).not.toBeChecked();
|
||
expect(screen.getByRole("button", { name: "保存修改" })).toBeDisabled();
|
||
fireEvent.click(screen.getByRole("radio", { name: "管理员" }));
|
||
expect(screen.getByRole("radio", { name: "管理员" })).toBeChecked();
|
||
expect(screen.getByText("有未保存的修改")).toBeVisible();
|
||
expect(screen.getByRole("button", { name: "保存修改" })).toBeEnabled();
|
||
expect(screen.getByRole("button", { name: "放弃修改" })).toBeEnabled();
|
||
expect(screen.getByRole("group", { name: "所属项目" })).toBeVisible();
|
||
expect(screen.getByRole("checkbox", { name: /东门观光车/ })).toBeChecked();
|
||
expect(screen.getByText("已授权")).toBeVisible();
|
||
expect(screen.getByRole("link", { name: "返回列表" })).toHaveAttribute("href", "/admin/accounts");
|
||
expect(screen.queryByRole("table", { name: "账号列表" })).not.toBeInTheDocument();
|
||
});
|
||
});
|