返回 DeepSeek-Reasonix
startupWindow.test.ts
根目录 / desktop / electron / src / main / startupWindow.test.ts
1 import assert from "node:assert/strict";
2 import { createRequire } from "node:module";
3 import { fileURLToPath } from "node:url";
4 import { build } from "esbuild";
5 import { test } from "node:test";
6
7 const bundled = build({ entryPoints: [fileURLToPath(new URL("./window.ts", import.meta.url))], bundle: true, platform: "node", format: "cjs", external: ["electron"], write: false });
8
9 async function fixture() {
10 const windows: FakeWindow[] = [];
11 const loads: (() => void)[] = [];
12 const scripts: string[] = [];
13 class FakeWindow {
14 destroyed = false;
15 shown = 0;
16 bounds: unknown;
17 webContents = {
18 setWindowOpenHandler() {},
19 on() {},
20 setZoomFactor() {},
21 isDestroyed() { return false; },
22 executeJavaScript: async (script: string) => { scripts.push(script); },
23 };
24 constructor(public options: unknown) { windows.push(this); }
25 getNormalBounds() { return { x: 0, y: 0, width: 1280, height: 820 }; }
26 on() {}
27 setMenuBarVisibility() {}
28 setMinimumSize() {}
29 setBounds(value: unknown) { this.bounds = value; }
30 isDestroyed() { return this.destroyed; }
31 destroy() { this.destroyed = true; }
32 loadURL() { return new Promise<void>((resolve) => loads.push(resolve)); }
33 show() { this.shown++; }
34 }
35 const display = { workArea: { x: 0, y: 0, width: 1920, height: 1080 } };
36 const electron = { BrowserWindow: FakeWindow, nativeTheme: {}, screen: { getPrimaryDisplay: () => display, getDisplayMatching: () => display } };
37 const module = { exports: {} as any };
38 const require = createRequire(import.meta.url);
39 new Function("require", "module", "exports", (await bundled).outputFiles[0].text)((name: string) => name === "electron" ? electron : require(name), module, module.exports);
40 const window = new module.exports.MainWindow({ platform: "win32", appURL: "reasonix://app/index.html", preloadPath: "unused", zoomStore: { current: { appZoomFactor: 1 } }, log: { warn() {}, error() {} }, onShellAction() {}, onAppDomReady() {} });
41 const geometry = { ...module.exports.DEFAULT_GEOMETRY };
42 return { window, windows, loads, scripts, geometry };
43 }
44
45 test("compatible startup window survives transition and applies service geometry", async () => {
46 const f = await fixture();
47 f.window.create(f.geometry);
48 const startup = f.window.showStartup("starting");
49 f.loads.shift()!();
50 await startup;
51 f.window.prepareApp({ ...f.geometry, width: 1000, height: 700 });
52 assert.equal(f.windows.length, 1);
53 assert.equal(f.windows[0].destroyed, false);
54 assert.equal((f.windows[0].bounds as { width: number }).width, 1000);
55 assert.equal(f.window.reattachApp(), false, "the real app must still be loaded");
56 });
57
58 test("late startup navigation completion cannot show over the app", async () => {
59 const f = await fixture();
60 f.window.create(f.geometry);
61 const startup = f.window.showStartup("starting");
62 f.window.prepareApp(f.geometry);
63 const app = f.window.loadApp();
64 f.loads.shift()!();
65 await startup;
66 assert.equal(f.windows[0].shown, 0);
67 f.loads.shift()!();
68 await app;
69 });
70
71 test("failure replaces startup presentation without accepting its stale completion", async () => {
72 const f = await fixture();
73 f.window.create(f.geometry);
74 const startup = f.window.showStartup("starting");
75 const failure = f.window.showFailure("failure");
76 f.loads.shift()!();
77 await startup;
78 assert.equal(f.windows[0].shown, 0);
79 f.loads.shift()!();
80 await failure;
81 assert.equal(f.windows[0].shown, 1);
82 });
83
84 test("incompatible native frame creates the replacement before destroying startup", async () => {
85 const f = await fixture();
86 f.window.create(f.geometry);
87 const startup = f.window.showStartup("starting");
88 f.window.prepareApp({ ...f.geometry, frameless: true });
89 assert.equal(f.windows.length, 2);
90 assert.equal(f.windows[0].destroyed, true);
91 f.loads.shift()!();
92 await startup;
93 assert.equal(f.windows[0].shown, 0);
94 });
95
96 test("app window flushes the renderer draft through a fixed internal script", async () => {
97 const f = await fixture();
98 f.window.create(f.geometry);
99 const app = f.window.loadApp();
100 await f.window.flushSessionDraft();
101 assert.deepEqual(f.scripts, ["Promise.resolve(globalThis.__reasonixFlushSessionDraft?.())"]);
102 f.loads.shift()!();
103 await app;
104 });
105
106 test("app window can release the renderer draft exit barrier after a veto", async () => {
107 const f = await fixture();
108 f.window.create(f.geometry);
109 const app = f.window.loadApp();
110 await f.window.resumeSessionDraftEditing();
111 assert.deepEqual(f.scripts, ["Promise.resolve(globalThis.__reasonixResumeSessionDraftEditing?.())"]);
112 f.loads.shift()!();
113 await app;
114 });
115
116 test("startup and failure pages do not execute renderer draft hooks", async () => {
117 const f = await fixture();
118 f.window.create(f.geometry);
119 await f.window.flushSessionDraft();
120 assert.deepEqual(f.scripts, []);
121 });
122
122 lines TYPESCRIPT