mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-05 08:22:06 +00:00
Use the prototype status list and gates on the board and wizard. Auto-derive Scheduled or Pending from a concrete schedule date.
328 lines
9.7 KiB
TypeScript
328 lines
9.7 KiB
TypeScript
import { fireEvent, screen } from "@testing-library/react";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { StatusCell } from "@/app/(protected)/workorders/_components/list/table/cells/status-cell";
|
|
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
|
import { renderWithProviders } from "@/test/test-utils";
|
|
|
|
function makeRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow {
|
|
return {
|
|
id: 1,
|
|
woNumber: "1001",
|
|
site: "BK5",
|
|
type: "PM",
|
|
severity: null,
|
|
dispatcherId: "u1",
|
|
dispatcherName: "Alex",
|
|
scheduledOn: "2026-07-15",
|
|
completedDate: "2026-07-20",
|
|
targetWeek: "2026-07-15",
|
|
dayGroup: "07/15",
|
|
dayLabel: "Tue",
|
|
location: "Lobby",
|
|
locationId: "loc-1",
|
|
pm: "HVAC",
|
|
serviceNotes: "",
|
|
extraServices: [],
|
|
vendorId: "7",
|
|
company: "Acme",
|
|
tech: "Sam",
|
|
techPhone: "",
|
|
apptStart: "",
|
|
apptEnd: "",
|
|
apptTime: "",
|
|
status: "In Progress",
|
|
docStatus: "Yes",
|
|
pocName: "",
|
|
pocPhone: "",
|
|
pocNotes: "",
|
|
dueDate: "",
|
|
rescheduleCount: 0,
|
|
carriedOver: 0,
|
|
originalWeek: "",
|
|
originalDate: "",
|
|
isPastDue: false,
|
|
scheduleWeekOnly: false,
|
|
isAddOn: false,
|
|
mediaCount: 1,
|
|
title: "",
|
|
priority: "",
|
|
primaryDispatchId: null,
|
|
rowVersion: "",
|
|
dispatchRowVersion: "",
|
|
flagColor: null,
|
|
additionalContacts: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("StatusCell pending uplift closability", () => {
|
|
it("disables Completed with tooltip when uplift is pending", () => {
|
|
const onChangeStatus = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<StatusCell
|
|
wo={makeRow({ hasPendingUplift: true })}
|
|
onChangeStatus={onChangeStatus}
|
|
onOpenSlideOver={vi.fn()}
|
|
/>,
|
|
{ withAuth: false },
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
|
|
|
const completed = screen.getByRole("button", { name: /completed/i });
|
|
expect(completed).toBeDisabled();
|
|
expect(completed).toHaveAttribute(
|
|
"title",
|
|
"This work order has an uplift pending approval and cannot be completed.",
|
|
);
|
|
|
|
fireEvent.click(completed);
|
|
expect(onChangeStatus).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("disables Completed with tooltip when board pendingUpliftCount maps to pending", () => {
|
|
const onChangeStatus = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<StatusCell
|
|
wo={makeRow({
|
|
hasPendingUplift: true,
|
|
upliftSummary: { hasUplift: true, pendingCount: 1 },
|
|
})}
|
|
onChangeStatus={onChangeStatus}
|
|
onOpenSlideOver={vi.fn()}
|
|
/>,
|
|
{ withAuth: false },
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
|
|
|
const completed = screen.getByRole("button", { name: /completed/i });
|
|
expect(completed).toBeDisabled();
|
|
expect(completed).toHaveAttribute(
|
|
"title",
|
|
"This work order has an uplift pending approval and cannot be completed.",
|
|
);
|
|
});
|
|
|
|
it("does not patch Completed when confirmation callback is omitted", () => {
|
|
const onChangeStatus = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<StatusCell wo={makeRow()} onChangeStatus={onChangeStatus} onOpenSlideOver={vi.fn()} />,
|
|
{ withAuth: false },
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
|
fireEvent.click(screen.getByRole("button", { name: /completed/i }));
|
|
|
|
expect(onChangeStatus).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("allows Completed when no pending uplift", () => {
|
|
const onChangeStatus = vi.fn();
|
|
const onRequestComplete = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<StatusCell
|
|
wo={makeRow()}
|
|
onChangeStatus={onChangeStatus}
|
|
onOpenSlideOver={vi.fn()}
|
|
onRequestComplete={onRequestComplete}
|
|
/>,
|
|
{ withAuth: false },
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
|
fireEvent.click(screen.getByRole("button", { name: /completed/i }));
|
|
|
|
expect(onRequestComplete).toHaveBeenCalled();
|
|
expect(onChangeStatus).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("disables Completed when company is missing", () => {
|
|
const onChangeStatus = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<StatusCell
|
|
wo={makeRow({ company: "", vendorId: "" })}
|
|
onChangeStatus={onChangeStatus}
|
|
onOpenSlideOver={vi.fn()}
|
|
/>,
|
|
{ withAuth: false },
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
|
|
|
const completed = screen.getByRole("button", { name: /completed/i });
|
|
expect(completed).toBeDisabled();
|
|
expect(completed).toHaveAttribute("title", "Fill in Vendor before completing this work order.");
|
|
fireEvent.click(completed);
|
|
expect(onChangeStatus).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("allows Completed when vendorId is set even if company label is empty", () => {
|
|
const onChangeStatus = vi.fn();
|
|
const onRequestComplete = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<StatusCell
|
|
wo={makeRow({ company: "", vendorId: "7", tech: "" })}
|
|
onChangeStatus={onChangeStatus}
|
|
onOpenSlideOver={vi.fn()}
|
|
onRequestComplete={onRequestComplete}
|
|
/>,
|
|
{ withAuth: false },
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
|
fireEvent.click(screen.getByRole("button", { name: /completed/i }));
|
|
|
|
expect(onRequestComplete).toHaveBeenCalled();
|
|
expect(onChangeStatus).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("allows Completed when vendorId has an empty company label and a live dispatch status", () => {
|
|
const onChangeStatus = vi.fn();
|
|
const onRequestComplete = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<StatusCell
|
|
wo={makeRow({ company: "", vendorId: "7", tech: "", primaryDispatchStatus: "Sent" })}
|
|
onChangeStatus={onChangeStatus}
|
|
onOpenSlideOver={vi.fn()}
|
|
onRequestComplete={onRequestComplete}
|
|
/>,
|
|
{ withAuth: false },
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
|
fireEvent.click(screen.getByRole("button", { name: /completed/i }));
|
|
|
|
expect(onRequestComplete).toHaveBeenCalled();
|
|
expect(onChangeStatus).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it.each(["Cancelled", "Canceled", "Refused"] as const)(
|
|
"disables Completed when vendorId is leftover from a %s dispatch",
|
|
(primaryDispatchStatus) => {
|
|
const onChangeStatus = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<StatusCell
|
|
wo={makeRow({
|
|
company: "",
|
|
vendorId: "45",
|
|
tech: "",
|
|
primaryDispatchStatus,
|
|
})}
|
|
onChangeStatus={onChangeStatus}
|
|
onOpenSlideOver={vi.fn()}
|
|
/>,
|
|
{ withAuth: false },
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
|
|
|
const completed = screen.getByRole("button", { name: /completed/i });
|
|
expect(completed).toBeDisabled();
|
|
expect(completed).toHaveAttribute(
|
|
"title",
|
|
"Fill in Vendor before completing this work order.",
|
|
);
|
|
fireEvent.click(completed);
|
|
expect(onChangeStatus).not.toHaveBeenCalled();
|
|
},
|
|
);
|
|
|
|
it("disables Completed when the board omits vendorId after a Refused primary", () => {
|
|
const onChangeStatus = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<StatusCell
|
|
wo={makeRow({
|
|
company: "",
|
|
vendorId: "",
|
|
tech: "",
|
|
primaryDispatchStatus: "Refused",
|
|
})}
|
|
onChangeStatus={onChangeStatus}
|
|
onOpenSlideOver={vi.fn()}
|
|
/>,
|
|
{ withAuth: false },
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
|
|
|
const completed = screen.getByRole("button", { name: /completed/i });
|
|
expect(completed).toBeDisabled();
|
|
expect(completed).toHaveAttribute("title", "Fill in Vendor before completing this work order.");
|
|
});
|
|
|
|
it("allows Completed after a vendor patch clears leftover refused status", () => {
|
|
const onChangeStatus = vi.fn();
|
|
const onRequestComplete = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<StatusCell
|
|
wo={makeRow({
|
|
company: "New Co",
|
|
vendorId: "9",
|
|
tech: "Pat",
|
|
primaryDispatchStatus: "",
|
|
})}
|
|
onChangeStatus={onChangeStatus}
|
|
onOpenSlideOver={vi.fn()}
|
|
onRequestComplete={onRequestComplete}
|
|
/>,
|
|
{ withAuth: false },
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
|
fireEvent.click(screen.getByRole("button", { name: /completed/i }));
|
|
|
|
expect(onRequestComplete).toHaveBeenCalled();
|
|
expect(onChangeStatus).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not open the status menu when readOnly", () => {
|
|
const onChangeStatus = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<StatusCell
|
|
wo={makeRow({ status: "Completed" })}
|
|
readOnly
|
|
onChangeStatus={onChangeStatus}
|
|
onOpenSlideOver={vi.fn()}
|
|
/>,
|
|
{ withAuth: false },
|
|
);
|
|
|
|
fireEvent.click(screen.getByLabelText("Status is locked because this work order is completed"));
|
|
|
|
expect(screen.queryByRole("button", { name: /scheduled/i })).not.toBeInTheDocument();
|
|
expect(onChangeStatus).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("allows Completed when technician is empty if company is set", () => {
|
|
const onChangeStatus = vi.fn();
|
|
const onRequestComplete = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<StatusCell
|
|
wo={makeRow({ tech: "" })}
|
|
onChangeStatus={onChangeStatus}
|
|
onOpenSlideOver={vi.fn()}
|
|
onRequestComplete={onRequestComplete}
|
|
/>,
|
|
{ withAuth: false },
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /in progress/i }));
|
|
fireEvent.click(screen.getByRole("button", { name: /completed/i }));
|
|
|
|
expect(onRequestComplete).toHaveBeenCalled();
|
|
expect(onChangeStatus).not.toHaveBeenCalled();
|
|
});
|
|
});
|