| 1 | import assert from "node:assert/strict"; |
| 2 | import { JSDOM } from "jsdom"; |
| 3 | import { baseSettings } from "../test-support/settingsTestFixtures"; |
| 4 | import { installDesktopHostStub } from "./desktopHostStub"; |
| 5 | |
| 6 | const dom = new JSDOM('<!doctype html><div id="workspace"><button id="opener">Settings</button></div><div id="root"></div>', { url: "http://localhost", pretendToBeVisual: true }); |
| 7 | Object.assign(globalThis, { window: dom.window, document: dom.window.document, HTMLElement: dom.window.HTMLElement, Node: dom.window.Node, Event: dom.window.Event, CustomEvent: dom.window.CustomEvent, localStorage: dom.window.localStorage, sessionStorage: dom.window.sessionStorage, IS_REACT_ACT_ENVIRONMENT: true }); |
| 8 | Object.defineProperty(globalThis, "navigator", { configurable: true, value: dom.window.navigator }); |
| 9 | globalThis.requestAnimationFrame = dom.window.requestAnimationFrame.bind(dom.window); |
| 10 | globalThis.cancelAnimationFrame = dom.window.cancelAnimationFrame.bind(dom.window); |
| 11 | window.matchMedia = () => ({ matches: true, addEventListener() {}, removeEventListener() {} }) as unknown as MediaQueryList; |
| 12 | window.scrollTo = () => {}; |
| 13 | const settings = baseSettings("standard"); |
| 14 | installDesktopHostStub(({ main: { App: { Settings: async () => settings, FetchAllProviderModels: async () => ({}) } } }).main.App); |
| 15 | const { default: React, act } = await import("react"); |
| 16 | const { createRoot } = await import("react-dom/client"); |
| 17 | const { useManagementWorkspace } = await import("../lib/useManagementWorkspace"); |
| 18 | const { useAppNavigationStore } = await import("../store/appNavigation"); |
| 19 | const { SettingsPanel } = await import("../components/SettingsPanel"); |
| 20 | const { LocaleProvider } = await import("../lib/i18n"); |
| 21 | const root = createRoot(document.getElementById("root")!); |
| 22 | const opener = document.getElementById("opener")!; |
| 23 | const workspaceRef = { current: document.getElementById("workspace") as HTMLDivElement }; |
| 24 | opener.focus(); |
| 25 | useAppNavigationStore.getState().openPage({ kind: "settings", tab: "general" }); |
| 26 | function WorkspaceBoundary({ children }: { children: React.ReactNode }) { useManagementWorkspace(workspaceRef, true); return children; } |
| 27 | let closes = 0; |
| 28 | const routes: string[] = []; |
| 29 | function RoutedSettings() { |
| 30 | const route = useAppNavigationStore((state) => state.page); |
| 31 | const navigate = useAppNavigationStore((state) => state.setSettingsTarget); |
| 32 | return <SettingsPanel initialTab={route.kind === "settings" ? route.tab : undefined} desktopPlatform="linux" onClose={() => { closes++; }} onChanged={() => {}} onUseSubagent={() => {}} onNavigate={(tab) => { routes.push(tab); navigate(tab); }} />; |
| 33 | } |
| 34 | await act(async () => { |
| 35 | root.render(<LocaleProvider><WorkspaceBoundary><RoutedSettings /></WorkspaceBoundary></LocaleProvider>); |
| 36 | }); |
| 37 | const page = document.querySelector<HTMLElement>(".settings-screen")!; |
| 38 | assert.ok(page); |
| 39 | assert.equal(workspaceRef.current.inert, true, "workspace cannot receive input while settings are open"); |
| 40 | assert.equal(document.querySelector(".management-modal-backdrop"), null); |
| 41 | assert.equal(document.activeElement?.textContent?.trim(), "Back to workspace"); |
| 42 | await act(async () => { document.dispatchEvent(new dom.window.KeyboardEvent("keydown", { key: "Escape", bubbles: true })); }); |
| 43 | assert.equal(closes, 0, "Escape must not leave a settings page"); |
| 44 | await act(async () => { page.dispatchEvent(new dom.window.MouseEvent("mousedown", { bubbles: true })); }); |
| 45 | assert.equal(closes, 0, "clicking the page background must not exit"); |
| 46 | const models = Array.from(page.querySelectorAll<HTMLButtonElement>("nav button")).find((button) => button.textContent?.trim() === "Model preferences")!; |
| 47 | assert.ok(models); |
| 48 | await act(async () => models.click()); |
| 49 | assert.deepEqual(routes, ["models"]); |
| 50 | assert.ok(page.querySelector('[aria-current="page"]')?.textContent?.includes("Model preferences")); |
| 51 | for (const tab of ["model-stats", "providers", "models", "providers"] as const) { |
| 52 | const navButtons = Array.from(page.querySelectorAll<HTMLButtonElement>("nav button")); |
| 53 | const label = tab === "providers" ? "Model services" : tab === "models" ? "Model preferences" : "Usage stats"; |
| 54 | const button = navButtons.find((item) => item.textContent?.trim() === label); |
| 55 | assert.ok(button, `${label} navigation exists`); |
| 56 | await act(async () => button.click()); |
| 57 | assert.deepEqual(useAppNavigationStore.getState().page, { kind: "settings", tab }); |
| 58 | assert.equal(useAppNavigationStore.getState().lastSettingsTarget, tab); |
| 59 | assert.equal(page.querySelector('[aria-current="page"]')?.textContent?.trim(), label); |
| 60 | assert.ok(page.querySelector(`.settings-page--${tab}`), `${label} remains rendered after route synchronization`); |
| 61 | } |
| 62 | const back = Array.from(page.querySelectorAll<HTMLButtonElement>("button")).find((button) => button.textContent?.trim() === "Back to workspace")!; |
| 63 | await act(async () => back.click()); |
| 64 | assert.equal(closes, 1); |
| 65 | await act(async () => useAppNavigationStore.getState().returnToWorkspace()); |
| 66 | await act(async () => root.unmount()); |
| 67 | assert.equal(workspaceRef.current.inert, false); |
| 68 | assert.equal(document.activeElement, opener, "return restores the original entry's focus"); |
| 69 | dom.window.close(); |
| 70 | console.log("PASS full-page settings, navigation, explicit return, Escape and focus restoration"); |
| 71 |