dodil/erp-ui
tests/data-table.test.tsx
import { screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { DataTable, type Column } from "../src";
import { a11y, renderInTheme } from "./helpers";
type Deal = { id: string; name: string; stage: string; amount: string | null; currency: string; close: string };
const rows: Deal[] = [
{ id: "O-1", name: "Acme renewal", stage: "proposal", amount: "9007199254740992.50", currency: "EUR", close: "2026-11-15" },
{ id: "O-2", name: "Beta print", stage: "closed_won", amount: "9007199254740993.00", currency: "EUR", close: "2026-10-01" },
{ id: "O-3", name: "Cyan digital", stage: "closed_lost", amount: null, currency: "EUR", close: "2026-12-01" },
];
const columns: Column<Deal>[] = [
{ key: "name", header: "Deal" },
{ key: "stage", header: "Stage", kind: "status" },
{ key: "amount", header: "Amount", kind: "money", currencyKey: "currency" },
{ key: "close", header: "Close", kind: "date" },
];
const names = () => screen.getAllByRole("row").slice(1).map((r) => within(r).getAllByRole("cell")[0].textContent);
test("rows show formatted, and money sorts exactly", async () => {
// The larger amount comes FIRST: a float sort sees the two as equal and leaves them as given — wrong.
const data = [rows[1], rows[0], rows[2]];
renderInTheme(<DataTable label="Deals" rows={data} columns={columns} rowKey={(r) => r.id} />);
expect(screen.getByText("9,007,199,254,740,993.00 EUR")).toBeInTheDocument();
expect(screen.getByText("closed won")).toBeInTheDocument();
const header = () => screen.getByRole("columnheader", { name: /Amount/ });
for (let i = 0; i < 3 && header().getAttribute("aria-sort") !== "ascending"; i++) {
await userEvent.click(screen.getByRole("button", { name: /Amount/ }));
}
expect(header()).toHaveAttribute("aria-sort", "ascending");
expect(names().slice(0, 2)).toEqual(["Acme renewal", "Beta print"]); // …992.50 before …993.00
});
test("a row opens with a click or Enter", async () => {
const opened: string[] = [];
renderInTheme(<DataTable label="Deals" rows={rows} columns={columns} rowKey={(r) => r.id} onRowClick={(r) => opened.push(r.id)} />);
await userEvent.click(screen.getByText("Acme renewal"));
screen.getByText("Beta print").closest("tr")!.focus();
await userEvent.keyboard("{Enter}");
expect(opened).toEqual(["O-1", "O-2"]);
});
test("loading, empty and error are states of the table", () => {
const { rerender } = renderInTheme(<DataTable label="Deals" rows={undefined} loading columns={columns} rowKey={(r) => r.id} />);
expect(screen.getByText("Loading…")).toBeInTheDocument();
rerender(<DataTable label="Deals" rows={[]} columns={columns} rowKey={(r) => r.id} empty="No deals yet" />);
expect(screen.getByText("No deals yet")).toBeInTheDocument();
rerender(<DataTable label="Deals" rows={[]} columns={columns} rowKey={(r) => r.id} error="403: missing permission" />);
expect(screen.getByRole("alert")).toHaveTextContent("403: missing permission");
});
test("pages", async () => {
const many = Array.from({ length: 30 }, (_, i) => ({ ...rows[0], id: `O-${i}`, name: `Deal ${String(i).padStart(2, "0")}` }));
renderInTheme(<DataTable label="Deals" rows={many} columns={columns} rowKey={(r) => r.id} pageSize={25} />);
expect(screen.getAllByRole("row")).toHaveLength(26);
await userEvent.click(screen.getByRole("button", { name: "Next" }));
expect(screen.getByText("Page 2 of 2")).toBeInTheDocument();
expect(screen.getAllByRole("row")).toHaveLength(6);
});
test("no accessibility violations", async () => {
const { container } = renderInTheme(<DataTable label="Deals" rows={rows} columns={columns} rowKey={(r) => r.id} />);
expect(await a11y(container)).toEqual([]);
});