// @vitest-environment jsdom
import { act } from "react";
import { createRoot } from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import { afterEach, expect, it, vi } from "vitest";
import { CloudAccessGate } from "./CloudAccessGate";
import { ShellConfigurationGate } from "./ShellConfigurationGate";
import { useStreamlinedUiEnabled } from "../hooks/useStreamlinedUiEnabled";
vi.mock("@/lib/router", async () => await import("react-router-dom"));
vi.mock("./AnimatedPaperclipIcon", () => ({ PaperclipLoading: () =>
Loading shell
}));
const { health, session, access, settings } = vi.hoisted(() => ({ health: vi.fn(), session: vi.fn(), access: vi.fn(), settings: vi.fn() }));
vi.mock("../api/health", () => ({ healthApi: { get: health } }));
vi.mock("../api/auth", () => ({ authApi: { getSession: session } }));
vi.mock("../api/access", () => ({ accessApi: { getCurrentBoardAccess: access } }));
vi.mock("../api/instanceSettings", () => ({ instanceSettingsApi: { getExperimental: settings } }));
(globalThis as Record).IS_REACT_ACT_ENVIRONMENT = true;
afterEach(() => vi.resetAllMocks());
it.each([
["/PAP/issues/PAP-1", true],
["/PAP/issues/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", true],
["/PAP/issues/all", false],
["/PAP/issues", false],
["/PAP/chats/agent", false],
["/PAP/apps", false],
])("prepares only guarded task data behind the loading view while preferences load: %s", async (path, permitted) => {
const container = document.createElement("div");
const root = createRoot(container);
try {
await act(async () => root.render(
}>
Guarded task content} />
));
expect(container.textContent).toContain("Loading shell");
expect(container.textContent?.includes("Guarded task content")).toBe(permitted);
const prepared = container.querySelector('[data-testid="shell-prepared-outlet"]');
expect(prepared?.hidden ?? null).toBe(permitted ? true : null);
if (prepared) expect(prepared.hasAttribute("inert")).toBe(true);
} finally { await act(async () => root.unmount()); }
});
it.each([false, true])("runs access checks while shell settings are pending (membership=%s)", async allowMembershipRequest => {
let resolveSettings!: (value: unknown) => void;
health.mockResolvedValue({ status: "ok", deploymentMode: "authenticated", bootstrapStatus: "ready" });
session.mockResolvedValue({ user: { id: "user" } });
access.mockResolvedValue({ companyIds: ["company"] });
settings.mockReturnValue(new Promise(resolve => { resolveSettings = resolve; }));
function RoutesUnderTest() {
const { loaded, enabled } = useStreamlinedUiEnabled();
return }>
}>
{enabled ? "Streamlined shell" : "Legacy shell"}} />
;
}
const container = document.createElement("div");
const root = createRoot(container);
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
try {
await act(async () => root.render());
await act(async () => { await new Promise(resolve => setTimeout(resolve, 20)); });
expect(health).toHaveBeenCalledTimes(1);
expect(session).toHaveBeenCalledTimes(1);
expect(access).toHaveBeenCalledTimes(1);
expect(container.textContent).not.toContain("Streamlined shell");
expect(container.textContent).not.toContain("Legacy shell");
await act(async () => { resolveSettings({ enableStreamlinedUi: false }); });
await act(async () => { await new Promise(resolve => setTimeout(resolve, 20)); });
expect(container.textContent).toBe("Legacy shell");
} finally {
await act(async () => root.unmount());
client.clear();
}
});
it("keeps prepared task content mounted without taking existing focus when the shell becomes visible", async () => {
const container = document.createElement("div");
const root = createRoot(container);
const input = document.createElement("input");
document.body.append(container, input);
const view = (loaded: boolean) =>
}>
Task data} />
;
try {
await act(async () => root.render(view(false)));
const task = container.querySelector('[data-testid="prepared-task"]');
expect(task).not.toBeNull();
input.focus();
await act(async () => root.render(view(true)));
await act(async () => { await new Promise(resolve => requestAnimationFrame(resolve)); });
expect(document.activeElement).toBe(input);
expect(container.querySelector('[data-testid="prepared-task"]')).toBe(task);
expect(container.querySelector('[data-testid="shell-prepared-outlet"]')?.hidden).toBe(false);
expect(container.querySelector('[data-testid="shell-configuration-loading"]')).toBeNull();
} finally { await act(async () => root.unmount()); container.remove(); input.remove(); }
});