import { describe, expect, it } from "vitest"; import { PERMS } from "@/lib/permission-slugs"; import { anyCapability, type CanonicalHousekeepingHref, type HousekeepingDomainManifest, } from "../contracts"; import { createHousekeepingRegistry } from "../registry"; import { matchHousekeepingRoute } from "./match-route"; const requirement = anyCapability(PERMS.USERS_VIEW); function peopleManifest( routes: HousekeepingDomainManifest["routes"], ): HousekeepingDomainManifest { return { id: "people", labelKey: "pages.housekeeping.domains.people.title", descriptionKey: "pages.housekeeping.domains.people.description", iconId: "users", canonicalHref: "/ase-next/people", capability: requirement, routes, searchProviders: [], inboxSources: [], widgets: [], }; } function route( id: string, href: CanonicalHousekeepingHref, ): HousekeepingDomainManifest["routes"][number] { return { id, labelKey: `pages.housekeeping.routes.${id}`, href, capability: requirement, }; } const registry = createHousekeepingRegistry([ peopleManifest([ route("people.user-detail", "/ase-next/people/users/:id"), route("people.user-create", "/ase-next/people/users/new"), route("people.users", "/ase-next/people/users"), route("people.ticket-note", "/ase-next/people/tickets/:ticketId/notes/:noteId"), route("people.literal-tool", "/ase-next/people/tools/a+b[1]"), ]), ]); describe("matchHousekeepingRoute", () => { it("matches a static route without parameters", () => { expect(matchHousekeepingRoute(registry, "/ase-next/people/users")).toEqual({ routeId: "people.users", domain: "people", params: {}, canonicalHref: "/ase-next/people/users", }); }); it("prefers a static route over a dynamic route with the same depth", () => { expect(matchHousekeepingRoute(registry, "/ase-next/people/users/new")).toEqual({ routeId: "people.user-create", domain: "people", params: {}, canonicalHref: "/ase-next/people/users/new", }); }); it("matches a dynamic segment and decodes its parameter", () => { expect( matchHousekeepingRoute(registry, "/ase-next/people/users/Ada%20Lovelace"), ).toEqual({ routeId: "people.user-detail", domain: "people", params: { id: "Ada Lovelace" }, canonicalHref: "/ase-next/people/users/Ada%20Lovelace", }); }); it("matches nested dynamic segments independently", () => { expect( matchHousekeepingRoute( registry, "/ase-next/people/tickets/42/notes/follow-up", ), ).toEqual({ routeId: "people.ticket-note", domain: "people", params: { ticketId: "42", noteId: "follow-up" }, canonicalHref: "/ase-next/people/tickets/42/notes/follow-up", }); }); it("treats regular-expression metacharacters as literal segment text", () => { expect( matchHousekeepingRoute(registry, "/ase-next/people/tools/a+b[1]"), )?.toMatchObject({ routeId: "people.literal-tool" }); expect( matchHousekeepingRoute(registry, "/ase-next/people/tools/ab1"), ).toBeNull(); }); it.each([ { routes: [ route("people.kind-settings", "/ase-next/people/:kind/settings"), route("people.user-detail", "/ase-next/people/users/:id"), ], }, { routes: [ route("people.user-detail", "/ase-next/people/users/:id"), route("people.kind-settings", "/ase-next/people/:kind/settings"), ], }, ])( "prefers the earliest static segment regardless of registration order", ({ routes }) => { const orderedRegistry = createHousekeepingRegistry([ peopleManifest(routes), ]); expect( matchHousekeepingRoute(orderedRegistry, "/ase-next/people/users/settings"), )?.toMatchObject({ routeId: "people.user-detail", params: { id: "settings" }, }); }, ); it.each([ "/ase-next/people/unknown", "/ase-next/economy/users", "/ase-next/system/users/Ada", ])("returns null for unknown or cross-domain path %s", (path) => { expect(matchHousekeepingRoute(registry, path)).toBeNull(); }); it.each([ "ase/people/users", "/ase-next//people/users", "/ase-next/people/users/", "/ase-next/people/users?tab=active", "/ase-next/people/users/%", "/ase-next/people/users/%2Fsystem", "/ase-next/people/users/..", "/ase-next\\people\\users", ])("fails closed for malformed canonical path %s", (path) => { expect(matchHousekeepingRoute(registry, path)).toBeNull(); }); });