diff --git a/ui/src/App.advanced-tools-routing.test.tsx b/ui/src/App.advanced-tools-routing.test.tsx new file mode 100644 index 00000000000..69df1267449 --- /dev/null +++ b/ui/src/App.advanced-tools-routing.test.tsx @@ -0,0 +1,129 @@ +// @vitest-environment jsdom + +import type { ReactNode } from "react"; +import { flushSync } from "react-dom"; +import { createRoot } from "react-dom/client"; +import { MemoryRouter } from "react-router-dom"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { App } from "./App"; + +// jsdom rejects a custom-property marker emitted by Sandpack's transitive +// styles. The advanced-tools route does not exercise Sandpack, so keep that +// eager import from obscuring the routing assertion. +vi.hoisted(() => { + const sheetProto = window.CSSStyleSheet.prototype as unknown as { + insertRule: (rule: string, index?: number) => number; + __pap17809Patched?: boolean; + }; + if (!sheetProto.__pap17809Patched) { + const original = sheetProto.insertRule; + sheetProto.insertRule = function patched(this: CSSStyleSheet, rule: string, index?: number) { + try { + return original.call(this, rule, index); + } catch { + try { + return original.call(this, ".pap17809-noop{}", index); + } catch { + return this.cssRules?.length ?? 0; + } + } + }; + sheetProto.__pap17809Patched = true; + } +}); + +vi.mock("./components/Layout", async () => { + const { Outlet } = await import("react-router-dom"); + return { Layout: () => }; +}); + +vi.mock("./components/CloudAccessGate", async () => { + const { Outlet } = await import("react-router-dom"); + return { CloudAccessGate: () => }; +}); + +vi.mock("./components/AppsExperimentalGate", async () => { + const { Outlet } = await import("react-router-dom"); + return { AppsExperimentalGate: () => }; +}); + +// Treat both EE host features as enabled. If a static gated route shadows the +// dynamic route, this test still enters it and observes the missing :tab param. +vi.mock("./components/HostFeatureGate", async () => { + const { Outlet } = await import("react-router-dom"); + return { HostFeatureGate: () => }; +}); + +vi.mock("./components/OnboardingWizardVariant", () => ({ + OnboardingWizardVariant: () => null, +})); + +// Keep the real App route tree and real router params. The sentinel mirrors the +// tab choice that ToolsAccess makes: an absent param falls back to run-your-own. +vi.mock("./pages/tools/AdvancedToolsRoute", async () => { + const { useParams } = await import("react-router-dom"); + return { + AdvancedToolsRoute: () => { + const { tab } = useParams<{ tab?: string }>(); + if (tab === "profiles") return
PROFILES_TAB_MOUNTED
; + if (tab === "gateways") return
GATEWAYS_TAB_MOUNTED
; + return
RUN_YOUR_OWN_TAB_MOUNTED
; + }, + }; +}); + +const PAP_COMPANY = { + id: "company-1", + name: "Paperclip", + issuePrefix: "PAP", + status: "active", +}; + +vi.mock("./context/CompanyContext", () => ({ + useCompany: () => ({ + companies: [PAP_COMPANY], + selectedCompanyId: PAP_COMPANY.id, + selectedCompany: PAP_COMPANY, + loading: false, + }), + CompanyProvider: ({ children }: { children: ReactNode }) => <>{children}, +})); + +function renderAppAt(container: HTMLElement, path: string) { + const root = createRoot(container); + flushSync(() => { + root.render( + + + , + ); + }); + return root; +} + +describe("App advanced tools routing", () => { + let container: HTMLDivElement; + + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + }); + + afterEach(() => { + container.remove(); + document.body.innerHTML = ""; + vi.clearAllMocks(); + }); + + it.each([ + ["profiles", "PROFILES_TAB_MOUNTED"], + ["gateways", "GATEWAYS_TAB_MOUNTED"], + ])("preserves the %s tab param through the real prefixed route tree", async (tab, expected) => { + const root = renderAppAt(container, `/PAP/apps/advanced/${tab}`); + + await vi.waitFor(() => expect(container.textContent).toContain(expected)); + expect(container.textContent).not.toContain("RUN_YOUR_OWN_TAB_MOUNTED"); + + flushSync(() => root.unmount()); + }); +}); diff --git a/ui/src/App.tsx b/ui/src/App.tsx index b357d91fde1..ca625c489f2 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -148,11 +148,9 @@ function boardRoutes() { } /> } /> } /> - } /> } /> }> - } /> } /> } /> } />