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 <help@stably.ai>
This commit is contained in:
SJCoetz 2026-08-03 15:13:12 +02:00
parent b89a4a7fa3
commit 760f088ec9

View file

@ -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;
}) => (
<div
data-testid="quick-draft-modal"
data-open={open}
data-edit-purchase-id={editPurchaseId ?? ""}
>
<button
type="button"
data-testid="quick-draft-modal-close"
onClick={() => onOpenChange(false)}
>
Close
</button>
</div>
),
}));
// Stub Next's <Link> 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;
}) => (
<a href={href} {...rest}>
{children}
</a>
),
}));
// 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<typeof StockOrdersTable>`
// 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(<StockOrdersTable rows={[]} currentStatus="" page={1} totalPages={1} />);
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(<StockOrdersTable rows={[]} currentStatus="" page={1} totalPages={1} />);
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(<StockOrdersTable rows={[]} currentStatus="" page={1} totalPages={1} />);
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(<StockOrdersTable rows={[]} currentStatus="" page={1} totalPages={1} />);
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(<StockOrdersTable rows={rows} currentStatus="" page={1} totalPages={1} />);
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(<StockOrdersTable rows={rows} currentStatus="" page={1} totalPages={1} />);
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(<StockOrdersTable rows={rows} currentStatus="" page={1} totalPages={1} />);
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(<StockOrdersTable rows={[row]} currentStatus="" page={1} totalPages={1} />);
// 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);
});
});