| 1 | import assert from "node:assert/strict"; |
| 2 | import React, { act, type ComponentProps } from "react"; |
| 3 | import { createRoot } from "react-dom/client"; |
| 4 | import { JSDOM } from "jsdom"; |
| 5 | import { TopicbarActionsRegion } from "../app-shell/TopicbarActionsRegion"; |
| 6 | import { LocaleProvider } from "../lib/i18n"; |
| 7 | import { ToastProvider } from "../lib/toast"; |
| 8 | |
| 9 | const dom = new JSDOM("<div id='root'></div>", { url: "http://localhost", pretendToBeVisual: true }); |
| 10 | Object.assign(globalThis, { window: dom.window, document: dom.window.document, |
| 11 | Node: dom.window.Node, HTMLElement: dom.window.HTMLElement, IS_REACT_ACT_ENVIRONMENT: true }); |
| 12 | const root = createRoot(document.getElementById("root")!); |
| 13 | const warnings: unknown[][] = []; |
| 14 | const originalError = console.error; |
| 15 | console.error = (...args) => { warnings.push(args); }; |
| 16 | const opened: string[] = []; |
| 17 | const bridge = { |
| 18 | async ExternalOpenersForTab() { return { openers: [{ id: "editor", name: "Editor", kind: "editor" as const }], preferred: "editor", workspaceOpenable: true }; }, |
| 19 | async SetPreferredExternalOpener() {}, |
| 20 | async OpenWorkspaceInExternalOpenerForTab(tabId: string) { opened.push(tabId); }, |
| 21 | }; |
| 22 | const noop = () => {}; |
| 23 | const session: ComponentProps<typeof TopicbarActionsRegion>["session"] = { |
| 24 | sessionHasContent: true, getSessionMarkdown: () => "fixture", exportSession: noop, |
| 25 | toggleTerminal: noop, terminalOpen: false, openSessionSummary: noop, tasksOpen: false, |
| 26 | }; |
| 27 | try { |
| 28 | let baseline = 0; |
| 29 | for (let index = 0; index < 128; index++) { |
| 30 | const tabId = index % 2 ? "B" : "A"; |
| 31 | await act(async () => root.render(<LocaleProvider><ToastProvider> |
| 32 | <TopicbarActionsRegion sessionIdentity={tabId} external={{ tabId, dismissSignal: index, bridge }} session={session} /> |
| 33 | </ToastProvider></LocaleProvider>)); |
| 34 | assert.equal(document.querySelectorAll(".external-opener").length, 1, "one live external opener after every resource replacement"); |
| 35 | const count = document.querySelectorAll("*").length; |
| 36 | if (!index) baseline = count; |
| 37 | assert.equal(count, baseline, "reconciliation never leaves attached orphan controls"); |
| 38 | } |
| 39 | await act(async () => document.querySelector<HTMLButtonElement>(".external-opener__primary")!.click()); |
| 40 | assert.deepEqual(opened, ["B"], "the surviving control routes only to the final session"); |
| 41 | assert.equal(warnings.length, 0, "resource replacement emits no duplicate-key or reconciliation warnings"); |
| 42 | await act(async () => root.unmount()); |
| 43 | assert.equal(document.querySelectorAll(".external-opener").length, 0); |
| 44 | console.log("topicbar lifecycle: 128 replacements retain one action group and no orphan DOM"); |
| 45 | } finally { console.error = originalError; dom.window.close(); } |
| 46 |