diff --git a/src/app/(app)/collection-confirm/page.test.tsx b/src/app/(app)/collection-confirm/page.test.tsx index 48e1ca2..89552b2 100644 --- a/src/app/(app)/collection-confirm/page.test.tsx +++ b/src/app/(app)/collection-confirm/page.test.tsx @@ -121,6 +121,7 @@ describe("CollectionConfirmPage", () => { it("loads rows via /api/v1/collection-confirm and renders one PoCard per PO", async () => { const fetchSpy = vi.spyOn(window, "fetch"); + const user = userEvent.setup(); render(); // PoCard renders after the fetch resolves. @@ -136,7 +137,8 @@ describe("CollectionConfirmPage", () => { }), ); - // Both rows are rendered inside the PoCard. + // Both rows are rendered inside the PoCard (body opened). + await user.click(screen.getByTestId("po-card-toggle-PO-1")); expect(screen.getByText("Wheel hub kit")).toBeInTheDocument(); expect(screen.getByText("20mm cable ties")).toBeInTheDocument(); @@ -168,6 +170,10 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // All cards start collapsed. Open PO-1's body so the chip + // strip and preview are mounted. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + // Two chips, one per filename. The preview block is mounted // as soon as a card has attachments — the first attachment is // shown by default. @@ -216,12 +222,17 @@ describe("CollectionConfirmPage", () => { ]; buildFetchSpy(photoRows); + const user = userEvent.setup(); render(); await waitFor(() => expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // All cards start collapsed. Open PO-1's body so the + // preview + scroll container are mounted. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + // No modal — the preview lives inside the card so the // operator can scroll the items table above and keep the // document in view. @@ -259,6 +270,9 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body so the preview is mounted. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + // Preview mounted by default with the first chip active. expect(screen.getByTestId("po-card-preview-PO-1")).toBeInTheDocument(); const chip = screen.getByRole("button", { name: /invoice-1\.pdf 1 of 1/i }); @@ -296,6 +310,9 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body so the chips and preview are mounted. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + // First chip is active by default; preview shows invoice-1. const preview = screen.getByTestId("po-card-preview-PO-1"); expect( @@ -335,6 +352,9 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body first. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + const chip = screen.getByRole("button", { name: /invoice-1\.pdf 1 of 1/i }); const preview = screen.getByTestId("po-card-preview-PO-1"); const viewer = within(preview).getByTestId("invoice-viewer"); @@ -367,6 +387,9 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body so the preview is mounted. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + // Preview is mounted by default — the open-new-tab button is // already on screen, no chip click needed. const viewer = within(screen.getByTestId("po-card-preview-PO-1")).getByTestId("invoice-viewer"); @@ -389,12 +412,16 @@ describe("CollectionConfirmPage", () => { ]; buildFetchSpy(noPhotoRows); + const user = userEvent.setup(); render(); await waitFor(() => expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body so the upload prompt is visible. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + // The PoCard shows an "Upload invoice" prompt in place of the // photo strip — the operator can upload a counter-collection // or late email invoice from right inside the card. @@ -433,11 +460,11 @@ describe("CollectionConfirmPage", () => { expect(screen.queryByTestId("collection-confirm-queue-PO-1")).not.toBeInTheDocument(); expect(screen.queryByTestId("po-detail-modal")).not.toBeInTheDocument(); - // The first card's body is open by default so the operator - // lands on a ready workspace: table, chip strip, and preview - // are all mounted without an extra click. - expect(card.getAttribute("data-open")).toBe("true"); - expect(within(card).getByText("Wheel hub kit")).toBeInTheDocument(); + // All cards are collapsed by default — only the header is + // visible. The operator opens the card they want to work on + // so a long queue is scannable. + expect(card.getAttribute("data-open")).toBe("false"); + expect(within(card).queryByText("Wheel hub kit")).not.toBeInTheDocument(); // Total = qty_collected (10) × cost_price (45) = 450.00. // ZAR currency uses "R 450,00" via en-ZA locale; the comma + // space is the localised thousands/decimal separator. @@ -450,8 +477,15 @@ describe("CollectionConfirmPage", () => { within(card).getByTestId("po-card-orders-PO-1"), ).toHaveTextContent("SO-row-photo-1"); - // Clicking the header collapses the body so a long queue - // stays scannable. + // Clicking the header expands the body so the operator can + // work on this PO. Toggle again to collapse. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + expect(card.getAttribute("data-open")).toBe("true"); + expect(within(card).getByText("Wheel hub kit")).toBeInTheDocument(); + expect( + within(card).getByTestId("po-card-preview-PO-1"), + ).toBeInTheDocument(); + await user.click(screen.getByTestId("po-card-toggle-PO-1")); expect(card.getAttribute("data-open")).toBe("false"); expect(within(card).queryByText("Wheel hub kit")).not.toBeInTheDocument(); @@ -504,8 +538,8 @@ describe("CollectionConfirmPage", () => { // Both POs render as their own cards — no selection state, no // queue sidebar, no swapping workspace. Each card is the - // whole workspace for its PO, but only the first one starts - // expanded so a long queue is scannable. + // whole workspace for its PO, but every card starts collapsed + // so a long queue is scannable. await waitFor(() => { expect(screen.getByTestId("po-card-PO-A")).toBeInTheDocument(); expect(screen.getByTestId("po-card-PO-B")).toBeInTheDocument(); @@ -514,21 +548,23 @@ describe("CollectionConfirmPage", () => { const cardA = screen.getByTestId("po-card-PO-A"); const cardB = screen.getByTestId("po-card-PO-B"); - // PO-A: open by default, table + chip + preview all visible. - expect(cardA.getAttribute("data-open")).toBe("true"); - expect(within(cardA).getByText("Alpha row")).toBeInTheDocument(); - expect( - within(cardA).getByRole("button", { name: /invoice-a\.pdf 1 of 1/i }), - ).toBeInTheDocument(); - expect(within(cardA).getByTestId("po-card-preview-PO-A")).toBeInTheDocument(); - // Order number rides in the header even for the open card. + // PO-A: collapsed by default. Header is visible (PO + supplier + // + order + total) so the operator can scan, but the table + // and preview are hidden until they expand it. + expect(cardA.getAttribute("data-open")).toBe("false"); + expect(within(cardA).getByTestId("po-card-title")).toHaveTextContent( + "PO PO-A — Alpha Bearings", + ); expect( within(cardA).getByTestId("po-card-orders-PO-A"), ).toHaveTextContent("SO-A"); + expect(within(cardA).getByTestId("po-card-total-PO-A")).toBeInTheDocument(); + expect(within(cardA).queryByText("Alpha row")).not.toBeInTheDocument(); + expect( + within(cardA).queryByTestId("po-card-preview-PO-A"), + ).not.toBeInTheDocument(); - // PO-B: collapsed by default. Header is visible (PO + supplier - // + order + total) so the operator can scan, but the table - // and preview are hidden until they expand it. + // PO-B: also collapsed by default. expect(cardB.getAttribute("data-open")).toBe("false"); expect(within(cardB).getByTestId("po-card-title")).toHaveTextContent( "PO PO-B — Beta Castings", @@ -542,8 +578,8 @@ describe("CollectionConfirmPage", () => { within(cardB).queryByTestId("po-card-preview-PO-B"), ).not.toBeInTheDocument(); - // Clicking PO-B's header expands it — operator moves on to - // the next PO when ready. + // Clicking PO-B's header expands it — operator opens the card + // they want to work on, then moves on. await user.click(screen.getByTestId("po-card-toggle-PO-B")); expect(cardB.getAttribute("data-open")).toBe("true"); expect(within(cardB).getByText("Beta row")).toBeInTheDocument(); @@ -577,6 +613,9 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body so the items table is mounted. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + fetchSpy.mockClear(); await user.click(screen.getByTestId("sku-edit-row-1")); const input = screen.getByTestId("sku-input-row-1"); @@ -612,12 +651,16 @@ describe("CollectionConfirmPage", () => { ]; buildFetchSpy(photoRows); + const user = userEvent.setup(); render(); await waitFor(() => expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body so the chips and preview are mounted. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + // The chips live on the card's strip — one per distinct // filename. The preview block is mounted by default with the // first chip active. @@ -682,6 +725,9 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body so the upload input is reachable. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + const fileInput = screen.getByLabelText(/Upload invoice for PO PO-1/i); const file = new File(["pdf"], "invoice-late.pdf", { type: "application/pdf" }); await user.upload(fileInput, file); @@ -702,6 +748,9 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body so the items table is mounted. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + // Wheel hub kit cost is 45 → displayed as R45.00. expect(screen.getByText("R45.00")).toBeInTheDocument(); @@ -723,6 +772,9 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body so the items table is mounted. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + // Reset spy after the initial GET to isolate the cost POST. fetchSpy.mockClear(); @@ -771,6 +823,9 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body so the items table is mounted. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + fetchSpy.mockClear(); await user.click(screen.getByTestId("cost-edit-row-1")); @@ -799,6 +854,10 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body so the items table and confirm button + // are reachable. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + // 1) Apply a cost override on row-1 (Enter triggers the cost POST + // records the override in local state for the confirm submit). await user.click(screen.getByTestId("cost-edit-row-1")); @@ -914,6 +973,9 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-SP-001")).toBeInTheDocument(), ); + // Open the card body so the footer is reachable. + await user.click(screen.getByTestId("po-card-toggle-SP-001")); + // Stock-only card: the "Book to stock" button is present, the // "Confirm for invoicing" button is hidden. expect( @@ -988,6 +1050,10 @@ describe("CollectionConfirmPage", () => { expect(screen.getByTestId("po-card-PO-1")).toBeInTheDocument(), ); + // Open the card body so the items table and confirm button + // are reachable. + await user.click(screen.getByTestId("po-card-toggle-PO-1")); + // Before the edit, the cell exposes `null` as its raw value. const cell = document.querySelector('[data-cost-value]'); expect(cell).not.toBeNull(); @@ -1070,12 +1136,15 @@ describe("CollectionConfirmPage", () => { render(); // Both POs render as their own self-contained cards (no queue - // sidebar, no modal). + // sidebar, no modal). All cards are collapsed by default. await waitFor(() => expect(screen.getByTestId("po-card-PO-A")).toBeInTheDocument(), ); expect(screen.getByTestId("po-card-PO-B")).toBeInTheDocument(); + // Open PO-A's body so its items table is reachable. + await user.click(screen.getByTestId("po-card-toggle-PO-A")); + // 1) Edit PO-A's row cost — Enter triggers the cost POST + records // the override in local state. await user.click(screen.getByTestId("cost-edit-row-a")); @@ -1087,8 +1156,8 @@ describe("CollectionConfirmPage", () => { expect(screen.getByText("R150.00")).toBeInTheDocument(), ); - // 2) Open PO-B's confirm dialog. PO-B is collapsed by default - // (only the first card starts open), so expand it first. + // 2) Open PO-B's confirm dialog. All cards are collapsed by + // default, so expand PO-B first. fetchSpy.mockClear(); await user.click(screen.getByTestId("po-card-toggle-PO-B")); await user.click(screen.getByTestId("po-card-confirm-PO-B")); diff --git a/src/app/(app)/collection-confirm/page.tsx b/src/app/(app)/collection-confirm/page.tsx index 7005d39..6fe7f12 100644 --- a/src/app/(app)/collection-confirm/page.tsx +++ b/src/app/(app)/collection-confirm/page.tsx @@ -422,7 +422,7 @@ export default function CollectionConfirmPage() {
- {groupedRows.map((group, index) => { + {groupedRows.map((group) => { const firstRow = group.rows[0]; const supplierId = firstRow?.expand?.purchase_order?.expand?.supplier?.id ?? ""; @@ -438,7 +438,6 @@ export default function CollectionConfirmPage() { rows={group.rows} poNumber={group.poNumber || "No PO"} supplier={{ id: supplierId, name: supplierName }} - initiallyOpen={index === 0} onCostChange={(id, cost) => void handleCostChange(id, cost)} onSkuChange={(id, sku) => void handleSkuChange(id, sku)} onConfirm={() =>