From 7df076f011ed0163ce0a685ca088dc980556087b Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Wed, 22 Jul 2026 17:25:14 -0300 Subject: [PATCH] fix(work-orders): wire Advanced Search sheet and cover SH-121 flow --- src/app/(protected)/workorders/index.tsx | 20 +++ ...ork-orders-advanced-search-wiring.test.tsx | 154 ++++++++++++++++++ ...ork-orders-board-core-affordances.test.tsx | 22 ++- 3 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 src/test/app/(protected)/workorders/work-orders-advanced-search-wiring.test.tsx diff --git a/src/app/(protected)/workorders/index.tsx b/src/app/(protected)/workorders/index.tsx index 8229b28e..d3ead5c3 100644 --- a/src/app/(protected)/workorders/index.tsx +++ b/src/app/(protected)/workorders/index.tsx @@ -3,6 +3,7 @@ import { Box } from "@mui/material"; import { useNavigate } from "react-router"; import { PageHeader } from "@/components/ui/page-header"; import { MediaUploadModal } from "@/app/(protected)/workorders/_components/media/media-upload-modal"; +import { WorkOrdersAdvancedSearchSheet } from "@/app/(protected)/workorders/_components/list/advanced-search/work-orders-advanced-search-sheet"; import { WorkOrdersAdvancedFiltersBanner } from "@/app/(protected)/workorders/_components/list/work-orders-advanced-filters-banner"; import { WorkOrdersFilterBar } from "@/app/(protected)/workorders/_components/list/filter-bar/work-orders-filter-bar"; import { WorkOrdersListHeader } from "@/app/(protected)/workorders/_components/list/work-orders-list-header"; @@ -44,6 +45,11 @@ export default function WorkOrdersListPage() { [problemOptions], ); + const vendorTechs = useMemo( + () => [...new Set(vendors.map((vendor) => vendor.companyName).filter(Boolean))], + [vendors], + ); + const totalCount = tableData.totalCount; const scopeTotalCount = tableData.scopeTotalCount; @@ -62,6 +68,7 @@ export default function WorkOrdersListPage() { filters.setAdvOpen(true)} activeFilterCount={filters.activeFilterCount} isFetching={tableData.isFetching && !tableData.isLoading} /> @@ -104,6 +111,19 @@ export default function WorkOrdersListPage() { }} onOpenMedia={setMediaRow} onClearSearch={() => filters.handleSearchChange("")} + onOpenAdvancedFilters={() => filters.setAdvOpen(true)} + /> + + {activeMediaRow && ( diff --git a/src/test/app/(protected)/workorders/work-orders-advanced-search-wiring.test.tsx b/src/test/app/(protected)/workorders/work-orders-advanced-search-wiring.test.tsx new file mode 100644 index 00000000..ab94481a --- /dev/null +++ b/src/test/app/(protected)/workorders/work-orders-advanced-search-wiring.test.tsx @@ -0,0 +1,154 @@ +import { fireEvent, screen, within } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import WorkOrdersListPage from "@/app/(protected)/workorders/index"; +import { EMPTY_ADVANCED_FILTERS } from "@/domain/work-orders/types/work-order-advanced-filters"; +import { renderWithProviders } from "@/test/test-utils"; + +const setAdvOpen = vi.fn(); +const applyAdvancedFilters = vi.fn(); +const clearAdvancedFilters = vi.fn(); +let advOpen = false; +let advApplied: typeof EMPTY_ADVANCED_FILTERS | null = null; + +vi.mock("react-router", async () => { + const actual = await vi.importActual("react-router"); + return { + ...actual, + useNavigate: () => vi.fn(), + }; +}); + +vi.mock("@/providers/auth-context", () => ({ + useAuthContext: () => ({ user: { id: "u1" } }), +})); + +vi.mock("@/app/(protected)/workorders/_hooks/use-work-orders-list-filters", () => ({ + useWorkOrdersListFilters: () => ({ + search: "", + handleSearchChange: vi.fn(), + advOpen, + setAdvOpen: (open: boolean) => { + advOpen = open; + setAdvOpen(open); + }, + advApplied, + advInitial: advApplied ?? EMPTY_ADVANCED_FILTERS, + applyAdvancedFilters: (filters: typeof EMPTY_ADVANCED_FILTERS) => { + advApplied = filters; + applyAdvancedFilters(filters); + }, + clearAdvancedFilters: () => { + advApplied = null; + clearAdvancedFilters(); + }, + clearAdvancedFilterChip: vi.fn(), + activeFilterCount: advApplied ? 1 : 0, + weekMonday: "2026-07-20", + setWeekMonday: vi.fn(), + dispatcherIds: new Set(), + setDispatcherIds: vi.fn(), + typeFilter: "All", + setTypeFilter: vi.fn(), + }), +})); + +vi.mock("@/app/(protected)/workorders/_hooks/use-work-orders-table-data", () => ({ + useWorkOrdersTableData: () => ({ + rows: [], + totalCount: 0, + scopeTotalCount: 0, + isFetching: false, + isLoading: false, + setSelectedId: vi.fn(), + patchRow: vi.fn(), + }), +})); + +vi.mock("@/domain/work-orders/use-cases/use-work-order-table-mutations", () => ({ + useWorkOrderTableMutations: () => ({ patchField: vi.fn() }), +})); + +vi.mock("@/domain/work-orders/use-cases/use-work-orders-list", () => ({ + useWorkOrderUsers: () => ({ + data: [{ id: "u1", name: "Dispatcher One" }], + }), +})); + +vi.mock("@/domain/work-orders/use-cases/use-work-order-detail", () => ({ + useWorkOrderLocations: () => ({ + data: [{ id: "loc-1", name: "Seattle" }], + }), + useWorkOrderVendors: () => ({ + data: [{ companyName: "Vendor Co", email: "real@vendor.com" }], + }), +})); + +vi.mock("@/domain/settings/dropdown-options/use-cases/use-dropdown-options-by-category", () => ({ + useDropdownOptionsByCategory: () => ({ data: [{ value: "HVAC" }] }), +})); + +vi.mock("@/domain/work-orders/mocks/is-wo-table-mock-enabled", () => ({ + isWoTableMockEnabled: () => false, +})); + +vi.mock("@/app/(protected)/workorders/_components/list/table/wo-table", () => ({ + WoTable: () => null, +})); + +vi.mock("@/app/(protected)/workorders/_components/list/filter-bar/work-orders-filter-bar", () => ({ + WorkOrdersFilterBar: () => null, +})); + +vi.mock("@/app/(protected)/workorders/_components/media/media-upload-modal", () => ({ + MediaUploadModal: () => null, +})); + +describe("WorkOrders Advanced Search page wiring (SH-121)", () => { + beforeEach(() => { + advOpen = false; + advApplied = null; + setAdvOpen.mockClear(); + applyAdvancedFilters.mockClear(); + clearAdvancedFilters.mockClear(); + }); + + it("opens the sheet from Advanced Filters, applies, then clears", () => { + const view = renderWithProviders(, { + route: "/workorders", + withAuth: false, + }); + + expect(screen.queryByRole("button", { name: /apply filters/i })).not.toBeInTheDocument(); + + fireEvent.click(screen.getByRole("button", { name: "Advanced Filters" })); + expect(setAdvOpen).toHaveBeenCalledWith(true); + + advOpen = true; + view.rerender(); + + const sheet = screen.getByRole("dialog", { name: "Advanced Filters" }); + expect(within(sheet).getByRole("button", { name: /apply filters/i })).toBeInTheDocument(); + + // Expand filter sections that start collapsed and assert option inputs are wired. + for (const label of ["Site", "Assigned to", "PM type", "Vendor / tech"]) { + fireEvent.click(within(sheet).getByRole("button", { name: new RegExp(label, "i") })); + } + expect(within(sheet).getByText("Seattle")).toBeInTheDocument(); + expect(within(sheet).getByText("Dispatcher One")).toBeInTheDocument(); + expect(within(sheet).getByText("HVAC")).toBeInTheDocument(); + expect(within(sheet).getByText("Vendor Co")).toBeInTheDocument(); + + fireEvent.click(within(sheet).getByRole("button", { name: /apply filters/i })); + expect(applyAdvancedFilters).toHaveBeenCalled(); + expect(setAdvOpen).toHaveBeenCalledWith(false); + + advOpen = true; + view.rerender(); + fireEvent.click( + within(screen.getByRole("dialog", { name: "Advanced Filters" })).getByRole("button", { + name: /clear all/i, + }), + ); + expect(clearAdvancedFilters).toHaveBeenCalled(); + }); +}); diff --git a/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx b/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx index effb2edc..92cd898b 100644 --- a/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx +++ b/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx @@ -25,10 +25,24 @@ vi.mock("@/app/(protected)/workorders/_hooks/use-work-orders-list-filters", () = search: "", handleSearchChange: vi.fn(), setAdvOpen: vi.fn(), - activeFilterCount: 0, + advOpen: false, advApplied: null, - clearAdvancedFilterChip: vi.fn(), + advInitial: { + dateRange: "this-week", + customFrom: "", + customTo: "", + sites: new Set(), + types: new Set(), + dispatchers: new Set(), + statuses: new Set(), + pmTypes: new Set(), + vendorTechs: new Set(), + docs: new Set(), + }, + applyAdvancedFilters: vi.fn(), clearAdvancedFilters: vi.fn(), + clearAdvancedFilterChip: vi.fn(), + activeFilterCount: 0, weekMonday: "2026-07-20", setWeekMonday: vi.fn(), dispatcherIds: new Set(), @@ -165,15 +179,15 @@ describe("WorkOrdersListHeader slice affordances", () => { }); describe("WorkOrdersListPage board-core public interface", () => { - it("does not render New WO or Advanced Filters, and View navigates to detail", () => { + it("does not render New WO, and View navigates to detail", () => { navigate.mockReset(); renderWithProviders(, { route: "/workorders", withAuth: false }); expect(screen.queryByRole("button", { name: /new wo/i })).not.toBeInTheDocument(); - expect(screen.queryByLabelText("Advanced Filters")).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: /view doc|draft/i })).not.toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: /view canceled row/i })); expect(navigate).toHaveBeenCalledWith("/workorders/77"); });