196 lines
6.2 KiB
TypeScript
196 lines
6.2 KiB
TypeScript
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<string, unknown>) =>
|
|
options && Object.keys(options).length
|
|
? `${key}:${JSON.stringify(options)}`
|
|
: key,
|
|
i18n: { language: "en" },
|
|
}),
|
|
}));
|
|
|
|
const unit = (overrides: Record<string, unknown> = {}) => ({
|
|
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(
|
|
<SeatAllocationDialog
|
|
unit={unit({ seat_limit: 8 })}
|
|
availableToThisUnit={40}
|
|
onClose={vi.fn()}
|
|
onSaved={vi.fn()}
|
|
/>
|
|
);
|
|
|
|
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(
|
|
<SeatAllocationDialog
|
|
unit={unit({ seat_limit: 8 })}
|
|
availableToThisUnit={40}
|
|
onClose={vi.fn()}
|
|
onSaved={vi.fn()}
|
|
/>
|
|
);
|
|
|
|
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(
|
|
<SeatAllocationDialog
|
|
unit={unit({ id: "u1", seat_limit: 8 })}
|
|
availableToThisUnit={40}
|
|
onClose={vi.fn()}
|
|
onSaved={vi.fn()}
|
|
/>
|
|
);
|
|
expect(screen.getByRole("spinbutton")).toHaveValue(8);
|
|
|
|
rerender(
|
|
<SeatAllocationDialog
|
|
unit={unit({ id: "u2", name: "Karachi", seat_limit: 3 })}
|
|
availableToThisUnit={40}
|
|
onClose={vi.fn()}
|
|
onSaved={vi.fn()}
|
|
/>
|
|
);
|
|
await waitFor(() => expect(screen.getByRole("spinbutton")).toHaveValue(3));
|
|
});
|
|
|
|
it("opens empty for a branch that has no cap", async () => {
|
|
render(
|
|
<SeatAllocationDialog
|
|
unit={unit({ seat_limit: null })}
|
|
availableToThisUnit={40}
|
|
onClose={vi.fn()}
|
|
onSaved={vi.fn()}
|
|
/>
|
|
);
|
|
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(
|
|
<SeatAllocationDialog
|
|
unit={unit({ seat_limit: 1 })}
|
|
availableToThisUnit={2}
|
|
onClose={vi.fn()}
|
|
onSaved={onSaved}
|
|
/>
|
|
);
|
|
|
|
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(
|
|
<SeatAllocationDialog
|
|
unit={unit({ seat_limit: 1 })}
|
|
availableToThisUnit={2}
|
|
onClose={onClose}
|
|
onSaved={vi.fn()}
|
|
/>
|
|
);
|
|
|
|
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(
|
|
<SeatAllocationDialog
|
|
unit={null}
|
|
availableToThisUnit={40}
|
|
onClose={vi.fn()}
|
|
onSaved={vi.fn()}
|
|
/>
|
|
);
|
|
expect(container).toBeEmptyDOMElement();
|
|
});
|
|
});
|
|
|
|
describe("SeatSummaryStrip", () => {
|
|
it("shows what is left to give", async () => {
|
|
render(<SeatSummaryStrip summary={{ purchased: 50, allocated: 12, unallocated: 38 }} />);
|
|
|
|
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(<SeatSummaryStrip summary={{ purchased: null, allocated: 12, unallocated: null }} />);
|
|
|
|
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(<SeatSummaryStrip summary={null} />);
|
|
expect(container).toBeEmptyDOMElement();
|
|
});
|
|
});
|