返回 DeepSeek-Reasonix
topicbar-actions-lifecycle.test.tsx
根目录 / desktop / frontend / src / __tests__ / topicbar-actions-lifecycle.test.tsx
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
46 lines Plain Text