| 1 | // Run: node --import ./scripts/svg-stub-register.mjs --import tsx src/__tests__/sidebar-active-remote-identity.test.tsx |
| 2 | // The project tree keys ancestor expansion, read marking and its row |
| 3 | // projection on the active remote reference, so a new object per render would |
| 4 | // re-run every tree walk on each transcript delta. The reference is owned by |
| 5 | // useActiveRemoteRef and the chrome assembly must pass it through untouched. |
| 6 | import assert from "node:assert/strict"; |
| 7 | import React, { act } from "react"; |
| 8 | import { createRoot } from "react-dom/client"; |
| 9 | import { JSDOM } from "jsdom"; |
| 10 | import { useActiveRemoteRef } from "../app-runtime/useActiveRemoteRef"; |
| 11 | import { buildSidebarRegionProps } from "../app-shell/chromeRegionBuilders"; |
| 12 | import type { RemoteTabRefView, TabMeta } from "../lib/types"; |
| 13 | |
| 14 | const dom = new JSDOM("<div id='root'></div>"); |
| 15 | Object.assign(globalThis, { window: dom.window, document: dom.window.document, IS_REACT_ACT_ENVIRONMENT: true }); |
| 16 | |
| 17 | const tab = (overrides: Partial<TabMeta>): TabMeta => ({ |
| 18 | id: "tab-remote", scope: "project", workspaceRoot: "~/app", workspaceName: "app", topicId: "", topicTitle: "app", |
| 19 | label: "box", ready: true, running: false, mode: "normal", active: true, cwd: "~/app", ...overrides, |
| 20 | } as TabMeta); |
| 21 | |
| 22 | const seen: Array<RemoteTabRefView | undefined> = []; |
| 23 | function Probe({ meta }: { meta: TabMeta | undefined }) { |
| 24 | seen.push(useActiveRemoteRef(meta)); |
| 25 | return null; |
| 26 | } |
| 27 | const root = createRoot(document.getElementById("root")!); |
| 28 | const show = (meta: TabMeta | undefined) => act(async () => { root.render(React.createElement(Probe, { meta })); }); |
| 29 | |
| 30 | try { |
| 31 | const remoteTab = tab({ remote: { hostId: "box", workspace: "~/app" }, sessionId: "session-a" }); |
| 32 | await show(remoteTab); |
| 33 | const first = seen[seen.length - 1]; |
| 34 | assert.deepEqual(first, { hostId: "box", workspace: "~/app", sessionId: "session-a" }, "the reference carries the host, workspace and bound session"); |
| 35 | |
| 36 | // A transcript delta re-renders the shell with the same tab meta. |
| 37 | await show(remoteTab); |
| 38 | assert.equal(seen[seen.length - 1], first, "re-rendering with the same tab meta keeps the reference identity"); |
| 39 | |
| 40 | // A tab-meta refresh (running flag, turn counters) replaces the meta and its |
| 41 | // nested remote object while the session binding is unchanged. |
| 42 | await show(tab({ remote: { hostId: "box", workspace: "~/app" }, sessionId: "session-a", running: true, turnId: "t1" })); |
| 43 | assert.equal(seen[seen.length - 1], first, "an unrelated tab-meta refresh does not churn the reference"); |
| 44 | |
| 45 | await show(tab({ remote: { hostId: "box", workspace: "~/app" }, sessionId: "session-b" })); |
| 46 | const rotated = seen[seen.length - 1]; |
| 47 | assert.notEqual(rotated, first, "rotating to another session publishes a new reference"); |
| 48 | assert.equal(rotated?.sessionId, "session-b"); |
| 49 | |
| 50 | await show(tab({ remote: { hostId: "other-box", workspace: "~/app" }, sessionId: "session-b" })); |
| 51 | assert.notEqual(seen[seen.length - 1], rotated, "moving to another host publishes a new reference"); |
| 52 | |
| 53 | await show(tab({})); |
| 54 | assert.equal(seen[seen.length - 1], undefined, "a local tab has no active remote reference"); |
| 55 | await show(undefined); |
| 56 | assert.equal(seen[seen.length - 1], undefined, "a draft surface (no active tab) has no active remote reference"); |
| 57 | |
| 58 | // The chrome assembly is a pure pass-through: rebuilding the object there |
| 59 | // would defeat the memo regardless of how stable its input is. |
| 60 | type Input = Parameters<typeof buildSidebarRegionProps>[0]; |
| 61 | const props = buildSidebarRegionProps({ |
| 62 | className: "", t: ((key: string) => key) as Input["t"], paletteShortcut: "", |
| 63 | shell: { sidebarCollapsed: false } as Input["shell"], |
| 64 | geometry: { sidebarResizeMinWidth: 264, sidebarRenderWidth: 264 } as Input["geometry"], |
| 65 | topics: {} as Input["topics"], commands: {} as Input["commands"], |
| 66 | projectTree: { |
| 67 | activeTab: remoteTab, activeRemote: first, |
| 68 | imTopicSources: {}, refreshSignal: 0, searchExpanded: false, searchFocusSignal: 0, |
| 69 | showShortcutBadges: false, shortcutPlatform: undefined, onVisibleTopicsChange: () => {}, |
| 70 | draftSummaries: [], onOpenDraft: undefined, |
| 71 | }, |
| 72 | }); |
| 73 | assert.equal(props.projectTree.activeRemote, first, "the sidebar assembly forwards the owned reference by identity"); |
| 74 | console.log("sidebar active remote identity: stable across deltas and meta refreshes, rotates on host/session change, forwarded by identity"); |
| 75 | } finally { |
| 76 | await act(async () => root.unmount()); |
| 77 | } |
| 78 |