shoc-frontend-new/e2e/work-orders/pending-status-menu.spec.ts

184 lines
6.6 KiB
TypeScript
Raw Normal View History

import { expect, test, type Page, type Route } from "@playwright/test";
const FROZEN_NOW = "2026-08-19T15:00:00.000Z";
const BOARD_ROW = {
id: 1,
woNumber: "WO-501",
workOrderType: 2,
siteCode: "STL-01",
locationName: "St. Louis HQ",
locationId: 10,
lifecycleStatusLabel: "In Progress",
scheduledDate: "2026-08-19",
dispatcherId: "1",
dispatcherName: "Pat Dispatcher",
pm: "HVAC",
rowVersion: "rv-1",
};
type Role = "Admin" | "Dispatcher";
async function fulfillJson(route: Route, body: unknown) {
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(body) });
}
async function mockApi(page: Page, role: Role, createPayloads: unknown[] = []) {
await page.clock.install({ time: new Date(FROZEN_NOW) });
await page.clock.resume();
await page.addInitScript(
(user) => {
localStorage.setItem("auth", JSON.stringify(user));
const style = document.createElement("style");
style.textContent = ".tsqd-parent-container { display: none !important; }";
document.documentElement.appendChild(style);
},
{
token: `wo-pending-${role.toLowerCase()}-e2e-token`,
expiration: "2030-01-01T00:00:00.000Z",
email: `${role.toLowerCase()}@seahavenind.com`,
userRoles: role,
phoneNumber: "5551234567",
fullname: `Work Order E2E ${role}`,
id: "1",
},
);
let row = { ...BOARD_ROW };
let createdRow: typeof BOARD_ROW | undefined;
await page.route(
(url) => url.pathname.startsWith("/api/"),
async (route) => {
const request = route.request();
const { pathname } = new URL(request.url());
if (pathname.includes("/workorders/board/search")) {
await fulfillJson(route, { items: [], totalCount: 0, page: 0, pageSize: 50 });
return;
}
if (pathname === "/api/workorders/board" && request.method() === "GET") {
await fulfillJson(route, {
weekStart: "2026-08-17",
weekEnd: "2026-08-21",
counts: { returned: createdRow ? 2 : 1, total: createdRow ? 2 : 1 },
unscheduled: createdRow ? [createdRow] : [],
scheduled: [row],
});
return;
}
if (pathname === "/api/workorders/board" && request.method() === "POST") {
createPayloads.push(request.postDataJSON());
createdRow = {
...BOARD_ROW,
id: 900,
woNumber: "WO-900",
lifecycleStatusLabel: "Pending",
scheduledDate: "",
rowVersion: "rv-created",
};
await fulfillJson(route, { data: { id: 900, internalWONumber: "WO-900" } });
return;
}
if (pathname === "/api/workorders/1/board" && request.method() === "PATCH") {
const body = request.postDataJSON() as { field: string; value: string };
row = { ...row, lifecycleStatusLabel: body.value, rowVersion: "rv-2" };
await fulfillJson(route, row);
return;
}
if (pathname.includes("/locations/sites") || pathname.includes("/Contact/GetLocations")) {
await fulfillJson(route, {
data: [{ id: 10, name: "STL-01 · St. Louis, MO", siteCode: "STL-01", code: "STL-01" }],
});
return;
}
if (pathname === "/api/locations/10") {
await fulfillJson(route, {
data: {
id: 10,
name: "St. Louis HQ",
siteCode: "STL-01",
contact: "Pat Site",
phone: "3145550111",
},
});
return;
}
await fulfillJson(route, { data: [], items: [] });
},
);
}
async function expectManualTransitionWithoutPending(page: Page) {
const row = page.locator("#wo-row-1");
await expect(row.getByText("WO-501")).toBeVisible();
await row.getByRole("button", { name: "In Progress" }).click();
const menu = page.locator(".MuiPopover-paper").last();
await expect(menu.getByRole("button", { name: "Pending", exact: true })).toHaveCount(0);
await expect(menu.getByRole("button", { name: "On Site", exact: true })).toBeEnabled();
await menu.getByRole("button", { name: "On Site", exact: true }).click();
await expect(row.getByRole("button", { name: "On Site" })).toBeVisible();
}
test.describe("existing work order status menu", () => {
test.setTimeout(60_000);
test("Admin can choose a manual status but not Pending", async ({ page }) => {
await mockApi(page, "Admin");
await page.goto("/workorders");
await expect(page.getByRole("heading", { name: "Work Orders" })).toBeVisible({
timeout: 20_000,
});
await expectManualTransitionWithoutPending(page);
});
test("Dispatcher can choose a manual status but not Pending", async ({ page }) => {
await mockApi(page, "Dispatcher");
await page.goto("/workorders");
await expect(page.getByRole("heading", { name: "Work Orders" })).toBeVisible({
timeout: 20_000,
});
await expectManualTransitionWithoutPending(page);
});
test("Admin can select Pending for a new work order when Schedule On has no date", async ({
page,
}) => {
const createPayloads: unknown[] = [];
await mockApi(page, "Admin", createPayloads);
await page.goto("/workorders");
await expect(page.getByRole("heading", { name: "Work Orders" })).toBeVisible({
timeout: 20_000,
});
await page.getByRole("button", { name: "New WO" }).click();
await expect(page.getByRole("heading", { name: "Type & schedule" })).toBeVisible();
await expect(page.getByRole("button", { name: "Pick a date" })).toBeVisible();
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: /select site/i }).click();
await page
.getByRole("button", { name: /STL-01/ })
.last()
.click();
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Incomplete", exact: true }).last().click();
const menu = page.locator(".MuiPopover-paper").last();
await expect(menu.getByRole("button", { name: "Pending", exact: true })).toBeEnabled();
await menu.getByRole("button", { name: "Pending", exact: true }).click();
await expect(page.getByRole("button", { name: "Pending", exact: true })).toBeVisible();
await page.getByRole("button", { name: "Create work order" }).click();
await expect(page.getByRole("heading", { name: "Type & schedule" })).toBeHidden();
await expect
.poll(() => createPayloads)
.toEqual([expect.objectContaining({ lifecycleStatus: 2 })]);
const createdRow = page.locator("#wo-row-900");
await expect(createdRow.getByText("WO-900")).toBeVisible();
await expect(createdRow.getByRole("button", { name: "Pending" })).toBeVisible();
});
});