返回 DeepSeek-Reasonix
write-access-decision.test.tsx
根目录 / desktop / frontend / src / __tests__ / write-access-decision.test.tsx
1 // Run: tsx src/__tests__/write-access-decision.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 { ApprovalModal } from "../components/ApprovalModal";
8 import { LocaleProvider } from "../lib/i18n";
9 import type { WireApproval } from "../lib/types";
10
11 let failed = 0;
12 function ok(value: unknown, label: string) {
13 process.stdout.write(` ${value ? "PASS" : "FAIL"} ${label}\n`);
14 if (!value) failed += 1;
15 }
16 function eq(actual: unknown, expected: unknown, label: string) {
17 ok(actual === expected, `${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
18 }
19 function flushTimers(ms = 0): Promise<void> {
20 return new Promise((resolve) => setTimeout(resolve, ms));
21 }
22 function installDom() {
23 const dom = new JSDOM("<!doctype html><html><body><div id=\"root\"></div></body></html>", {
24 pretendToBeVisual: true,
25 url: "http://localhost/",
26 });
27 (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
28 globalThis.window = dom.window as unknown as Window & typeof globalThis;
29 globalThis.document = dom.window.document;
30 Object.defineProperty(globalThis, "navigator", { configurable: true, value: dom.window.navigator });
31 globalThis.Node = dom.window.Node;
32 globalThis.Element = dom.window.Element;
33 globalThis.HTMLElement = dom.window.HTMLElement;
34 globalThis.HTMLButtonElement = dom.window.HTMLButtonElement;
35 globalThis.KeyboardEvent = dom.window.KeyboardEvent;
36 globalThis.MouseEvent = dom.window.MouseEvent;
37 globalThis.requestAnimationFrame = (callback: FrameRequestCallback) => setTimeout(() => callback(performance.now()), 0) as unknown as number;
38 globalThis.cancelAnimationFrame = (id: number) => clearTimeout(id);
39 return dom;
40 }
41
42 const dom = installDom();
43 const root = createRoot(document.getElementById("root")!);
44 const answers: Array<[boolean, boolean, boolean]> = [];
45 const approval: WireApproval = {
46 id: "write-1",
47 tool: "bash",
48 subject: "mkdir -p ~/.local/bin && cp tool ~/.local/bin/tool",
49 kind: "write_access",
50 write_access: {
51 directories: ["/Users/me/.local"],
52 display_directories: ["~/.local"],
53 justification: "install the user-requested local command",
54 broad_home_access: true,
55 ordinary_permission_needed: true,
56 persist_allowed: true,
57 },
58 };
59 await act(async () => {
60 root.render(
61 <LocaleProvider>
62 <ApprovalModal approval={approval} onAnswer={(a, s, p) => answers.push([a, s, p])} onStop={() => undefined} />
63 </LocaleProvider>,
64 );
65 await flushTimers();
66 });
67 ok(document.body.textContent?.includes("Extend write access"), "write-access card uses the directory-expansion title");
68 ok(document.body.textContent?.includes("~/.local"), "write-access card lists friendly directories");
69 ok(document.body.textContent?.includes("install the user-requested local command"), "write-access card shows justification");
70 ok(document.body.textContent?.includes("entire home directory"), "home grant shows the high-risk warning");
71 ok(document.body.textContent?.includes("also authorizes the current matching"), "merged Ask permission is explained");
72 const actions = [...document.querySelectorAll(".prompt-shelf__actions .prompt-action")] as HTMLButtonElement[];
73 eq(actions.length, 3, "write-access approval has once, session, and deny options");
74 ok(actions[0].textContent?.includes("Allow once"), "first option is allow once");
75 ok(actions[1].textContent?.includes("this session"), "second option is session grant");
76 ok(actions[2].textContent?.includes("Deny"), "third option is deny");
77 await act(async () => {
78 actions[1].click();
79 await flushTimers();
80 });
81 eq(answers.length, 0, "clicking session only selects");
82 const confirm = document.querySelector(".decision-confirm-bar__confirm") as HTMLButtonElement;
83 await act(async () => {
84 confirm.click();
85 confirm.click();
86 await flushTimers(220);
87 });
88 eq(answers.length, 1, "write-access confirm submits once");
89 eq(JSON.stringify(answers[0]), JSON.stringify([true, true, false]), "session maps to (true,true,false)");
90 await act(async () => root.unmount());
91 dom.window.close();
92 if (failed > 0) process.exit(1);
93
93 lines Plain Text