Files
saas_frontend/src/application/organisation/SeatAllocationDialog.test.tsx
T
2026-08-31 20:40:20 -04:00

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