| 1 | import assert from "node:assert/strict"; |
| 2 | import React, { act } from "react"; |
| 3 | import { createRoot } from "react-dom/client"; |
| 4 | import { JSDOM } from "jsdom"; |
| 5 | import { useTerminalPanelCommands } from "../app-runtime/useTerminalPanelCommands"; |
| 6 | import { useLayoutStore } from "../store/layout"; |
| 7 | import { useAppNavigationStore } from "../store/appNavigation"; |
| 8 | import { useTerminalStore } from "../store/terminal"; |
| 9 | import { AppBottomRegions } from "../app-shell/AppBottomRegions"; |
| 10 | import { TopicbarSessionActions } from "../components/TopicbarSessionActions"; |
| 11 | import { LocaleProvider, useT } from "../lib/i18n"; |
| 12 | import { ToastProvider } from "../lib/toast"; |
| 13 | |
| 14 | const dom = new JSDOM("<div id='root'></div>", { url: "http://localhost" }); |
| 15 | Object.assign(globalThis, { window: dom.window, document: dom.window.document, localStorage: dom.window.localStorage, |
| 16 | KeyboardEvent: dom.window.KeyboardEvent, IS_REACT_ACT_ENVIRONMENT: true }); |
| 17 | const root = createRoot(document.getElementById("root")!); |
| 18 | const originalCreate = useTerminalStore.getState().createSession; |
| 19 | const calls: string[] = []; |
| 20 | useTerminalStore.setState({ createSession: async (tab, path) => { calls.push(`${tab}:${path}`); return null; } }); |
| 21 | let commands!: ReturnType<typeof useTerminalPanelCommands>; |
| 22 | const noop = () => {}; |
| 23 | function Probe({ remote }: { remote: boolean }) { |
| 24 | const page = useAppNavigationStore(state => state.page); |
| 25 | commands = useTerminalPanelCommands({ tabId: "A", enabled: !remote, shortcutsEnabled: page.kind === "workspace" }); |
| 26 | const t = useT(); |
| 27 | return <> |
| 28 | <TopicbarSessionActions sessionHasContent={false} getSessionMarkdown={() => ""} exportSession={noop} |
| 29 | toggleTerminal={commands.toggleTerminalPanel} terminalEnabled={!remote} terminalOpen={false} openSessionSummary={noop} tasksOpen={false} /> |
| 30 | {remote && <AppBottomRegions terminal={{ open: false, contentVisible: true, remoteSurface: true, t, |
| 31 | panel: { tabId: "A", open: false, onClose: commands.closeTerminalPanel }, |
| 32 | resizer: { min: 100, max: 400, value: 200, onPointerDown: noop, onKeyDown: noop, onReset: noop }, |
| 33 | }} />} |
| 34 | </>; |
| 35 | } |
| 36 | const paint = (remote: boolean) => act(async () => root.render(<LocaleProvider><ToastProvider><Probe remote={remote} /></ToastProvider></LocaleProvider>)); |
| 37 | const key = (shiftKey = false) => document.dispatchEvent(new KeyboardEvent("keydown", { key: "`", ctrlKey: true, shiftKey, bubbles: true })); |
| 38 | try { |
| 39 | useLayoutStore.getState().setTerminalPanelOpen(false); |
| 40 | await paint(true); |
| 41 | const terminalButton = document.querySelector(".lucide-terminal")?.closest("button") |
| 42 | ?? [...document.querySelectorAll("button")].find(button => button.getAttribute("aria-label") === "Terminal"); |
| 43 | assert.ok(terminalButton); |
| 44 | assert.equal(terminalButton.disabled, true); |
| 45 | assert.equal(document.querySelector(".terminal-drawer")?.childElementCount, 0, "remote surface cannot mount a warm local TerminalPanel"); |
| 46 | await act(async () => { key(); key(true); commands.openTerminalForPath("remote-path"); }); |
| 47 | assert.equal(useLayoutStore.getState().terminalPanelOpen, false); |
| 48 | assert.deepEqual(calls, [], "remote shortcut and direct commands share one local-tool capability gate"); |
| 49 | await paint(false); |
| 50 | await act(async () => useAppNavigationStore.getState().openPage({ kind: "automation" })); |
| 51 | await act(async () => key()); |
| 52 | assert.equal(useAppNavigationStore.getState().page.kind, "automation"); |
| 53 | assert.equal(useLayoutStore.getState().terminalPanelOpen, false, "management pages suppress workspace shortcuts without changing stored geometry"); |
| 54 | await act(async () => useAppNavigationStore.getState().returnToWorkspace()); |
| 55 | await act(async () => key()); |
| 56 | assert.equal(useLayoutStore.getState().terminalPanelOpen, true); |
| 57 | await act(async () => key(true)); |
| 58 | assert.deepEqual(calls, ["A:."]); |
| 59 | await act(async () => commands.closeTerminalPanel()); |
| 60 | assert.equal(useLayoutStore.getState().terminalPanelOpen, false); |
| 61 | await act(async () => root.unmount()); |
| 62 | commands.openTerminalForPath("stale"); key(true); |
| 63 | assert.deepEqual(calls, ["A:."], "unmount revokes commands and removes shortcut listeners"); |
| 64 | console.log("terminal commands: remote capability, warm mount exclusion, native key routing and disposal passed"); |
| 65 | } finally { useTerminalStore.setState({ createSession: originalCreate }); dom.window.close(); } |
| 66 |