import { Row } from "@tanstack/react-table"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { describe, expect, it, vi } from "vitest"; // The forms pull in server actions (`@/actions/users/users`) that can't run in // jsdom, so stub them with identifiable markers to assert which modal opens. vi.mock("../forms", () => ({ DeleteForm: ({ userId }: { userId: string }) => (
delete-form:{userId}
), EditForm: ({ userId }: { userId: string }) => (
edit-form:{userId}
), ExpelUserForm: ({ userId }: { userId: string }) => (
expel-form:{userId}
), })); import { DataTableRowActions } from "./data-table-row-actions"; interface RowOptions { id?: string; isCurrentUser?: boolean; canBeExpelled?: boolean; currentTenantId?: string; } const createRow = ({ id = "user-1", isCurrentUser, canBeExpelled, currentTenantId, }: RowOptions = {}) => ({ original: { id, attributes: { name: "Jane Doe", email: "jane@example.com", company_name: "Acme", role: { name: "admin" }, }, isCurrentUser, canBeExpelled, currentTenantId, }, }) as unknown as Row<{ id: string }>; const openMenu = async (user: ReturnType) => { await user.click(screen.getByRole("button", { name: "Open actions menu" })); }; describe("DataTableRowActions (users)", () => { it("always renders the Edit User action", async () => { const user = userEvent.setup(); render(); await openMenu(user); expect(screen.getByText("Edit User")).toBeInTheDocument(); }); it("shows Delete User only for the current user's row", async () => { const user = userEvent.setup(); render(); await openMenu(user); expect(screen.getByText("Delete User")).toBeInTheDocument(); expect(screen.getByText("Danger zone")).toBeInTheDocument(); }); it("does NOT show Delete User for another user's row", async () => { const user = userEvent.setup(); render(); await openMenu(user); expect(screen.queryByText("Delete User")).not.toBeInTheDocument(); }); it("does NOT show Delete User when isCurrentUser is undefined", async () => { const user = userEvent.setup(); render(); await openMenu(user); expect(screen.queryByText("Delete User")).not.toBeInTheDocument(); }); it("hides the Danger zone entirely when the user can neither be deleted nor expelled", async () => { const user = userEvent.setup(); render( , ); await openMenu(user); // Only the non-destructive Edit action remains. expect(screen.getByText("Edit User")).toBeInTheDocument(); expect(screen.queryByText("Danger zone")).not.toBeInTheDocument(); expect(screen.queryByText("Delete User")).not.toBeInTheDocument(); expect( screen.queryByText("Expel from organization"), ).not.toBeInTheDocument(); }); it("shows Expel but not Delete User for an expellable, non-current user", async () => { const user = userEvent.setup(); render( , ); await openMenu(user); expect(screen.getByText("Danger zone")).toBeInTheDocument(); expect(screen.getByText("Expel from organization")).toBeInTheDocument(); expect(screen.queryByText("Delete User")).not.toBeInTheDocument(); }); it("renders Delete User with destructive styling", async () => { const user = userEvent.setup(); render(); await openMenu(user); const menuItem = screen .getByText("Delete User") .closest("[role='menuitem']"); expect(menuItem).toBeInTheDocument(); expect(menuItem).toHaveClass("text-text-error-primary"); }); it("opens the delete confirmation modal when Delete User is selected", async () => { const user = userEvent.setup(); render( , ); await openMenu(user); await user.click(screen.getByText("Delete User")); expect(screen.getByText("Are you absolutely sure?")).toBeInTheDocument(); expect(screen.getByTestId("delete-form")).toHaveTextContent( "delete-form:user-42", ); }); });