/**
* 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)
})
})