/** * Who may open the Directory, and who may open the console. * * Two predicates that used to be one, so what is worth pinning is that they have * not quietly become one again. People Management and Course Management are * the roster a coordinator works from, so faculty need * it; the console configures the workspace, so they do not get that. A student * gets neither, in every identity. * * Asserted through the doors rather than only through the predicate, because a * predicate nobody reads is not a gate: the chips on home, the rows in the product * switcher, and the routes all have to answer the same way. */ import { render, screen } from "@testing-library/react" import { MemoryRouter } from "react-router" import { beforeEach, describe, expect, it, vi } from "vitest" import { HomeDirectorySection } from "@/components/product-home/product-home-parts" import { ProductSwitcherMenuItems } from "@/components/product-switcher-menu-items" import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { TooltipProvider } from "@/components/ui/tooltip" import { buildProductHomeInventory } from "@/lib/product-home" import { DEFAULT_LOGIN_SESSION, setLoginSession, type WorkspaceRole, } from "@/lib/login-session" import { canReadDirectory, isWorkspaceAdmin } from "@/lib/workspace-role" // Which product is open is not under test, and the real provider boots a tenant // catalogue over `fetch` on mount. vi.mock("@/contexts/product-context", () => ({ useProduct: () => ({ product: "exxat-prism", customProducts: [], activeCustomIndex: undefined, hiddenProducts: [], }), useProductSwitch: () => () => {}, })) function signInAs(role: WorkspaceRole) { setLoginSession({ ...DEFAULT_LOGIN_SESSION, role }) } function renderIn(ui: React.ReactNode) { return render( {ui} , ) } const RECORDS = ["People Management", "Course Management"] beforeEach(() => { window.localStorage.clear() }) describe("Directory chips on the products home", () => { it.each(["administrator", "member"])("open for %s", role => { signInAs(role) renderIn() expect(screen.getByText("Directory")).toBeInTheDocument() for (const label of RECORDS) { expect(screen.getByRole("link", { name: label })).toBeInTheDocument() } expect( screen.queryByRole("link", { name: "Personnel Management" }), ).not.toBeInTheDocument() }) it("are absent for a student, who would be reading their whole cohort", () => { signInAs("student") const { container } = renderIn() expect(container).toBeEmptyDOMElement() }) }) describe("Directory rows in the product switcher", () => { function renderSwitcher() { return renderIn( Apps , ) } it("are there for faculty, who place the people they look up", () => { signInAs("member") renderSwitcher() for (const label of RECORDS) { expect(screen.getByRole("menuitem", { name: new RegExp(label) })).toBeInTheDocument() } expect( screen.queryByRole("menuitem", { name: /Personnel Management/ }), ).not.toBeInTheDocument() }) it("are gone for a student", () => { signInAs("student") renderSwitcher() for (const label of RECORDS) { expect(screen.queryByRole("menuitem", { name: new RegExp(label) })).not.toBeInTheDocument() } }) }) describe("the console keeps the narrower gate", () => { const hasAdminCard = () => buildProductHomeInventory([], []).owned.some(card => card.product === "exxat-admin") it("offers Administrator to an administrator only", () => { signInAs("administrator") expect(hasAdminCard()).toBe(true) signInAs("member") expect(hasAdminCard()).toBe(false) // The point of the split: the same session that is refused the console still // gets the roster. expect(canReadDirectory()).toBe(true) expect(isWorkspaceAdmin()).toBe(false) }) it("refuses a single-app student both", () => { signInAs("student") expect(hasAdminCard()).toBe(false) expect(canReadDirectory()).toBe(false) }) it("refuses a student with two apps both Directory and console", () => { setLoginSession({ ...DEFAULT_LOGIN_SESSION, role: "student", products: ["exxat-prism", "exxat-one-schools"], }) expect(hasAdminCard()).toBe(false) expect(canReadDirectory()).toBe(false) expect(isWorkspaceAdmin()).toBe(false) }) })