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([]);
});