返回 DeepSeek-Reasonix
topicbar-region.test.tsx
根目录 / desktop / frontend / src / __tests__ / topicbar-region.test.tsx
1 import assert from "node:assert/strict";
2 import { JSDOM } from "jsdom";
3 import type { TopicbarView } from "../app-shell/TopicbarRegion";
4
5 const dom = new JSDOM("<div id='root'></div>", { pretendToBeVisual: true });
6 Object.assign(globalThis, { window: dom.window, document: dom.window.document, HTMLElement: dom.window.HTMLElement,
7 KeyboardEvent: dom.window.KeyboardEvent, IS_REACT_ACT_ENVIRONMENT: true });
8 const { default: React, act } = await import("react");
9 const { createRoot } = await import("react-dom/client");
10 const { TopicbarRegion } = await import("../app-shell/TopicbarRegion");
11 const { LocaleProvider } = await import("../lib/i18n");
12 const root = createRoot(document.getElementById("root")!);
13 const calls: string[] = [];
14 const commands = {
15 openAutomation: () => { calls.push("automation"); }, toggleSidebar: () => { calls.push("sidebar"); },
16 setTitleDraft: (value: string) => { calls.push(`draft:${value}`); }, commitRename: () => { calls.push("commit"); },
17 cancelRename: () => { calls.push("cancel"); }, startRename: () => { calls.push("rename"); },
18 openWorktree: (id: string) => { calls.push(`worktree:${id}`); },
19 };
20 const view: TopicbarView = {
21 automationReturn: true, automationReturnLabel: "Back to automation", chromeHidden: true, brand: false,
22 sidebar: { title: "Sidebar", blocked: false, pressed: false, collapsed: true },
23 title: { text: "A", hover: "Full A", renameLabel: "Rename", editing: false, draft: "Draft A", editSize: 12, canRename: true, workspaceLabel: "Project" },
24 subtitle: { visible: true, title: "Workspace", worktreeTabId: "A", mergeLabel: "Merge", mergeTooltip: "Merge back", sourcePlatform: "feishu", sourceLabel: "Channel" },
25 };
26 const paint = (next = view) => act(async () => root.render(<LocaleProvider><TopicbarRegion view={next} commands={commands}>
27 <div className="topicbar__actions"><button>Fixture action</button></div>
28 </TopicbarRegion></LocaleProvider>));
29 const click = (selector: string) => act(async () => document.querySelector<HTMLButtonElement>(selector)!.click());
30 try {
31 await paint();
32 assert.deepEqual([...document.querySelector("header")!.children].map(node => node.className),
33 ["btn btn--small", "tooltip-trigger", "topicbar__identity", "topicbar__spacer", "topicbar__actions"], "region extraction adds no DOM wrapper");
34 await paint({ ...view, brand: true });
35 assert.deepEqual([...document.querySelector("header")!.children].map(node => node.className),
36 ["topicbar__brand", "btn btn--small", "tooltip-trigger", "topicbar__identity", "topicbar__spacer", "topicbar__actions"],
37 "the Windows bar leads with the app mark");
38 assert.equal(document.querySelector(".topicbar__brand-logo")!.tagName, "IMG", "the leading mark is the app logo asset");
39 await paint();
40 const action = document.querySelector(".topicbar__actions button");
41 assert.equal(document.querySelectorAll(".topicbar__subtitle .worktree-badge").length, 1);
42 assert.ok(document.querySelector(".worktree-badge")!.getAttribute("aria-label"), "isolated worktree identity is accessible");
43 await click(".topicbar__title-button");
44 await click(".topicbar__worktree-btn");
45 await click(".topicbar__chrome-btn");
46 await click(".btn");
47 assert.deepEqual(calls, ["rename", "worktree:A", "sidebar", "automation"]);
48 assert.equal(document.activeElement, document.querySelector(".btn"), "return control establishes focus synchronously before navigating");
49 await paint({ ...view, sidebar: { ...view.sidebar, blocked: true }, subtitle: { ...view.subtitle, worktreeTabId: "B" } });
50 await click(".topicbar__chrome-btn");
51 await click(".topicbar__worktree-btn");
52 assert.equal(calls.filter(value => value === "sidebar").length, 1);
53 assert.equal(calls.at(-1), "worktree:B", "synchronous command receives the rendered source identity");
54 await paint({ ...view, title: { ...view.title, editing: true } });
55 const input = document.querySelector<HTMLInputElement>("input")!;
56 assert.equal(input.value, "Draft A"); assert.equal(input.size, 12);
57 assert.equal(input.selectionStart, 0); assert.equal(input.selectionEnd, input.value.length);
58 await act(async () => {
59 Object.getOwnPropertyDescriptor(dom.window.HTMLInputElement.prototype, "value")!.set!.call(input, "Renamed A");
60 input.dispatchEvent(new dom.window.Event("input", { bubbles: true }));
61 });
62 assert.equal(calls.at(-1), "draft:Renamed A", "DOM event is converted synchronously to a draft value");
63 const enter = new KeyboardEvent("keydown", { key: "Enter", bubbles: true, cancelable: true });
64 const escape = new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true });
65 await act(async () => { input.dispatchEvent(enter); input.dispatchEvent(escape); });
66 assert.equal(enter.defaultPrevented, true); assert.equal(escape.defaultPrevented, true);
67 assert.deepEqual(calls.slice(-2), ["commit", "cancel"]);
68 await act(async () => input.blur());
69 assert.equal(calls.at(-1), "commit", "blur retains the existing rename commit contract");
70 assert.equal(action, document.querySelector(".topicbar__actions button"), "title editing preserves action subtree identity");
71 await paint({ ...view, subtitle: { ...view.subtitle, worktreeTabId: undefined } });
72 assert.equal(document.querySelector(".worktree-badge"), null, "ordinary topics do not claim isolated worktree identity");
73 assert.equal(document.querySelector(".topicbar__worktree-btn"), null, "ordinary topics expose no worktree merge action");
74 await act(async () => root.unmount());
75 console.log("topicbar region: DOM structure, source commands, focus, rename keys and action identity passed");
76 } finally { dom.window.close(); }
77
77 lines Plain Text