feat: add party-size queueing and call modes
This commit is contained in:
1 parent
7f751bebae
commit
66951b4dc3
53 files changed
+3348
-533
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
|
||||
@@ -11,8 +11,8 @@ vi.mock("../hooks/usePollingResource", () => ({
|
||||
usePollingResource: (_load: unknown, options: { intervalMs: number }) => options.intervalMs === 60_000
|
||||
? {
|
||||
data: { projects: [
|
||||
{ id: "project-1", name: "东门观光车", status: "RUNNING", batch_size: 2 },
|
||||
{ id: "project-2", name: "西门观光车", status: "RUNNING", batch_size: 2 },
|
||||
{ id: "project-1", name: "东门观光车", status: "RUNNING", batch_size: 2, call_mode: "BOTH", default_call_ticket_count: 2, max_call_ticket_count: 20, default_call_people_count: 5, max_call_people_count: 30, min_party_size: 1, max_party_size: 8 },
|
||||
{ id: "project-2", name: "西门观光车", status: "RUNNING", batch_size: 2, call_mode: "BOTH", default_call_ticket_count: 2, max_call_ticket_count: 20, default_call_people_count: 5, max_call_people_count: 30, min_party_size: 1, max_party_size: 8 },
|
||||
] },
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
@@ -23,17 +23,18 @@ vi.mock("../hooks/usePollingResource", () => ({
|
||||
}
|
||||
: {
|
||||
data: {
|
||||
project: { id: "project-1", name: "东门观光车", status: "RUNNING", batch_size: 2 },
|
||||
project: { id: "project-1", name: "东门观光车", status: "RUNNING", batch_size: 2, call_mode: "BOTH", default_call_ticket_count: 2, max_call_ticket_count: 20, default_call_people_count: 5, max_call_people_count: 30, min_party_size: 1, max_party_size: 8 },
|
||||
revision: 8,
|
||||
waiting: [{
|
||||
id: "waiting-1",
|
||||
ticket_number: "00013",
|
||||
waiting: Array.from({ length: 25 }, (_, index) => ({
|
||||
id: `waiting-${index + 1}`,
|
||||
ticket_number: String(index + 13).padStart(5, "0"),
|
||||
phone: "13900139000",
|
||||
last_name: "李",
|
||||
honorific: "女士",
|
||||
status: "WAITING",
|
||||
party_size: index === 0 ? 4 : 2,
|
||||
created_at: "2026-07-10T09:02:00Z",
|
||||
}],
|
||||
})),
|
||||
current_batch: {
|
||||
id: "batch-1",
|
||||
batch_number: 4,
|
||||
@@ -46,13 +47,18 @@ vi.mock("../hooks/usePollingResource", () => ({
|
||||
last_name: "张",
|
||||
honorific: "先生",
|
||||
status: "CALLED",
|
||||
party_size: 2,
|
||||
}],
|
||||
ticket_count: 1,
|
||||
people_count: 2,
|
||||
},
|
||||
metrics: {
|
||||
waiting_count: 1,
|
||||
waiting_count: 25,
|
||||
waiting_ticket_count: 25,
|
||||
waiting_people_count: 52,
|
||||
estimated_wait: { available: true, estimate_minutes: 15, min_minutes: 15, max_minutes: 15 },
|
||||
last_ticket_number: "00013",
|
||||
next_ticket_number: "00014",
|
||||
last_ticket_number: "00037",
|
||||
next_ticket_number: "00038",
|
||||
},
|
||||
last_success_at: new Date().toISOString(),
|
||||
},
|
||||
@@ -90,19 +96,136 @@ describe("StaffPage scene-focused H5", () => {
|
||||
expect(screen.queryByRole("link", { name: "核验" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "到场" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "过号" })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "按号数/人数叫号" })).toBeVisible();
|
||||
expect(screen.getByRole("region", { name: "按号数/人数叫号" }).parentElement).toHaveClass("staff-call-sticky");
|
||||
expect(screen.queryByText("按号码叫号", { selector: ".staff-call-mode > strong" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("按人数叫号", { selector: ".staff-call-mode > strong" })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "快速叫下一个号" })).toBeEnabled();
|
||||
expect(screen.getByRole("button", { name: "叫号段" })).toBeEnabled();
|
||||
expect(screen.getByRole("button", { name: "批量叫号" })).toBeEnabled();
|
||||
expect(screen.getByRole("button", { name: "批量叫号" })).toHaveClass("button--secondary");
|
||||
expect(screen.getByRole("button", { name: "批量叫人" })).toBeEnabled();
|
||||
expect(screen.getByRole("button", { name: "批量叫人" })).toHaveClass("button--secondary");
|
||||
expect(screen.queryByRole("button", { name: "按号码叫号" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "按人数叫号" })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("region", { name: "叫号" })).toHaveTextContent("000134 人");
|
||||
expect(screen.queryByText(/本次共叫/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText("等待队列表,可横向滚动")).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("group", { name: "数据视图" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("演示")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("实时")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("region", { name: "叫号" })).toHaveTextContent("最末号预计时长约 15 分钟");
|
||||
const callRegion = screen.getByRole("region", { name: "叫号" });
|
||||
expect(callRegion).toHaveTextContent("下个号人数4 人");
|
||||
expect(callRegion).toHaveTextContent("剩余未叫号25 个");
|
||||
expect(callRegion).toHaveTextContent("剩余未叫人数52 人");
|
||||
expect(callRegion).not.toHaveTextContent("最末号数");
|
||||
expect(callRegion).not.toHaveTextContent("最末号预计时长");
|
||||
});
|
||||
|
||||
it("reveals waiting tickets ten at a time and can collapse", () => {
|
||||
renderScene("/staff");
|
||||
|
||||
const waitingList = screen.getByRole("list", { name: "待叫号码" });
|
||||
expect(within(waitingList).getAllByRole("listitem")).toHaveLength(10);
|
||||
expect(screen.queryByText("00023")).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "查看更多" }));
|
||||
expect(within(waitingList).getAllByRole("listitem")).toHaveLength(20);
|
||||
expect(screen.getByText("00032")).toBeVisible();
|
||||
expect(screen.queryByText("00033")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "查看更多" })).toBeVisible();
|
||||
expect(screen.getByRole("button", { name: "收起" })).toBeVisible();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "查看更多" }));
|
||||
expect(within(waitingList).getAllByRole("listitem")).toHaveLength(25);
|
||||
expect(screen.getByText("00037")).toBeVisible();
|
||||
expect(screen.queryByRole("button", { name: "查看更多" })).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "收起" }));
|
||||
expect(within(waitingList).getAllByRole("listitem")).toHaveLength(10);
|
||||
expect(screen.getByRole("button", { name: "查看更多" })).toBeVisible();
|
||||
expect(screen.queryByText("00023")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps business success primary when a device acknowledgement fails", async () => {
|
||||
const callNext = vi.spyOn(api, "callNext").mockResolvedValueOnce({
|
||||
batch: {
|
||||
id: "batch-2",
|
||||
batch_number: 5,
|
||||
status: "CALLED",
|
||||
called_at: "2026-07-10T09:03:00Z",
|
||||
ticket_count: 1,
|
||||
people_count: 4,
|
||||
tickets: [{ id: "called-2", ticket_number: "00013", status: "CALLED", party_size: 4 }],
|
||||
},
|
||||
revision: 9,
|
||||
device_results: [{ channel: "SIMULATOR", status: "FAILURE", message: "模拟设备执行失败;业务叫号已成功" }],
|
||||
});
|
||||
|
||||
try {
|
||||
renderScene("/staff");
|
||||
fireEvent.click(screen.getByRole("button", { name: "快速叫下一个号" }));
|
||||
|
||||
await waitFor(() => expect(screen.getByRole("status")).toHaveTextContent("叫号已生效:00013"));
|
||||
expect(screen.getByRole("status")).toHaveTextContent("业务叫号已生效;1 个设备通道执行失败");
|
||||
expect(screen.queryByText("叫号未生效")).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole("button", { name: "删除叫号通知" }));
|
||||
expect(screen.queryByText("叫号已生效:00013")).not.toBeInTheDocument();
|
||||
} finally {
|
||||
callNext.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("automatically removes a call notice after five seconds", async () => {
|
||||
vi.useFakeTimers();
|
||||
const callNext = vi.spyOn(api, "callNext").mockResolvedValueOnce({
|
||||
batch: {
|
||||
id: "batch-2",
|
||||
batch_number: 5,
|
||||
status: "CALLED",
|
||||
called_at: "2026-07-10T09:03:00Z",
|
||||
ticket_count: 1,
|
||||
people_count: 4,
|
||||
tickets: [{ id: "called-2", ticket_number: "00013", status: "CALLED", party_size: 4 }],
|
||||
},
|
||||
revision: 9,
|
||||
device_results: [{ channel: "SIMULATOR", status: "SUCCESS" }],
|
||||
});
|
||||
|
||||
try {
|
||||
renderScene("/staff");
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByRole("button", { name: "快速叫下一个号" }));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(screen.getByText("叫号已生效:00013")).toBeVisible();
|
||||
act(() => vi.advanceTimersByTime(4_999));
|
||||
expect(screen.getByText("叫号已生效:00013")).toBeVisible();
|
||||
act(() => vi.advanceTimersByTime(1));
|
||||
expect(screen.queryByText("叫号已生效:00013")).not.toBeInTheDocument();
|
||||
} finally {
|
||||
callNext.mockRestore();
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects an over-limit people target instead of silently capping it", async () => {
|
||||
const callNext = vi.spyOn(api, "callNext");
|
||||
try {
|
||||
renderScene("/staff");
|
||||
fireEvent.change(screen.getByRole("spinbutton", { name: "按人数叫号数量" }), { target: { value: "31" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "批量叫人" }));
|
||||
|
||||
expect(callNext).not.toHaveBeenCalled();
|
||||
expect(screen.getByRole("alert")).toHaveTextContent("人数请输入 1 到 30 的整数");
|
||||
} finally {
|
||||
callNext.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("updates number indicators after creation without showing a success card", async () => {
|
||||
vi.spyOn(api, "createTicket").mockResolvedValueOnce({
|
||||
ticket: { id: "ticket-30", ticket_number: "00030", status: "WAITING" },
|
||||
ticket: { id: "ticket-30", ticket_number: "00030", status: "WAITING", party_size: 3 },
|
||||
revision: 9,
|
||||
});
|
||||
renderScene("/staff/tickets");
|
||||
@@ -111,15 +234,22 @@ describe("StaffPage scene-focused H5", () => {
|
||||
expect(screen.getByRole("region", { name: "取号" })).toBeVisible();
|
||||
expect(screen.getByLabelText("手机号")).toBeVisible();
|
||||
expect(screen.getByRole("button", { name: "创建排队号码" })).toBeVisible();
|
||||
expect(screen.getByRole("region", { name: "取号指标" })).toHaveTextContent("上个取号00013");
|
||||
expect(screen.getByRole("region", { name: "取号指标" })).toHaveTextContent("下个取号00014");
|
||||
expect(screen.getByRole("region", { name: "取号指标" })).toHaveTextContent("上个取号00037");
|
||||
expect(screen.getByRole("region", { name: "取号指标" })).toHaveTextContent("下个取号00038");
|
||||
expect(screen.queryByLabelText("等待队列表,可横向滚动")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("叫下一批(1 个号码)")).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.change(screen.getByLabelText("手机号"), { target: { value: "13800138000" } });
|
||||
const partySizeInput = screen.getByRole("spinbutton", { name: "同行人数" });
|
||||
expect(partySizeInput).toHaveValue(1);
|
||||
fireEvent.change(partySizeInput, { target: { value: "" } });
|
||||
expect(partySizeInput).toHaveValue(null);
|
||||
fireEvent.change(partySizeInput, { target: { value: "3" } });
|
||||
expect(partySizeInput).toHaveValue(3);
|
||||
fireEvent.click(screen.getByRole("button", { name: "创建排队号码" }));
|
||||
|
||||
await waitFor(() => expect(screen.getByRole("region", { name: "取号指标" })).toHaveTextContent("上个取号00030"));
|
||||
expect(screen.getByRole("region", { name: "取号指标" })).toHaveTextContent("3 人");
|
||||
expect(screen.getByRole("region", { name: "取号指标" })).toHaveTextContent("下个取号00031");
|
||||
expect(screen.queryByText("取号成功:00030")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("打开游客状态页")).not.toBeInTheDocument();
|
||||
|
||||
Reference in new issue
Block a user