返回 DeepSeek-Reasonix
session-clear-commands.test.tsx
根目录 / desktop / frontend / src / __tests__ / session-clear-commands.test.tsx
1 import assert from "node:assert/strict";
2 import React, { act } from "react";
3 import { createRoot } from "react-dom/client";
4 import { JSDOM } from "jsdom";
5 import { useSessionClearCommands, type SessionClearCommandsInput } from "../app-runtime/useSessionClearCommands";
6 import type { Translator } from "../lib/i18n";
7
8 const dom = new JSDOM("<div id='root'></div>");
9 Object.assign(globalThis, { window: dom.window, document: dom.window.document, IS_REACT_ACT_ENVIRONMENT: true });
10 const root = createRoot(document.getElementById("root")!);
11
12 const t = ((key: string) => key) as Translator;
13 const authority = { checkpoint() {}, ownsUI: () => true };
14
15 const operationCalls: { target: unknown; channel: string; input: unknown }[] = [];
16 const portCalls: string[] = [];
17 const notices: { text: string; level?: string }[] = [];
18 let dockRefreshes = 0;
19 let clearError: Error | null = null;
20
21 const operations: SessionClearCommandsInput["operations"] = async (target, channel, input, execute) => {
22 operationCalls.push({ target, channel, input });
23 try {
24 const value = await execute(input, authority);
25 return { status: "completed", value };
26 } catch (error) {
27 return { status: "failed", error };
28 }
29 };
30
31 let states!: ReturnType<typeof useSessionClearCommands>;
32 function Probe({ activeTabId, remote = false }: { activeTabId?: string; remote?: boolean }) {
33 states = useSessionClearCommands({
34 activeTabId,
35 activeSessionIdentity: "A:1",
36 remote,
37 t,
38 notice: (text, level) => { notices.push({ text, level }); },
39 operations,
40 refreshDock: () => { dockRefreshes += 1; },
41 ports: {
42 clearSession: async () => {
43 portCalls.push("local");
44 if (clearError) throw clearError;
45 },
46 clearRemoteSession: async (tabId) => { portCalls.push(`remote:${tabId}`); },
47 retryRemoteHydration: async () => { portCalls.push("hydrate"); },
48 },
49 });
50 return null;
51 }
52 const paint = (props?: { activeTabId?: string; remote?: boolean }) =>
53 act(async () => root.render(<Probe activeTabId="A" {...props} />));
54
55 try {
56 await paint();
57 assert.equal(states.clearContextPending, false, "clear confirmation starts closed");
58 await act(async () => { states.setClearContextPending(true); });
59 assert.equal(states.clearContextPending, true, "requesting clear opens the confirmation");
60 await act(async () => { states.cancelClearContext(); });
61 assert.equal(states.clearContextPending, false, "cancel closes the confirmation");
62
63 await act(async () => { states.setClearContextPending(true); });
64 await act(async () => { await states.confirmClearContext(); });
65 assert.equal(states.clearContextPending, false, "confirm closes the confirmation before executing");
66 assert.deepEqual(operationCalls, [{ target: { tabId: "A", sessionKey: "A:1" }, channel: "clear-context", input: { remote: false } }],
67 "confirm captures the committed tab and session identity at click time");
68 assert.deepEqual(portCalls, ["local"], "local confirm clears through the controller port");
69 assert.equal(dockRefreshes, 1, "a completed clear refreshes the dock");
70 assert.deepEqual(notices, [{ text: "clearContext.done", level: undefined }], "a completed clear notices success");
71
72 operationCalls.length = 0;
73 portCalls.length = 0;
74 notices.length = 0;
75 dockRefreshes = 0;
76 await paint({ remote: true });
77 await act(async () => { await states.confirmClearContext(); });
78 assert.deepEqual(operationCalls[0]?.input, { remote: true }, "remote surfaces route the remote flag into the operation");
79 assert.deepEqual(portCalls, ["remote:A", "hydrate"], "remote confirm clears the remote tab and retries hydration");
80 assert.equal(dockRefreshes, 1, "remote completion still refreshes the dock");
81
82 operationCalls.length = 0;
83 portCalls.length = 0;
84 notices.length = 0;
85 dockRefreshes = 0;
86 await paint();
87 clearError = new Error("boom");
88 await act(async () => { await states.confirmClearContext(); });
89 assert.deepEqual(notices, [{ text: "boom", level: "warn" }], "a failed clear surfaces the error as a warning");
90 assert.equal(dockRefreshes, 0, "a failed clear does not refresh the dock");
91
92 notices.length = 0;
93 clearError = new Error("");
94 await act(async () => { await states.confirmClearContext(); });
95 assert.deepEqual(notices, [{ text: "clearContext.failed", level: "warn" }], "an empty error falls back to the localized failure notice");
96 clearError = null;
97
98 operationCalls.length = 0;
99 await paint({ activeTabId: undefined });
100 await act(async () => { states.setClearContextPending(true); });
101 await act(async () => { await states.confirmClearContext(); });
102 assert.equal(operationCalls.length, 0, "confirm without an active tab runs no operation");
103 assert.equal(states.clearContextPending, true, "confirm without a target leaves the confirmation untouched");
104
105 console.log("session clear commands: pending lifecycle, local/remote chains, failure notices and empty-target gate passed");
106 } finally { dom.window.close(); }
107
107 lines Plain Text