mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 05:43:12 +00:00
268 lines
10 KiB
TypeScript
268 lines
10 KiB
TypeScript
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[] = [],
|
|
searchUrls: string[] = [],
|
|
) {
|
|
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")) {
|
|
searchUrls.push(request.url());
|
|
await fulfillJson(route, {
|
|
items: createdRow ? [createdRow] : [],
|
|
totalCount: createdRow ? 1 : 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") {
|
|
const payload = request.postDataJSON() as {
|
|
lifecycleStatus: number;
|
|
scheduledDate?: string;
|
|
};
|
|
createPayloads.push(payload);
|
|
createdRow = {
|
|
...BOARD_ROW,
|
|
id: 900,
|
|
woNumber: "WO-900",
|
|
lifecycleStatusLabel: payload.lifecycleStatus === 3 ? "Scheduled" : "Pending",
|
|
scheduledDate: payload.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[] = [];
|
|
const searchUrls: string[] = [];
|
|
await mockApi(page, "Admin", createPayloads, searchUrls);
|
|
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("dialog").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 filtersSheet = page.getByRole("dialog", { name: "Advanced Filters" });
|
|
await page.getByRole("button", { name: "Advanced Filters" }).click();
|
|
await expect(filtersSheet).toBeVisible();
|
|
await filtersSheet.getByRole("button", { name: "Date range" }).click();
|
|
await filtersSheet.getByRole("button", { name: "Unscheduled", exact: true }).click();
|
|
await filtersSheet.getByRole("button", { name: "Apply filters" }).click();
|
|
|
|
await expect
|
|
.poll(() => searchUrls.some((url) => url.includes("unscheduledOnly=true")))
|
|
.toBe(true);
|
|
const createdRow = page.locator("#wo-row-900");
|
|
await expect(createdRow.getByText("WO-900")).toBeVisible();
|
|
await expect(createdRow.getByRole("button", { name: "Pending" })).toBeVisible();
|
|
});
|
|
|
|
test("Admin gets Scheduled when adding Schedule On after selecting Pending", 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 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 menu.getByRole("button", { name: "Pending", exact: true }).click();
|
|
await expect(
|
|
page.getByRole("dialog").getByRole("button", { name: "Pending", exact: true }),
|
|
).toBeVisible();
|
|
|
|
await page.getByRole("button", { name: "Back" }).click();
|
|
await page.getByRole("button", { name: "Back" }).click();
|
|
await page.getByRole("button", { name: "Pick a date" }).click();
|
|
const dateTab = page.getByRole("button", { name: "Date", exact: true });
|
|
if (await dateTab.isVisible()) {
|
|
await dateTab.click();
|
|
}
|
|
await page.getByRole("button", { name: "20", exact: true }).click();
|
|
await expect(page.getByRole("button", { name: /Aug 20/ })).toBeVisible();
|
|
await page.getByRole("button", { name: "Continue" }).click();
|
|
await page.getByRole("button", { name: "Continue" }).click();
|
|
|
|
const wizardDialog = page.getByRole("dialog");
|
|
const scheduledStatus = wizardDialog.getByRole("button", {
|
|
name: "Scheduled",
|
|
exact: true,
|
|
});
|
|
await expect(scheduledStatus).toBeVisible();
|
|
await scheduledStatus.click();
|
|
const statusMenu = page.locator(".MuiPopover-paper").last();
|
|
await expect(statusMenu.getByRole("button", { name: "Pending", exact: true })).toBeDisabled();
|
|
await statusMenu.getByRole("button", { name: "Scheduled", exact: true }).click();
|
|
await expect(scheduledStatus).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: 3, scheduledDate: "2026-08-20" })]);
|
|
});
|
|
});
|