mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-01 17:53:11 +00:00
167 lines
7.6 KiB
TypeScript
167 lines
7.6 KiB
TypeScript
import { screen, waitFor, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import CompletionDocsPage from "@/app/(protected)/completion-docs/index";
|
|
import { renderWithProviders } from "@/test/test-utils";
|
|
import { BACKFLOW, SCRUBBER, serveTemplates, templateDto } from "./completion-docs-fixtures";
|
|
|
|
const api = vi.hoisted(() => ({
|
|
apiGet: vi.fn(),
|
|
apiPost: vi.fn(),
|
|
apiPut: vi.fn(),
|
|
apiDeleteNoContent: vi.fn(),
|
|
}));
|
|
const auth = vi.hoisted(() => ({ userRoles: "Admin" }));
|
|
|
|
vi.mock("@/api/api", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("@/api/api")>()),
|
|
...api,
|
|
}));
|
|
|
|
vi.mock("@/providers/auth-context", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("@/providers/auth-context")>()),
|
|
useAuthContext: () => ({ user: { id: "u1", userRoles: auth.userRoles } }),
|
|
}));
|
|
|
|
// delay: null skips per-keystroke timer yields that only slow typing-heavy tests down.
|
|
let user = userEvent.setup({ delay: null });
|
|
|
|
function renderPage() {
|
|
return renderWithProviders(<CompletionDocsPage />, {
|
|
route: "/completion-docs",
|
|
withAuth: false,
|
|
});
|
|
}
|
|
|
|
async function openTemplate(name: string) {
|
|
await user.click(await screen.findByRole("row", { name: new RegExp(name) }));
|
|
return screen.findByRole("dialog");
|
|
}
|
|
|
|
beforeEach(() => {
|
|
user = userEvent.setup({ delay: null });
|
|
auth.userRoles = "Admin";
|
|
serveTemplates(api, [BACKFLOW, SCRUBBER]);
|
|
});
|
|
|
|
describe("Completion Docs page", { timeout: 15_000 }, () => {
|
|
it("lists every template with its Document Title, Last Updated and Created By", async () => {
|
|
renderPage();
|
|
|
|
const backflow = await screen.findByRole("row", { name: /Backflow Test Report/ });
|
|
const table = screen.getByRole("table", { name: "Completion document templates" });
|
|
const headers = within(table)
|
|
.getAllByRole("columnheader")
|
|
.map((cell) => cell.textContent);
|
|
expect(headers.slice(0, 3)).toEqual(["Document Title", "Last Updated", "Created By"]);
|
|
|
|
expect(within(backflow).getByText("Sep 25, 2026")).toBeInTheDocument();
|
|
expect(within(backflow).getByText("Jane Doe")).toBeInTheDocument();
|
|
const scrubber = within(table).getByRole("row", { name: /Scrubber Pit Augering/ });
|
|
// Never updated: Last Updated falls back to the creation date; no creator shows a dash.
|
|
expect(within(scrubber).getByText("Jun 1, 2026")).toBeInTheDocument();
|
|
expect(within(scrubber).getByText("—")).toBeInTheDocument();
|
|
expect(screen.getByRole("heading", { name: "Completion Docs" })).toBeInTheDocument();
|
|
expect(screen.getAllByText("2 templates")).toHaveLength(2);
|
|
});
|
|
|
|
it("filters by Document Title as the user types and explains an empty result", async () => {
|
|
renderPage();
|
|
await screen.findByRole("row", { name: /Scrubber Pit Augering/ });
|
|
const search = screen.getByRole("searchbox", { name: "Search document title" });
|
|
|
|
await user.type(search, "backf");
|
|
expect(
|
|
await screen.findByText("1 template", { selector: "span" }, { timeout: 2000 }),
|
|
).toBeInTheDocument();
|
|
expect(screen.queryByRole("row", { name: /Scrubber Pit Augering/ })).not.toBeInTheDocument();
|
|
expect(api.apiGet).toHaveBeenCalledWith("workorders/completion-templates", {
|
|
searchParams: { search: "backf" },
|
|
});
|
|
|
|
await user.clear(search);
|
|
await user.type(search, "no such document");
|
|
expect(
|
|
await screen.findByText("No templates found", {}, { timeout: 2000 }),
|
|
).toBeInTheDocument();
|
|
expect(screen.getByText("Try a different search, or add a new template.")).toBeInTheDocument();
|
|
expect(screen.queryByRole("row", { name: /Backflow Test Report/ })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("reports a failed load without the empty-search message, including after a failed Retry", async () => {
|
|
api.apiGet.mockRejectedValue(new Error("Templates are unavailable"));
|
|
renderPage();
|
|
|
|
const retry = await screen.findByRole("button", { name: "Retry" });
|
|
expect(screen.getByText("Templates are unavailable")).toBeInTheDocument();
|
|
expect(screen.queryByText("No templates found")).not.toBeInTheDocument();
|
|
|
|
const callsBeforeRetry = api.apiGet.mock.calls.length;
|
|
await user.click(retry);
|
|
await waitFor(() => expect(api.apiGet.mock.calls.length).toBeGreaterThan(callsBeforeRetry));
|
|
expect(await screen.findByText("Templates are unavailable")).toBeInTheDocument();
|
|
expect(screen.queryByText("No templates found")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("opens a read-only detail panel from a row with every part of the template", async () => {
|
|
renderPage();
|
|
const dialog = await openTemplate("Backflow Test Report");
|
|
|
|
expect(within(dialog).getByText("Document preview")).toBeInTheDocument();
|
|
for (const field of ["WO#", "Site Code", "Address", "Service Type"]) {
|
|
expect(within(dialog).getByText(field)).toBeInTheDocument();
|
|
}
|
|
const safety = within(dialog).getByRole("list", { name: "Safety notes" });
|
|
expect(within(safety).getAllByRole("listitem")).toHaveLength(4);
|
|
expect(within(safety).getByText("Wear gloves")).toBeInTheDocument();
|
|
expect(within(dialog).queryByRole("textbox")).not.toBeInTheDocument();
|
|
expect(
|
|
within(dialog).getByText("Created by Jane Doe · Updated Sep 25, 2026"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows that a template without procedures has none defined yet", async () => {
|
|
renderPage();
|
|
const dialog = await openTemplate("Scrubber Pit Augering");
|
|
|
|
expect(within(dialog).getByText("No procedure defined yet")).toBeInTheDocument();
|
|
expect(within(dialog).getByText("No procedure added yet.")).toBeInTheDocument();
|
|
});
|
|
|
|
it("gives an Admin every template action", async () => {
|
|
renderPage();
|
|
const row = await screen.findByRole("row", { name: /Backflow Test Report/ });
|
|
|
|
expect(screen.getByRole("button", { name: /Add Template/ })).toBeInTheDocument();
|
|
expect(within(row).getByRole("button", { name: "Edit" })).toBeInTheDocument();
|
|
const dialog = await openTemplate("Backflow Test Report");
|
|
expect(within(dialog).getByRole("button", { name: /Edit/ })).toBeInTheDocument();
|
|
expect(within(dialog).getByRole("button", { name: /Delete/ })).toBeInTheDocument();
|
|
});
|
|
|
|
it("hides delete from a Scheduler, who may still add and edit", async () => {
|
|
auth.userRoles = "Scheduler";
|
|
renderPage();
|
|
const row = await screen.findByRole("row", { name: /Backflow Test Report/ });
|
|
|
|
expect(screen.getByRole("button", { name: /Add Template/ })).toBeInTheDocument();
|
|
expect(within(row).getByRole("button", { name: "Edit" })).toBeInTheDocument();
|
|
const dialog = await openTemplate("Backflow Test Report");
|
|
expect(within(dialog).getByRole("button", { name: /Edit/ })).toBeInTheDocument();
|
|
expect(within(dialog).queryByRole("button", { name: /Delete/ })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("hides add, edit and delete entirely from a user without those permissions", async () => {
|
|
auth.userRoles = "Dispatcher";
|
|
serveTemplates(api, [templateDto()]);
|
|
renderPage();
|
|
const row = await screen.findByRole("row", { name: /Backflow Test Report/ });
|
|
|
|
expect(screen.queryByRole("button", { name: /Add Template/ })).not.toBeInTheDocument();
|
|
expect(within(row).queryByRole("button", { name: "Edit" })).not.toBeInTheDocument();
|
|
expect(within(row).getByRole("button", { name: "View details" })).toBeInTheDocument();
|
|
const dialog = await openTemplate("Backflow Test Report");
|
|
expect(within(dialog).queryByRole("button", { name: /Edit/ })).not.toBeInTheDocument();
|
|
expect(within(dialog).queryByRole("button", { name: /Delete/ })).not.toBeInTheDocument();
|
|
});
|
|
});
|