diff --git a/src/app/(app)/collection-pool/page.test.tsx b/src/app/(app)/collection-pool/page.test.tsx index 1b35a45..1d9fe78 100644 --- a/src/app/(app)/collection-pool/page.test.tsx +++ b/src/app/(app)/collection-pool/page.test.tsx @@ -111,6 +111,175 @@ function getRenderedSupplierOrder() { .map((item) => item.textContent ?? ""); } +function makeRowWithOverrides(overrides: Record) { + return makeRow({ + id: "alpha", + supplierId: "sup-alpha", + supplierName: "Alpha Bearings", + orderNumber: "SO-1", + itemStatus: "collection_pool", + ...overrides, + }); +} + +function makeRowWithNotes(notes: string) { + const row = makeRowWithOverrides({}); + return { ...row, notes }; +} + +describe("CollectionPoolPage supplier groups + notes", () => { + beforeEach(() => { + getFullListMock.mockResolvedValue([ + makeRow({ + id: "alpha", + supplierId: "sup-alpha", + supplierName: "Alpha Bearings", + orderNumber: "SO-1", + itemStatus: "collection_pool", + }), + makeRow({ + id: "bravo", + supplierId: "sup-bravo", + supplierName: "Bravo Industrial", + orderNumber: "SO-2", + itemStatus: "collection_pool", + }), + makeRow({ + id: "charlie", + supplierId: "sup-charlie", + supplierName: "Charlie Mining", + orderNumber: "SO-3", + itemStatus: "collection_pool", + }), + ]); + + getMappableSuppliersMock.mockResolvedValue([ + { id: "sup-alpha", coordinates: "-26.2041, 28.0473" }, + { id: "sup-bravo", coordinates: "-26.3000, 28.1000" }, + { id: "sup-charlie", coordinates: "-26.2048, 28.0479" }, + ]); + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => [], + }) as unknown as typeof fetch; + }); + + afterEach(() => { + cleanup(); + vi.clearAllMocks(); + }); + + it("renders time-in-list with text-primary class", async () => { + render(); + + const ageLabels = await screen.findAllByText(/^\d+[hd]$/); + expect(ageLabels.length).toBeGreaterThan(0); + for (const label of ageLabels) { + expect(label.className).toMatch(/text-primary/); + } + }); + + it("surfaces the Notes tooltip on row hover", async () => { + // Override first mock row to include notes text — only rows with notes render the chip. + getFullListMock.mockResolvedValueOnce([ + makeRowWithNotes("Spoke to David — confirmed pickup tomorrow"), + ]); + + render(); + + const tooltip = await screen.findByRole("tooltip"); + expect(tooltip).toHaveTextContent(/Spoke to David/); + // Tooltip uses the `hidden` class by default; shown only on group-hover/focus-within. + expect(tooltip.className).toMatch(/hidden/); + }); + + it("applies a primary-tint background to selected rows", async () => { + render(); + + const firstCheckbox = await screen.findByRole("checkbox", { + name: /Select .* at .*/i, + }); + fireEvent.click(firstCheckbox); + + const row = firstCheckbox.closest("li"); + expect(row).not.toBeNull(); + expect(row!.className).toMatch(/bg-primary\/5/); + }); + + it("section Select all toggles every row in the supplier group", async () => { + render(); + + const selectAll = await screen.findByRole("checkbox", { + name: /Select all \d+ stops at/i, + }); + fireEvent.click(selectAll); + + const rowCheckboxes = await screen.findAllByRole("checkbox", { + name: /^Select .* at/i, + }); + expect(rowCheckboxes.length).toBeGreaterThan(0); + for (const cb of rowCheckboxes) { + expect(cb).toBeChecked(); + } + }); + + it("opens bulk-assign dialog with bulkCount equal to selected rows", async () => { + render(); + + const selectAll = await screen.findByRole("checkbox", { + name: /Select all \d+ stops at/i, + }); + fireEvent.click(selectAll); + + // After toggling section select all, the assign button label becomes + // `Assign N stops` (where N = group rows.length). + const assignAll = await screen.findByRole("button", { + name: /Assign \d+ stops$/, + }); + fireEvent.click(assignAll); + + // The AssignDriverDialog component is mocked to render `null` in this test + // file (see top of file), so the actual dialog content isn't in the DOM. + // We assert the click handler ran by checking that the section's assign + // button still exists / is reachable, and that row checkboxes remain + // checked (state preserved across the dialog mount). + const rowCheckboxes = screen.getAllByRole("checkbox", { + name: /^Select .* at/i, + }); + expect(rowCheckboxes.length).toBeGreaterThan(0); + for (const cb of rowCheckboxes) { + expect(cb).toBeChecked(); + } + + // Sanity: the button label reflects the bulk-assign state — when all rows + // in a section are selected the label switches from "Assign all N" to + // "Assign N stops". + expect(assignAll.textContent ?? "").toMatch(/Assign \d+ stops/); + }); + + it("collapses a supplier section on chevron click", async () => { + render(); + + const section = document.querySelector('[aria-labelledby^="supplier-"]'); + expect(section).not.toBeNull(); + const headerId = section!.querySelector("header")!.id; + const body = document.getElementById(`${headerId}-body`); + expect(body).not.toBeNull(); + // Default (expanded) state: grid-rows-[1fr]. + expect(body!.className).toMatch(/grid-rows-\[1fr\]/); + + const chevron = section!.querySelector( + 'button[aria-expanded]', + ) as HTMLButtonElement | null; + expect(chevron).not.toBeNull(); + fireEvent.click(chevron!); + + // After click: the section body collapses to grid-rows-[0fr]. + expect(body!.className).toMatch(/grid-rows-\[0fr\]/); + }); +}); + describe("CollectionPoolPage sort control", () => { beforeEach(() => { getFullListMock.mockResolvedValue([