// @vitest-environment happy-dom import { describe, it, expect, vi, beforeEach } from "vitest"; import { render } from "@testing-library/react"; import { cleanup } from "@testing-library/react"; vi.mock("./useAuth", () => ({ useAuth: vi.fn(), })); import { useAuth } from "./useAuth"; import { RoleGate } from "./RoleGate"; function mockUser(role: "admin" | "user" | "viewer" | null) { (useAuth as unknown as ReturnType).mockReturnValue({ user: role ? { id: 1, username: "x", role } : null, status: role ? "authenticated" : "unauthenticated", }); } describe("RoleGate", () => { beforeEach(() => { vi.resetAllMocks(); cleanup(); }); it("renders children when role is allowed", () => { mockUser("admin"); const { container } = render( ); expect(container.querySelector("button")).not.toBeNull(); expect(container.textContent).toContain("Do thing"); }); it("renders disabled (via span wrap) when role is NOT allowed", () => { mockUser("viewer"); const { container } = render( ); const buttons = container.querySelectorAll("button"); expect(buttons.length).toBeGreaterThan(0); // The single child element is wrapped in a span that carries the // disabled visual signal — opacity-50 plus pointer-events-none so // the inner control can't be clicked. const wrapper = container.querySelector("span"); expect(wrapper).not.toBeNull(); expect(wrapper?.className).toContain("opacity-50"); expect(wrapper?.className).toContain("pointer-events-none"); // The wrapper also carries a native `title` attribute that // explains why the affordance is disabled. expect(wrapper?.getAttribute("title")).toMatch(/role \(viewer\) cannot perform this action/); }); it("renders fallback when provided and role not allowed", () => { mockUser("viewer"); const { container } = render( NO}> ); expect(container.textContent).toContain("NO"); // Fallback path does NOT wrap children — original button is absent. expect(container.querySelector("button")).toBeNull(); }); it("renders nothing when user is null", () => { mockUser(null); const { container } = render( ); expect(container.querySelector("button")).toBeNull(); expect(container.textContent).not.toContain("Do thing"); }); });