Files
prowler/ui/components/shadcn/table/data-table.test.tsx
2026-07-16 16:02:22 +02:00

179 lines
6.1 KiB
TypeScript

import type { ColumnDef } from "@tanstack/react-table";
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import type { MetaDataProps } from "@/types";
import { DataTable } from "./data-table";
vi.mock("next/navigation", () => ({
usePathname: () => "/findings",
useRouter: () => ({
push: vi.fn(),
}),
useSearchParams: () => new URLSearchParams(),
}));
vi.mock("@/contexts", () => ({
useFilterTransitionOptional: () => null,
}));
vi.mock("@/lib", () => ({
cn: (...classes: Array<string | false | null | undefined>) =>
classes.filter(Boolean).join(" "),
getPaginationInfo: (metadata: MetaDataProps) => ({
currentPage: metadata.pagination.page,
totalPages: metadata.pagination.pages,
totalEntries: metadata.pagination.count,
itemsPerPageOptions: metadata.pagination.itemsPerPage ?? [10, 20, 50],
}),
}));
interface TestRow {
name: string;
}
const columns: ColumnDef<TestRow>[] = [
{
accessorKey: "name",
header: "Name",
},
];
const columnsWithActions: ColumnDef<TestRow>[] = [
{
accessorKey: "name",
header: "Name",
},
{
id: "actions",
header: "",
cell: () => <button type="button">Actions</button>,
enableSorting: false,
},
];
const metadata: MetaDataProps = {
pagination: {
page: 1,
pages: 1,
count: 7,
},
version: "v1",
};
describe("DataTable", () => {
describe("when toolbar right content is provided", () => {
it("should render it before the total entries count", () => {
// Given
render(
<DataTable
columns={columns}
data={[{ name: "Finding A" }]}
metadata={metadata}
toolbarRightContent={<span>Include muted findings</span>}
/>,
);
// When
const toolbarContent = screen.getByText("Include muted findings");
const totalEntries = screen.getByText("7 Total Entries");
const tableContainerText =
toolbarContent.parentElement?.textContent ?? "";
// Then
expect(toolbarContent).toBeInTheDocument();
expect(totalEntries).toBeInTheDocument();
expect(tableContainerText.indexOf("Include muted findings")).toBeLessThan(
tableContainerText.indexOf("7 Total Entries"),
);
});
it("should stack the right content below search when the container is narrow", () => {
// Given
render(
<DataTable
columns={columns}
data={[{ name: "Finding A" }]}
metadata={metadata}
showSearch
toolbarRightContent={<span>Include muted findings</span>}
/>,
);
// When
const toolbar = screen.getByTestId("data-table-toolbar");
const rightContent = screen.getByTestId("data-table-toolbar-right");
// Then: md is a container query app-wide, so this also reacts when the
// side panel shrinks the page; the right group stays on one row.
expect(toolbar).toHaveClass("flex-col");
expect(toolbar).toHaveClass("md:flex-row");
expect(rightContent).toHaveClass("w-full");
expect(rightContent).toHaveClass("md:w-auto");
expect(rightContent).toHaveClass("flex-wrap", "items-center");
});
});
describe("when an actions column is present", () => {
it("should keep the actions header and cells sticky on the right edge", () => {
// Given
render(
<DataTable
columns={columnsWithActions}
data={[{ name: "Finding A" }]}
/>,
);
// When
const columnHeaders = screen.getAllByRole("columnheader");
const nameHeader = columnHeaders[0];
const actionsHeader = columnHeaders.at(-1);
expect(actionsHeader).toBeDefined();
const actionsHeaderElement = actionsHeader as HTMLElement;
const nameCell = screen.getByText("Finding A").closest("td");
const actionsCell = screen
.getByRole("button", {
name: "Actions",
})
.closest("td");
// Then
expect(nameHeader).not.toHaveClass("sticky");
expect(nameCell).not.toHaveClass("sticky");
expect(nameHeader).toHaveClass("pr-6");
expect(nameCell).toHaveClass("pr-6");
expect(actionsHeaderElement).not.toHaveClass("sticky");
expect(actionsHeaderElement).not.toHaveClass("pr-6");
expect(actionsHeaderElement).not.toHaveClass("right-0");
expect(actionsHeaderElement).not.toHaveClass("z-20");
expect(actionsHeaderElement).toHaveClass("bg-bg-neutral-tertiary");
expect(actionsHeaderElement).toHaveClass("border-y");
expect(actionsHeaderElement).toHaveClass("last:border-r");
expect(actionsHeaderElement).toHaveClass("last:rounded-r-full");
expect(actionsHeaderElement).not.toHaveClass("bg-transparent");
expect(actionsHeaderElement).not.toHaveClass("border-y-0");
expect(actionsHeaderElement).not.toHaveClass("border-l-0");
expect(actionsHeaderElement).not.toHaveClass("border-r-0");
expect(actionsHeaderElement).not.toHaveClass("last:border-r-0");
expect(actionsHeaderElement).not.toHaveClass("backdrop-blur-none");
expect(actionsHeaderElement).not.toHaveClass("last:rounded-r-none");
expect(actionsHeaderElement).not.toHaveClass("before:bg-gradient-to-r");
expect(actionsHeaderElement).not.toHaveClass("before:content-['']");
expect(actionsHeaderElement).not.toHaveClass("after:content-['']");
expect(actionsHeaderElement).not.toHaveClass("after:rounded-r-full");
expect(actionsHeaderElement.querySelector("div")).not.toBeInTheDocument();
expect(actionsCell).toHaveClass("sticky");
expect(actionsCell).not.toHaveClass("pr-6");
expect(actionsCell).toHaveClass("right-0");
expect(actionsCell).toHaveClass("z-20");
expect(actionsCell).toHaveClass("bg-bg-neutral-secondary");
expect(actionsCell).toHaveClass("last:rounded-r-none!");
expect(actionsCell).not.toHaveClass("border-l");
expect(actionsCell).toHaveClass("before:bg-gradient-to-r");
expect(actionsCell).toHaveClass("before:from-transparent");
expect(actionsCell).toHaveClass("before:to-bg-neutral-secondary");
});
});
});