const test = require("node:test"); const assert = require("node:assert/strict"); const { buildAppMenu, clampWindowOpacity, applyWindowOpacity, isWindowUsable, registerMainWindow, registerWindowHandlers, resolveSettingsWindowBounds, restoreWindowInputFocus, showAndFocusMainWindow, notifyWindowFocusRequested, notifyWindowWillHide, requestWindowCommandClose, sendWhenRendererReady, setPluginApplicationMenuProvider, shouldCloseWindowFromInput, unregisterMainWindow, } = require("./windowManager.cjs"); const { createMainWindowApi, setTerminalKeyboardFocusForWindow, } = require("./windowManager/mainWindow.cjs"); function createWindowStub({ destroyed = false, webContents } = {}) { return { isDestroyed() { return destroyed; }, isVisible() { return true; }, webContents, }; } test("clampWindowOpacity keeps values within 0.5 and 1", () => { assert.equal(clampWindowOpacity(1), 1); assert.equal(clampWindowOpacity(0.85), 0.85); assert.equal(clampWindowOpacity(0.3), 0.5); assert.equal(clampWindowOpacity(1.5), 1); assert.equal(clampWindowOpacity("bad"), 1); }); test("applyWindowOpacity clamps and applies to registered main windows", () => { const opacityCalls = []; const win = { isDestroyed() { return false; }, setOpacity(value) { opacityCalls.push(value); }, webContents: { id: 901, isDestroyed() { return false; }, }, }; registerMainWindow(win); try { assert.equal(applyWindowOpacity(0.85), 0.85); assert.deepEqual(opacityCalls, [0.85]); assert.equal(applyWindowOpacity(0.2), 0.5); assert.deepEqual(opacityCalls, [0.85, 0.5]); } finally { unregisterMainWindow(win); applyWindowOpacity(1); } }); test("isWindowUsable returns false when webContents is crashed", () => { const win = createWindowStub({ webContents: { isDestroyed() { return false; }, isCrashed() { return true; }, }, }); assert.equal(isWindowUsable(win), false); }); test("isWindowUsable returns true for a healthy live window", () => { const win = createWindowStub({ webContents: { isDestroyed() { return false; }, isCrashed() { return false; }, }, }); assert.equal(isWindowUsable(win), true); }); test("isWindowUsable can require a visible window", () => { const hiddenWin = { ...createWindowStub({ webContents: { isDestroyed() { return false; }, isCrashed() { return false; }, }, }), isVisible() { return false; }, }; assert.equal(isWindowUsable(hiddenWin, { requireVisible: true }), false); assert.equal(isWindowUsable(hiddenWin, { requireVisible: false }), true); }); test("restoreWindowInputFocus focuses the window and renderer on Windows without showing hidden windows", () => { const calls = []; const win = { isDestroyed() { return false; }, show() { calls.push("show"); }, focus() { calls.push("focus"); }, setAlwaysOnTop(value) { calls.push(`alwaysOnTop:${value}`); }, webContents: { isDestroyed() { return false; }, focus() { calls.push("webContents.focus"); }, }, }; const restored = restoreWindowInputFocus(win, { platform: "win32" }); assert.equal(restored, true); assert.deepEqual(calls, [ "alwaysOnTop:true", "focus", "alwaysOnTop:false", "webContents.focus", ]); }); test("restoreWindowInputFocus clears Windows always-on-top even if window focus throws", () => { const calls = []; const win = { isDestroyed() { return false; }, focus() { calls.push("focus"); throw new Error("focus failed"); }, setAlwaysOnTop(value) { calls.push(`alwaysOnTop:${value}`); }, webContents: { isDestroyed() { return false; }, focus() { calls.push("webContents.focus"); }, }, }; const restored = restoreWindowInputFocus(win, { platform: "win32" }); assert.equal(restored, true); assert.deepEqual(calls, [ "alwaysOnTop:true", "focus", "alwaysOnTop:false", "webContents.focus", ]); }); test("restoreWindowInputFocus can show the window when requested", () => { const calls = []; const win = { isDestroyed() { return false; }, show() { calls.push("show"); }, focus() { calls.push("focus"); }, webContents: { isDestroyed() { return false; }, focus() { calls.push("webContents.focus"); }, send(channel) { calls.push(`send:${channel}`); }, }, }; const restored = restoreWindowInputFocus(win, { platform: "darwin", show: true }); assert.equal(restored, true); assert.deepEqual(calls, ["show", "focus", "webContents.focus"]); }); test("showAndFocusMainWindow restores minimized windows before focusing", () => { const calls = []; const win = { isDestroyed() { return false; }, isMinimized() { return true; }, restore() { calls.push("restore"); }, show() { calls.push("show"); }, focus() { calls.push("focus"); }, webContents: { isDestroyed() { return false; }, focus() { calls.push("webContents.focus"); }, send(channel) { calls.push(`send:${channel}`); }, }, }; const restored = showAndFocusMainWindow(win); assert.equal(restored, true); assert.deepEqual(calls, [ "restore", "show", "focus", "webContents.focus", "send:netcatty:window:focus-requested", ]); }); test("notifyWindowFocusRequested sends only the explicit focus IPC", () => { const sent = []; const win = { isDestroyed() { return false; }, webContents: { isDestroyed() { return false; }, send(channel) { sent.push(channel); }, }, }; notifyWindowFocusRequested(win); assert.deepEqual(sent, ["netcatty:window:focus-requested"]); }); test("notifyWindowWillHide sends will-hide IPC before native hide", () => { const sent = []; const win = { isDestroyed() { return false; }, webContents: { isDestroyed() { return false; }, send(channel) { sent.push(channel); }, }, }; notifyWindowWillHide(win); assert.deepEqual(sent, ["netcatty:window:will-hide"]); }); test("buildAppMenu closes a non-app window directly when Cmd+W is invoked", () => { let capturedTemplate = null; const Menu = { buildFromTemplate(template) { capturedTemplate = template; return { template }; }, }; buildAppMenu(Menu, { name: "Netcatty" }, true); const windowMenu = capturedTemplate.find((item) => item.label === "Window"); assert.ok(windowMenu); const closeItem = windowMenu.submenu.find((item) => item.accelerator === "CommandOrControl+W"); assert.ok(closeItem); assert.equal(closeItem.label, "Close Window"); const calls = []; closeItem.click(null, { isDestroyed() { return false; }, close() { calls.push("close"); }, webContents: { isDestroyed() { return false; }, send(channel) { calls.push(`send:${channel}`); }, }, }); assert.deepEqual(calls, ["close"]); }); test("buildAppMenu sends Cmd+W to any registered main window renderer", () => { let capturedTemplate = null; const Menu = { buildFromTemplate(template) { capturedTemplate = template; return { template }; }, }; const calls = []; const firstMainWindow = { isDestroyed() { return false; }, on() {}, close() { calls.push("first:close"); }, webContents: { isDestroyed() { return false; }, send(channel) { calls.push(`first:${channel}`); }, }, }; const secondMainWindow = { isDestroyed() { return false; }, on() {}, webContents: { isDestroyed() { return false; }, send(channel) { calls.push(`second:${channel}`); }, }, }; registerMainWindow(firstMainWindow); registerMainWindow(secondMainWindow); try { buildAppMenu(Menu, { name: "Netcatty" }, true); const windowMenu = capturedTemplate.find((item) => item.label === "Window"); const closeItem = windowMenu.submenu.find((item) => item.accelerator === "CommandOrControl+W"); closeItem.click(null, firstMainWindow, { triggeredByAccelerator: true }); assert.deepEqual(calls, ["first:netcatty:window:command-close"]); closeItem.click(null, firstMainWindow, { triggeredByAccelerator: false }); assert.deepEqual(calls, [ "first:netcatty:window:command-close", "first:close", ]); } finally { unregisterMainWindow(firstMainWindow); unregisterMainWindow(secondMainWindow); } }); test("buildAppMenu keeps app reload click-only so custom reload-like shortcuts reach the renderer", () => { let capturedTemplate = null; const Menu = { buildFromTemplate(template) { capturedTemplate = template; return { template }; }, }; buildAppMenu(Menu, { name: "Netcatty" }, false); const viewMenu = capturedTemplate.find((item) => item.label === "View"); assert.ok(viewMenu); assert.equal(viewMenu.submenu.some((item) => item.role === "reload"), false); assert.equal(viewMenu.submenu.some((item) => item.role === "forceReload"), false); assert.equal(viewMenu.submenu.some((item) => item.accelerator === "CommandOrControl+R"), false); assert.equal(viewMenu.submenu.some((item) => item.accelerator === "CommandOrControl+Shift+R"), false); const reloadItem = viewMenu.submenu.find((item) => item.label === "Reload"); assert.ok(reloadItem); assert.equal(reloadItem.role, undefined); assert.equal(reloadItem.accelerator, undefined); const calls = []; reloadItem.click(null, { reload() { calls.push("reload"); }, }); assert.deepEqual(calls, ["reload"]); }); test("buildAppMenu renders localized plugin commands with checked and disabled state", () => { let capturedTemplate = null; const Menu = { buildFromTemplate(template) { capturedTemplate = template; return { template }; }, }; const calls = []; const icon = { kind: "native-image" }; setPluginApplicationMenuProvider(() => [ { id: "com.example.menu.toggle", label: "切换功能", enabled: false, checked: true, icon, group: "navigation", click: () => calls.push("clicked"), }, { id: "com.example.menu.open", label: "打开视图", group: "views", click: () => calls.push("opened"), }, ]); try { buildAppMenu(Menu, { name: "Netcatty" }, false, "zh-CN"); const pluginsMenu = capturedTemplate.find((item) => item.label === "插件"); assert.ok(pluginsMenu); assert.deepEqual(pluginsMenu.submenu.map(({ id, label, enabled, checked, type }) => ({ id, label, enabled, checked, type, })), [ { id: "com.example.menu.toggle", label: "切换功能", enabled: false, checked: true, type: "checkbox", }, { id: undefined, label: undefined, enabled: undefined, checked: undefined, type: "separator" }, { id: "com.example.menu.open", label: "打开视图", enabled: true, checked: false, type: "normal", }, ]); assert.equal(pluginsMenu.submenu[0].icon, icon); pluginsMenu.submenu[0].click(); assert.deepEqual(calls, ["clicked"]); } finally { setPluginApplicationMenuProvider(null); } }); test("requestWindowCommandClose sends command-close to renderer-capable windows", () => { const sentChannels = []; const win = { isDestroyed() { return false; }, webContents: { isDestroyed() { return false; }, send(channel) { sentChannels.push(channel); }, }, }; assert.equal(requestWindowCommandClose(win), true); assert.deepEqual(sentChannels, ["netcatty:window:command-close"]); }); test("shouldCloseWindowFromInput only matches macOS Command+W keydown", () => { assert.equal(shouldCloseWindowFromInput({ type: "keyDown", meta: true, key: "w" }), true); assert.equal(shouldCloseWindowFromInput({ type: "keyDown", meta: true, key: "W" }), true); assert.equal(shouldCloseWindowFromInput({ type: "keyUp", meta: true, key: "w" }), false); assert.equal(shouldCloseWindowFromInput({ type: "keyDown", control: true, key: "w" }), false); assert.equal(shouldCloseWindowFromInput({ type: "keyDown", meta: true, shift: true, key: "w" }), false); }); test("main window asks renderer to close tabs from macOS Command+W before-input-event", async () => { let beforeInputHandler = null; const commandCloseRequests = []; class BrowserWindowStub { constructor() { this.webContents = { id: 1, on(channel, handler) { if (channel === "before-input-event") beforeInputHandler = handler; }, once() {}, isDestroyed() { return false; }, isCrashed() { return false; }, setIgnoreMenuShortcuts() {}, setWindowOpenHandler() {}, openDevTools() {}, }; } on() {} once() {} isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { x: 0, y: 0, width: 1400, height: 900 }; } setBackgroundColor() {} setOpacity() {} async loadURL() {} close() {} } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: new Set(), __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { return null; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState() { return null; }, queueWindowStateSave() {}, saveWindowStateSync() {}, setupDeferredShow() {}, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose(win) { commandCloseRequests.push(win); return true; }, shouldCloseWindowFromInput, applyWindowOpacityToWindow() {}, closeSettingsWindow() {}, hideSettingsWindow() {}, }); await api.createWindow( { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }, { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: true, electronDir: __dirname, }, ); let prevented = false; beforeInputHandler({ preventDefault: () => { prevented = true; } }, { type: "keyDown", meta: true, key: "w", }); assert.equal(prevented, true); assert.equal(commandCloseRequests.length, 1); }); test("main window leaves primary-modifier reload-like shortcuts available to renderer handlers", async () => { let beforeInputHandler = null; const ignoreMenuShortcutValues = []; class BrowserWindowStub { constructor() { this.webContents = { id: 1, on(channel, handler) { if (channel === "before-input-event") beforeInputHandler = handler; }, once() {}, isDestroyed() { return false; }, isCrashed() { return false; }, setIgnoreMenuShortcuts(value) { ignoreMenuShortcutValues.push(value); }, setWindowOpenHandler() {}, openDevTools() {}, }; } on() {} once() {} isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { x: 0, y: 0, width: 1400, height: 900 }; } setBackgroundColor() {} setOpacity() {} async loadURL() {} close() {} } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: new Set(), __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { return null; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState() { return null; }, queueWindowStateSave() {}, saveWindowStateSync() {}, setupDeferredShow() {}, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose() { return true; }, shouldCloseWindowFromInput, applyWindowOpacityToWindow() {}, closeSettingsWindow() {}, hideSettingsWindow() {}, }); await api.createWindow( { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }, { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: false, electronDir: __dirname, }, ); let prevented = false; beforeInputHandler({ preventDefault: () => { prevented = true; } }, { type: "keyDown", control: true, shift: true, key: "R", }); assert.equal(prevented, false); assert.deepEqual(ignoreMenuShortcutValues, [false]); }); test("main window treats Ctrl+= as zoom-in even when Electron only reserves Ctrl++", async () => { let beforeInputHandler = null; const ignoreMenuShortcutValues = []; const zoomFactorCalls = []; class BrowserWindowStub { constructor() { this.webContents = { id: 1, on(channel, handler) { if (channel === "before-input-event") beforeInputHandler = handler; }, once() {}, isDestroyed() { return false; }, isCrashed() { return false; }, setIgnoreMenuShortcuts(value) { ignoreMenuShortcutValues.push(value); }, setWindowOpenHandler() {}, openDevTools() {}, getZoomFactor() { return 1; }, setZoomFactor(value) { zoomFactorCalls.push(value); }, }; } on() {} once() {} isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { x: 0, y: 0, width: 1400, height: 900 }; } setBackgroundColor() {} setOpacity() {} async loadURL() {} close() {} } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: new Set(), __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { return null; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState() { return null; }, queueWindowStateSave() {}, saveWindowStateSync() {}, setupDeferredShow() {}, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose() { return true; }, shouldCloseWindowFromInput, applyWindowOpacityToWindow() {}, closeSettingsWindow() {}, hideSettingsWindow() {}, }); await api.createWindow( { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }, { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: false, electronDir: __dirname, }, ); let prevented = false; beforeInputHandler({ preventDefault: () => { prevented = true; } }, { type: "keyDown", control: true, shift: false, key: "=", }); assert.equal(prevented, true); assert.deepEqual(zoomFactorCalls, [1.1]); assert.deepEqual(ignoreMenuShortcutValues, []); }); test("main window treats Ctrl+- as zoom-out via explicit shortcut handling", async () => { let beforeInputHandler = null; const ignoreMenuShortcutValues = []; const zoomFactorCalls = []; class BrowserWindowStub { constructor() { this.webContents = { id: 1, on(channel, handler) { if (channel === "before-input-event") beforeInputHandler = handler; }, once() {}, isDestroyed() { return false; }, isCrashed() { return false; }, setIgnoreMenuShortcuts(value) { ignoreMenuShortcutValues.push(value); }, setWindowOpenHandler() {}, openDevTools() {}, getZoomFactor() { return 1; }, setZoomFactor(value) { zoomFactorCalls.push(value); }, }; } on() {} once() {} isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { x: 0, y: 0, width: 1400, height: 900 }; } setBackgroundColor() {} setOpacity() {} async loadURL() {} close() {} } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: new Set(), __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { return null; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState() { return null; }, queueWindowStateSave() {}, saveWindowStateSync() {}, setupDeferredShow() {}, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose() { return true; }, shouldCloseWindowFromInput, applyWindowOpacityToWindow() {}, closeSettingsWindow() {}, hideSettingsWindow() {}, }); await api.createWindow( { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }, { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: false, electronDir: __dirname, }, ); let prevented = false; beforeInputHandler({ preventDefault: () => { prevented = true; } }, { type: "keyDown", control: true, shift: false, key: "-", }); assert.equal(prevented, true); assert.deepEqual(zoomFactorCalls, [0.9]); assert.deepEqual(ignoreMenuShortcutValues, []); }); test("main window treats Ctrl+0 as reset-zoom via explicit shortcut handling", async () => { let beforeInputHandler = null; const ignoreMenuShortcutValues = []; const zoomFactorCalls = []; class BrowserWindowStub { constructor() { this.webContents = { id: 1, on(channel, handler) { if (channel === "before-input-event") beforeInputHandler = handler; }, once() {}, isDestroyed() { return false; }, isCrashed() { return false; }, setIgnoreMenuShortcuts(value) { ignoreMenuShortcutValues.push(value); }, setWindowOpenHandler() {}, openDevTools() {}, getZoomFactor() { return 1.5; }, setZoomFactor(value) { zoomFactorCalls.push(value); }, }; } on() {} once() {} isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { x: 0, y: 0, width: 1400, height: 900 }; } setBackgroundColor() {} setOpacity() {} async loadURL() {} close() {} } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: new Set(), __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { return null; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState() { return null; }, queueWindowStateSave() {}, saveWindowStateSync() {}, setupDeferredShow() {}, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose() { return true; }, shouldCloseWindowFromInput, applyWindowOpacityToWindow() {}, closeSettingsWindow() {}, hideSettingsWindow() {}, }); await api.createWindow( { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }, { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: false, electronDir: __dirname, }, ); let prevented = false; beforeInputHandler({ preventDefault: () => { prevented = true; } }, { type: "keyDown", control: true, shift: false, key: "0", }); assert.equal(prevented, true); assert.deepEqual(zoomFactorCalls, [1]); assert.deepEqual(ignoreMenuShortcutValues, []); }); test("main window leaves terminal font shortcuts for the renderer while terminal input is focused", async () => { let beforeInputHandler = null; const ignoreMenuShortcutValues = []; const zoomFactorCalls = []; let createdWindow = null; class BrowserWindowStub { constructor() { createdWindow = this; this.webContents = { id: 1, on(channel, handler) { if (channel === "before-input-event") beforeInputHandler = handler; }, once() {}, isDestroyed() { return false; }, isCrashed() { return false; }, setIgnoreMenuShortcuts(value) { ignoreMenuShortcutValues.push(value); }, setWindowOpenHandler() {}, openDevTools() {}, getZoomFactor() { return 1; }, setZoomFactor(value) { zoomFactorCalls.push(value); }, }; } on() {} once() {} isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { x: 0, y: 0, width: 1400, height: 900 }; } setBackgroundColor() {} setOpacity() {} async loadURL() {} close() {} } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: new Set(), __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { return null; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState() { return null; }, queueWindowStateSave() {}, saveWindowStateSync() {}, setupDeferredShow() {}, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose() { return true; }, shouldCloseWindowFromInput, applyWindowOpacityToWindow() {}, closeSettingsWindow() {}, hideSettingsWindow() {}, }); await api.createWindow( { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }, { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: false, electronDir: __dirname, }, ); setTerminalKeyboardFocusForWindow(createdWindow, true); ignoreMenuShortcutValues.length = 0; for (const key of ["=", "-", "0"]) { let prevented = false; beforeInputHandler({ preventDefault: () => { prevented = true; } }, { type: "keyDown", control: true, shift: false, key, }); assert.equal(prevented, false); } assert.deepEqual(zoomFactorCalls, []); assert.deepEqual(ignoreMenuShortcutValues, [true, true, true]); beforeInputHandler({ preventDefault() {} }, { type: "keyDown", control: true, shift: false, key: "A", }); assert.equal(ignoreMenuShortcutValues.at(-1), false); }); test("main window clears renderer readiness when the main frame starts navigating", async () => { const webContentsHandlers = {}; const rendererReadyIds = new Set([7]); const clearedReadyIds = []; class BrowserWindowStub { constructor() { this.webContents = { id: 7, on(channel, handler) { webContentsHandlers[channel] = handler; }, once() {}, isDestroyed() { return false; }, isCrashed() { return false; }, setIgnoreMenuShortcuts() {}, setWindowOpenHandler() {}, openDevTools() {}, }; } on() {} once() {} isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { x: 0, y: 0, width: 1400, height: 900 }; } setBackgroundColor() {} setOpacity() {} async loadURL() {} close() {} } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: rendererReadyIds, __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { return null; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState() { return null; }, queueWindowStateSave() {}, saveWindowStateSync() {}, setupDeferredShow() {}, clearRendererReadyForWebContents(id) { clearedReadyIds.push(id); rendererReadyIds.delete(id); }, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose() { return true; }, shouldCloseWindowFromInput, applyWindowOpacityToWindow() {}, closeSettingsWindow() {}, hideSettingsWindow() {}, }); await api.createWindow( { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }, { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: false, electronDir: __dirname, }, ); assert.equal(rendererReadyIds.has(7), true); assert.equal(typeof webContentsHandlers["did-start-navigation"], "function"); assert.equal(typeof webContentsHandlers["will-navigate"], "function"); webContentsHandlers["did-start-navigation"]({}, "app://netcatty/index.html", false, false); assert.equal(rendererReadyIds.has(7), true); assert.deepEqual(clearedReadyIds, []); webContentsHandlers["did-start-navigation"]({}, "app://netcatty/index.html#/vault", true, true); assert.equal(rendererReadyIds.has(7), true); assert.deepEqual(clearedReadyIds, []); let blockedNavigation = false; webContentsHandlers["will-navigate"]( { preventDefault() { blockedNavigation = true; } }, "https://example.com/", ); assert.equal(blockedNavigation, true); webContentsHandlers["did-start-navigation"]({}, "https://example.com/", false, true); assert.equal(rendererReadyIds.has(7), true); assert.deepEqual(clearedReadyIds, []); let blockedAppPortNavigation = false; webContentsHandlers["will-navigate"]( { preventDefault() { blockedAppPortNavigation = true; } }, "app://netcatty:123/index.html", ); assert.equal(blockedAppPortNavigation, true); webContentsHandlers["did-start-navigation"]({}, "app://netcatty:123/index.html", false, true); assert.equal(rendererReadyIds.has(7), true); assert.deepEqual(clearedReadyIds, []); webContentsHandlers["did-start-navigation"]({}, "app://netcatty/index.html", false, true); assert.equal(rendererReadyIds.has(7), false); assert.deepEqual(clearedReadyIds, [7]); }); test("createWindow registers each main window as an independent app window", async () => { const registered = []; const unregistered = []; class BrowserWindowStub { constructor() { this.webContents = { id: registered.length + 1, on() {}, once() {}, isDestroyed() { return false; }, isCrashed() { return false; }, setIgnoreMenuShortcuts() {}, setWindowOpenHandler() {}, openDevTools() {}, }; } on(channel, handler) { if (channel === "closed") this._closedHandler = handler; } once() {} isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { x: 0, y: 0, width: 1400, height: 900 }; } setBackgroundColor() {} setOpacity() {} async loadURL() {} close() { this._closedHandler?.(); } } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: new Set(), __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { return null; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState() { return null; }, queueWindowStateSave() {}, saveWindowStateSync() {}, setupDeferredShow() {}, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose() { return true; }, shouldCloseWindowFromInput, registerMainWindow(win) { registered.push(win); }, unregisterMainWindow(win) { unregistered.push(win); }, applyWindowOpacityToWindow() {}, closeSettingsWindow() {}, hideSettingsWindow() {}, }); const electronModule = { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }; const options = { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: true, electronDir: __dirname, }; const first = await api.createWindow(electronModule, options); const second = await api.createWindow(electronModule, options); assert.equal(registered.length, 2); assert.equal(registered[0], first); assert.equal(registered[1], second); assert.notEqual(first, second); first.close(); assert.deepEqual(unregistered, [first]); }); test("each main window close saves its own state", async () => { const closeHandlers = []; const savedStates = []; class BrowserWindowStub { constructor() { this.webContents = { id: closeHandlers.length + 1, on() {}, once() {}, isDestroyed() { return false; }, isCrashed() { return false; }, setIgnoreMenuShortcuts() {}, setWindowOpenHandler() {}, openDevTools() {}, }; } on(channel, handler) { if (channel === "close") closeHandlers.push(handler); } once() {} isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { x: 0, y: 0, width: 1400, height: 900 }; } setBackgroundColor() {} setOpacity() {} async loadURL() {} close() {} } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: new Set(), __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { return null; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState(win) { return { windowId: win.webContents.id }; }, queueWindowStateSave() {}, saveWindowStateSync(state) { savedStates.push(state); }, setupDeferredShow() {}, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose() { return true; }, shouldCloseWindowFromInput, registerMainWindow() {}, unregisterMainWindow() {}, registerAppContentWindow() {}, unregisterAppContentWindow() {}, queryDirtyEditors: async () => false, applyWindowOpacityToWindow() {}, closeSettingsWindow() {}, hideSettingsWindow() {}, }); const electronModule = { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }; const options = { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: true, electronDir: __dirname, }; await api.createWindow(electronModule, options); await api.createWindow(electronModule, options); assert.equal(closeHandlers.length, 2); closeHandlers[0]({ preventDefault() {} }); closeHandlers[1]({ preventDefault() {} }); await new Promise((resolve) => setTimeout(resolve, 0)); closeHandlers[0]({ preventDefault() {} }); closeHandlers[1]({ preventDefault() {} }); assert.deepEqual(savedStates, [{ windowId: 1 }, { windowId: 2 }]); }); test("peer session windows do not load or save shared main window state", async () => { const closeHandlers = []; const resizeHandlers = []; const savedStates = []; let loadWindowStateCount = 0; let closeSettingsWindowCount = 0; class BrowserWindowStub { constructor() { this.webContents = { id: 1, on() {}, setWindowOpenHandler() {}, openDevTools() {}, send() {}, isDestroyed: () => false, }; } on(channel, handler) { if (channel === "close") closeHandlers.push(handler); if (channel === "resize") resizeHandlers.push(handler); } once() {} isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { width: 1000, height: 700 }; } setBackgroundColor() {} loadURL() { return Promise.resolve(); } } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: new Set(), __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { loadWindowStateCount += 1; return { width: 1234, height: 777 }; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState(win) { return { windowId: win.webContents.id }; }, queueWindowStateSave(state) { savedStates.push(state); }, saveWindowStateSync(state) { savedStates.push(state); }, setupDeferredShow() {}, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose() { return true; }, shouldCloseWindowFromInput, registerMainWindow() { throw new Error("peer window should not register as main"); }, unregisterMainWindow() {}, applyWindowOpacityToWindow() {}, closeSettingsWindow() { closeSettingsWindowCount += 1; }, hideSettingsWindow() {}, }); const win = await api.createWindow( { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }, { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: true, electronDir: __dirname, route: "session-window", registerAsMainWindow: false, }, ); assert.ok(win); assert.equal(loadWindowStateCount, 0); resizeHandlers[0]?.(); closeHandlers[0]?.({ preventDefault() {} }); assert.deepEqual(savedStates, []); assert.equal(closeSettingsWindowCount, 0); }); test("peer session windows register as app content windows for dirty editor guard", async () => { const registeredContentWindows = []; const unregisteredContentWindows = []; const closeHandlers = []; class BrowserWindowStub { constructor() { this.webContents = { id: 1, on() {}, setWindowOpenHandler() {}, openDevTools() {}, send() {}, isDestroyed: () => false, }; } on(channel, handler) { if (channel === "closed") closeHandlers.push(handler); } once() {} isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { width: 1000, height: 700 }; } setBackgroundColor() {} loadURL() { return Promise.resolve(); } } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: new Set(), __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { return null; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState(win) { return { windowId: win.webContents.id }; }, queueWindowStateSave() {}, saveWindowStateSync() {}, setupDeferredShow() {}, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose() { return true; }, shouldCloseWindowFromInput, registerMainWindow() { throw new Error("peer window should not register as main"); }, unregisterMainWindow() {}, registerAppContentWindow(win) { registeredContentWindows.push(win); }, unregisterAppContentWindow(win) { unregisteredContentWindows.push(win); }, applyWindowOpacityToWindow() {}, closeSettingsWindow() {}, hideSettingsWindow() {}, }); const win = await api.createWindow( { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }, { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: true, electronDir: __dirname, route: "session-window", registerAsMainWindow: false, }, ); assert.deepEqual(registeredContentWindows, [win]); closeHandlers[0]?.(); assert.deepEqual(unregisteredContentWindows, [win]); }); test("peer session window close queries dirty editors before destroying the renderer", async () => { const closeHandlers = []; const queriedWebContents = []; let preventDefaultCount = 0; class BrowserWindowStub { constructor() { this.closeCalls = 0; this.webContents = { id: 1, on() {}, setWindowOpenHandler() {}, openDevTools() {}, send() {}, isDestroyed: () => false, isCrashed: () => false, }; } on(channel, handler) { if (channel === "close") closeHandlers.push(handler); } once() {} close() { this.closeCalls += 1; } isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { width: 1000, height: 700 }; } setBackgroundColor() {} loadURL() { return Promise.resolve(); } } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: new Set(), __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { return null; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState() { return null; }, queueWindowStateSave() {}, saveWindowStateSync() {}, setupDeferredShow() {}, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose() { return true; }, shouldCloseWindowFromInput, registerMainWindow() { throw new Error("peer window should not register as main"); }, unregisterMainWindow() {}, registerAppContentWindow() {}, unregisterAppContentWindow() {}, queryDirtyEditors: async (webContents) => { queriedWebContents.push(webContents); return true; }, applyWindowOpacityToWindow() {}, closeSettingsWindow() {}, hideSettingsWindow() {}, }); await api.createWindow( { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }, { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: true, electronDir: __dirname, route: "session-window", registerAsMainWindow: false, }, ); closeHandlers[0]?.({ preventDefault() { preventDefaultCount += 1; }, }); await new Promise((resolve) => setTimeout(resolve, 0)); assert.equal(preventDefaultCount, 1); assert.equal(queriedWebContents.length, 1); }); test("main window close queries dirty editors before destroying the renderer", async () => { const closeHandlers = []; const queriedWebContents = []; let preventDefaultCount = 0; let savedStateCount = 0; class BrowserWindowStub { constructor() { this.closeCalls = 0; this.webContents = { id: 1, on() {}, setWindowOpenHandler() {}, openDevTools() {}, send() {}, isDestroyed: () => false, isCrashed: () => false, }; } on(channel, handler) { if (channel === "close") closeHandlers.push(handler); } once() {} close() { this.closeCalls += 1; } isDestroyed() { return false; } isMaximized() { return false; } isFullScreen() { return false; } getBounds() { return { width: 1000, height: 700 }; } setBackgroundColor() {} setOpacity() {} loadURL() { return Promise.resolve(); } } const api = createMainWindowApi({ mainWindow: null, electronApp: null, currentTheme: "light", isQuitting: false, pendingWindowStateWrite: null, queuedWindowState: null, windowStateCloseRequested: false, DEFAULT_WINDOW_WIDTH: 1400, DEFAULT_WINDOW_HEIGHT: 900, MIN_WINDOW_WIDTH: 1100, MIN_WINDOW_HEIGHT: 640, V8_CACHE_OPTIONS: "bypassHeatCheck", THEME_COLORS: { light: { background: "#fff" } }, unhealthyWebContentsIds: new Set(), rendererReadySeenByWebContentsId: new Set(), __dirname, URL, require, console, setTimeout, clearTimeout, getGlobalShortcutBridge() { return { handleWindowClose: () => false }; }, debugLog() {}, resolveFrontendBackgroundColor() { return null; }, loadWindowState() { return null; }, getDevRendererBaseUrl(url) { return url; }, getWindowBoundsState() { return { width: 1000, height: 700 }; }, queueWindowStateSave() {}, saveWindowStateSync() { savedStateCount += 1; }, setupDeferredShow() {}, createExternalOnlyWindowOpenHandler() { return {}; }, createAppWindowOpenHandler() { return {}; }, attachOAuthLoadingOverlay() {}, registerWindowHandlers() {}, requestWindowCommandClose() { return true; }, shouldCloseWindowFromInput, registerMainWindow() {}, unregisterMainWindow() {}, queryDirtyEditors: async (webContents) => { queriedWebContents.push(webContents); return true; }, applyWindowOpacityToWindow() {}, closeSettingsWindow() {}, hideSettingsWindow() {}, }); await api.createWindow( { BrowserWindow: BrowserWindowStub, nativeTheme: {}, app: {}, screen: {}, shell: {}, ipcMain: {}, }, { preload: "/tmp/preload.cjs", devServerUrl: "http://localhost:5173", isDev: true, appIcon: null, isMac: true, electronDir: __dirname, }, ); closeHandlers[0]?.({ preventDefault() { preventDefaultCount += 1; }, }); await new Promise((resolve) => setTimeout(resolve, 0)); assert.equal(preventDefaultCount, 1); assert.equal(queriedWebContents.length, 1); assert.equal(savedStateCount, 0); }); test("window IPC handlers target the sender owner window", async () => { const handlers = new Map(); const ipcMain = { handle(channel, handler) { handlers.set(channel, handler); }, on(channel, handler) { handlers.set(channel, handler); }, }; const calls = []; const titles = []; const guardedTitles = []; const win = { isDestroyed() { return false; }, setTitle(title) { titles.push(title); }, focus() { calls.push("focus"); }, webContents: { id: 101, isDestroyed() { return false; }, focus() { calls.push("webContents.focus"); }, }, }; const minimizeCalls = []; const minimizeCloseCalls = []; const minimizeHideCalls = []; const minimizeSentMessages = []; const minimizeWin = { isDestroyed() { return false; }, minimize() { minimizeCalls.push("minimize"); }, close() { minimizeCloseCalls.push("close"); }, hide() { minimizeHideCalls.push("hide"); }, webContents: { id: 404, isDestroyed() { return false; }, send(channel, payload) { minimizeSentMessages.push([channel, payload]); }, }, }; buildAppMenu({ buildFromTemplate(template) { return { template }; }, }, { name: "Netcatty" }, false, "en", { setAppLockWindowTitle(target, title) { guardedTitles.push([target, title]); target.setTitle(title); return true; }, }); registerWindowHandlers(ipcMain, { themeSource: "light" }); const result = await handlers.get("netcatty:window:focus")({ sender: { id: 202, getOwnerBrowserWindow() { return win; }, }, }); assert.equal(result, true); assert.deepEqual(calls, ["focus", "webContents.focus"]); const titleResult = await handlers.get("netcatty:window:setTitle")({ sender: { id: 202, getOwnerBrowserWindow() { return win; }, }, }, "Prod SSH"); assert.equal(titleResult, true); assert.deepEqual(titles, ["Prod SSH"]); assert.deepEqual(guardedTitles, [[win, "Prod SSH"]]); const opacityCalls = []; registerMainWindow({ isDestroyed() { return false; }, setOpacity(value) { opacityCalls.push(value); }, webContents: { id: 303, isDestroyed() { return false; }, }, }); const opacityResult = await handlers.get("netcatty:setWindowOpacity")(null, 0.7); assert.equal(opacityResult, true); assert.deepEqual(opacityCalls, [0.7]); await handlers.get("netcatty:window:minimize")({ sender: { id: 505, getOwnerBrowserWindow() { return minimizeWin; }, }, }); assert.deepEqual(minimizeCalls, ["minimize"]); assert.deepEqual(minimizeCloseCalls, []); assert.deepEqual(minimizeHideCalls, []); assert.deepEqual(minimizeSentMessages, []); }); test("resolveSettingsWindowBounds centers settings on the requesting window display", () => { const sourceWindow = { getBounds() { return { x: 2100, y: 80, width: 900, height: 700 }; }, isDestroyed() { return false; }, }; const electronModule = { screen: { getDisplayMatching(bounds) { assert.deepEqual(bounds, { x: 2100, y: 80, width: 900, height: 700 }); return { workArea: { x: 1920, y: 0, width: 1440, height: 900 } }; }, }, }; assert.deepEqual( resolveSettingsWindowBounds(electronModule, { sourceWindow, settingsWidth: 980, settingsHeight: 720, }), { x: 2150, y: 90 }, ); }); function createSendableWindowStub({ destroyed = false, webContentsDestroyed = false } = {}) { const sent = []; return { sent, win: { isDestroyed() { return destroyed; }, webContents: { id: 7, isDestroyed() { return webContentsDestroyed; }, send(channel, payload) { sent.push({ channel, payload }); }, }, }, }; } test("sendWhenRendererReady delivers the payload once the renderer reports ready", async () => { const { win, sent } = createSendableWindowStub(); const waited = []; const result = await sendWhenRendererReady( win, "netcatty:window:openSession", { title: "Prod" }, { timeoutMs: 8000, waitForReady: async (target, opts) => { waited.push({ target, opts }); }, }, ); assert.deepEqual(result, { success: true }); assert.equal(waited.length, 1); assert.equal(waited[0].target, win); assert.deepEqual(waited[0].opts, { timeoutMs: 8000 }); assert.deepEqual(sent, [ { channel: "netcatty:window:openSession", payload: { title: "Prod" } }, ]); }); test("sendWhenRendererReady reports failure without sending when readiness times out", async () => { const { win, sent } = createSendableWindowStub(); const result = await sendWhenRendererReady( win, "netcatty:window:openSession", { title: "Prod" }, { timeoutMs: 5, waitForReady: async () => { throw new Error("Renderer did not report ready before timeout."); }, }, ); assert.equal(result.success, false); assert.equal(sent.length, 0); }); test("sendWhenRendererReady reports failure without sending when the window is gone after readiness", async () => { const { win, sent } = createSendableWindowStub({ destroyed: true }); const result = await sendWhenRendererReady( win, "netcatty:window:openSession", { title: "Prod" }, { timeoutMs: 8000, waitForReady: async () => {}, }, ); assert.equal(result.success, false); assert.equal(sent.length, 0); }); test("sendWhenRendererReady can cancel after readiness before sending", async () => { const { win, sent } = createSendableWindowStub(); const result = await sendWhenRendererReady( win, "netcatty:window:openSession", { title: "Prod" }, { timeoutMs: 8000, waitForReady: async () => {}, shouldSend: () => false, cancelReason: "disabled", }, ); assert.deepEqual(result, { success: false, reason: "disabled" }); assert.equal(sent.length, 0); }); test("sendWhenRendererReady reports cancellation instead of timeout after cancellation", async () => { const { win, sent } = createSendableWindowStub(); const result = await sendWhenRendererReady( win, "netcatty:window:openSession", { title: "Prod" }, { timeoutMs: 5, waitForReady: async () => { throw new Error("Renderer did not report ready before timeout."); }, shouldSend: () => false, cancelReason: "disabled", }, ); assert.deepEqual(result, { success: false, reason: "disabled" }); assert.equal(sent.length, 0); });