import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; import SeatAllocationDialog from "./SeatAllocationDialog"; import SeatSummaryStrip from "./SeatSummaryStrip"; const setSeats = vi.fn(); vi.mock("./OrgApi", () => ({ orgApi: { setSeats: (id: string, limit: number | null) => setSeats(id, limit), }, })); vi.mock("react-i18next", () => ({ useTranslation: () => ({ t: (key: string, options?: Record) => options && Object.keys(options).length ? `${key}:${JSON.stringify(options)}` : key, i18n: { language: "en" }, }), })); const unit = (overrides: Record = {}) => ({ id: "u1", name: "Lahore", code: null, parent_id: null, path: "/u1/", depth: 0, is_active: true, seat_limit: null as number | null, seats_used: 2, ...overrides, }); beforeEach(() => { setSeats.mockReset().mockResolvedValue({ purchased: 50, allocated: 10, unallocated: 40, }); }); describe("SeatAllocationDialog", () => { it("sends null when the box is cleared, not zero", async () => { const user = userEvent.setup({ delay: null }); render( ); await user.clear(screen.getByRole("spinbutton")); await user.click(screen.getByRole("button", { name: /actions\.save/ })); await waitFor(() => expect(setSeats).toHaveBeenCalledWith("u1", null)); }); it("sends zero when zero is typed", async () => { const user = userEvent.setup({ delay: null }); render( ); const field = screen.getByRole("spinbutton"); await user.clear(field); await user.type(field, "0"); await user.click(screen.getByRole("button", { name: /actions\.save/ })); await waitFor(() => expect(setSeats).toHaveBeenCalledWith("u1", 0)); }); it("opens showing the branch's own limit rather than the last one edited", async () => { const { rerender } = render( ); expect(screen.getByRole("spinbutton")).toHaveValue(8); rerender( ); await waitFor(() => expect(screen.getByRole("spinbutton")).toHaveValue(3)); }); it("opens empty for a branch that has no cap", async () => { render( ); expect(screen.getByRole("spinbutton")).toHaveValue(null); }); it("shows the server's refusal rather than a generic message", async () => { setSeats.mockRejectedValue(new Error("At most 2 can go to Lahore.")); const user = userEvent.setup({ delay: null }); const onSaved = vi.fn(); render( ); const field = screen.getByRole("spinbutton"); await user.clear(field); await user.type(field, "9"); await user.click(screen.getByRole("button", { name: /actions\.save/ })); expect(await screen.findByText("At most 2 can go to Lahore.")).toBeInTheDocument(); expect(onSaved).not.toHaveBeenCalled(); }); it("stays open when the save fails", async () => { setSeats.mockRejectedValue(new Error("nope")); const user = userEvent.setup({ delay: null }); const onClose = vi.fn(); render( ); await user.click(screen.getByRole("button", { name: /actions\.save/ })); await waitFor(() => expect(screen.getByText("nope")).toBeInTheDocument()); expect(onClose).not.toHaveBeenCalled(); }); it("renders nothing when no unit is open", () => { const { container } = render( ); expect(container).toBeEmptyDOMElement(); }); }); describe("SeatSummaryStrip", () => { it("shows what is left to give", async () => { render(); expect(screen.getByText("50")).toBeInTheDocument(); expect(screen.getByText("12")).toBeInTheDocument(); expect(screen.getByText("38")).toBeInTheDocument(); }); it("does not invent a remainder out of an unlimited plan", () => { render(); expect(screen.getByText("seats.unlimited")).toBeInTheDocument(); expect(screen.queryByText("seats.unallocated")).not.toBeInTheDocument(); }); it("shows nothing rather than zeroes when the position is unknown", () => { const { container } = render(); expect(container).toBeEmptyDOMElement(); }); });