| 1 | import assert from "node:assert/strict"; |
| 2 | import React, { act } from "react"; |
| 3 | import { renderFilesWorkspace, waitFor } from "./workspace-panel-test-harness"; |
| 4 | import { WorkspaceDockRegion, type WorkspaceDockRegionProps } from "../app-shell/WorkspaceDockRegion"; |
| 5 | import { LocaleProvider } from "../lib/i18n"; |
| 6 | import { performResourceAction } from "../lib/presentedFileNavigation"; |
| 7 | import { fileNavigationOwner } from "../lib/fileNavigationCommands"; |
| 8 | import { fileNavigationKey } from "../lib/fileNavigationOwner"; |
| 9 | import { useActivityBarStore } from "../store/activityBar"; |
| 10 | import { useRemoteStore } from "../store/remote"; |
| 11 | |
| 12 | const reads: string[] = []; |
| 13 | const preview = (path: string, mode: string) => { |
| 14 | reads.push(`${mode}:${path}`); |
| 15 | return { path, body: `${mode} content ${path}`, size: 20, truncated: false, binary: false }; |
| 16 | }; |
| 17 | const { dom, root } = await renderFilesWorkspace({ |
| 18 | ReadPresentedFileForTab: async (_tab, _tool, path) => preview(path, "preview"), |
| 19 | ReadPresentedFileSourceForTab: async (_tab, _tool, path) => preview(path, "source"), |
| 20 | ListRemoteDir: async () => [], |
| 21 | ReadRemoteFile: async (_host, path) => ({ ...preview(path, "remote"), mtimeUnix: 1 }), |
| 22 | ResolveRemoteWorkspacePathForTab: async (_tab, _host, _tool, path) => { |
| 23 | if (path === "denied.txt") throw new Error("permission denied"); |
| 24 | return path; |
| 25 | }, |
| 26 | ResolveRemotePresentedPathForTab: async (_tab, _host, _tool, path) => path, |
| 27 | }); |
| 28 | const props: WorkspaceDockRegionProps = { |
| 29 | visible: false, overlay: false, mode: "files", showContext: false, |
| 30 | t: key => key, onPickEntry: () => {}, remote: { onClose: () => {} }, context: {} as WorkspaceDockRegionProps["context"], |
| 31 | workspace: { open: true, tabId: "navigation-session", cwd: "/repo", maximized: false, onClose: () => {}, onToggleMaximized: () => {} }, |
| 32 | workspaceKey: "navigation-test", workspaceRoot: "/repo", |
| 33 | }; |
| 34 | const paint = (visible: boolean) => act(async () => root.render(<LocaleProvider><WorkspaceDockRegion {...props} visible={visible} /></LocaleProvider>)); |
| 35 | // The committed record of whichever dock the command targeted. |
| 36 | const committed = (dockTabId: string | null | undefined) => dockTabId |
| 37 | ? fileNavigationOwner().getSnapshot(fileNavigationKey({ sessionTabId: "navigation-session", dockTabId })) |
| 38 | : null; |
| 39 | await act(async () => { useActivityBarStore.setState({ workspaceRoot: "/repo", tabs: [], activeTabId: null }); }); |
| 40 | await paint(false); |
| 41 | for (const hostId of ["local", "remote-test"]) { |
| 42 | await act(async () => useRemoteStore.setState({ statuses: { "remote-test": { state: "connected" } } })); |
| 43 | for (const action of ["preview", "source", "reveal-tree"] as const) { |
| 44 | const path = `${hostId}-${action}.txt`; |
| 45 | await act(async () => performResourceAction({ hostId, tabId: "navigation-session", path, source: "presented", toolCallId: "call" }, action)); |
| 46 | await paint(true); |
| 47 | await waitFor(path, () => document.body.textContent?.includes(`content ${path}`) === true); |
| 48 | const record = committed(useActivityBarStore.getState().activeTabId); |
| 49 | assert.equal(record?.selected?.resource.path, path, "the targeted dock committed the opened resource"); |
| 50 | assert.equal(record?.selected?.resource.access.source, "presented"); |
| 51 | assert.equal(record?.selected?.resource.access.toolCallId, "call"); |
| 52 | await paint(true); |
| 53 | assert(document.body.textContent?.includes(`content ${path}`)); |
| 54 | console.log("PASS real dock", hostId, action); |
| 55 | } |
| 56 | if (hostId === "local") { |
| 57 | const original = useActivityBarStore.getState().activeTabId!; |
| 58 | await act(async () => useActivityBarStore.getState().addTab("file", "Other files")); |
| 59 | await act(async () => useActivityBarStore.getState().activateTab(original)); |
| 60 | await waitFor("restored presented resource", () => document.body.textContent?.includes("content local-reveal-tree.txt") === true); |
| 61 | console.log("PASS presented resource identity survives view remount without replay"); |
| 62 | await act(async () => performResourceAction({ hostId, tabId: "navigation-session", path: "src/alias.ts", source: "workspace" }, "preview")); |
| 63 | await act(async () => performResourceAction({ hostId, tabId: "navigation-session", path: "/repo/src/alias.ts", source: "workspace" }, "preview")); |
| 64 | const aliasRecord = committed(useActivityBarStore.getState().activeTabId)!; |
| 65 | assert.equal(aliasRecord.entries.filter((entry) => entry.resource.identityPath === "/repo/src/alias.ts").length, 1, |
| 66 | "canonical identity deduplicates relative and absolute spellings through the real command path"); |
| 67 | } |
| 68 | } |
| 69 | // A remote workspace path the host refuses reports its failure to the row that |
| 70 | // asked, and commits nothing the dock could render. |
| 71 | await paint(false); |
| 72 | const denied = await performResourceAction({ hostId: "remote-test", tabId: "navigation-session", path: "denied.txt", source: "workspace", toolCallId: "call" }, "preview"); |
| 73 | assert.equal(denied.status, "failed"); |
| 74 | assert.match((denied as { error: Error }).error.message, /permission denied/); |
| 75 | assert(!committed(useActivityBarStore.getState().activeTabId)?.entries.some(entry => entry.resource.path === "denied.txt"), |
| 76 | "a refused path never reaches the dock's record"); |
| 77 | |
| 78 | await act(async () => root.unmount()); |
| 79 | dom.window.close(); |
| 80 | assert(reads.length >= 6); |
| 81 |