mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 06:53:12 +00:00
606 lines
22 KiB
TypeScript
606 lines
22 KiB
TypeScript
import { expect, test, type Page, type Request, type Route } from "@playwright/test";
|
|
|
|
// Technicians registered inline from work-order entry points
|
|
// become real vendor records only on save, through the additive vendor-company roster
|
|
// PATCH (never a whole-vendor POST that restates or blanks company contact fields).
|
|
|
|
const FROZEN_NOW = "2026-08-19T15:00:00.000Z";
|
|
|
|
const TEST_USER = {
|
|
token: "wo-inline-technician-e2e-token",
|
|
expiration: "2030-01-01T00:00:00.000Z",
|
|
email: "admin@seahavenind.com",
|
|
userRoles: "Admin",
|
|
phoneNumber: "5551234567",
|
|
fullname: "Work Order E2E Admin",
|
|
id: "1",
|
|
};
|
|
|
|
const VENDORS = [
|
|
{
|
|
id: 41,
|
|
companyName: "Vinewood LLC",
|
|
contactName: "Adam Whyte",
|
|
tradeSpecialties: "HVAC",
|
|
address: "1 Market St",
|
|
},
|
|
];
|
|
const COASTAL_VENDOR = {
|
|
id: 50,
|
|
companyName: "Coastal HVAC",
|
|
contactName: "Avery Chen",
|
|
tradeSpecialties: "HVAC",
|
|
address: "9 Shore Rd",
|
|
};
|
|
|
|
const ROSTER = {
|
|
companyId: 7,
|
|
rowVersion: "AAAAAAAAB9E=",
|
|
name: "Vinewood LLC",
|
|
companyPhone: "(314) 555-0100",
|
|
email: "",
|
|
address: "1 Market St",
|
|
city: "St. Louis",
|
|
state: "MO",
|
|
zip: "63101",
|
|
notes: "",
|
|
technicians: [{ id: 41, contactName: "Adam Whyte", phone: "", email: "", isActive: true }],
|
|
};
|
|
|
|
const BOARD_ROW = {
|
|
id: 1,
|
|
woNumber: "WO-501",
|
|
workOrderType: 2,
|
|
siteCode: "STL-01",
|
|
locationName: "St. Louis HQ",
|
|
locationId: 10,
|
|
lifecycleStatusLabel: "Scheduled",
|
|
scheduledDate: "2026-08-19",
|
|
dispatcherId: "1",
|
|
dispatcherName: "Pat Dispatcher",
|
|
pm: "HVAC",
|
|
vendorId: 41,
|
|
vendorName: "Vinewood LLC",
|
|
techName: "Adam Whyte",
|
|
technicianAssigned: true,
|
|
completedDate: "2026-08-19",
|
|
docStatus: "Yes",
|
|
mediaCount: 1,
|
|
title: "Quarterly HVAC PM",
|
|
rowVersion: "rv-1",
|
|
};
|
|
|
|
type Captured = { method: string; pathname: string; body: unknown };
|
|
|
|
async function fulfillJson(route: Route, body: unknown, status = 200) {
|
|
await route.fulfill({ status, contentType: "application/json", body: JSON.stringify(body) });
|
|
}
|
|
|
|
function bodyOf(request: Request): unknown {
|
|
try {
|
|
return request.postDataJSON();
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function rosterAfterAdd(body: { addTechnicians?: Array<Record<string, unknown>> }) {
|
|
const added = (body.addTechnicians ?? []).map((technician, index) => ({
|
|
...technician,
|
|
id: 42 + index,
|
|
}));
|
|
return { ...ROSTER, rowVersion: "AAAAAAAAB9F=", technicians: [...ROSTER.technicians, ...added] };
|
|
}
|
|
|
|
async function mockApi(
|
|
page: Page,
|
|
writes: Captured[],
|
|
initialRows: Array<Record<string, unknown>> = [BOARD_ROW],
|
|
) {
|
|
const currentBoardRows = [...initialRows];
|
|
await page.clock.install({ time: new Date(FROZEN_NOW) });
|
|
await page.clock.resume();
|
|
await page.addInitScript((user) => localStorage.setItem("auth", JSON.stringify(user)), TEST_USER);
|
|
|
|
await page.route("**/api/**", async (route) => {
|
|
const request = route.request();
|
|
const pathname = new URL(request.url()).pathname;
|
|
if (!pathname.startsWith("/api/")) return route.fallback();
|
|
const method = request.method();
|
|
if (method !== "GET") writes.push({ method, pathname, body: bodyOf(request) });
|
|
|
|
if (pathname.includes("/workorders/board/search")) {
|
|
return fulfillJson(route, { items: [], totalCount: 0, page: 0, pageSize: 50 });
|
|
}
|
|
if (pathname === "/api/workorders/board" && method === "GET") {
|
|
return fulfillJson(route, {
|
|
weekStart: "2026-08-17",
|
|
weekEnd: "2026-08-21",
|
|
counts: { returned: currentBoardRows.length, total: currentBoardRows.length },
|
|
unscheduled: [],
|
|
scheduled: currentBoardRows,
|
|
});
|
|
}
|
|
if (/^\/api\/workorders\/\d+\/detail$/u.test(pathname) && method === "GET") {
|
|
const id = Number(pathname.split("/")[3]);
|
|
const detailRow = currentBoardRows.find((row) => row.id === id);
|
|
if (detailRow == null) return fulfillJson(route, { message: "Missing fixture row" }, 404);
|
|
return fulfillJson(route, {
|
|
info: detailRow,
|
|
completion: { docStatus: 1 },
|
|
comments: [],
|
|
audit: [],
|
|
media: [],
|
|
});
|
|
}
|
|
if (pathname === "/api/workorders/board" && method === "POST") {
|
|
const body = bodyOf(request) as { vendorId?: number; technicianAssigned?: boolean };
|
|
const vendorId = body.vendorId ?? 0;
|
|
currentBoardRows.push({
|
|
...BOARD_ROW,
|
|
id: 900,
|
|
woNumber: "SH-900",
|
|
lifecycleStatusLabel: "Incomplete",
|
|
vendorId: vendorId || null,
|
|
vendorName: vendorId > 0 ? "Vinewood LLC" : null,
|
|
technicianAssigned: body.technicianAssigned,
|
|
techName: body.technicianAssigned === true ? "Jordan Lee" : null,
|
|
techPhone: body.technicianAssigned === true ? "(314) 555-0199" : null,
|
|
});
|
|
return fulfillJson(route, { data: { id: 900, woNumber: "SH-900" } });
|
|
}
|
|
if (/^\/api\/workorders\/\d+\/board$/u.test(pathname) && method === "PATCH") {
|
|
const body = bodyOf(request) as {
|
|
field?: string;
|
|
value?: string;
|
|
technicianAssigned?: boolean;
|
|
};
|
|
const id = Number(pathname.split("/")[3]);
|
|
const index = currentBoardRows.findIndex((row) => row.id === id);
|
|
if (index < 0) return fulfillJson(route, { message: "Missing fixture row" }, 404);
|
|
const current = currentBoardRows[index];
|
|
if (current == null) return fulfillJson(route, { message: "Missing fixture row" }, 404);
|
|
if (body.field === "vendorId") {
|
|
const vendorId = Number(body.value);
|
|
currentBoardRows[index] = {
|
|
...current,
|
|
vendorId,
|
|
vendorName: vendorId === 50 ? "Coastal HVAC" : "Vinewood LLC",
|
|
...(body.technicianAssigned === false
|
|
? { technicianAssigned: false, techName: null, techPhone: null }
|
|
: body.technicianAssigned === true
|
|
? {
|
|
technicianAssigned: true,
|
|
techName: vendorId === 50 ? "Avery Chen" : "Adam Whyte",
|
|
techPhone: "(314) 555-0141",
|
|
}
|
|
: {}),
|
|
rowVersion: "rv-2",
|
|
};
|
|
} else if (body.field === "lifecycleStatus") {
|
|
currentBoardRows[index] = {
|
|
...current,
|
|
lifecycleStatusLabel: body.value,
|
|
...(body.value === "Completed"
|
|
? {
|
|
frozenCompany: {
|
|
name: current.vendorName,
|
|
contactName: current.vendorName === "Coastal HVAC" ? "Avery Chen" : "Adam Whyte",
|
|
phone: "555-0100",
|
|
technicianAssigned: current.technicianAssigned,
|
|
},
|
|
}
|
|
: {}),
|
|
rowVersion: "rv-3",
|
|
};
|
|
}
|
|
return fulfillJson(route, currentBoardRows[index]);
|
|
}
|
|
if (pathname === "/api/vendor-company-roster" && method === "GET") {
|
|
return fulfillJson(route, ROSTER);
|
|
}
|
|
if (pathname === "/api/vendor-company-roster/7" && method === "PATCH") {
|
|
return fulfillJson(route, rosterAfterAdd(bodyOf(request) as never));
|
|
}
|
|
if (pathname.toLowerCase().includes("/vendors/dropdown")) {
|
|
return fulfillJson(route, { data: [...VENDORS, COASTAL_VENDOR] });
|
|
}
|
|
if (pathname.includes("/locations/sites")) {
|
|
return fulfillJson(route, {
|
|
data: [{ id: 10, name: "STL-01 · St. Louis, MO", siteCode: "STL-01", code: "STL-01" }],
|
|
});
|
|
}
|
|
if (pathname === "/api/locations/10") {
|
|
return fulfillJson(route, {
|
|
data: {
|
|
id: 10,
|
|
name: "St. Louis HQ",
|
|
siteCode: "STL-01",
|
|
contact: "Pat Site",
|
|
phone: "3145550111",
|
|
},
|
|
});
|
|
}
|
|
return fulfillJson(route, { data: [] });
|
|
});
|
|
}
|
|
|
|
function vendorWrites(writes: Captured[]) {
|
|
return writes.filter(
|
|
(write) =>
|
|
write.pathname.startsWith("/api/vendors") ||
|
|
write.pathname.startsWith("/api/vendor-company-roster"),
|
|
);
|
|
}
|
|
|
|
async function openWizardVendorStep(page: Page) {
|
|
await page.goto("/workorders");
|
|
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 expect(page.getByRole("button", { name: "Continue" })).toBeEnabled();
|
|
await page.getByRole("button", { name: "Continue" }).click();
|
|
|
|
await page.getByRole("button", { name: /select company/i }).click();
|
|
await page.getByRole("button", { name: "Vinewood LLC" }).last().click();
|
|
await page.getByRole("button", { name: "+ Add technician" }).click();
|
|
await page.getByLabel("New Technician 1 name").fill("Jordan Lee");
|
|
await page.getByLabel("New Technician 1 phone").fill("3145550199");
|
|
}
|
|
|
|
test.describe("Inline technician registration", () => {
|
|
test("wizard persists the technician under the company on create and assigns it", async ({
|
|
page,
|
|
}) => {
|
|
const writes: Captured[] = [];
|
|
await mockApi(page, writes);
|
|
await openWizardVendorStep(page);
|
|
|
|
// Staging a technician is local only.
|
|
expect(vendorWrites(writes)).toEqual([]);
|
|
|
|
await page.getByRole("button", { name: "Create work order" }).click();
|
|
|
|
await expect
|
|
.poll(() => writes.find((write) => write.pathname === "/api/workorders/board"))
|
|
.toBeTruthy();
|
|
expect(vendorWrites(writes)).toEqual([
|
|
{
|
|
method: "PATCH",
|
|
pathname: "/api/vendor-company-roster/7",
|
|
body: {
|
|
rowVersion: "AAAAAAAAB9E=",
|
|
addTechnicians: [
|
|
{
|
|
contactName: "Jordan Lee",
|
|
phone: "(314) 555-0199",
|
|
email: "",
|
|
tradeSpecialties: "HVAC",
|
|
isActive: true,
|
|
preferredContact: "Phone",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
]);
|
|
const create = writes.find((write) => write.pathname === "/api/workorders/board");
|
|
expect(create?.body).toEqual(expect.objectContaining({ vendorId: 42 }));
|
|
});
|
|
|
|
test("wizard No technician selection overrides a staged technician through create and reopen", async ({
|
|
page,
|
|
}) => {
|
|
const writes: Captured[] = [];
|
|
await mockApi(page, writes);
|
|
await openWizardVendorStep(page);
|
|
|
|
await page.getByRole("button", { name: "No technician" }).first().click();
|
|
await page.getByRole("button", { name: "No technician" }).last().click();
|
|
await page.getByRole("button", { name: "Create work order" }).click();
|
|
|
|
await expect
|
|
.poll(() =>
|
|
writes.find(
|
|
(write) => write.pathname === "/api/workorders/board" && write.method === "POST",
|
|
),
|
|
)
|
|
.toBeTruthy();
|
|
const create = writes.find(
|
|
(write) => write.pathname === "/api/workorders/board" && write.method === "POST",
|
|
);
|
|
expect(create?.body).toEqual(
|
|
expect.objectContaining({ vendorId: 41, technicianAssigned: false }),
|
|
);
|
|
expect(vendorWrites(writes)).toEqual([
|
|
expect.objectContaining({ pathname: "/api/vendor-company-roster/7" }),
|
|
]);
|
|
|
|
await page.reload();
|
|
const createdRow = page.locator("#wo-row-900");
|
|
await expect(createdRow.getByRole("button", { name: /Vinewood LLC/ })).toBeVisible();
|
|
await createdRow.getByRole("button", { name: /Vinewood LLC/ }).click();
|
|
const vendorDialog = page.getByRole("dialog", { name: "Vendor" });
|
|
await expect(vendorDialog.getByRole("button", { name: "No technician" })).toBeVisible();
|
|
await expect(vendorDialog.locator('input[type="tel"]')).toHaveCount(0);
|
|
await expect(vendorDialog.getByRole("button", { name: "Jordan Lee" })).toHaveCount(0);
|
|
});
|
|
|
|
test("company-only save returns no technician and stays empty after reopen and reload", async ({
|
|
page,
|
|
}) => {
|
|
const writes: Captured[] = [];
|
|
await mockApi(page, writes, [{ ...BOARD_ROW, lifecycleStatusLabel: "In Progress" }]);
|
|
await page.goto("/workorders");
|
|
|
|
const row = page.locator("#wo-row-1");
|
|
await row.getByRole("button", { name: /Adam Whyte/ }).click();
|
|
const dialog = page.getByRole("dialog");
|
|
await dialog.getByRole("button", { name: "Vinewood LLC" }).click();
|
|
await page.getByRole("button", { name: "Coastal HVAC" }).last().click();
|
|
await dialog.getByRole("button", { name: /^save$/i }).click();
|
|
|
|
await expect(dialog).toBeHidden();
|
|
const boardPatch = writes.find((write) => write.pathname === "/api/workorders/1/board");
|
|
expect(boardPatch?.body).toEqual(
|
|
expect.objectContaining({ field: "vendorId", value: "50", technicianAssigned: false }),
|
|
);
|
|
await expect(row.getByRole("button", { name: /Coastal HVAC/ })).toBeVisible();
|
|
await row.getByRole("button", { name: /Coastal HVAC/ }).click();
|
|
await expect(
|
|
page.getByRole("dialog").getByRole("button", { name: "No technician" }),
|
|
).toBeVisible();
|
|
await expect(page.locator('input[type="tel"]')).toHaveCount(0);
|
|
await page
|
|
.getByRole("dialog")
|
|
.getByRole("button", { name: /cancel/i })
|
|
.click();
|
|
|
|
await page.reload();
|
|
const reloadedRow = page.locator("#wo-row-1");
|
|
await expect(reloadedRow.getByRole("button", { name: /Coastal HVAC/ })).toBeVisible();
|
|
await reloadedRow.getByRole("button", { name: /Coastal HVAC/ }).click();
|
|
await expect(
|
|
page.getByRole("dialog").getByRole("button", { name: "No technician" }),
|
|
).toBeVisible();
|
|
await page
|
|
.getByRole("dialog")
|
|
.getByRole("button", { name: /cancel/i })
|
|
.click();
|
|
await reloadedRow.getByRole("button", { name: /in progress/i }).click();
|
|
await page.getByRole("button", { name: "Completed" }).click();
|
|
await page
|
|
.getByRole("dialog", { name: "Mark as Complete?" })
|
|
.getByRole("button", { name: "Mark as Complete" })
|
|
.click();
|
|
await expect
|
|
.poll(() =>
|
|
writes.some(
|
|
(write) =>
|
|
write.pathname === "/api/workorders/1/board" &&
|
|
(write.body as { field?: string }).field === "lifecycleStatus",
|
|
),
|
|
)
|
|
.toBe(true);
|
|
await expect(reloadedRow.getByText("Completed", { exact: true })).toBeVisible();
|
|
await reloadedRow.getByRole("button", { name: /Coastal HVAC/ }).click();
|
|
const completedDialog = page.getByRole("dialog");
|
|
await expect(completedDialog.getByText("—", { exact: true })).toBeVisible();
|
|
await expect(completedDialog.getByRole("button", { name: /^save$/i })).toHaveCount(0);
|
|
expect(
|
|
writes.some(
|
|
(write) =>
|
|
write.pathname === "/api/workorders/1/board" &&
|
|
(write.body as { field?: string }).field === "lifecycleStatus",
|
|
),
|
|
).toBe(true);
|
|
expect(writes.some((write) => write.pathname.startsWith("/api/vendors"))).toBe(false);
|
|
});
|
|
|
|
test("company absence blocks completion for PM, Emergency, Reactive, and Overdue", async ({
|
|
page,
|
|
}) => {
|
|
const writes: Captured[] = [];
|
|
const withoutCompany = [
|
|
[1, 2],
|
|
[2, 3],
|
|
[3, 6],
|
|
[4, 8],
|
|
].map(([id, workOrderType]) => ({
|
|
...BOARD_ROW,
|
|
id,
|
|
woNumber: `WO-${600 + id}`,
|
|
workOrderType,
|
|
lifecycleStatusLabel: "In Progress",
|
|
vendorId: null,
|
|
vendorName: null,
|
|
techName: "Avery Chen",
|
|
technicianAssigned: true,
|
|
docStatus: "Yes",
|
|
mediaCount: 1,
|
|
}));
|
|
await mockApi(page, writes, withoutCompany);
|
|
await page.goto("/workorders");
|
|
|
|
for (const id of [1, 2, 3, 4]) {
|
|
const row = page.locator(`#wo-row-${id}`);
|
|
await row.getByRole("button", { name: /in progress/i }).click();
|
|
const completed = page.getByRole("button", { name: "Completed" });
|
|
await expect(completed).toBeDisabled();
|
|
await expect(completed).toHaveAttribute(
|
|
"title",
|
|
"Fill in Vendor before completing this work order.",
|
|
);
|
|
await page.keyboard.press("Escape");
|
|
}
|
|
expect(writes.filter((write) => write.pathname.endsWith("/board"))).toEqual([]);
|
|
});
|
|
|
|
test("explicit technician choice is retained and can complete after reload", async ({ page }) => {
|
|
const writes: Captured[] = [];
|
|
const unassigned = {
|
|
...BOARD_ROW,
|
|
lifecycleStatusLabel: "In Progress",
|
|
technicianAssigned: false,
|
|
techName: null,
|
|
techPhone: null,
|
|
};
|
|
await mockApi(page, writes, [unassigned]);
|
|
await page.goto("/workorders");
|
|
|
|
const row = page.locator("#wo-row-1");
|
|
await row.getByRole("button", { name: /Vinewood LLC/ }).click();
|
|
const dialog = page.getByRole("dialog");
|
|
await dialog.getByRole("button", { name: "No technician" }).click();
|
|
await page.getByRole("button", { name: "Adam Whyte" }).last().click();
|
|
await expect(page.locator('input[type="tel"]')).toBeVisible();
|
|
await dialog.getByRole("button", { name: /^save$/i }).click();
|
|
|
|
const assignmentWrite = writes.find((write) => write.pathname === "/api/workorders/1/board");
|
|
expect(assignmentWrite?.body).toEqual(
|
|
expect.objectContaining({ field: "vendorId", value: "41", technicianAssigned: true }),
|
|
);
|
|
await expect(row.getByRole("button", { name: /Adam Whyte/ })).toBeVisible();
|
|
await page.reload();
|
|
const reloadedRow = page.locator("#wo-row-1");
|
|
await expect(reloadedRow.getByRole("button", { name: /Adam Whyte/ })).toBeVisible();
|
|
await reloadedRow.getByRole("button", { name: /Adam Whyte/ }).click();
|
|
const reopenedDialog = page.getByRole("dialog");
|
|
await expect(reopenedDialog.getByRole("button", { name: "Adam Whyte" })).toBeVisible();
|
|
await expect(reopenedDialog.locator('input[type="tel"]')).toHaveValue("(314) 555-0141");
|
|
await page
|
|
.getByRole("dialog")
|
|
.getByRole("button", { name: /cancel/i })
|
|
.click();
|
|
await reloadedRow.getByRole("button", { name: /in progress/i }).click();
|
|
await page.getByRole("button", { name: "Completed" }).click();
|
|
await page
|
|
.getByRole("dialog", { name: "Mark as Complete?" })
|
|
.getByRole("button", { name: "Mark as Complete" })
|
|
.click();
|
|
await expect
|
|
.poll(() => writes.filter((write) => write.pathname === "/api/workorders/1/board").length)
|
|
.toBe(2);
|
|
await expect(reloadedRow.getByText("Completed", { exact: true })).toBeVisible();
|
|
expect(
|
|
writes.some(
|
|
(write) =>
|
|
write.pathname === "/api/workorders/1/board" &&
|
|
(write.body as { field?: string; value?: string }).field === "lifecycleStatus",
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
test("slide-over saves a same-company no-technician selection with its vendor anchor", async ({
|
|
page,
|
|
}) => {
|
|
const writes: Captured[] = [];
|
|
const companyOnly = {
|
|
...BOARD_ROW,
|
|
vendorId: 50,
|
|
vendorName: "Coastal HVAC",
|
|
technicianAssigned: false,
|
|
techName: null,
|
|
techPhone: null,
|
|
};
|
|
await mockApi(page, writes, [companyOnly]);
|
|
await page.goto("/workorders");
|
|
|
|
const row = page.locator("#wo-row-1");
|
|
await row.hover();
|
|
await row.getByRole("button", { name: "View details" }).click();
|
|
const details = page.getByRole("dialog", { name: "Work order details" });
|
|
await expect(details).toBeVisible();
|
|
await details.getByRole("button", { name: "Edit WO" }).click();
|
|
|
|
await details.getByRole("button", { name: /Coastal HVAC/ }).click();
|
|
const assignment = page.getByRole("dialog").last();
|
|
await assignment.getByRole("button", { name: "Coastal HVAC" }).click();
|
|
await page.getByRole("button", { name: "Vinewood LLC" }).last().click();
|
|
await assignment.getByRole("button", { name: /select technician/i }).click();
|
|
await page.getByRole("button", { name: "No technician" }).last().click();
|
|
await assignment.getByRole("button", { name: /^save$/i }).click();
|
|
await details.getByRole("button", { name: "Save", exact: true }).click();
|
|
|
|
const slideOverPatch = writes.find(
|
|
(write) =>
|
|
write.pathname === "/api/workorders/1/board" &&
|
|
(write.body as { field?: string }).field === "vendorId",
|
|
);
|
|
expect(slideOverPatch?.body).toEqual(
|
|
expect.objectContaining({ field: "vendorId", value: "41", technicianAssigned: false }),
|
|
);
|
|
|
|
await page.reload();
|
|
const reloadedRow = page.locator("#wo-row-1");
|
|
await reloadedRow.hover();
|
|
await reloadedRow.getByRole("button", { name: "View details" }).click();
|
|
const reloadedDetails = page.getByRole("dialog", { name: "Work order details" });
|
|
await expect(reloadedDetails).toBeVisible();
|
|
await reloadedDetails.getByRole("button", { name: "Edit WO" }).click();
|
|
await reloadedDetails.getByRole("button", { name: /Vinewood LLC/ }).click();
|
|
await expect(
|
|
page.getByRole("dialog").last().getByRole("button", { name: "No technician" }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("cancelling the wizard after staging a technician writes no vendor (SH-366)", async ({
|
|
page,
|
|
}) => {
|
|
const writes: Captured[] = [];
|
|
await mockApi(page, writes);
|
|
await openWizardVendorStep(page);
|
|
|
|
await page.getByRole("button", { name: "Back" }).click();
|
|
await page.getByRole("button", { name: "Back" }).click();
|
|
await page.getByRole("button", { name: "Cancel" }).click();
|
|
await expect(page.getByRole("heading", { name: "Type & schedule" })).toBeHidden();
|
|
|
|
// Reopening starts clean: the staged technician was discarded, not persisted.
|
|
await page.getByRole("button", { name: "New WO" }).click();
|
|
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 expect(page.getByRole("group", { name: "New Technician 1" })).toHaveCount(0);
|
|
|
|
expect(writes).toEqual([]);
|
|
});
|
|
|
|
test("vendor assignment modal saves an inline technician for an existing company (SH-365)", async ({
|
|
page,
|
|
}) => {
|
|
const writes: Captured[] = [];
|
|
await mockApi(page, writes);
|
|
await page.goto("/workorders");
|
|
|
|
await page
|
|
.locator("#wo-row-1")
|
|
.getByRole("button", { name: /Adam Whyte/ })
|
|
.click();
|
|
const dialog = page.getByRole("dialog");
|
|
await dialog.getByRole("button", { name: /\+ add technician/i }).click();
|
|
await dialog.getByPlaceholder("Full name").fill("Jordan Lee");
|
|
await dialog.getByPlaceholder("(000) 000-0000").fill("3145550199");
|
|
await dialog.getByRole("button", { name: /^add$/i }).click();
|
|
await dialog.getByRole("button", { name: /^save$/i }).click();
|
|
|
|
await expect(dialog).toBeHidden();
|
|
await expect
|
|
.poll(() => writes.find((write) => write.pathname === "/api/workorders/1/board"))
|
|
.toBeTruthy();
|
|
expect(writes.some((write) => write.pathname.startsWith("/api/vendors"))).toBe(false);
|
|
const rosterPatch = writes.find((write) => write.pathname === "/api/vendor-company-roster/7");
|
|
expect(rosterPatch?.body).not.toHaveProperty("companyFields");
|
|
const boardPatch = writes.find((write) => write.pathname === "/api/workorders/1/board");
|
|
expect(JSON.stringify(boardPatch?.body)).toContain("42");
|
|
});
|
|
});
|