import assert from "node:assert/strict"; import test from "node:test"; import { buildPluginChrome } from "./chrome.ts"; import { DEFAULT_MENU, type MenuConfig } from "./menu-config.ts"; import type { NavNode } from "./nav.ts"; import type { Plugin } from "../plugin-host/plugin.ts"; const scheduling: Plugin = { apiVersion: "1.0.0", id: "scheduling", nav: [{ children: [{ href: "/scheduling/shifts", id: "scheduling:shifts", label: "Shifts", permission: "scheduling:read" }], icon: "i-cal", id: "scheduling", label: "Scheduling", }], }; // A plugin with a public nav node (reachable by anyone, signed in or not). const portal: Plugin = { apiVersion: "1.0.0", id: "portal", nav: [{ href: "/portal", id: "portal", label: "Portal", public: true }] }; // A gated section fragment like the admin plugin's nav: the header carries the permission, so // composeNav drops the whole subtree for a non-holder (the admin screens ship as a drop-in plugin). const adminLike: Plugin = { apiVersion: "1.0.0", id: "admin", nav: [{ children: [ { href: "/admin/users", id: "users", label: "Users" }, { href: "/admin/groups", id: "groups", label: "Groups" }, ], icon: "i-shield", id: "admin", label: "Admin", permission: "admin", }], }; const labels = (nodes: NavNode[]): string[] => nodes.map((n) => n.label); test("anonymous: brand from menu, Guest user; the gated Dashboard link is hidden, a public node still shows", () => { const chrome = buildPluginChrome({ menu: DEFAULT_MENU, plugins: [scheduling, portal] }); assert.equal(chrome.brand.name, DEFAULT_MENU.branding.name); assert.equal(chrome.user.name, "Guest"); // Dashboard points at the gated /dashboard — showing it to an anonymous visitor only dead-ends them // at /login, so it's dropped. Scheduling's only child is gated (dropped); the explicitly public // Portal node remains. assert.deepEqual(labels(chrome.nav), ["Portal"]); }); test("anonymous shell Sign-in link carries the current page as return_to", () => { assert.equal(buildPluginChrome({ menu: DEFAULT_MENU }).signInHref, "/login"); // no path known assert.equal(buildPluginChrome({ currentPath: "/portal", menu: DEFAULT_MENU }).signInHref, "/login?return_to=%2Fportal"); }); test("a permission holder sees the Dashboard link + plugin nav; current path opens the active leaf", () => { const chrome = buildPluginChrome({ currentPath: "/scheduling/shifts", menu: DEFAULT_MENU, plugins: [scheduling], user: { email: "ada@x.io", id: "u1", roles: ["scheduling:read"] }, }); assert.deepEqual(labels(chrome.nav), ["Dashboard", "Scheduling"]); // Dashboard shown to a signed-in user const section = chrome.nav.find((n) => n.label === "Scheduling")!; assert.equal(section.open, true); // ancestor of the current leaf opened assert.equal(section.children!.find((c) => c.label === "Shifts")!.current, true); assert.equal(chrome.user.name, "ada"); // email local part }); test("a gated section (like the admin plugin) shows to a holder; a sub-path marks its base leaf current", () => { const chrome = buildPluginChrome({ currentPath: "/admin/users/new", menu: DEFAULT_MENU, plugins: [adminLike], user: { email: "a@b.c", id: "u1", roles: ["admin"] } }); const admin = chrome.nav.find((n) => n.label === "Admin")!; assert.ok(admin); // gated section visible to an admin assert.equal(admin.open, true); // ancestor of the current leaf opened // /admin/users/new is under the Users base (/admin/users) → that leaf is current, not Groups. assert.equal(admin.children!.find((c) => c.label === "Users")!.current, true); assert.equal(admin.children!.find((c) => c.label === "Groups")!.current, undefined); }); test("branding logo + default theme flow through when set", () => { const menu: MenuConfig = { branding: { logo: "/logo.svg", name: "Acme", theme: "dark" }, override: {} }; const chrome = buildPluginChrome({ menu }); assert.equal(chrome.brand.logo, "/logo.svg"); assert.equal(chrome.brand.name, "Acme"); assert.equal(chrome.theme, "dark"); });