返回 DeepSeek-Reasonix
terminal-events.test.ts
根目录 / desktop / frontend / src / __tests__ / terminal-events.test.ts
1 import { JSDOM } from "jsdom";
2
3 import { __emitMockTerminalExit, __emitMockTerminalOutput } from "../lib/bridge";
4 import {
5 __resetTerminalEventBus,
6 forgetTerminalSession,
7 registerTerminalSink,
8 startTerminalEventBridge,
9 terminalEventBufferLimit,
10 } from "../lib/terminalEvents";
11
12 function base64(bytes: Uint8Array): string {
13 return Buffer.from(bytes).toString("base64");
14 }
15
16 function check(condition: unknown, message: string): void {
17 if (!condition) throw new Error(message);
18 process.stdout.write(`PASS ${message}\n`);
19 }
20
21 const dom = new JSDOM("<!doctype html><html><body></body></html>");
22 const previousWindow = globalThis.window;
23 const previousAtob = globalThis.atob;
24 const previousBtoa = globalThis.btoa;
25 globalThis.window = dom.window as unknown as Window & typeof globalThis;
26 globalThis.atob = (value: string) => Buffer.from(value, "base64").toString("binary");
27 globalThis.btoa = (value: string) => Buffer.from(value, "binary").toString("base64");
28
29 try {
30 __resetTerminalEventBus();
31 startTerminalEventBridge();
32 const first: number[] = [];
33 __emitMockTerminalOutput({ id: "utf8", data: base64(new Uint8Array([0xe4, 0xbd])) });
34 __emitMockTerminalOutput({ id: "utf8", data: base64(new Uint8Array([0xa0])) });
35 const unregister = registerTerminalSink("utf8", (bytes) => first.push(...bytes));
36 unregister();
37 check(JSON.stringify(first) === JSON.stringify([0xe4, 0xbd, 0xa0]), "split UTF-8 output is delivered as original bytes");
38
39 const live: number[] = [];
40 const unregisterLive = registerTerminalSink("replay", (bytes) => live.push(...bytes));
41 __emitMockTerminalOutput({ id: "replay", data: base64(new Uint8Array([1, 2])) });
42 unregisterLive();
43 __emitMockTerminalOutput({ id: "replay", data: base64(new Uint8Array([3])) });
44 const replayed: number[] = [];
45 const unregisterReplay = registerTerminalSink("replay", (bytes) => replayed.push(...bytes));
46 unregisterReplay();
47 check(JSON.stringify(live) === JSON.stringify([1, 2]), "attached terminal receives live output");
48 check(JSON.stringify(replayed) === JSON.stringify([1, 2, 3]), "remounted terminal replays complete output history");
49
50 const chunk = new Uint8Array(64 * 1024);
51 chunk.fill(65);
52 for (let index = 0; index < 24; index += 1) {
53 __emitMockTerminalOutput({ id: "bounded", data: base64(chunk) });
54 }
55 const bounded: number[] = [];
56 const unregisterBounded = registerTerminalSink("bounded", (bytes) => bounded.push(...bytes));
57 unregisterBounded();
58 check(bounded.length <= terminalEventBufferLimit, "terminal output history is bounded to one MiB per session");
59
60 __emitMockTerminalOutput({ id: "forgotten", data: base64(new Uint8Array([9])) });
61 forgetTerminalSession("forgotten");
62 const forgotten: number[] = [];
63 const unregisterForgotten = registerTerminalSink("forgotten", (bytes) => forgotten.push(...bytes));
64 unregisterForgotten();
65 check(forgotten.length === 0, "explicitly closed terminal history is discarded");
66
67 __emitMockTerminalOutput({ id: "natural-exit", data: base64(new Uint8Array([7])) });
68 __emitMockTerminalExit({ id: "natural-exit", exitCode: 0 });
69 const naturalExit: number[] = [];
70 const unregisterNaturalExit = registerTerminalSink("natural-exit", (bytes) => naturalExit.push(...bytes));
71 unregisterNaturalExit();
72 check(JSON.stringify(naturalExit) === JSON.stringify([7]), "natural exit preserves terminal history");
73
74 __emitMockTerminalOutput({ id: "removed-exit", data: base64(new Uint8Array([8])) });
75 __emitMockTerminalExit({ id: "removed-exit", exitCode: 0, removed: true });
76 const removedExit: number[] = [];
77 const unregisterRemovedExit = registerTerminalSink("removed-exit", (bytes) => removedExit.push(...bytes));
78 unregisterRemovedExit();
79 check(removedExit.length === 0, "removed terminal exit discards terminal history");
80 } finally {
81 __resetTerminalEventBus();
82 if (previousWindow) globalThis.window = previousWindow;
83 else delete (globalThis as { window?: unknown }).window;
84 if (previousAtob) globalThis.atob = previousAtob;
85 if (previousBtoa) globalThis.btoa = previousBtoa;
86 }
87
87 lines TYPESCRIPT