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"; import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; import { renderWithProviders } from "@/test/test-utils"; const { searchBoard, getBoard, patchBoardField, emptyList } = vi.hoisted(() => ({ searchBoard: vi.fn(), getBoard: vi.fn(), patchBoardField: vi.fn(), emptyList: [] as const, })); vi.mock("@/domain/work-orders/api/work-orders-api", () => ({ workOrdersApi: { searchBoard: (...args: unknown[]) => searchBoard(...args), getBoard: (...args: unknown[]) => getBoard(...args), patchBoardField: (...args: unknown[]) => patchBoardField(...args), }, })); vi.mock("@/domain/work-orders/use-cases/use-work-order-detail", () => ({ useWorkOrderLocations: () => ({ data: emptyList }), useWorkOrderVendors: () => ({ data: emptyList }), })); vi.mock("@/domain/locations/use-cases/use-location-sites", () => ({ useLocationSites: () => ({ data: emptyList }), })); vi.mock("@/app/(protected)/workorders/_components/list/table/use-wo-table-inline-create", () => ({ useWoTableInlineCreate: () => ({ handleInlineSave: vi.fn(), saving: false }), })); vi.mock("react-toastify", () => ({ toast: { error: vi.fn(), success: vi.fn() } })); import { WoTable } from "@/app/(protected)/workorders/_components/list/table/wo-table"; import { useWorkOrdersListFilters } from "@/app/(protected)/workorders/_hooks/use-work-orders-list-filters"; import { useWorkOrdersTableData } from "@/app/(protected)/workorders/_hooks/use-work-orders-table-data"; import { useWorkOrderTableMutations } from "@/domain/work-orders/use-cases/use-work-order-table-mutations"; const PAGE_SIZE = 25; const INITIAL_TOTAL = 26; const SHRUNK_TOTAL = 20; function makeRow(overrides: Partial = {}): WorkOrderTableRow { return { id: "1", woNumber: "WO-1", site: "CLV2", type: "PM", severity: null, dispatcherId: "", dispatcherName: "", scheduledOn: "2026-08-17", completedDate: "", targetWeek: "", dayGroup: "", dayLabel: "", location: "Cleveland, OH", locationId: "", pm: "Backflow", serviceNotes: "", extraServices: [], company: "", tech: "", techPhone: "", apptStart: "", apptEnd: "", apptTime: "—", status: "Incomplete", docStatus: "No", pocName: "", pocPhone: "", pocNotes: "", dueDate: "", rescheduleCount: 0, carriedOver: 0, isAddOn: false, originalWeek: "", originalDate: "", isPastDue: false, scheduleWeekOnly: false, mediaCount: 0, title: "", priority: "", vendorId: "", primaryDispatchId: null, rowVersion: "v1", dispatchRowVersion: "", flagColor: null, additionalContacts: [], ...overrides, }; } function emptyBoardResult() { return { items: [] as WorkOrderTableRow[], scheduled: [] as WorkOrderTableRow[], unscheduled: [] as WorkOrderTableRow[], totalCount: 0, returnedCount: 0, page: 0, pageSize: PAGE_SIZE, }; } function pagedResult(rows: WorkOrderTableRow[], page: number, pageSize: number) { const items = rows.slice(page * pageSize, page * pageSize + pageSize); return { items, scheduled: items, unscheduled: [] as WorkOrderTableRow[], totalCount: rows.length, returnedCount: items.length, page, pageSize, }; } // 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; return params.page ?? 0; }); } function AdvSearchPageSyncHarness() { const filters = useWorkOrdersListFilters(); const tableData = useWorkOrdersTableData(filters); const mutations = useWorkOrderTableMutations({ onPatch: tableData.patchRow, clearPatch: tableData.clearRowPatch, getRow: (id) => tableData.rows.find((row) => String(row.id) === String(id)), }); return (
{`adv=${String(tableData.advActive)} rows=${tableData.advResults.length} total=${tableData.scopeTotalCount} page=${filters.page}`}
undefined} onOpenMedia={() => undefined} onPatch={(id, patch) => { void mutations.patchFieldAsync(id, patch); }} />
); } describe("advanced search page sync after mutation", () => { let serverRows: WorkOrderTableRow[]; beforeEach(() => { serverRows = Array.from({ length: INITIAL_TOTAL }, (_, index) => makeRow({ id: `wo-${index}`, woNumber: `WO-${index}` }), ); searchBoard.mockReset(); getBoard.mockReset(); patchBoardField.mockReset(); getBoard.mockResolvedValue(emptyBoardResult()); searchBoard.mockImplementation(async (params: WorkOrderBoardSearchQueryParams) => pagedResult(serverRows, params.page ?? 0, params.pageSize ?? PAGE_SIZE), ); patchBoardField.mockImplementation(async (id: string | number) => { serverRows = serverRows.slice(0, SHRUNK_TOTAL); return makeRow({ id, woNumber: `WO-${String(id).replace("wo-", "")}`, serviceNotes: "after-mutation", }); }); }); it("refetches the last valid search page after a mutation shrinks results to one page", async () => { renderWithProviders(, { withAuth: false }); fireEvent.click(screen.getByRole("button", { name: "Apply advanced filters" })); await waitFor(() => { expect(screen.getByTestId("harness-state")).toHaveTextContent( "adv=true rows=25 total=26 page=0", ); }); expect(screen.getByText("WO-0")).toBeInTheDocument(); expect(pagination().getByText("Page 1 of 2")).toBeInTheDocument(); expect(screen.queryByText("WO-25")).not.toBeInTheDocument(); 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(pagination().getByText("Page 2 of 2")).toBeInTheDocument(); }); const callsBeforeMutation = searchBoard.mock.calls.length; fireEvent.click(screen.getByRole("button", { name: "Save row patch" })); await waitFor(() => { expect(patchBoardField).toHaveBeenCalled(); expect(pagesAfter(callsBeforeMutation)).toContain(1); expect(pagesAfter(callsBeforeMutation)).toContain(0); }); await waitFor(() => { expect(screen.getByTestId("harness-state")).toHaveTextContent( "adv=true rows=20 total=20 page=0", ); 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(pagination().getByRole("button", { name: "Previous page" })).toBeDisabled(); expect(pagination().getByRole("button", { name: "Next page" })).toBeDisabled(); }, 15000); });