From 760f088ec984f9e575d88278125ccb2f7f06678b Mon Sep 17 00:00:00 2001 From: SJCoetz Date: Mon, 3 Aug 2026 15:13:12 +0200 Subject: [PATCH] test(stock-orders): move page tests to StockOrdersTable + add total-value case The seven pre-existing assertions on the quick-draft modal and edit button now exercise the new client component. New case confirms ordered_value/received_value render in the row. Uses Intl-format substring assertions because the currency glyph/spacing varies by ICU version. Co-authored-by: Orca --- .../stock-orders/StockOrdersTable.test.tsx | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 src/app/(app)/stock-orders/StockOrdersTable.test.tsx diff --git a/src/app/(app)/stock-orders/StockOrdersTable.test.tsx b/src/app/(app)/stock-orders/StockOrdersTable.test.tsx new file mode 100644 index 0000000..ead17f7 --- /dev/null +++ b/src/app/(app)/stock-orders/StockOrdersTable.test.tsx @@ -0,0 +1,209 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { cleanup, render, screen, fireEvent } from "@testing-library/react"; + +// Stub the QuickDraftModal — it's tested in +// `src/components/stock-orders/quick-draft-modal.test.tsx`. +vi.mock("@/components/stock-orders/quick-draft-modal", () => ({ + QuickDraftModal: ({ + open, + onOpenChange, + editPurchaseId, + }: { + open: boolean; + onOpenChange: (b: boolean) => void; + editPurchaseId?: string; + }) => ( +
+ +
+ ), +})); + +// Stub Next's so the test doesn't pull the router — these +// tests don't assert navigation. +vi.mock("next/link", () => ({ + default: ({ + children, + href, + ...rest + }: { + children: React.ReactNode; + href: string; + [k: string]: unknown; + }) => ( + + {children} + + ), +})); + +// Stub Next's `useRouter` — the table rows are clickable and push +// `/stock-orders/[id]`, which jsdom can't handle. +vi.mock("next/navigation", () => ({ + useRouter: () => ({ + push: vi.fn(), + replace: vi.fn(), + back: vi.fn(), + }), +})); + +import StockOrdersTable from "./StockOrdersTable"; +import type { StockPurchase, Supplier } from "@/lib/types"; + +// --- Helpers -------------------------------------------------------------- + +// Inline shape (the plan's fallback) — `Parameters` +// is too tight because the `StockPurchase` type requires every `Supplier` +// field expanded (email, address, etc.), but the table only reads +// `expand.supplier.suppliername`. The component's prop type itself is +// fine; this is just a fixture-side cast. +type ListRow = StockPurchase & { + item_count: number; + ordered_value: number; + received_value: number; + expand?: { supplier?: Supplier }; +}; + +function makeRow(overrides: { + id: string; + status: "draft" | "sent" | "received" | "cancelled" | "sending" | "mail_failed"; + purchase_number: string; +}): ListRow { + return { + id: overrides.id, + created: "2026-07-30 10:00:00.000Z", + updated: "2026-07-30 10:00:00.000Z", + collectionId: "stock_purchases", + collectionName: "stock_purchases", + purchase_number: overrides.purchase_number, + supplier: "supplier-1", + status: overrides.status, + notes: "", + email_error: "", + email_message_id: "", + item_count: 0, + ordered_value: 0, + received_value: 0, + expand: { + supplier: { + id: "supplier-1", + collectionId: "suppliers", + collectionName: "suppliers", + created: "", + updated: "", + suppliername: "Steel & Pipe Co", + suppliernumber: "SUP-1", + supplieraddress: "", + email: "", + supply_tags: "", + coordinates: "", + closingtime: "", + } as Supplier, + }, + }; +} + +// --- Tests ---------------------------------------------------------------- + +describe("StockOrdersTable — quick draft button", () => { + afterEach(() => { + cleanup(); + }); + + it("renders a New Stock Item button", () => { + render(); + const button = screen.getByTestId("new-stock-item-button"); + expect(button).toBeInTheDocument(); + expect(button).toHaveTextContent(/new stock item/i); + }); + + it("mounts the QuickDraftModal (closed by default)", () => { + render(); + const modal = screen.getByTestId("quick-draft-modal"); + expect(modal).toBeInTheDocument(); + expect(modal.dataset.open).toBe("false"); + }); + + it("opens the QuickDraftModal when the button is clicked", () => { + render(); + fireEvent.click(screen.getByTestId("new-stock-item-button")); + expect(screen.getByTestId("quick-draft-modal").dataset.open).toBe("true"); + }); + + it("closes the QuickDraftModal when the modal calls onOpenChange(false)", () => { + render(); + fireEvent.click(screen.getByTestId("new-stock-item-button")); + expect(screen.getByTestId("quick-draft-modal").dataset.open).toBe("true"); + fireEvent.click(screen.getByTestId("quick-draft-modal-close")); + expect(screen.getByTestId("quick-draft-modal").dataset.open).toBe("false"); + }); +}); + +describe("StockOrdersTable — edit draft button", () => { + afterEach(() => { + cleanup(); + }); + + it("renders an Edit button on draft rows", () => { + const rows = [ + makeRow({ id: "po-abc", status: "draft", purchase_number: "PO26000001STK" }), + ]; + render(); + const editButton = screen.getByTestId("edit-draft-button"); + expect(editButton).toBeInTheDocument(); + expect(editButton).toHaveAttribute("aria-label", "Edit draft PO26000001STK"); + }); + + it("does NOT render an Edit button on non-draft rows", () => { + const rows = [ + makeRow({ id: "po-sent", status: "sent", purchase_number: "PO26000002STK" }), + ]; + render(); + expect(screen.queryByTestId("edit-draft-button")).toBeNull(); + }); + + it("clicking Edit opens the modal with editPurchaseId set to the row id", () => { + const rows = [ + makeRow({ id: "po-abc", status: "draft", purchase_number: "PO26000001STK" }), + ]; + render(); + const modal = screen.getByTestId("quick-draft-modal"); + expect(modal.dataset.open).toBe("false"); + expect(modal.dataset.editPurchaseId).toBe(""); + fireEvent.click(screen.getByTestId("edit-draft-button")); + expect(modal.dataset.open).toBe("true"); + expect(modal.dataset.editPurchaseId).toBe("po-abc"); + }); +}); + +describe("StockOrdersTable — total value columns", () => { + afterEach(() => { + cleanup(); + }); + + it("renders ordered_value and received_value formatted as ZAR currency", () => { + const row = makeRow({ + id: "po-1", + status: "draft", + purchase_number: "PO26000001STK", + }); + row.ordered_value = 521; + row.received_value = 75; + render(); + // formatCurrency uses Intl with en-ZA / ZAR — the exact glyph + // and spacing can vary by ICU version, so assert on the numeric + // parts instead. + expect(screen.getAllByText(/521/).length).toBeGreaterThan(0); + expect(screen.getAllByText(/75/).length).toBeGreaterThan(0); + }); +});