diff --git a/src/app/(protected)/workorders/_hooks/use-work-orders-table-day-view.ts b/src/app/(protected)/workorders/_hooks/use-work-orders-table-day-view.ts
index 3f15b3cb..07e2ce06 100644
--- a/src/app/(protected)/workorders/_hooks/use-work-orders-table-day-view.ts
+++ b/src/app/(protected)/workorders/_hooks/use-work-orders-table-day-view.ts
@@ -4,6 +4,7 @@ import { useWorkOrdersDayPagination } from "@/app/(protected)/workorders/_hooks/
import { currentWeekMonday } from "@/domain/work-orders/utils/list-week-utils";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils";
+import { todayIso } from "@/domain/work-orders/utils/wizard-date-utils";
import { pageSelectedDayGroups } from "@/domain/work-orders/utils/day-pagination";
import {
buildSelectedDayGroup,
@@ -71,7 +72,7 @@ export function useWorkOrdersTableDayView({
const isEmptyWeek = !advActive && scheduled.length === 0 && !searchActive;
const todayKey =
filters.weekMonday === currentWeekMonday()
- ? deriveDayFields(new Date().toISOString().slice(0, 10)).dayGroup
+ ? deriveDayFields(todayIso()).dayGroup
: null;
return {
diff --git a/src/domain/work-orders/utils/table-filter-utils.ts b/src/domain/work-orders/utils/table-filter-utils.ts
index 4f2a045b..3d8eb132 100644
--- a/src/domain/work-orders/utils/table-filter-utils.ts
+++ b/src/domain/work-orders/utils/table-filter-utils.ts
@@ -10,6 +10,7 @@ import { ADVANCED_FILTER_PREDICATES } from "@/domain/work-orders/utils/advanced-
import { isCarryOverDisplayRow } from "@/domain/work-orders/utils/carry-over-display";
import { isDateInWeek } from "@/domain/work-orders/utils/table-day-utils";
import { weekRange } from "@/domain/work-orders/utils/list-week-utils";
+import { todayIso } from "@/domain/work-orders/utils/wizard-date-utils";
const TERMINAL_STATUSES = new Set(["Completed", "Canceled", "Done", "Cancelled"]);
@@ -17,8 +18,10 @@ export function computeIsPastDue(row: WorkOrderTableRow): boolean {
if (!row.scheduledOn || TERMINAL_STATUSES.has(row.status)) {
return false;
}
- const today = new Date().toISOString().slice(0, 10);
- return row.scheduledOn.slice(0, 10) < today;
+ // scheduledOn is a calendar date, so compare it to the user's local calendar
+ // day. A UTC "today" rolls over hours early west of UTC and flags work
+ // scheduled for today as Past Due every evening.
+ return row.scheduledOn.slice(0, 10) < todayIso();
}
export function enrichTableRow(row: WorkOrderTableRow): WorkOrderTableRow {
diff --git a/src/test/app/(protected)/pmschedules/pm-schedules-list-actions.test.tsx b/src/test/app/(protected)/pmschedules/pm-schedules-list-actions.test.tsx
index f26b9ea5..efb56316 100644
--- a/src/test/app/(protected)/pmschedules/pm-schedules-list-actions.test.tsx
+++ b/src/test/app/(protected)/pmschedules/pm-schedules-list-actions.test.tsx
@@ -1,6 +1,9 @@
import { screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import PmSchedulesListPage from "@/app/(protected)/pmschedules/index";
+// Static import: a dynamic import inside the test transforms the edit page's
+// module graph against the 5s test budget, which times out under load.
+import EditPage from "@/app/(protected)/pmschedules/edit/[id]";
import { renderWithProviders } from "@/test/test-utils";
const usePmSchedulesList = vi.fn();
@@ -75,9 +78,7 @@ describe("PmSchedulesListPage actions", () => {
});
describe("PmScheduleEditUnavailablePage", () => {
- it("shows unavailable message instead of edit form when canEdit is false", async () => {
- const EditPage = (await import("@/app/(protected)/pmschedules/edit/[id]")).default;
-
+ it("shows unavailable message instead of edit form when canEdit is false", () => {
renderWithProviders(, {
route: "/pmschedules/edit/1",
routerProps: { initialEntries: ["/pmschedules/edit/1"] },
diff --git a/src/test/app/(protected)/vendors/vendor-create-modal.test.tsx b/src/test/app/(protected)/vendors/vendor-create-modal.test.tsx
index 355f99f1..644fc329 100644
--- a/src/test/app/(protected)/vendors/vendor-create-modal.test.tsx
+++ b/src/test/app/(protected)/vendors/vendor-create-modal.test.tsx
@@ -2,7 +2,7 @@ import { fireEvent, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
-import { describe, expect, it, vi } from "vitest";
+import { beforeEach, describe, expect, it, vi } from "vitest";
import { VendorCreateModal } from "@/app/(protected)/vendors/_components/vendor-create-modal";
import { VendorRosterFormFields } from "@/app/(protected)/vendors/_components/vendor-roster-form-fields";
import {
@@ -49,6 +49,10 @@ function renderCreateModal() {
});
}
+// delay: null skips user-event's per-keystroke setTimeout yields, which only
+// add real-timer latency here.
+let user = userEvent.setup({ delay: null });
+
async function fillCompanyNameAndSubmit(name = "Gateway Plumbing") {
fireEvent.change(screen.getByRole("combobox", { name: "Company (required)" }), {
target: { value: name },
@@ -57,7 +61,7 @@ async function fillCompanyNameAndSubmit(name = "Gateway Plumbing") {
// phone-or-email rule, so the address is filled to keep the form's only
// outstanding problem the one under test.
fillRequiredAddress();
- await userEvent.click(screen.getByRole("button", { name: "Add Vendor" }));
+ await user.click(screen.getByRole("button", { name: "Add Vendor" }));
}
function fillRequiredAddress({
@@ -74,7 +78,15 @@ function fillRequiredAddress({
pickVendorState(state);
}
-describe("VendorCreateModal validation", () => {
+// Each test mounts the full MUI dialog (Autocompletes, 50-option State list)
+// and costs ~1s of CPU when idle; the first also pays JIT warm-up. Under
+// parallel agent load the 5s default is exceeded by CPU starvation alone,
+// matching the 10s budgets already used by vendors-list.test.tsx.
+describe("VendorCreateModal validation", { timeout: 15_000 }, () => {
+ beforeEach(() => {
+ user = userEvent.setup({ delay: null });
+ });
+
it("explains the company phone-or-email requirement after submission", async () => {
renderCreateModal();
await fillCompanyNameAndSubmit();
@@ -138,8 +150,8 @@ describe("VendorCreateModal validation", () => {
const phone = screen.getByRole("textbox", { name: "Company Phone (optional)" });
const email = screen.getByRole("textbox", { name: "Email (optional)" });
- await userEvent.type(phone, "314");
- await userEvent.click(screen.getByRole("button", { name: "Add Vendor" }));
+ await user.type(phone, "314");
+ await user.click(screen.getByRole("button", { name: "Add Vendor" }));
expect(await screen.findByText("Enter a 10-digit phone number")).toBeInTheDocument();
expect(phone).toHaveAttribute("aria-invalid", "true");
@@ -153,8 +165,8 @@ describe("VendorCreateModal validation", () => {
const phone = screen.getByRole("textbox", { name: "Company Phone (optional)" });
const email = screen.getByRole("textbox", { name: "Email (optional)" });
- await userEvent.type(email, "not-an-email");
- await userEvent.click(screen.getByRole("button", { name: "Add Vendor" }));
+ await user.type(email, "not-an-email");
+ await user.click(screen.getByRole("button", { name: "Add Vendor" }));
expect(await screen.findByText("Invalid email")).toBeInTheDocument();
expect(email).toHaveAttribute("aria-invalid", "true");
@@ -169,7 +181,7 @@ describe("VendorCreateModal validation", () => {
expect(await screen.findByText(VENDOR_COMPANY_CONTACT_REQUIRED_MESSAGE)).toBeInTheDocument();
- await userEvent.type(phone, "3145550100");
+ await user.type(phone, "3145550100");
await waitFor(() =>
expect(screen.queryByText(VENDOR_COMPANY_CONTACT_REQUIRED_MESSAGE)).not.toBeInTheDocument(),
);
diff --git a/src/test/app/(protected)/vendors/vendor-detail-drawer.test.tsx b/src/test/app/(protected)/vendors/vendor-detail-drawer.test.tsx
index 0da2e959..0e5ea3eb 100644
--- a/src/test/app/(protected)/vendors/vendor-detail-drawer.test.tsx
+++ b/src/test/app/(protected)/vendors/vendor-detail-drawer.test.tsx
@@ -68,11 +68,19 @@ function rosterWith(technicians: Array>) {
};
}
+// delay: null skips user-event's per-keystroke setTimeout yields, which only
+// add real-timer latency here.
+let user = userEvent.setup({ delay: null });
+
beforeEach(() => {
saveMutate.mockReset();
+ user = userEvent.setup({ delay: null });
});
-describe("VendorDetailDrawer selected-technician display", () => {
+// The edit-mode drawer is a heavy MUI render and the first test pays JIT
+// warm-up; under parallel agent load the 5s default is exceeded by CPU
+// starvation alone, matching the 10s budgets in vendors-list.test.tsx.
+describe("VendorDetailDrawer selected-technician display", { timeout: 15_000 }, () => {
it.each([
{ removedPosition: 1, remainingId: 2, remainingName: "Beth" },
{ removedPosition: 2, remainingId: 1, remainingName: "Adam" },
@@ -107,10 +115,10 @@ describe("VendorDetailDrawer selected-technician display", () => {
{ route: "/vendors", withAuth: false },
);
- await userEvent.click(
+ await user.click(
screen.getByRole("button", { name: `Remove technician ${removedPosition}` }),
);
- await userEvent.click(screen.getByRole("button", { name: "Save changes" }));
+ await user.click(screen.getByRole("button", { name: "Save changes" }));
await waitFor(() => expect(saveMutate).toHaveBeenCalledTimes(1));
expect(saveMutate).toHaveBeenCalledWith(
@@ -161,12 +169,12 @@ describe("VendorDetailDrawer selected-technician display", () => {
{ route: "/vendors", withAuth: false },
);
- await userEvent.click(screen.getByRole("button", { name: "Remove technician 2" }));
+ await user.click(screen.getByRole("button", { name: "Remove technician 2" }));
const save = screen.getByRole("button", { name: "Save changes" });
expect(save).toBeEnabled();
- await userEvent.click(save);
+ await user.click(save);
expect(
await screen.findByText("Provide a company phone or email (at least one required)"),
@@ -444,10 +452,10 @@ describe("VendorDetailDrawer deactivation prompt", () => {
);
const toggle = screen.getByRole("switch", { name: "Active status" });
- await userEvent.click(toggle);
+ await user.click(toggle);
expect(toggle).toBeChecked();
- await userEvent.click(toggle);
+ await user.click(toggle);
expect(toggle).not.toBeChecked();
expect(onRequestDeactivation).not.toHaveBeenCalled();
@@ -472,7 +480,7 @@ describe("VendorDetailDrawer deactivation prompt", () => {
{ route: "/vendors", withAuth: false },
);
- await userEvent.click(screen.getByRole("switch", { name: "Active status" }));
+ await user.click(screen.getByRole("switch", { name: "Active status" }));
expect(onRequestDeactivation).toHaveBeenCalledWith(vendor);
});
diff --git a/src/test/app/(protected)/workorders/wo-table-adv-search-page-sync.integration.test.tsx b/src/test/app/(protected)/workorders/wo-table-adv-search-page-sync.integration.test.tsx
index 1ef0734c..4ca3e8d8 100644
--- a/src/test/app/(protected)/workorders/wo-table-adv-search-page-sync.integration.test.tsx
+++ b/src/test/app/(protected)/workorders/wo-table-adv-search-page-sync.integration.test.tsx
@@ -1,4 +1,4 @@
-import { fireEvent, screen, waitFor } from "@testing-library/react";
+import { fireEvent, screen, waitFor, within } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { WorkOrderBoardSearchQueryParams } from "@/domain/work-orders/types/work-order-board";
import { EMPTY_ADVANCED_FILTERS } from "@/domain/work-orders/types/work-order-advanced-filters";
@@ -120,6 +120,12 @@ function pagedResult(rows: WorkOrderTableRow[], page: number, pageSize: number)
};
}
+// Role and text queries over the whole board scan every row cell; scoping them
+// to the pagination footer keeps each waitFor poll cheap on a loaded machine.
+function pagination() {
+ return within(screen.getByTestId("wo-day-pagination"));
+}
+
function pagesAfter(callCount: number): number[] {
return searchBoard.mock.calls.slice(callCount).map((call) => {
const params = call[0] as WorkOrderBoardSearchQueryParams;
@@ -213,14 +219,14 @@ describe("advanced search page sync after mutation", () => {
);
});
expect(screen.getByText("WO-0")).toBeInTheDocument();
- expect(screen.getByText("Page 1 of 2")).toBeInTheDocument();
+ expect(pagination().getByText("Page 1 of 2")).toBeInTheDocument();
expect(screen.queryByText("WO-25")).not.toBeInTheDocument();
- fireEvent.click(screen.getByRole("button", { name: "Next page" }));
+ fireEvent.click(pagination().getByRole("button", { name: "Next page" }));
await waitFor(() => {
expect(screen.getByTestId("harness-state")).toHaveTextContent("page=1");
expect(screen.getByText("WO-25")).toBeInTheDocument();
- expect(screen.getByText("Page 2 of 2")).toBeInTheDocument();
+ expect(pagination().getByText("Page 2 of 2")).toBeInTheDocument();
});
const callsBeforeMutation = searchBoard.mock.calls.length;
@@ -236,12 +242,12 @@ describe("advanced search page sync after mutation", () => {
expect(screen.getByTestId("harness-state")).toHaveTextContent(
"adv=true rows=20 total=20 page=0",
);
- expect(screen.getByText("Page 1 of 1")).toBeInTheDocument();
+ expect(pagination().getByText("Page 1 of 1")).toBeInTheDocument();
});
expect(screen.getByText("WO-0")).toBeInTheDocument();
expect(screen.queryByText("WO-25")).not.toBeInTheDocument();
expect(screen.getByText("RESULTS ยท 20 items")).toBeInTheDocument();
- expect(screen.getByRole("button", { name: "Previous page" })).toBeDisabled();
- expect(screen.getByRole("button", { name: "Next page" })).toBeDisabled();
+ expect(pagination().getByRole("button", { name: "Previous page" })).toBeDisabled();
+ expect(pagination().getByRole("button", { name: "Next page" })).toBeDisabled();
}, 15000);
});
diff --git a/src/test/domain/work-orders/utils/table-filter-utils.test.ts b/src/test/domain/work-orders/utils/table-filter-utils.test.ts
index a2e1f2eb..022d36fe 100644
--- a/src/test/domain/work-orders/utils/table-filter-utils.test.ts
+++ b/src/test/domain/work-orders/utils/table-filter-utils.test.ts
@@ -1,4 +1,4 @@
-import { describe, expect, it } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { EMPTY_UPLIFT_SUMMARY } from "@/domain/work-orders/types/work-order-uplift";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
import { EMPTY_ADVANCED_FILTERS } from "@/domain/work-orders/types/work-order-advanced-filters";
@@ -526,6 +526,41 @@ describe("computeIsPastDue", () => {
});
});
+describe("computeIsPastDue across the local/UTC day boundary", () => {
+ beforeEach(() => {
+ // UTC-3 with no DST: local evening is already the next day in UTC.
+ vi.stubEnv("TZ", "America/Sao_Paulo");
+ vi.useFakeTimers({ toFake: ["Date"] });
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ vi.unstubAllEnvs();
+ });
+
+ it("does not flag today's work as Past Due at 23:30 local (02:30 UTC next day)", () => {
+ vi.setSystemTime(new Date("2026-09-17T02:30:00.000Z"));
+
+ expect(computeIsPastDue(makeRow({ scheduledOn: "2026-09-16", status: "Scheduled" }))).toBe(
+ false,
+ );
+ expect(computeIsPastDue(makeRow({ scheduledOn: "2026-09-15", status: "Scheduled" }))).toBe(
+ true,
+ );
+ });
+
+ it("flags yesterday's work as Past Due at 00:30 local", () => {
+ vi.setSystemTime(new Date("2026-09-17T03:30:00.000Z"));
+
+ expect(computeIsPastDue(makeRow({ scheduledOn: "2026-09-16", status: "Scheduled" }))).toBe(
+ true,
+ );
+ expect(computeIsPastDue(makeRow({ scheduledOn: "2026-09-17", status: "Scheduled" }))).toBe(
+ false,
+ );
+ });
+});
+
describe("enrichTableRow", () => {
it("recomputes isPastDue from scheduledOn and ignores sticky API true", () => {
const enriched = enrichTableRow(