import { describe, it, expect, vi } from "vitest"; import { useState } from "react"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import type { ColumnDef } from "@tanstack/react-table"; import { DataTable } from "../data-table"; interface Row { id: string; name: string; role: string; } const rows: Row[] = [ { id: "1", name: "Alice", role: "Admin" }, { id: "2", name: "Bob", role: "Editor" }, { id: "3", name: "Carol", role: "Viewer" }, ]; const columns: ColumnDef[] = [ { accessorKey: "name", header: () => "Name", cell: ({ row }) => row.original.name, }, { accessorKey: "role", header: () => "Role", cell: ({ row }) => row.original.role, }, ]; /** Wrapper so the DataTable's controlled state can update during interaction. */ function Harness({ onRowClick, initialSelection = {}, }: { onRowClick?: (row: Row) => void; initialSelection?: Record; }) { const [selection, setSelection] = useState>(initialSelection); const [visibility, setVisibility] = useState>({}); return ( row.id} enableRowSelection rowSelection={selection} onRowSelectionChange={setSelection} onRowClick={onRowClick} enableColumnVisibilityToggle columnVisibility={visibility} onColumnVisibilityChange={setVisibility} /> ); } describe("DataTable (slice 7a — TanStack wrapper)", () => { it("renders the column headers and rows", () => { render(); expect(screen.getByText("Name")).toBeInTheDocument(); expect(screen.getByText("Role")).toBeInTheDocument(); expect(screen.getByText("Alice")).toBeInTheDocument(); expect(screen.getByText("Carol")).toBeInTheDocument(); }); it("toggles row selection via the per-row checkbox and reflects state", async () => { render(); // Header select-all checkbox + one per-row checkbox exist before rows. expect(screen.getAllByRole("checkbox", { name: "Select row" }).length).toBe( rows.length, ); const aliceCheckbox = screen.getAllByRole("checkbox", { name: "Select row", })[0]; await userEvent.click(aliceCheckbox); expect(aliceCheckbox).toBeChecked(); // Toggling again un-selects (controlled membership flips). await userEvent.click(aliceCheckbox); expect(aliceCheckbox).not.toBeChecked(); }); it("selects all page rows via the header select-all checkbox", async () => { render(); const selectAll = screen.getByRole("checkbox", { name: "Select all rows on this page", }); await userEvent.click(selectAll); for (const cb of screen.getAllByRole("checkbox", { name: "Select row" })) { expect(cb).toBeChecked(); } await userEvent.click(selectAll); for (const cb of screen.getAllByRole("checkbox", { name: "Select row" })) { expect(cb).not.toBeChecked(); } }); it("toggles column visibility via the Columns dropdown (column disappears)", async () => { render(); // Role column header present initially. expect(screen.getByText("Role")).toBeInTheDocument(); await userEvent.click(screen.getByRole("button", { name: /Columns/ })); await userEvent.click( screen.getByRole("menuitemcheckbox", { name: "role" }), ); // Role header + all role cells vanish from the table. expect(screen.queryByText("Role")).toBeNull(); expect(screen.queryByText("Admin")).toBeNull(); expect(screen.queryByText("Viewer")).toBeNull(); // Name column is unaffected. expect(screen.getByText("Name")).toBeInTheDocument(); expect(screen.getByText("Alice")).toBeInTheDocument(); }); it("fires onRowClick with row.original when a row body is clicked", async () => { const onRowClick = vi.fn(); render(); await userEvent.click(screen.getByText("Bob")); expect(onRowClick).toHaveBeenCalledTimes(1); expect(onRowClick).toHaveBeenCalledWith( expect.objectContaining({ id: "2", name: "Bob", role: "Editor" }), ); }); it("does NOT fire onRowClick when the selection checkbox is toggled", async () => { const onRowClick = vi.fn(); render(); const firstCheckbox = screen.getAllByRole("checkbox", { name: "Select row", })[0]; await userEvent.click(firstCheckbox); expect(onRowClick).not.toHaveBeenCalled(); }); it("renders the empty message when data is empty", () => { render( , ); expect(screen.getByText("No files in this directory.")).toBeInTheDocument(); }); it("renders client pagination controls when enabled", () => { render( , ); expect(screen.getByText(/Page 1 of/)).toBeInTheDocument(); expect( screen.getByRole("button", { name: "Previous page" }), ).toBeDisabled(); expect(screen.getByRole("button", { name: "Next page" })).toBeEnabled(); }); it("renders the manual pagination total when rowCount is supplied", () => { render( , ); expect(screen.getByText("42 rows")).toBeInTheDocument(); expect(screen.getByText(/Page 1 of 21/)).toBeInTheDocument(); }); });