import { readdirSync, statSync } from "node:fs"; import * as path from "node:path"; import { describe, it, expect, vi } from "vitest"; import { AdminSidebar } from "./AdminSidebar"; import type { SideBarItem } from "./SidebarItem"; import { canonicalAdminRouteSlugs, disabledPluginRouteSlugs } from "~/utils/app/PublicRouteSegments"; import { resolveRepoPath } from "~/utils/testing/repoPaths"; function flattenSidebarItems(items: SideBarItem[]): SideBarItem[] { return items.flatMap((item) => [item, ...flattenSidebarItems(item.items ?? [])]); } function walkRouteFiles(directory: string): string[] { return readdirSync(directory).flatMap((entry) => { const absolutePath = path.join(directory, entry); const stats = statSync(absolutePath); if (stats.isDirectory()) { return walkRouteFiles(absolutePath); } if (!/\.(tsx?|jsx?)$/.test(entry) || /\.(test|spec)\.(tsx?|jsx?)$/.test(entry) || entry.endsWith(".server.ts")) { return []; } return [absolutePath]; }); } function routePathFromAdminRouteFile(absolutePath: string): string | null { const routeRoot = resolveRepoPath("apps/web/app/routes/admin"); const relativePath = path.relative(routeRoot, absolutePath).replace(/\\/g, "/"); if (relativePath === "404.tsx" || relativePath.startsWith("internal/")) { return null; } const withoutExtension = relativePath.replace(/\.(tsx?|jsx?)$/, ""); const parts = withoutExtension .split("/") .flatMap((part) => part.split(".")) .filter((part) => part && part !== "index" && !part.startsWith("__")); return parts.join("/"); } function collectAdminRoutePaths(): string[] { return walkRouteFiles(resolveRepoPath("apps/web/app/routes/admin")) .map(routePathFromAdminRouteFile) .filter((routePath): routePath is string => routePath !== null); } function targetMatchesRoute(target: string, routePath: string): boolean { const targetParts = target.split("/").filter(Boolean); const routeParts = routePath.split("/").filter(Boolean); if (targetParts.length !== routeParts.length) { return false; } return routeParts.every((routePart, index) => routePart.startsWith("$") || routePart === targetParts[index]); } function normalizeSidebarPath(pathname: string): string | null { if (!pathname.startsWith("/") || pathname === "/") { return null; } return pathname.split(/[?#]/)[0]?.replace(/^\/+|\/+$/g, "") || null; } describe("AdminSidebar", () => { const t = vi.fn().mockImplementation((v: string) => v) as any; it("returns an array of sidebar items", () => { const sidebar = AdminSidebar({ t, appConfiguration: null }); const serialized = JSON.stringify(sidebar); const allPaths = flattenSidebarItems(sidebar) .map((item) => item.path) .filter((path): path is string => Boolean(path) && path !== "/" && path.startsWith("/")); expect(Array.isArray(sidebar)).toBe(true); expect(serialized).toContain("/dashboard"); expect(serialized).toContain("/accounts"); expect(serialized).toContain("/analytics/overview"); expect(serialized).toContain("/audit-trails"); expect(serialized).toContain("/notifications/messages"); expect(serialized).toContain("/feature-flags/flags"); expect(serialized).toContain("/onboarding/onboardings"); expect(serialized).toContain("/metrics/summary"); expect(serialized).toContain("/settings/profile"); expect(sidebar).toEqual( expect.arrayContaining([ expect.objectContaining({ items: expect.arrayContaining([ expect.objectContaining({ id: "ui.nav.admin.dashboard", path: "/dashboard" }), ]), }), ]) ); expect(sidebar.at(-1)?.items).toEqual( expect.arrayContaining([ expect.objectContaining({ id: "ui.nav.admin.switchToApp", path: "/", exact: true }), ]) ); const emittedPaths = serialized.match(/"path":"[^"]*"/g) ?? []; expect(emittedPaths.filter((path) => path.includes("/admin/"))).toEqual([]); expect(serialized).not.toContain("/admin/pages"); expect(serialized).not.toContain("/admin/blog"); expect(serialized).not.toContain("/admin/crons"); expect(serialized).not.toContain("/email-marketing"); expect(serialized).not.toContain("/forms"); expect(serialized).not.toContain("/help-desk"); expect(serialized).not.toContain("/knowledge-base"); expect(serialized).not.toContain("/portals"); expect(serialized).not.toContain("/prompts"); expect(serialized).not.toContain("/workflow-engine"); expect(serialized).not.toContain("\"path\":\"/crm\""); expect(serialized).not.toContain("ui.nav.admin.crm"); expect(allPaths.map((path) => path.split("/").filter(Boolean)[0])).not.toEqual(expect.arrayContaining([...disabledPluginRouteSlugs])); expect(allPaths.map((path) => path.split("/").filter(Boolean)[0])).toEqual(expect.arrayContaining(["dashboard", "accounts", "settings"])); allPaths.forEach((path) => { expect(canonicalAdminRouteSlugs).toContain(path.split("/").filter(Boolean)[0] as any); }); sidebar.forEach((item) => { expect(typeof item.title).toBe("string"); expect(typeof item.path).toBe("string"); if (item.items) { expect(Array.isArray(item.items)).toBe(true); item.items.forEach((subItem) => { expect(typeof subItem.title).toBe("string"); expect(typeof subItem.path).toBe("string"); }); } }); }); it("only emits canonical admin sidebar paths backed by route files", () => { const routePaths = collectAdminRoutePaths(); const sidebar = AdminSidebar({ t, appConfiguration: { app: { features: { tenantApiKeys: true, }, }, } as any, }); const sidebarTargets = flattenSidebarItems(sidebar) .map((item) => item.path) .filter((pathname): pathname is string => Boolean(pathname)) .map(normalizeSidebarPath) .filter((pathname): pathname is string => pathname !== null); expect(sidebarTargets).not.toContain("admin"); expect(sidebarTargets.map((pathname) => pathname.split("/")[0])).not.toEqual(expect.arrayContaining([...disabledPluginRouteSlugs])); const missingTargets = sidebarTargets.filter((target) => !routePaths.some((routePath) => targetMatchesRoute(target, routePath))); expect(missingTargets).toEqual([]); }); });