返回 DeepSeek-Reasonix
status-bar-items-editor.test.tsx
根目录 / desktop / frontend / src / __tests__ / status-bar-items-editor.test.tsx
1 // Run: tsx src/__tests__/status-bar-items-editor.test.tsx
2
3 import { JSDOM } from "jsdom";
4 import React, { useState } from "react";
5 import { act } from "react";
6 import { createRoot } from "react-dom/client";
7 import { StatusBarItemsEditor } from "../components/StatusBarItemsEditor";
8 import { LocaleProvider } from "../lib/i18n";
9 import { DEFAULT_STATUS_BAR_ITEMS, type StatusBarItemId } from "../lib/statusBarItems";
10
11 let passed = 0;
12 let failed = 0;
13
14 function ok(value: boolean, label: string) {
15 if (value) {
16 process.stdout.write(` PASS ${label}\n`);
17 passed += 1;
18 } else {
19 process.stdout.write(` FAIL ${label}\n`);
20 failed += 1;
21 }
22 }
23
24 function flush() {
25 return new Promise((resolve) => setTimeout(resolve, 0));
26 }
27
28 const dom = new JSDOM("<!doctype html><html><body><div id=\"root\"></div></body></html>", {
29 pretendToBeVisual: true,
30 url: "http://localhost/",
31 });
32 (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
33 globalThis.window = dom.window as unknown as Window & typeof globalThis;
34 globalThis.document = dom.window.document;
35 Object.defineProperty(globalThis, "navigator", { configurable: true, value: dom.window.navigator });
36 globalThis.Node = dom.window.Node;
37 globalThis.HTMLElement = dom.window.HTMLElement;
38 globalThis.HTMLButtonElement = dom.window.HTMLButtonElement;
39 globalThis.HTMLInputElement = dom.window.HTMLInputElement;
40 globalThis.Event = dom.window.Event;
41 globalThis.MouseEvent = dom.window.MouseEvent;
42 globalThis.requestAnimationFrame = dom.window.requestAnimationFrame.bind(dom.window);
43 globalThis.cancelAnimationFrame = dom.window.cancelAnimationFrame.bind(dom.window);
44 Object.defineProperty(window, "matchMedia", {
45 configurable: true,
46 value: () => ({
47 matches: true,
48 media: "(prefers-reduced-motion: reduce)",
49 onchange: null,
50 addEventListener() {},
51 removeEventListener() {},
52 addListener() {},
53 removeListener() {},
54 dispatchEvent: () => false,
55 }),
56 });
57
58 let latestItems: StatusBarItemId[] = [];
59
60 function Harness() {
61 const [items, setItems] = useState<StatusBarItemId[]>([...DEFAULT_STATUS_BAR_ITEMS]);
62 latestItems = items;
63 return (
64 <LocaleProvider>
65 <StatusBarItemsEditor
66 items={items}
67 busy={false}
68 onChange={setItems}
69 itemLabel={(id) => id}
70 />
71 </LocaleProvider>
72 );
73 }
74
75 const rootElement = document.getElementById("root");
76 if (!rootElement) throw new Error("missing root");
77 const root = createRoot(rootElement);
78
79 console.log("\nstatus bar items editor");
80
81 await act(async () => {
82 root.render(<Harness />);
83 await flush();
84 });
85
86 const expand = document.querySelector<HTMLButtonElement>('button[aria-label="Expand status bar items"]');
87 ok(expand instanceof HTMLButtonElement, "collapsed editor exposes an accessible expand control");
88
89 await act(async () => {
90 expand?.click();
91 await flush();
92 });
93
94 ok(document.body.textContent?.includes(`Shown · ${DEFAULT_STATUS_BAR_ITEMS.length}`) === true, "expanded editor labels the visible zone with its count");
95 ok(document.body.textContent?.includes("Hidden · 0") === true, "expanded editor labels the hidden zone with its count");
96 ok(document.querySelectorAll('[data-statusbar-drop-zone="hidden"]').length === 1, "hidden zone is an explicit drag target");
97
98 const balanceRow = document.querySelector<HTMLElement>('[data-statusbar-setting-item="balance"]');
99 const balanceToggle = balanceRow?.querySelector<HTMLInputElement>('input[type="checkbox"]');
100 await act(async () => {
101 balanceToggle?.click();
102 await flush();
103 });
104
105 ok(latestItems.length === DEFAULT_STATUS_BAR_ITEMS.length - 1 && !latestItems.includes("balance"), "clearing a visible item removes it from the persisted order");
106 ok(document.body.textContent?.includes(`Shown · ${DEFAULT_STATUS_BAR_ITEMS.length - 1}`) === true, "visible count updates after hiding an item");
107 ok(document.body.textContent?.includes("Hidden · 1") === true, "hidden count updates after hiding an item");
108 ok(document.querySelector('[data-statusbar-drop-zone="hidden"] [data-statusbar-setting-item="balance"]') != null, "hidden item moves into the hidden zone");
109
110 const showAll = Array.from(document.querySelectorAll<HTMLButtonElement>("button")).find((button) => button.textContent === "Show all");
111 await act(async () => {
112 showAll?.click();
113 await flush();
114 });
115 ok(latestItems.length === DEFAULT_STATUS_BAR_ITEMS.length && latestItems.at(-1) === "balance", "show all restores hidden items without discarding the current visible order");
116
117 const moveWorkspaceDown = document.querySelector<HTMLButtonElement>('button[aria-label="Move workspace down"]');
118 await act(async () => {
119 moveWorkspaceDown?.click();
120 await flush();
121 });
122 ok(latestItems[0] === "cache" && latestItems[1] === "workspace", "keyboard order controls update the visible order");
123
124 const restoreDefault = Array.from(document.querySelectorAll<HTMLButtonElement>("button")).find((button) => button.textContent === "Restore default");
125 await act(async () => {
126 restoreDefault?.click();
127 await flush();
128 });
129 ok(latestItems.every((id, index) => id === DEFAULT_STATUS_BAR_ITEMS[index]), "restore default returns all items to canonical order");
130
131 await act(async () => root.unmount());
132 dom.window.close();
133
134 console.log(`\n${passed} passed, ${failed} failed, ${passed + failed} total`);
135 if (failed > 0) process.exit(1);
136
136 lines Plain Text