返回 DeepSeek-Reasonix
tool-card-error-details.test.tsx
根目录 / desktop / frontend / src / __tests__ / tool-card-error-details.test.tsx
1 // Run: tsx src/__tests__/tool-card-error-details.test.tsx
2
3 import { JSDOM } from "jsdom";
4 import React from "react";
5 import { act } from "react";
6 import { createRoot } from "react-dom/client";
7 import { ToolCard } from "../components/ToolCard";
8 import { LocaleProvider } from "../lib/i18n";
9 import type { Item } from "../lib/useController";
10
11 type ToolItem = Extract<Item, { kind: "tool" }>;
12
13 let passed = 0;
14 let failed = 0;
15
16 function ok(value: unknown, label: string) {
17 if (value) {
18 process.stdout.write(` PASS ${label}\n`);
19 passed += 1;
20 } else {
21 process.stdout.write(` FAIL ${label}\n`);
22 failed += 1;
23 }
24 }
25
26 function eq(actual: unknown, expected: unknown, label: string) {
27 if (actual === expected) ok(true, label);
28 else ok(false, `${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
29 }
30
31 function flushTimers(): Promise<void> {
32 return new Promise((resolve) => setTimeout(resolve, 0));
33 }
34
35 function installDom() {
36 const dom = new JSDOM("<!doctype html><html><body><div id=\"root\"></div></body></html>", {
37 pretendToBeVisual: true,
38 url: "http://localhost/",
39 });
40 (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
41 globalThis.window = dom.window as unknown as Window & typeof globalThis;
42 globalThis.document = dom.window.document;
43 Object.defineProperty(globalThis, "navigator", { configurable: true, value: dom.window.navigator });
44 globalThis.Node = dom.window.Node;
45 globalThis.Element = dom.window.Element;
46 globalThis.HTMLElement = dom.window.HTMLElement;
47 globalThis.Event = dom.window.Event;
48 globalThis.MouseEvent = dom.window.MouseEvent;
49 globalThis.requestAnimationFrame = dom.window.requestAnimationFrame.bind(dom.window);
50 globalThis.cancelAnimationFrame = dom.window.cancelAnimationFrame.bind(dom.window);
51 dom.window.matchMedia = () => ({
52 matches: true,
53 media: "(prefers-reduced-motion: reduce)",
54 onchange: null,
55 addListener: () => undefined,
56 removeListener: () => undefined,
57 addEventListener: () => undefined,
58 removeEventListener: () => undefined,
59 dispatchEvent: () => false,
60 });
61 return dom;
62 }
63
64 console.log("\ntool card error details");
65
66 {
67 const dom = installDom();
68 const rootEl = document.getElementById("root");
69 if (!rootEl) throw new Error("missing root");
70 const root = createRoot(rootEl);
71 const error = "evidence 1: verification command \"Select-String -Path @(...long...)\" has no matching successful receipt — cite the command exactly as it ran in the session; commands that ran: [\"unique-command-tail\"] — pick the matching one and retry complete_step";
72 const item: ToolItem = {
73 kind: "tool",
74 id: "complete-step-error",
75 name: "complete_step",
76 args: "",
77 readOnly: true,
78 status: "error",
79 output: `error: ${error}`,
80 error,
81 durationMs: 62,
82 };
83
84 await act(async () => {
85 root.render(
86 React.createElement(LocaleProvider, null,
87 React.createElement(ToolCard, { item }),
88 ),
89 );
90 await flushTimers();
91 });
92
93 eq(document.querySelectorAll(".code-block").length, 0, "duplicate error output is not rendered as a code block");
94 ok(document.querySelector(".tool__err-summary")?.textContent?.includes("verification command has no matching successful receipt"), "compact error summary is visible");
95 ok(!document.querySelector(".tool__err-details"), "full error details are hidden by default");
96 ok(!document.body.textContent?.includes("unique-command-tail"), "long receipt list is not visible before expansion");
97
98 const toggle = document.querySelector(".tool__err-toggle") as HTMLButtonElement | null;
99 if (!toggle) throw new Error("error details toggle did not render");
100 await act(async () => {
101 toggle.click();
102 await flushTimers();
103 });
104
105 ok(document.querySelector(".tool__err-details")?.textContent?.includes("unique-command-tail"), "full error details render after expansion");
106
107 await act(async () => {
108 root.unmount();
109 });
110 dom.window.close();
111 }
112
113 console.log(`\n${passed} passed, ${failed} failed, ${passed + failed} total`);
114 if (failed > 0) process.exit(1);
115
115 lines Plain Text