| 1 | // Run: node --import ./scripts/svg-stub-register.mjs --import tsx src/__tests__/file-navigation-session.test.tsx |
| 2 | // |
| 3 | // What a session change does to a dock that keeps its identity: the previews |
| 4 | // stay where the user left them, their read credentials do not cross over, and |
| 5 | // the new session's first command is still delivered as a navigation. |
| 6 | |
| 7 | import assert from "node:assert/strict"; |
| 8 | import React, { act } from "react"; |
| 9 | import { renderFilesWorkspace, waitFor } from "./workspace-panel-test-harness"; |
| 10 | import { WorkspaceDockRegion, type WorkspaceDockRegionProps } from "../app-shell/WorkspaceDockRegion"; |
| 11 | import { LocaleProvider } from "../lib/i18n"; |
| 12 | import { performResourceAction, fileNavigationOwner } from "../lib/fileNavigationCommands"; |
| 13 | import { fileNavigationKey } from "../lib/fileNavigationOwner"; |
| 14 | |
| 15 | const reads: string[] = []; |
| 16 | const { dom, root, dockTabId } = await renderFilesWorkspace({ |
| 17 | ReadPresentedFileForTab: async (_tab, _tool, path) => { |
| 18 | reads.push(`presented:${path}`); |
| 19 | return { path, body: `CONTENT ${path}`, size: 20, truncated: false, binary: false }; |
| 20 | }, |
| 21 | ReadFileForTab: async (_tab, path) => { |
| 22 | reads.push(`workspace:${path}`); |
| 23 | return { path, body: `CONTENT ${path}`, size: 20, truncated: false, binary: false }; |
| 24 | }, |
| 25 | ListDirForTab: async (_tab, dir) => dir === "" ? [{ name: "app.ts", isDir: false }] : [], |
| 26 | }); |
| 27 | const props: WorkspaceDockRegionProps = { |
| 28 | visible: true, overlay: false, mode: "files", showContext: false, |
| 29 | t: key => key, onPickEntry: () => {}, remote: { onClose: () => {} }, context: {} as WorkspaceDockRegionProps["context"], |
| 30 | workspace: { open: true, tabId: "session-tab", cwd: "/repo", maximized: false, onClose: () => {}, onToggleMaximized: () => {} }, |
| 31 | workspaceKey: "session-test", workspaceRoot: "/repo", |
| 32 | }; |
| 33 | // A topic switch inside one project: the tab and the project memory key stay, |
| 34 | // only the session scope the dock is bound to changes. |
| 35 | let sessionTabId = "session-tab"; |
| 36 | let sessionScope = "scope-one"; |
| 37 | const paint = () => act(async () => root.render( |
| 38 | <LocaleProvider><WorkspaceDockRegion {...props} workspace={{ |
| 39 | ...props.workspace, workspaceScopeKey: sessionScope, workspaceMemoryKey: "project-memory", |
| 40 | }} visible /></LocaleProvider>, |
| 41 | )); |
| 42 | const record = () => fileNavigationOwner().getSnapshot(fileNavigationKey({ sessionTabId, dockTabId })); |
| 43 | const open = async (path: string) => { |
| 44 | await act(async () => performResourceAction({ hostId: "local", tabId: sessionTabId, path, source: "presented", toolCallId: "call" }, "preview")); |
| 45 | }; |
| 46 | await paint(); |
| 47 | |
| 48 | // A presented file is open in the first session. |
| 49 | await open("notes.md"); |
| 50 | await paint(); |
| 51 | await waitFor("first preview", () => document.body.textContent?.includes("CONTENT notes.md") === true); |
| 52 | assert.equal(record()?.selected?.resource.access.source, "presented"); |
| 53 | const previewBody = document.querySelector(".workspace-preview__body"); |
| 54 | assert(previewBody, "the preview area is mounted before the session changes"); |
| 55 | |
| 56 | // Another session in the same project keeps the dock and what it shows, but the |
| 57 | // presented tool scope belongs to the session that captured it and must not |
| 58 | // authorize a read here. |
| 59 | sessionScope = "scope-two"; |
| 60 | await paint(); |
| 61 | const afterSwitch = record(); |
| 62 | assert(afterSwitch, "the dock keeps its record across a session change"); |
| 63 | assert.equal(afterSwitch.selected?.resource.path, "notes.md", "the selected file survives a session change"); |
| 64 | assert.equal(afterSwitch.selected?.resource.access.source, "workspace", "the presented scope does not cross sessions"); |
| 65 | assert.equal(afterSwitch.selected?.resource.access.toolCallId, undefined); |
| 66 | assert.equal(document.querySelector(".workspace-preview__body"), previewBody, "the preview element is the same DOM node"); |
| 67 | assert.equal(afterSwitch.revision, record()!.revision, "the record is the one the dock already had"); |
| 68 | await waitFor("rebound preview", () => reads.includes("workspace:notes.md")); |
| 69 | assert(document.body.textContent?.includes("CONTENT notes.md"), "the file is read again under the new session"); |
| 70 | |
| 71 | await act(async () => root.unmount()); |
| 72 | dom.window.close(); |
| 73 | console.log("PASS session change: previews survive, presented scope does not, first navigation still applies"); |
| 74 |