Files
orca/src/main/window/createMainWindow.test.ts
T
NeilandOrca 5327f27ccc fix(ime): carry the composition markers through the last two rebuilt inputs
Two more places rebuilt a keybinding input field-by-field and dropped the
markers the matcher refuses on.

`matchesRecentTabSwitcherChord` is the harder one: its callers pass the raw
event, so a call-site sweep reads clean, but the helper normalizes Shift away
by hand-building a literal one level down. Ctrl+Tab therefore still resolved
mid-composition.

The two `before-input-event` handlers omitted `isComposing` when feeding the
double-tap detector. `Electron.Input` does carry it — a completed gesture
dispatches `{ doubleTapModifier }` with no key or modifier flags, so once one
is armed the matcher has nothing left to refuse on.

Also covers the floating terminal panel, whose product fix already landed but
whose suite passed unchanged when both ownership decisions were mutated away.

Every guard here is mutation-checked: disabling one fails exactly the tests
that name it, and no others.

Co-authored-by: Orca <help@stably.ai>
2026-08-02 03:22:37 -07:00

3900 lines
125 KiB
TypeScript

/* oxlint-disable max-lines */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const {
browserWindowMock,
openExternalMock,
attachGuestPoliciesMock,
buildFromTemplateMock,
menuPopupMock,
notificationMock,
notificationShowMock,
powerMonitorOnMock,
powerMonitorRemoveListenerMock,
isMock,
macosTahoeMock
} = vi.hoisted(() => {
const menuPopupMock = vi.fn()
const notificationShowMock = vi.fn()
return {
browserWindowMock: vi.fn(),
openExternalMock: vi.fn(),
attachGuestPoliciesMock: vi.fn(),
buildFromTemplateMock: vi.fn(() => ({ popup: menuPopupMock })),
menuPopupMock,
notificationMock: vi.fn(function () {
return { show: notificationShowMock }
}),
notificationShowMock,
powerMonitorOnMock: vi.fn(),
powerMonitorRemoveListenerMock: vi.fn(),
isMock: { dev: false },
macosTahoeMock: { value: false }
}
})
vi.mock('electron', () => ({
app: { on: vi.fn(), removeListener: vi.fn() },
BrowserWindow: browserWindowMock,
ipcMain: { on: vi.fn(), removeListener: vi.fn(), handle: vi.fn(), removeHandler: vi.fn() },
Menu: { buildFromTemplate: buildFromTemplateMock },
Notification: notificationMock,
nativeTheme: { shouldUseDarkColors: false },
powerMonitor: { on: powerMonitorOnMock, removeListener: powerMonitorRemoveListenerMock },
screen: {
getPrimaryDisplay: () => ({ workAreaSize: { width: 1440, height: 900 } }),
getDisplayMatching: () => ({ scaleFactor: 2 })
},
shell: { openExternal: openExternalMock }
}))
vi.mock('@electron-toolkit/utils', () => ({
is: isMock
}))
vi.mock('./macos-tahoe-release', () => ({
isMacosTahoeOrNewer: vi.fn(() => macosTahoeMock.value)
}))
vi.mock('../app-icon', () => ({
getAppIconPath: vi.fn(() => 'icon')
}))
vi.mock('../browser/browser-manager', () => ({
browserManager: {
attachGuestPolicies: attachGuestPoliciesMock,
setDictationShortcutForwardingPredicate: vi.fn()
}
}))
import {
createMainWindow,
loadMainWindow,
WINDOW_QUIT_RENDERER_ACK_TIMEOUT_MS
} from './createMainWindow'
import { ipcMain } from 'electron'
import { shouldRecoverRendererAfterProcessGone } from '../crash-reporting/process-gone-classification'
import { BROWSER_WINDOW_CLOSE_ALLOWED_PRELOAD } from '../../shared/browser-window-close-policy'
import { fileURLToPath } from 'node:url'
function withPlatform<T>(platform: NodeJS.Platform, run: () => T): T {
const original = process.platform
Object.defineProperty(process, 'platform', { configurable: true, value: platform })
try {
return run()
} finally {
Object.defineProperty(process, 'platform', { configurable: true, value: original })
}
}
describe('createMainWindow', () => {
beforeEach(() => {
browserWindowMock.mockReset()
openExternalMock.mockReset()
attachGuestPoliciesMock.mockReset()
buildFromTemplateMock.mockClear()
menuPopupMock.mockClear()
notificationMock.mockClear()
notificationShowMock.mockClear()
powerMonitorOnMock.mockReset()
powerMonitorRemoveListenerMock.mockReset()
isMock.dev = false
macosTahoeMock.value = false
vi.mocked(ipcMain.on).mockReset()
vi.mocked(ipcMain.removeListener).mockReset()
vi.mocked(ipcMain.handle).mockReset()
vi.mocked(ipcMain.removeHandler).mockReset()
vi.useRealTimers()
})
it('can defer renderer loading until startup IPC handlers are registered', () => {
const webContents = {
on: vi.fn(),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
const win = createMainWindow(null, { deferLoad: true })
expect(browserWindowInstance.loadFile).not.toHaveBeenCalled()
expect(browserWindowInstance.loadURL).not.toHaveBeenCalled()
loadMainWindow(win)
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(1)
expect(browserWindowInstance.loadURL).not.toHaveBeenCalled()
})
it('enables renderer sandboxing and opens external links safely', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn((handler) => {
windowHandlers.windowOpen = handler
}),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
expect(browserWindowMock).toHaveBeenCalledWith(
expect.objectContaining({
webPreferences: expect.objectContaining({ sandbox: true })
})
)
const browserWindowOptions = browserWindowMock.mock.calls[0]?.[0]
// Why: macOS swallows the app-activating click unless the window accepts
// first mouse, forcing a second click to focus the floating workspace.
expect(browserWindowOptions.acceptFirstMouse).toBe(true)
if (process.platform === 'darwin') {
expect(browserWindowOptions).toMatchObject({
titleBarStyle: 'hiddenInset'
})
} else if (process.platform === 'win32') {
expect(browserWindowOptions).toMatchObject({
titleBarStyle: 'hidden'
})
} else {
// Linux: native frame is dropped so the renderer titlebar isn't stacked
// under the WM title bar (double title bar). titleBarStyle stays unset.
expect(browserWindowOptions.titleBarStyle).toBeUndefined()
expect(browserWindowOptions.frame).toBe(false)
}
expect(windowHandlers.windowOpen({ url: 'https://example.com' })).toEqual({ action: 'deny' })
expect(windowHandlers.windowOpen({ url: 'localhost:3000' })).toEqual({ action: 'deny' })
expect(windowHandlers.windowOpen({ url: 'file:///etc/passwd' })).toEqual({ action: 'deny' })
expect(windowHandlers.windowOpen({ url: 'not a url' })).toEqual({ action: 'deny' })
expect(openExternalMock).toHaveBeenCalledTimes(2)
expect(openExternalMock).toHaveBeenCalledWith('https://example.com/')
expect(openExternalMock).toHaveBeenCalledWith('http://localhost:3000/')
const preventDefault = vi.fn()
windowHandlers['will-navigate']({ preventDefault } as never, 'https://example.com/docs')
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(openExternalMock).toHaveBeenCalledTimes(3)
expect(openExternalMock).toHaveBeenLastCalledWith('https://example.com/docs')
const localhostPreventDefault = vi.fn()
windowHandlers['will-navigate'](
{ preventDefault: localhostPreventDefault } as never,
'localhost:3000'
)
expect(localhostPreventDefault).toHaveBeenCalledTimes(1)
expect(openExternalMock).toHaveBeenCalledTimes(4)
expect(openExternalMock).toHaveBeenLastCalledWith('http://localhost:3000/')
const fileNavigationPreventDefault = vi.fn()
windowHandlers['will-navigate'](
{ preventDefault: fileNavigationPreventDefault } as never,
'file:///etc/passwd'
)
expect(fileNavigationPreventDefault).toHaveBeenCalledTimes(1)
expect(openExternalMock).toHaveBeenCalledTimes(4)
const allowBlankEvent = { preventDefault: vi.fn() }
const allowBlankPrefs = { partition: 'persist:orca-browser' }
windowHandlers['will-attach-webview'](
allowBlankEvent as never,
allowBlankPrefs as never,
{ src: 'data:text/html,' } as never
)
expect(allowBlankEvent.preventDefault).not.toHaveBeenCalled()
expect(allowBlankPrefs).toMatchObject({
disableHtmlFullscreenWindowResize: true,
partition: 'persist:orca-browser',
preload: expect.stringMatching(/browser-window-close-preload\.js$/),
sandbox: true
})
const denyInlineHtmlEvent = { preventDefault: vi.fn() }
windowHandlers['will-attach-webview'](
denyInlineHtmlEvent as never,
{ partition: 'persist:orca-browser' } as never,
{ src: 'data:text/html,<script>alert(1)</script>' } as never
)
expect(denyInlineHtmlEvent.preventDefault).toHaveBeenCalledTimes(1)
const guest = { marker: 'guest' }
windowHandlers['did-attach-webview']({} as never, guest as never)
expect(attachGuestPoliciesMock).toHaveBeenCalledWith(guest)
const allowWindowCloseEvent = { preventDefault: vi.fn() }
const allowWindowCloseParams = {
src: 'data:text/html,',
preload: BROWSER_WINDOW_CLOSE_ALLOWED_PRELOAD
}
const allowWindowClosePrefs = { partition: 'persist:orca-browser' }
windowHandlers['will-attach-webview'](
allowWindowCloseEvent as never,
allowWindowClosePrefs as never,
allowWindowCloseParams as never
)
expect(allowWindowCloseEvent.preventDefault).not.toHaveBeenCalled()
expect(allowWindowCloseParams.preload).toBeUndefined()
expect(allowWindowClosePrefs).not.toHaveProperty('preload')
const allowWindowClosePathPrefs = {
partition: 'persist:orca-browser',
preload: fileURLToPath(BROWSER_WINDOW_CLOSE_ALLOWED_PRELOAD)
}
windowHandlers['will-attach-webview'](
{ preventDefault: vi.fn() } as never,
allowWindowClosePathPrefs as never,
{ src: 'data:text/html,', preload: '' } as never
)
expect(allowWindowClosePathPrefs).not.toHaveProperty('preload')
const cliGuest = { marker: 'cli-guest' }
windowHandlers['did-attach-webview']({} as never, cliGuest as never)
expect(attachGuestPoliciesMock).toHaveBeenLastCalledWith(cliGuest)
})
it('sets platform-specific titlebar and frame options for every desktop platform', () => {
for (const [platform, expected] of [
['darwin', { titleBarStyle: 'hiddenInset', frame: undefined }],
['win32', { titleBarStyle: 'hidden', frame: undefined }],
['linux', { titleBarStyle: undefined, frame: false }]
] satisfies [
NodeJS.Platform,
{ titleBarStyle: string | undefined; frame: boolean | undefined }
][]) {
browserWindowMock.mockReset()
const webContents = {
on: vi.fn(),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
setWindowButtonPosition: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
withPlatform(platform, () => createMainWindow(null))
const browserWindowOptions = browserWindowMock.mock.calls[0]?.[0]
expect(browserWindowOptions.titleBarStyle).toBe(expected.titleBarStyle)
expect(browserWindowOptions.frame).toBe(expected.frame)
}
})
it('never requests macOS vibrancy or transparency when window blur is enabled (#8482)', () => {
for (const [platform, expected] of [
['darwin', { backgroundMaterial: undefined }],
['win32', { backgroundMaterial: 'acrylic' }],
['linux', { backgroundMaterial: undefined }]
] satisfies [NodeJS.Platform, { backgroundMaterial: string | undefined }][]) {
browserWindowMock.mockReset()
const webContents = {
on: vi.fn(),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => false),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
getBounds: vi.fn(() => ({ x: 10, y: 20, width: 1000, height: 700 })),
setSize: vi.fn(),
setWindowButtonPosition: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
withPlatform(platform, () =>
createMainWindow({
getUI: () => ({}),
getSettings: () => ({ windowBackgroundBlur: true }),
updateUI: vi.fn()
} as never)
)
const browserWindowOptions = browserWindowMock.mock.calls[0]?.[0]
expect(browserWindowOptions.vibrancy).toBeUndefined()
expect(browserWindowOptions.transparent).toBeUndefined()
expect(browserWindowOptions.backgroundMaterial).toBe(expected.backgroundMaterial)
expect(browserWindowOptions.backgroundColor).toBe('#ffffff')
}
})
it('keeps macOS background throttling enabled while repainting visibility transitions', () => {
vi.useFakeTimers()
const windowHandlers = new Map<string, ((...args: any[]) => void)[]>()
let windowSize: [number, number] = [1200, 800]
const webContents = {
on: vi.fn(),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
isDestroyed: vi.fn(() => false),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
const handlers = windowHandlers.get(event) ?? []
handlers.push(handler)
windowHandlers.set(event, handlers)
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => false),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => windowSize),
setSize: vi.fn((width: number, height: number) => {
windowSize = [width, height]
}),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
withPlatform('darwin', () => createMainWindow(null))
expect(webContents.setBackgroundThrottling).toHaveBeenCalledWith(true)
expect(webContents.setBackgroundThrottling).not.toHaveBeenCalledWith(false)
expect(windowHandlers.get('restore')).toHaveLength(1)
expect(windowHandlers.get('show')).toHaveLength(1)
expect(windowHandlers.get('focus')).toHaveLength(1)
windowHandlers.get('show')?.[0]?.()
windowHandlers.get('restore')?.[0]?.()
expect(webContents.invalidate).toHaveBeenCalledTimes(2)
// Why: the size nudge must never run inside the show/restore dispatch itself.
expect(browserWindowInstance.setSize).not.toHaveBeenCalled()
vi.advanceTimersByTime(0)
expect(browserWindowInstance.setSize).toHaveBeenNthCalledWith(1, 1201, 800)
expect(browserWindowInstance.setSize).toHaveBeenCalledTimes(1)
vi.advanceTimersByTime(32)
expect(browserWindowInstance.setSize).toHaveBeenNthCalledWith(2, 1200, 800)
vi.advanceTimersByTime(217)
expect(webContents.invalidate).toHaveBeenCalledTimes(2)
vi.advanceTimersByTime(1)
expect(webContents.invalidate).toHaveBeenCalledTimes(3)
// Why: focus covers occlusion-uncover with invalidate only — no setSize
// jiggle that would resize terminals on every window focus.
const setSizeCalls = browserWindowInstance.setSize.mock.calls.length
windowHandlers.get('focus')?.[0]?.()
expect(webContents.invalidate).toHaveBeenCalledTimes(4)
expect(browserWindowInstance.setSize).toHaveBeenCalledTimes(setSizeCalls)
})
it('runs a full repaint when the renderer relays a genuine window reveal (STA-2383)', () => {
vi.useFakeTimers()
const windowHandlers = new Map<string, ((...args: any[]) => void)[]>()
let windowSize: [number, number] = [1200, 800]
const webContents = {
on: vi.fn(),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
isDestroyed: vi.fn(() => false),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
const handlers = windowHandlers.get(event) ?? []
handlers.push(handler)
windowHandlers.set(event, handlers)
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => false),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => windowSize),
setSize: vi.fn((width: number, height: number) => {
windowSize = [width, height]
}),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
withPlatform('darwin', () => createMainWindow(null))
const revealHandler = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:window-revealed')?.[1]
expect(revealHandler).toBeTypeOf('function')
// Why: a reveal relayed by another window's webContents must not repaint this one.
revealHandler?.({ sender: {} } as never)
expect(browserWindowInstance.setSize).not.toHaveBeenCalled()
expect(webContents.invalidate).not.toHaveBeenCalled()
// The genuine reveal runs the pre-Tahoe compositor jiggle that bare focus avoids.
revealHandler?.({ sender: webContents } as never)
expect(webContents.invalidate).toHaveBeenCalledTimes(1)
// Why: the nudge is deferred off the event dispatch turn.
expect(browserWindowInstance.setSize).not.toHaveBeenCalled()
vi.advanceTimersByTime(0)
expect(browserWindowInstance.setSize).toHaveBeenNthCalledWith(1, 1201, 800)
vi.advanceTimersByTime(32)
expect(browserWindowInstance.setSize).toHaveBeenNthCalledWith(2, 1200, 800)
// Repeated reveal signals while a jiggle is active repaint but do not multiply terminal resizes.
revealHandler?.({ sender: webContents } as never)
revealHandler?.({ sender: webContents } as never)
expect(webContents.invalidate).toHaveBeenCalledTimes(3)
vi.advanceTimersByTime(0)
expect(browserWindowInstance.setSize).toHaveBeenNthCalledWith(3, 1201, 800)
expect(browserWindowInstance.setSize).toHaveBeenCalledTimes(3)
// A user resize that lands during the jiggle must not be rolled back to stale bounds.
windowSize = [1400, 900]
vi.advanceTimersByTime(32)
expect(browserWindowInstance.setSize).toHaveBeenCalledTimes(3)
windowHandlers.get('closed')?.[0]?.()
expect(ipcMain.removeListener).toHaveBeenCalledWith('ui:window-revealed', revealHandler)
})
it('repaints without the size nudge on macOS 26+ where re-entrant frame updates can deadlock AppKit', () => {
vi.useFakeTimers()
macosTahoeMock.value = true
const windowHandlers = new Map<string, ((...args: any[]) => void)[]>()
const webContents = {
on: vi.fn(),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
isDestroyed: vi.fn(() => false),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
const handlers = windowHandlers.get(event) ?? []
handlers.push(handler)
windowHandlers.set(event, handlers)
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => false),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
withPlatform('darwin', () => createMainWindow(null))
windowHandlers.get('show')?.[0]?.()
expect(webContents.invalidate).toHaveBeenCalledTimes(1)
// Why: the delayed second repaint must also stay setSize-free on Tahoe.
vi.advanceTimersByTime(300)
expect(webContents.invalidate).toHaveBeenCalledTimes(2)
expect(browserWindowInstance.setSize).not.toHaveBeenCalled()
})
it('invalidates a maximized macOS 26 window without changing its frame', () => {
vi.useFakeTimers()
macosTahoeMock.value = true
const windowHandlers = new Map<string, ((...args: any[]) => void)[]>()
const webContents = {
on: vi.fn(),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
isDestroyed: vi.fn(() => false),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
const handlers = windowHandlers.get(event) ?? []
handlers.push(handler)
windowHandlers.set(event, handlers)
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => true),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
withPlatform('darwin', () => createMainWindow(null))
windowHandlers.get('show')?.[0]?.()
vi.advanceTimersByTime(300)
expect(webContents.invalidate).toHaveBeenCalledTimes(2)
expect(browserWindowInstance.setSize).not.toHaveBeenCalled()
})
it('invalidates without frame or device emulation when macOS 26 wakes from sleep', () => {
vi.useFakeTimers()
macosTahoeMock.value = true
const windowHandlers = new Map<string, ((...args: any[]) => void)[]>()
const webContents = {
on: vi.fn(),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
isDestroyed: vi.fn(() => false),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn(),
enableDeviceEmulation: vi.fn(),
disableDeviceEmulation: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
const handlers = windowHandlers.get(event) ?? []
handlers.push(handler)
windowHandlers.set(event, handlers)
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => false),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
withPlatform('darwin', () => createMainWindow(null))
const resumeHandler = powerMonitorOnMock.mock.calls.find(
([event]) => event === 'resume'
)?.[1] as (() => void) | undefined
expect(resumeHandler).toBeDefined()
resumeHandler?.()
expect(webContents.invalidate).toHaveBeenCalled()
vi.advanceTimersByTime(300)
expect(browserWindowInstance.setSize).not.toHaveBeenCalled()
expect(webContents.enableDeviceEmulation).not.toHaveBeenCalled()
expect(webContents.disableDeviceEmulation).not.toHaveBeenCalled()
})
it('supports all minus key variants for terminal zoom out', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const beforeInputEvent = windowHandlers['before-input-event']
const primary =
process.platform === 'darwin'
? { control: false, meta: true }
: { control: true, meta: false }
for (const input of [
{ type: 'keyDown', ...primary, alt: false, key: '-' },
{ type: 'keyDown', ...primary, alt: false, key: 'Minus' },
{ type: 'keyDown', ...primary, alt: false, key: 'Subtract' },
{ type: 'keyDown', ...primary, alt: false, key: '', code: 'Minus' },
{ type: 'keyDown', ...primary, alt: false, key: '', code: 'NumpadSubtract' }
]) {
const preventDefault = vi.fn()
beforeInputEvent({ preventDefault } as never, input as never)
expect(preventDefault).toHaveBeenCalledTimes(1)
}
expect(webContents.send).toHaveBeenCalledTimes(5)
expect(webContents.send).toHaveBeenNthCalledWith(1, 'terminal:zoom', 'out')
expect(webContents.send).toHaveBeenNthCalledWith(2, 'terminal:zoom', 'out')
expect(webContents.send).toHaveBeenNthCalledWith(3, 'terminal:zoom', 'out')
expect(webContents.send).toHaveBeenNthCalledWith(4, 'terminal:zoom', 'out')
expect(webContents.send).toHaveBeenNthCalledWith(5, 'terminal:zoom', 'out')
const undoPreventDefault = vi.fn()
beforeInputEvent(
{ preventDefault: undoPreventDefault } as never,
{ type: 'keyDown', ...primary, alt: false, shift: true, key: '_' } as never
)
expect(undoPreventDefault).not.toHaveBeenCalled()
})
it('routes Electron zoom command events to terminal zoom', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const onZoomChanged = windowHandlers['zoom-changed']
const preventDefault = vi.fn()
onZoomChanged({ preventDefault } as never, 'out')
onZoomChanged({ preventDefault } as never, 'in')
expect(preventDefault).toHaveBeenCalledTimes(2)
expect(webContents.send).toHaveBeenCalledTimes(2)
expect(webContents.send).toHaveBeenNthCalledWith(1, 'terminal:zoom', 'out')
expect(webContents.send).toHaveBeenNthCalledWith(2, 'terminal:zoom', 'in')
})
it('respects custom zoom bindings for Electron zoom command fallbacks', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null, {
getKeybindings: () => ({
'zoom.in': ['Mod+Y'],
'zoom.out': []
})
})
const onZoomChanged = windowHandlers['zoom-changed']
const preventDefault = vi.fn()
onZoomChanged({ preventDefault } as never, 'out')
onZoomChanged({ preventDefault } as never, 'in')
expect(preventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalled()
})
it('does not intercept ctrl/cmd+r in before-input-event', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
for (const input of [
{ type: 'keyDown', code: 'KeyR', key: 'r', meta: false, control: true, alt: false },
{ type: 'keyDown', code: 'KeyR', key: 'r', meta: true, control: false, alt: false }
]) {
const preventDefault = vi.fn()
windowHandlers['before-input-event']({ preventDefault } as never, input as never)
expect(preventDefault).not.toHaveBeenCalled()
}
expect(webContents.send).not.toHaveBeenCalled()
})
it('forwards the platform tab-number jump shortcut to the renderer', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const input =
process.platform === 'darwin'
? { type: 'keyDown', code: 'Digit5', key: '5', meta: false, control: true, alt: false }
: { type: 'keyDown', code: 'Digit5', key: '5', meta: false, control: false, alt: true }
const preventDefault = vi.fn()
windowHandlers['before-input-event']({ preventDefault } as never, input as never)
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('ui:jumpToTabIndex', 4)
})
// While the floating panel owns the keyboard, L1 yields the initial indexed-switch keydown to the
// renderer (no preventDefault, no dispatch) so L2 selects a floating tab, and it contains held-key
// repeats in main (preventDefault, no dispatch) since the renderer skips e.repeat.
it('yields indexed-switch chords to the floating panel and contains their repeats', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const setFloatingFocus = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:setFloatingFocus')?.[1]
expect(setFloatingFocus).toBeTypeOf('function')
setFloatingFocus?.(
{ sender: webContents } as never,
{ panelFocused: true, terminalFocused: false } as never
)
const beforeInputEvent = windowHandlers['before-input-event']
const isDarwin = process.platform === 'darwin'
// jumpToTabIndex chord (Ctrl+digit on mac, Alt+digit elsewhere) and jumpToWorktreeIndex chord
// (Mod+digit) both yield while the panel owns focus.
const tabIndexInput = isDarwin
? { type: 'keyDown', code: 'Digit5', key: '5', meta: false, control: true, alt: false }
: { type: 'keyDown', code: 'Digit5', key: '5', meta: false, control: false, alt: true }
const worktreeIndexInput = isDarwin
? { type: 'keyDown', code: 'Digit5', key: '5', meta: true, control: false, alt: false }
: { type: 'keyDown', code: 'Digit5', key: '5', meta: false, control: true, alt: false }
for (const input of [tabIndexInput, worktreeIndexInput]) {
// Initial (non-repeat) keydown: yielded to the renderer — neither prevented nor dispatched.
const yieldPreventDefault = vi.fn()
beforeInputEvent({ preventDefault: yieldPreventDefault } as never, input as never)
expect(yieldPreventDefault).not.toHaveBeenCalled()
// Held-key repeat: contained in main — prevented, still not dispatched.
const repeatPreventDefault = vi.fn()
beforeInputEvent(
{ preventDefault: repeatPreventDefault } as never,
{ ...input, isAutoRepeat: true } as never
)
expect(repeatPreventDefault).toHaveBeenCalledTimes(1)
}
expect(webContents.send).not.toHaveBeenCalledWith('ui:jumpToTabIndex', expect.anything())
expect(webContents.send).not.toHaveBeenCalledWith('ui:jumpToWorktreeIndex', expect.anything())
})
it('lets main-window Ctrl+Tab flow to the renderer held switcher', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const beforeInputEvent = windowHandlers['before-input-event']
const dispatchInput = (input: Electron.Input): ReturnType<typeof vi.fn> => {
const preventDefault = vi.fn()
beforeInputEvent({ preventDefault } as never, input as never)
return preventDefault
}
const ctrlTabInput = {
code: 'Tab',
key: 'Tab',
control: true,
meta: false,
alt: false
}
const preventDefaults = [
{ type: 'keyDown', shift: false },
{ type: 'keyDown', shift: true },
{ type: 'keyUp', shift: true },
{ type: 'keyUp', code: 'ControlLeft', key: 'Control', control: false, shift: false }
].map((input) => dispatchInput({ ...ctrlTabInput, ...input } as Electron.Input))
for (const preventDefault of preventDefaults) {
expect(preventDefault).not.toHaveBeenCalled()
}
expect(webContents.send).not.toHaveBeenCalledWith('ui:ctrlTabKeyDown', expect.anything())
expect(webContents.send).not.toHaveBeenCalledWith('ui:ctrlTabKeyUp')
})
it('does not hardcode Ctrl+Tab when the recent-tab binding is disabled', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null, { getKeybindings: () => ({ 'tab.previousRecent': [] }) })
const preventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault } as never,
{
type: 'keyDown',
code: 'Tab',
key: 'Tab',
control: true,
meta: false,
alt: false,
shift: false
} as never
)
expect(preventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalledWith('ui:ctrlTabKeyDown', expect.anything())
expect(webContents.send).not.toHaveBeenCalledWith('ui:switchRecentTab')
})
it('only intercepts the dictation chord when enabled toggle mode can handle it', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
const voice: { enabled: boolean; sttModel: string; dictationMode: 'toggle' | 'hold' } = {
enabled: false,
sttModel: '',
dictationMode: 'toggle'
}
createMainWindow({
getUI: () => ({}) as never,
getSettings: () => ({ windowBackgroundBlur: false, voice }) as never,
updateUI: vi.fn()
} as never)
const isDarwin = process.platform === 'darwin'
const dictationInput = {
type: 'keyDown',
code: 'KeyE',
key: 'e',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: false
}
const disabledPreventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault: disabledPreventDefault } as never,
dictationInput as never
)
expect(disabledPreventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalledWith('ui:dictationKeyDown')
voice.enabled = true
voice.sttModel = 'test-model'
voice.dictationMode = 'hold'
const holdPreventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault: holdPreventDefault } as never,
dictationInput as never
)
expect(holdPreventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalledWith('ui:dictationKeyDown')
voice.dictationMode = 'toggle'
const togglePreventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault: togglePreventDefault } as never,
dictationInput as never
)
expect(togglePreventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('ui:dictationKeyDown')
webContents.send.mockClear()
const repeatPreventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault: repeatPreventDefault } as never,
{ ...dictationInput, isAutoRepeat: true } as never
)
expect(repeatPreventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).not.toHaveBeenCalled()
})
it('only intercepts double-tap dictation when enabled toggle mode can handle it', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
const voice: { enabled: boolean; sttModel: string; dictationMode: 'toggle' | 'hold' } = {
enabled: false,
sttModel: '',
dictationMode: 'toggle'
}
createMainWindow(
{
getUI: () => ({}),
getSettings: () => ({ windowBackgroundBlur: false, voice }) as never,
updateUI: vi.fn()
} as never,
{
getKeybindings: () => ({ 'voice.dictation': ['DoubleTap+Shift'] })
}
)
const triggerDoubleTapShift = (): ReturnType<typeof vi.fn> => {
const modifierInput = {
code: 'ShiftLeft',
key: 'Shift',
shift: true,
meta: false,
control: false,
alt: false
}
windowHandlers['before-input-event'](
{ preventDefault: vi.fn() } as never,
{ ...modifierInput, type: 'keyDown' } as never
)
windowHandlers['before-input-event'](
{ preventDefault: vi.fn() } as never,
{ ...modifierInput, type: 'keyUp' } as never
)
const preventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault } as never,
{ ...modifierInput, type: 'keyDown' } as never
)
windowHandlers['before-input-event'](
{ preventDefault: vi.fn() } as never,
{ ...modifierInput, type: 'keyUp' } as never
)
return preventDefault
}
const disabledPreventDefault = triggerDoubleTapShift()
expect(disabledPreventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalledWith('ui:dictationKeyDown')
voice.enabled = true
voice.sttModel = 'test-model'
voice.dictationMode = 'hold'
const holdPreventDefault = triggerDoubleTapShift()
expect(holdPreventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalledWith('ui:dictationKeyDown')
voice.dictationMode = 'toggle'
const togglePreventDefault = triggerDoubleTapShift()
expect(togglePreventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('ui:dictationKeyDown')
// `Electron.Input` carries `isComposing`. A completed gesture dispatches
// `{ doubleTapModifier }` with no key or modifier flags, so the shortcut matcher has nothing
// left to refuse on — the ownership check has to happen before the detector sees the press.
const modifierInput = {
code: 'ShiftLeft',
key: 'Shift',
shift: true,
meta: false,
control: false,
alt: false
}
for (const markedIndex of [1, 0]) {
webContents.send.mockClear()
const steps = [
{ ...modifierInput, type: 'keyUp' },
{ ...modifierInput, type: 'keyDown' }
]
windowHandlers['before-input-event'](
{ preventDefault: vi.fn() } as never,
{ ...modifierInput, type: 'keyDown' } as never
)
for (const [index, step] of steps.entries()) {
windowHandlers['before-input-event'](
{ preventDefault: vi.fn() } as never,
(index === markedIndex ? { ...step, isComposing: true } : step) as never
)
}
expect(webContents.send).not.toHaveBeenCalledWith('ui:dictationKeyDown')
// Clear the trailing armed state so the next case starts from idle.
windowHandlers['before-input-event'](
{ preventDefault: vi.fn() } as never,
{ code: 'KeyA', key: 'a', type: 'keyDown' } as never
)
}
})
it('forwards ctrl/cmd+j to the worktree palette toggle event', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const isDarwin = process.platform === 'darwin'
for (const input of [
{
type: 'keyDown',
code: 'KeyJ',
key: 'j',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: !isDarwin
},
{
type: 'keyDown',
code: 'KeyJ',
key: '',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: !isDarwin
}
]) {
const preventDefault = vi.fn()
windowHandlers['before-input-event']({ preventDefault } as never, input as never)
expect(preventDefault).toHaveBeenCalledTimes(1)
}
expect(webContents.send).toHaveBeenCalledTimes(2)
expect(webContents.send).toHaveBeenNthCalledWith(1, 'ui:toggleWorktreePalette')
expect(webContents.send).toHaveBeenNthCalledWith(2, 'ui:toggleWorktreePalette')
})
it('suppresses auto-repeat quick-command menu toggles from before-input-event', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null, {
getKeybindings: () => ({
'tab.openQuickCommandsMenu': ['Mod+Shift+Q']
})
})
const isDarwin = process.platform === 'darwin'
const input = {
type: 'keyDown',
code: 'KeyQ',
key: 'q',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: true
}
const firstPreventDefault = vi.fn()
windowHandlers['before-input-event']({ preventDefault: firstPreventDefault } as never, input)
expect(firstPreventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('ui:toggleQuickCommandsMenu')
webContents.send.mockClear()
const repeatPreventDefault = vi.fn()
windowHandlers['before-input-event']({ preventDefault: repeatPreventDefault } as never, {
...input,
isAutoRepeat: true
})
expect(repeatPreventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).not.toHaveBeenCalled()
})
it('lets Terminal-first pass risky app shortcuts through when terminal input is focused', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow({
getUI: () => ({}),
getSettings: () => ({ terminalShortcutPolicy: 'terminal-first' })
} as never)
const setFocusedListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:setTerminalInputFocused')?.[1]
expect(setFocusedListener).toBeTypeOf('function')
setFocusedListener?.({ sender: webContents } as never, true)
const preventDefault = vi.fn()
const isDarwin = process.platform === 'darwin'
windowHandlers['before-input-event'](
{ preventDefault } as never,
{
type: 'keyDown',
code: 'KeyJ',
key: 'j',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: !isDarwin
} as never
)
expect(preventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalled()
})
it('allows double-tap shortcuts while terminal input is focused with Terminal-first policy', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(
{
getUI: () => ({}),
getSettings: () => ({ terminalShortcutPolicy: 'terminal-first' })
} as never,
{
getKeybindings: () => ({ 'worktree.quickOpen': ['DoubleTap+Shift'] })
}
)
const setFocusedListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:setTerminalInputFocused')?.[1]
expect(setFocusedListener).toBeTypeOf('function')
setFocusedListener?.({ sender: webContents } as never, true)
const modifierInput = {
code: 'ShiftLeft',
key: 'Shift',
shift: true,
meta: false,
control: false,
alt: false
}
const firstDownPreventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault: firstDownPreventDefault } as never,
{ ...modifierInput, type: 'keyDown' } as never
)
const firstUpPreventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault: firstUpPreventDefault } as never,
{ ...modifierInput, type: 'keyUp' } as never
)
const secondDownPreventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault: secondDownPreventDefault } as never,
{ ...modifierInput, type: 'keyDown' } as never
)
expect(firstDownPreventDefault).not.toHaveBeenCalled()
expect(firstUpPreventDefault).not.toHaveBeenCalled()
expect(secondDownPreventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('ui:openQuickOpen')
})
it('notifies before Orca-first captures a risky terminal-focused shortcut', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow({
getUI: () => ({}),
getSettings: () => ({ terminalShortcutPolicy: 'orca-first' })
} as never)
const setFocusedListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:setTerminalInputFocused')?.[1]
expect(setFocusedListener).toBeTypeOf('function')
setFocusedListener?.({ sender: webContents } as never, true)
const preventDefault = vi.fn()
const isDarwin = process.platform === 'darwin'
windowHandlers['before-input-event'](
{ preventDefault } as never,
{
type: 'keyDown',
code: 'KeyJ',
key: 'j',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: !isDarwin
} as never
)
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenNthCalledWith(1, 'ui:terminalShortcutCaptured', {
actionId: 'worktree.palette'
})
expect(webContents.send).toHaveBeenNthCalledWith(2, 'ui:toggleWorktreePalette')
})
it('notifies before Orca-first captures a terminal-focused double-tap shortcut', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(
{
getUI: () => ({}),
getSettings: () => ({ terminalShortcutPolicy: 'orca-first' })
} as never,
{
getKeybindings: () => ({ 'worktree.quickOpen': ['DoubleTap+Shift'] })
}
)
const setFocusedListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:setTerminalInputFocused')?.[1]
expect(setFocusedListener).toBeTypeOf('function')
setFocusedListener?.({ sender: webContents } as never, true)
const modifierInput = {
code: 'ShiftLeft',
key: 'Shift',
shift: true,
meta: false,
control: false,
alt: false
}
windowHandlers['before-input-event'](
{ preventDefault: vi.fn() } as never,
{ ...modifierInput, type: 'keyDown' } as never
)
windowHandlers['before-input-event'](
{ preventDefault: vi.fn() } as never,
{ ...modifierInput, type: 'keyUp' } as never
)
const preventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault } as never,
{ ...modifierInput, type: 'keyDown' } as never
)
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenNthCalledWith(1, 'ui:terminalShortcutCaptured', {
actionId: 'worktree.quickOpen'
})
expect(webContents.send).toHaveBeenNthCalledWith(2, 'ui:openQuickOpen')
})
it('forwards the configured workspace delete shortcut while terminal input is focused', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(
{
getUI: () => ({}),
getSettings: () => ({ terminalShortcutPolicy: 'terminal-first' })
} as never,
{
getKeybindings: () => ({ 'workspace.delete': ['Mod+Shift+Backspace'] })
}
)
const setFocusedListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:setTerminalInputFocused')?.[1]
expect(setFocusedListener).toBeTypeOf('function')
setFocusedListener?.({ sender: webContents } as never, true)
const isDarwin = process.platform === 'darwin'
const preventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault } as never,
{
type: 'keyDown',
code: 'Backspace',
key: 'Backspace',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: true
} as never
)
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('ui:deleteCurrentWorkspace')
})
it('toggles devtools on F12 in development', () => {
isMock.dev = true
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(() => false),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const preventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault } as never,
{ type: 'keyDown', code: 'F12', key: 'F12', meta: false, control: false, alt: false } as never
)
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(webContents.openDevTools).toHaveBeenCalledWith({ mode: 'undocked' })
expect(webContents.closeDevTools).not.toHaveBeenCalled()
})
it('clears the quit latch when the renderer prevents unload', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
const onQuitAborted = vi.fn()
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null, { getIsQuitting: () => true, onQuitAborted })
const preventDefault = vi.fn()
windowHandlers.close({ preventDefault } as never)
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('window:close-requested', {
isQuitting: true,
requestId: expect.any(Number)
})
windowHandlers['will-prevent-unload']()
expect(onQuitAborted).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('window:unload-prevented')
})
it('allows close after the renderer process is gone', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isCrashed: vi.fn(() => false)
}
const browserWindowInstance = {
webContents,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null, { getIsQuitting: () => true })
windowHandlers['render-process-gone']?.(
{} as never,
{
reason: 'crashed',
exitCode: 5
} as never
)
const preventDefault = vi.fn()
windowHandlers.close({ preventDefault } as never)
expect(preventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalledWith(
'window:close-requested',
expect.objectContaining({ isQuitting: true })
)
consoleError.mockRestore()
})
it('does not notify the crash recorder when renderer teardown follows a confirmed window close', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const ipcHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isCrashed: vi.fn(() => false)
}
const browserWindowInstance = {
webContents,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn(),
close: vi.fn(() => {
windowHandlers.close({} as never)
})
}
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
vi.mocked(ipcMain.on).mockImplementation((channel, handler) => {
ipcHandlers[channel] = handler as (...args: any[]) => void
return ipcMain
})
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
const onRendererProcessGone = vi.fn()
createMainWindow(null, { onRendererProcessGone })
ipcHandlers['window:confirm-close']?.()
windowHandlers['render-process-gone']?.(
{} as never,
{
reason: 'killed',
exitCode: 9
} as never
)
expect(onRendererProcessGone).not.toHaveBeenCalled()
consoleError.mockRestore()
})
it('does not persist pending bounds after bypassing close for a gone renderer', () => {
vi.useFakeTimers()
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isCrashed: vi.fn(() => false)
}
const browserWindowInstance = {
webContents,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => false),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
getBounds: vi.fn(() => ({ x: 10, y: 20, width: 1000, height: 700 })),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
const updateUI = vi.fn()
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow({
getUI: () => ({}),
getSettings: () => ({ windowBackgroundBlur: false }),
updateUI
} as never)
windowHandlers.resize()
windowHandlers['render-process-gone']?.(
{} as never,
{
reason: 'crashed',
exitCode: 5
} as never
)
const preventDefault = vi.fn()
windowHandlers.close({ preventDefault } as never)
vi.advanceTimersByTime(500)
expect(preventDefault).not.toHaveBeenCalled()
expect(updateUI).not.toHaveBeenCalled()
consoleError.mockRestore()
})
it('resumes close confirmation after a renderer process reloads', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isCrashed: vi.fn(() => false)
}
const browserWindowInstance = {
webContents,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null, { getIsQuitting: () => true })
windowHandlers['render-process-gone']?.(
{} as never,
{
reason: 'crashed',
exitCode: 5
} as never
)
windowHandlers['did-finish-load']?.()
const preventDefault = vi.fn()
windowHandlers.close({ preventDefault } as never)
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('window:close-requested', {
isQuitting: true,
requestId: expect.any(Number)
})
consoleError.mockRestore()
})
it('allows close when Electron reports a crashed webContents', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isCrashed: vi.fn(() => true)
}
const browserWindowInstance = {
webContents,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null, { getIsQuitting: () => true })
const preventDefault = vi.fn()
windowHandlers.close({ preventDefault } as never)
expect(preventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalledWith(
'window:close-requested',
expect.objectContaining({ isQuitting: true })
)
})
// Why (#5787): a hung-but-ALIVE renderer (never gone, never crashed) must NOT
// silently bypass the close guard — force-killing it that way is what destroyed
// other sessions. It must route through window:close-requested so the
// save/running-process confirmation runs.
it('requests confirmation for a hung-but-alive renderer instead of bypassing', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isCrashed: vi.fn(() => false)
}
const browserWindowInstance = {
webContents,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
// No render-process-gone and isCrashed() === false: the renderer is alive.
const preventDefault = vi.fn()
windowHandlers.close({ preventDefault } as never)
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('window:close-requested', {
isQuitting: false,
requestId: expect.any(Number)
})
})
it('destroys an already-unresponsive renderer after an app-wide quit deadline', async () => {
vi.useFakeTimers()
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
id: 42,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isCrashed: vi.fn(() => false)
}
const destroy = vi.fn()
browserWindowMock.mockImplementation(function () {
return {
webContents,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
destroy,
loadFile: vi.fn(),
loadURL: vi.fn()
}
})
createMainWindow(null, { getIsQuitting: () => true })
windowHandlers.close({ preventDefault: vi.fn() } as never)
await vi.advanceTimersByTimeAsync(WINDOW_QUIT_RENDERER_ACK_TIMEOUT_MS - 1)
expect(destroy).not.toHaveBeenCalled()
await vi.advanceTimersByTimeAsync(1)
expect(destroy).toHaveBeenCalledOnce()
})
it('keeps the renderer-owned close flow after the quit request is acknowledged', async () => {
vi.useFakeTimers()
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const ipcHandlers: Record<string, (...args: any[]) => void> = {}
vi.mocked(ipcMain.on).mockImplementation((channel, handler) => {
ipcHandlers[channel] = handler as (...args: any[]) => void
return ipcMain
})
const webContents = {
id: 42,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isCrashed: vi.fn(() => false)
}
const destroy = vi.fn()
browserWindowMock.mockImplementation(function () {
return {
webContents,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
destroy,
loadFile: vi.fn(),
loadURL: vi.fn()
}
})
createMainWindow(null, { getIsQuitting: () => true })
windowHandlers.close({ preventDefault: vi.fn() } as never)
windowHandlers.close({ preventDefault: vi.fn() } as never)
const closeRequests = vi
.mocked(webContents.send)
.mock.calls.filter(([channel]) => channel === 'window:close-requested')
.map(([, request]) => request as { requestId: number })
expect(closeRequests).toHaveLength(2)
const [staleRequest, currentRequest] = closeRequests
ipcHandlers['window:close-request-received']?.({ sender: { id: 99 } }, currentRequest.requestId)
ipcHandlers['window:close-request-received']?.({ sender: { id: 42 } }, staleRequest.requestId)
await vi.advanceTimersByTimeAsync(WINDOW_QUIT_RENDERER_ACK_TIMEOUT_MS - 1)
expect(destroy).not.toHaveBeenCalled()
ipcHandlers['window:close-request-received']?.({ sender: { id: 42 } }, currentRequest.requestId)
await vi.advanceTimersByTimeAsync(1)
expect(destroy).not.toHaveBeenCalled()
})
it('ignores traffic light sync IPC on non-macOS', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
setWindowButtonPosition: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const syncListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:sync-traffic-lights')?.[1]
expect(syncListener).toBeTypeOf('function')
syncListener?.({} as never, 1.2)
if (process.platform === 'darwin') {
expect(browserWindowInstance.setWindowButtonPosition).toHaveBeenCalledWith({ x: 16, y: 16 })
return
}
expect(browserWindowInstance.setWindowButtonPosition).not.toHaveBeenCalled()
})
it('intercepts Cmd+B for sidebar when the markdown editor is not focused', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const preventDefault = vi.fn()
const isDarwin = process.platform === 'darwin'
windowHandlers['before-input-event'](
{ preventDefault } as never,
{
type: 'keyDown',
code: 'KeyB',
key: 'b',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: false
} as never
)
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('ui:toggleLeftSidebar')
})
it('skips Cmd+B interception when the markdown editor is focused', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const setFocusedListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:setMarkdownEditorFocused')?.[1]
expect(setFocusedListener).toBeTypeOf('function')
setFocusedListener?.({ sender: webContents } as never, true)
const preventDefault = vi.fn()
const isDarwin = process.platform === 'darwin'
windowHandlers['before-input-event'](
{ preventDefault } as never,
{
type: 'keyDown',
code: 'KeyB',
key: 'b',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: false
} as never
)
expect(preventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalledWith('ui:toggleLeftSidebar')
})
it('lets the shortcut recorder capture app shortcuts before main interception', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const setFocusedListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:setShortcutRecorderFocused')?.[1]
expect(setFocusedListener).toBeTypeOf('function')
setFocusedListener?.({ sender: webContents } as never, true)
const preventDefault = vi.fn()
const isDarwin = process.platform === 'darwin'
windowHandlers['before-input-event'](
{ preventDefault } as never,
{
type: 'keyDown',
code: 'KeyB',
key: 'b',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: false
} as never
)
expect(preventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalledWith('ui:toggleLeftSidebar')
})
it('skips Cmd+B interception when floating terminal input is focused', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const setFocusedListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:setFloatingFocus')?.[1]
expect(setFocusedListener).toBeTypeOf('function')
setFocusedListener?.(
{ sender: webContents } as never,
{ panelFocused: true, terminalFocused: true } as never
)
const preventDefault = vi.fn()
const isDarwin = process.platform === 'darwin'
windowHandlers['before-input-event'](
{ preventDefault } as never,
{
type: 'keyDown',
code: 'KeyB',
key: 'b',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: false
} as never
)
expect(preventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalledWith('ui:toggleLeftSidebar')
webContents.send.mockClear()
const newWorkspacePreventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault: newWorkspacePreventDefault } as never,
{
type: 'keyDown',
code: 'KeyN',
key: 'n',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: false
} as never
)
expect(newWorkspacePreventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('ui:openNewWorkspace')
})
it('still intercepts Cmd+Shift+B and Cmd+Alt+B when the markdown editor is focused', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const setFocusedListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:setMarkdownEditorFocused')?.[1]
setFocusedListener?.({ sender: webContents } as never, true)
const isDarwin = process.platform === 'darwin'
// Cmd+Shift+B is not in the policy allowlist, so no action resolves and no
// preventDefault fires — but the carve-out must not be what lets it through.
const shiftPreventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault: shiftPreventDefault } as never,
{
type: 'keyDown',
code: 'KeyB',
key: 'B',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: true
} as never
)
expect(shiftPreventDefault).not.toHaveBeenCalled()
// Cmd+Alt+B is not a modifier chord in the policy (alt excluded), so the
// policy returns null and no preventDefault fires. Assert the carve-out
// is not what's short-circuiting this — it requires !alt.
const altPreventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault: altPreventDefault } as never,
{
type: 'keyDown',
code: 'KeyB',
key: 'b',
meta: isDarwin,
control: !isDarwin,
alt: true,
shift: false
} as never
)
expect(altPreventDefault).not.toHaveBeenCalled()
})
it('coerces non-boolean setMarkdownEditorFocused payloads to false', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const setFocusedListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:setMarkdownEditorFocused')?.[1]
// Seed to true with a legitimate payload, then send a non-boolean and
// assert the flag returns to false by checking Cmd+B resumes interception.
setFocusedListener?.({ sender: webContents } as never, true)
setFocusedListener?.({ sender: webContents } as never, { malicious: true } as never)
const preventDefault = vi.fn()
const isDarwin = process.platform === 'darwin'
windowHandlers['before-input-event'](
{ preventDefault } as never,
{
type: 'keyDown',
code: 'KeyB',
key: 'b',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: false
} as never
)
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('ui:toggleLeftSidebar')
})
it('shows spellcheck context menu for editable text without relying on markdown focus mirror', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn(),
replaceMisspelling: vi.fn(),
session: { addWordToSpellCheckerDictionary: vi.fn() }
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
windowHandlers['context-menu'](
{} as never,
{
x: 42,
y: 84,
isEditable: true,
spellcheckEnabled: true,
dictionarySuggestions: ['reference'],
misspelledWord: 'refrence'
} as Electron.ContextMenuParams
)
expect(buildFromTemplateMock).toHaveBeenCalledWith(
expect.arrayContaining([expect.objectContaining({ label: 'reference' })])
)
expect(menuPopupMock).toHaveBeenCalledWith({ window: browserWindowInstance, x: 42, y: 84 })
})
it('does not read destroyed webContents during closed cleanup', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn(),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
let webContentsDestroyed = false
const browserWindowInstance = {
get webContents() {
if (webContentsDestroyed) {
throw new Error('Object has been destroyed')
}
return webContents
},
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
webContentsDestroyed = true
// Why: Electron may destroy webContents before BrowserWindow's `closed`
// cleanup runs during updater shutdown. The cleanup must not crash, or
// Squirrel.Mac never reaches the relaunch step.
expect(() => windowHandlers.closed?.()).not.toThrow()
})
it('resets the markdown editor focus flag on renderer crash, navigation, and destroy', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDevToolsOpened: vi.fn(),
openDevTools: vi.fn(),
closeDevTools: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
const setFocusedListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'ui:setMarkdownEditorFocused')?.[1]
const isDarwin = process.platform === 'darwin'
const cmdBInput = {
type: 'keyDown',
code: 'KeyB',
key: 'b',
meta: isDarwin,
control: !isDarwin,
alt: false,
shift: false
} as never
const assertInterceptsAfterReset = (): void => {
webContents.send.mockClear()
const preventDefault = vi.fn()
windowHandlers['before-input-event']({ preventDefault } as never, cmdBInput)
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(webContents.send).toHaveBeenCalledWith('ui:toggleLeftSidebar')
}
// render-process-gone
setFocusedListener?.({ sender: webContents } as never, true)
windowHandlers['render-process-gone']?.()
assertInterceptsAfterReset()
// did-start-navigation (main frame)
setFocusedListener?.({ sender: webContents } as never, true)
windowHandlers['did-start-navigation']?.({} as never, 'https://example.com/', false, true)
assertInterceptsAfterReset()
// did-start-navigation (sub-frame) should NOT reset the flag
setFocusedListener?.({ sender: webContents } as never, true)
windowHandlers['did-start-navigation']?.({} as never, 'https://example.com/', false, false)
webContents.send.mockClear()
const subframePreventDefault = vi.fn()
windowHandlers['before-input-event'](
{ preventDefault: subframePreventDefault } as never,
cmdBInput
)
expect(subframePreventDefault).not.toHaveBeenCalled()
expect(webContents.send).not.toHaveBeenCalledWith('ui:toggleLeftSidebar')
// destroyed
setFocusedListener?.({ sender: webContents } as never, true)
windowHandlers['destroyed']?.()
assertInterceptsAfterReset()
})
it('notifies the caller when the renderer process is gone', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
id: 142,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
const onRendererProcessGone = vi.fn()
createMainWindow(null, { onRendererProcessGone })
const details = { reason: 'crashed', exitCode: 5 } as Electron.RenderProcessGoneDetails
windowHandlers['render-process-gone']?.({} as never, details)
expect(onRendererProcessGone).toHaveBeenCalledWith(details, 142)
})
it('passes the renderer webContents id through crash recording and recovery callbacks', () => {
vi.useFakeTimers()
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
id: 424,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
const onRendererProcessGone = vi.fn()
const shouldRecoverRenderer = vi.fn(() => true)
try {
createMainWindow(null, {
onRendererProcessGone,
shouldRecoverRenderer
})
const details = { reason: 'crashed', exitCode: 5 } as Electron.RenderProcessGoneDetails
windowHandlers['render-process-gone']?.({} as never, details)
vi.advanceTimersByTime(250)
expect(onRendererProcessGone).toHaveBeenCalledWith(details, 424)
expect(shouldRecoverRenderer).toHaveBeenCalledWith(details, 424)
} finally {
consoleError.mockRestore()
}
})
it('forwards expected renderer teardowns so the recorder can diagnose suppression', () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
id: 142,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn(),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
const onRendererProcessGone = vi.fn()
createMainWindow(null, { onRendererProcessGone })
windowHandlers['render-process-gone']?.(
{} as never,
{
reason: 'killed',
exitCode: 15
} as Electron.RenderProcessGoneDetails
)
expect(onRendererProcessGone).toHaveBeenCalledWith(
expect.objectContaining({ reason: 'killed', exitCode: 15 }),
expect.any(Number)
)
consoleError.mockRestore()
})
const createRendererRecoveryWindowHarness = () => {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
id: 143,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
return { browserWindowInstance, windowHandlers }
}
it('reloads the app shell after an unexpected renderer process loss', () => {
vi.useFakeTimers()
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const { browserWindowInstance, windowHandlers } = createRendererRecoveryWindowHarness()
createMainWindow(null)
windowHandlers['render-process-gone']?.(
{} as never,
{
reason: 'crashed',
exitCode: 5
} as Electron.RenderProcessGoneDetails
)
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(1)
vi.advanceTimersByTime(250)
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(2)
expect(browserWindowInstance.loadURL).not.toHaveBeenCalled()
consoleError.mockRestore()
})
it('does not reload after renderer loss when recovery is disabled', () => {
vi.useFakeTimers()
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const { browserWindowInstance, windowHandlers } = createRendererRecoveryWindowHarness()
createMainWindow(null, { shouldRecoverRenderer: () => false })
windowHandlers['render-process-gone']?.(
{} as never,
{
reason: 'crashed',
exitCode: 5
} as Electron.RenderProcessGoneDetails
)
vi.advanceTimersByTime(250)
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(1)
expect(browserWindowInstance.loadURL).not.toHaveBeenCalled()
consoleError.mockRestore()
})
it('rechecks the renderer recovery predicate before reloading', () => {
vi.useFakeTimers()
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const { browserWindowInstance, windowHandlers } = createRendererRecoveryWindowHarness()
let shouldRecover = true
createMainWindow(null, { shouldRecoverRenderer: () => shouldRecover })
windowHandlers['render-process-gone']?.(
{} as never,
{
reason: 'crashed',
exitCode: 5
} as Electron.RenderProcessGoneDetails
)
shouldRecover = false
vi.advanceTimersByTime(250)
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(1)
expect(browserWindowInstance.loadURL).not.toHaveBeenCalled()
consoleError.mockRestore()
})
it('coalesces repeated renderer losses into one recovery reload', () => {
vi.useFakeTimers()
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const { browserWindowInstance, windowHandlers } = createRendererRecoveryWindowHarness()
createMainWindow(null)
const details = {
reason: 'crashed',
exitCode: 5
} as Electron.RenderProcessGoneDetails
windowHandlers['render-process-gone']?.({} as never, details)
windowHandlers['render-process-gone']?.({} as never, details)
vi.advanceTimersByTime(250)
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(2)
expect(browserWindowInstance.loadURL).not.toHaveBeenCalled()
consoleError.mockRestore()
})
it('does not reload after a clean renderer exit', () => {
vi.useFakeTimers()
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const { browserWindowInstance, windowHandlers } = createRendererRecoveryWindowHarness()
createMainWindow(null)
windowHandlers['render-process-gone']?.(
{} as never,
{
reason: 'clean-exit',
exitCode: 0
} as Electron.RenderProcessGoneDetails
)
vi.advanceTimersByTime(250)
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(1)
expect(browserWindowInstance.loadURL).not.toHaveBeenCalled()
consoleError.mockRestore()
})
it('cancels renderer recovery when the crashed window is closing', () => {
vi.useFakeTimers()
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const { browserWindowInstance, windowHandlers } = createRendererRecoveryWindowHarness()
createMainWindow(null)
windowHandlers['render-process-gone']?.(
{} as never,
{
reason: 'crashed',
exitCode: 5
} as Electron.RenderProcessGoneDetails
)
windowHandlers.close({ preventDefault: vi.fn() } as never)
vi.advanceTimersByTime(250)
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(1)
expect(browserWindowInstance.loadURL).not.toHaveBeenCalled()
consoleError.mockRestore()
})
it('stops auto-reloading after a rapid renderer crash loop trips the breaker', () => {
vi.useFakeTimers()
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const onRendererRecoveryExhausted = vi.fn()
const { browserWindowInstance, windowHandlers } = createRendererRecoveryWindowHarness()
createMainWindow(null, { onRendererRecoveryExhausted })
const details = { reason: 'crashed', exitCode: 5 } as Electron.RenderProcessGoneDetails
// Each cycle: renderer dies, breaker allows the first 3 reloads, then opens.
const driveCrashCycle = (): void => {
windowHandlers['render-process-gone']?.({} as never, details)
vi.advanceTimersByTime(250)
}
driveCrashCycle()
driveCrashCycle()
driveCrashCycle()
// 1 initial load + 3 recoveries.
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(4)
expect(onRendererRecoveryExhausted).not.toHaveBeenCalled()
// 4th crash within the window: breaker is open, no further reload.
driveCrashCycle()
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(4)
expect(onRendererRecoveryExhausted).toHaveBeenCalledTimes(1)
expect(onRendererRecoveryExhausted).toHaveBeenCalledWith(
expect.objectContaining({ recentRecoveryCount: 3 })
)
consoleError.mockRestore()
})
it('bounds renderer launch-failed recovery with the crash-loop breaker', () => {
vi.useFakeTimers()
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const onRendererRecoveryExhausted = vi.fn()
const { browserWindowInstance, windowHandlers } = createRendererRecoveryWindowHarness()
try {
createMainWindow(null, {
onRendererRecoveryExhausted,
shouldRecoverRenderer: (details) =>
shouldRecoverRendererAfterProcessGone({
reason: details.reason,
expectedTeardown: 'none'
})
})
const details = {
reason: 'launch-failed',
exitCode: 18
} as Electron.RenderProcessGoneDetails
const driveLaunchFailure = (): void => {
windowHandlers['render-process-gone']?.({} as never, details)
vi.advanceTimersByTime(250)
}
driveLaunchFailure()
driveLaunchFailure()
driveLaunchFailure()
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(4)
driveLaunchFailure()
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(4)
expect(onRendererRecoveryExhausted).toHaveBeenCalledOnce()
expect(onRendererRecoveryExhausted).toHaveBeenCalledWith(
expect.objectContaining({ details, recentRecoveryCount: 3 })
)
} finally {
consoleError.mockRestore()
}
})
function createStartupRevealWindowFixture() {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn()
}
const browserWindowInstance = {
webContents,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => false),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
setWindowButtonPosition: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
return { browserWindowInstance, windowHandlers }
}
function createStartupRevealStore(savedMaximized: boolean) {
return {
getUI: () =>
({
windowMaximized: savedMaximized
}) as never,
getSettings: () => ({ windowBackgroundBlur: false }) as never,
updateUI: vi.fn()
}
}
it('ignores duplicate ready-to-show events after startup maximize has already run', () => {
const { browserWindowInstance, windowHandlers } = createStartupRevealWindowFixture()
createMainWindow({
getUI: () =>
({
windowMaximized: true
}) as never,
getSettings: () => ({ windowBackgroundBlur: false }) as never,
updateUI: vi.fn()
} as never)
windowHandlers['ready-to-show']()
windowHandlers['ready-to-show']()
expect(browserWindowInstance.maximize).toHaveBeenCalledTimes(1)
expect(browserWindowInstance.show).toHaveBeenCalledTimes(1)
})
it('reveals the startup window on Windows when ready-to-show never fires', () => {
vi.useFakeTimers()
const { browserWindowInstance } = createStartupRevealWindowFixture()
withPlatform('win32', () => {
createMainWindow(null)
vi.advanceTimersByTime(9_999)
expect(browserWindowInstance.show).not.toHaveBeenCalled()
vi.advanceTimersByTime(1)
expect(browserWindowInstance.show).toHaveBeenCalledTimes(1)
})
})
it('cancels the Windows startup reveal fallback after ready-to-show', () => {
vi.useFakeTimers()
const { browserWindowInstance, windowHandlers } = createStartupRevealWindowFixture()
withPlatform('win32', () => {
createMainWindow(null)
windowHandlers['ready-to-show']()
vi.advanceTimersByTime(10_000)
expect(browserWindowInstance.show).toHaveBeenCalledTimes(1)
})
})
it('reveals the startup window on Linux when ready-to-show never fires', () => {
vi.useFakeTimers()
const { browserWindowInstance } = createStartupRevealWindowFixture()
withPlatform('linux', () => {
createMainWindow(null)
vi.advanceTimersByTime(9_999)
expect(browserWindowInstance.show).not.toHaveBeenCalled()
vi.advanceTimersByTime(1)
expect(browserWindowInstance.show).toHaveBeenCalledTimes(1)
})
})
it('cancels the Linux startup reveal fallback after ready-to-show', () => {
vi.useFakeTimers()
const { browserWindowInstance, windowHandlers } = createStartupRevealWindowFixture()
withPlatform('linux', () => {
createMainWindow(null)
windowHandlers['ready-to-show']()
vi.advanceTimersByTime(10_000)
expect(browserWindowInstance.show).toHaveBeenCalledTimes(1)
})
})
it('does not install the startup reveal fallback on macOS', () => {
vi.useFakeTimers()
const { browserWindowInstance } = createStartupRevealWindowFixture()
withPlatform('darwin', () => {
createMainWindow(null)
vi.advanceTimersByTime(10_000)
expect(browserWindowInstance.show).not.toHaveBeenCalled()
expect(browserWindowInstance.maximize).not.toHaveBeenCalled()
})
})
it('keeps the headless E2E window hidden when the Windows fallback fires', () => {
vi.useFakeTimers()
const previousHeadless = process.env.ORCA_E2E_HEADLESS
process.env.ORCA_E2E_HEADLESS = '1'
const { browserWindowInstance } = createStartupRevealWindowFixture()
try {
withPlatform('win32', () => {
createMainWindow(createStartupRevealStore(true) as never)
vi.advanceTimersByTime(10_000)
expect(browserWindowInstance.show).not.toHaveBeenCalled()
expect(browserWindowInstance.maximize).not.toHaveBeenCalled()
})
} finally {
if (previousHeadless === undefined) {
delete process.env.ORCA_E2E_HEADLESS
} else {
process.env.ORCA_E2E_HEADLESS = previousHeadless
}
}
})
it('clears the Windows startup reveal fallback when the window is closed', () => {
vi.useFakeTimers()
const { browserWindowInstance, windowHandlers } = createStartupRevealWindowFixture()
withPlatform('win32', () => {
createMainWindow(createStartupRevealStore(true) as never)
windowHandlers.closed()
vi.advanceTimersByTime(10_000)
expect(browserWindowInstance.show).not.toHaveBeenCalled()
expect(browserWindowInstance.maximize).not.toHaveBeenCalled()
})
})
it('does not show or maximize a destroyed window when the Windows fallback fires', () => {
vi.useFakeTimers()
const { browserWindowInstance } = createStartupRevealWindowFixture()
withPlatform('win32', () => {
createMainWindow(createStartupRevealStore(true) as never)
browserWindowInstance.isDestroyed.mockReturnValue(true)
vi.advanceTimersByTime(10_000)
expect(browserWindowInstance.show).not.toHaveBeenCalled()
expect(browserWindowInstance.maximize).not.toHaveBeenCalled()
})
})
it('keeps the headless E2E window hidden when the Linux fallback fires', () => {
vi.useFakeTimers()
const previousHeadless = process.env.ORCA_E2E_HEADLESS
process.env.ORCA_E2E_HEADLESS = '1'
const { browserWindowInstance } = createStartupRevealWindowFixture()
try {
withPlatform('linux', () => {
createMainWindow(createStartupRevealStore(true) as never)
vi.advanceTimersByTime(10_000)
expect(browserWindowInstance.show).not.toHaveBeenCalled()
expect(browserWindowInstance.maximize).not.toHaveBeenCalled()
})
} finally {
if (previousHeadless === undefined) {
delete process.env.ORCA_E2E_HEADLESS
} else {
process.env.ORCA_E2E_HEADLESS = previousHeadless
}
}
})
it('clears the Linux startup reveal fallback when the window is closed', () => {
vi.useFakeTimers()
const { browserWindowInstance, windowHandlers } = createStartupRevealWindowFixture()
withPlatform('linux', () => {
createMainWindow(createStartupRevealStore(true) as never)
windowHandlers.closed()
vi.advanceTimersByTime(10_000)
expect(browserWindowInstance.show).not.toHaveBeenCalled()
expect(browserWindowInstance.maximize).not.toHaveBeenCalled()
})
})
it('does not show or maximize a destroyed window when the Linux fallback fires', () => {
vi.useFakeTimers()
const { browserWindowInstance } = createStartupRevealWindowFixture()
withPlatform('linux', () => {
createMainWindow(createStartupRevealStore(true) as never)
browserWindowInstance.isDestroyed.mockReturnValue(true)
vi.advanceTimersByTime(10_000)
expect(browserWindowInstance.show).not.toHaveBeenCalled()
expect(browserWindowInstance.maximize).not.toHaveBeenCalled()
})
})
describe('system resume relay', () => {
function setupResumeWindow() {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn(),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isDestroyed: vi.fn(() => false),
id: 1
}
const instance = {
webContents,
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
// Why: maximized keeps forceRepaint from scheduling its size-nudge timer.
isMaximized: vi.fn(() => true),
isFullScreen: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return instance
})
return { windowHandlers, webContents, instance }
}
function getPowerResumeListener(): () => void {
const resumeCall = powerMonitorOnMock.mock.calls.find(
(call: unknown[]) => call[0] === 'resume'
)
if (!resumeCall) {
throw new Error('missing powerMonitor resume listener')
}
return resumeCall[1] as () => void
}
it('relays powerMonitor resume to the live window and forces a repaint', () => {
const { webContents } = setupResumeWindow()
createMainWindow(null)
const onResume = getPowerResumeListener()
webContents.send.mockClear()
webContents.invalidate.mockClear()
onResume()
expect(webContents.send).toHaveBeenCalledWith('system:resumed')
expect(webContents.invalidate).toHaveBeenCalledTimes(1)
})
it('does not send the resume event once the window is destroyed', () => {
const { webContents, instance } = setupResumeWindow()
createMainWindow(null)
const onResume = getPowerResumeListener()
instance.isDestroyed.mockReturnValue(true)
webContents.send.mockClear()
webContents.invalidate.mockClear()
onResume()
expect(webContents.send).not.toHaveBeenCalled()
expect(webContents.invalidate).not.toHaveBeenCalled()
})
it('does not send the resume event once webContents is destroyed', () => {
const { webContents } = setupResumeWindow()
createMainWindow(null)
const onResume = getPowerResumeListener()
webContents.isDestroyed.mockReturnValue(true)
webContents.send.mockClear()
webContents.invalidate.mockClear()
onResume()
expect(webContents.send).not.toHaveBeenCalled()
expect(webContents.invalidate).not.toHaveBeenCalled()
})
it('removes the powerMonitor resume listener when the window closes', () => {
const { windowHandlers } = setupResumeWindow()
createMainWindow(null)
const onResume = getPowerResumeListener()
windowHandlers.closed()
expect(powerMonitorRemoveListenerMock).toHaveBeenCalledWith('resume', onResume)
})
})
describe('minimize to tray on close (win32)', () => {
const originalPlatform = process.platform
function setPlatform(platform: NodeJS.Platform): void {
Object.defineProperty(process, 'platform', { value: platform, configurable: true })
}
type CloseFixture = {
windowHandlers: Record<string, (...args: any[]) => void>
webContents: { send: ReturnType<typeof vi.fn> }
instance: { hide: ReturnType<typeof vi.fn>; isMinimized: ReturnType<typeof vi.fn> }
}
function setupCloseWindow(): CloseFixture {
const windowHandlers: Record<string, (...args: any[]) => void> = {}
const webContents = {
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
setZoomLevel: vi.fn(),
setBackgroundThrottling: vi.fn(),
invalidate: vi.fn(),
setWindowOpenHandler: vi.fn(),
send: vi.fn(),
isCrashed: vi.fn(() => false),
id: 1
}
const instance = {
webContents,
on: vi.fn((event, handler) => {
windowHandlers[event] = handler
}),
isDestroyed: vi.fn(() => false),
isMaximized: vi.fn(() => false),
isFullScreen: vi.fn(() => false),
isMinimized: vi.fn(() => false),
getSize: vi.fn(() => [1200, 800]),
setSize: vi.fn(),
maximize: vi.fn(),
show: vi.fn(),
hide: vi.fn(),
loadFile: vi.fn(),
loadURL: vi.fn()
}
browserWindowMock.mockImplementation(function () {
return instance
})
return { windowHandlers, webContents, instance }
}
function makeStore(minimizeToTrayOnClose: boolean, trayMinimizeNoticeShown: boolean) {
return {
getUI: vi.fn(() => ({ trayMinimizeNoticeShown })),
getSettings: vi.fn(() => ({ windowBackgroundBlur: false, minimizeToTrayOnClose })),
updateUI: vi.fn()
}
}
afterEach(() => {
setPlatform(originalPlatform)
})
it('hides to the tray instead of closing when the setting is on', () => {
setPlatform('win32')
const { windowHandlers, webContents, instance } = setupCloseWindow()
const store = makeStore(true, true)
createMainWindow(store as never, { getIsQuitting: () => false })
const preventDefault = vi.fn()
windowHandlers.close({ preventDefault } as never)
expect(preventDefault).toHaveBeenCalled()
expect(instance.hide).toHaveBeenCalledTimes(1)
expect(webContents.send).not.toHaveBeenCalledWith('window:close-requested', expect.anything())
// Notice already shown, so it must not fire again.
expect(notificationMock).not.toHaveBeenCalled()
})
it('keeps the normal close flow when the setting is off', () => {
setPlatform('win32')
const { windowHandlers, webContents, instance } = setupCloseWindow()
const store = makeStore(false, true)
createMainWindow(store as never, { getIsQuitting: () => false })
windowHandlers.close({ preventDefault: vi.fn() } as never)
expect(instance.hide).not.toHaveBeenCalled()
expect(webContents.send).toHaveBeenCalledWith('window:close-requested', {
isQuitting: false,
requestId: expect.any(Number)
})
})
it('does not hide on a real quit even with the setting on', () => {
setPlatform('win32')
const { windowHandlers, webContents, instance } = setupCloseWindow()
const store = makeStore(true, true)
createMainWindow(store as never, { getIsQuitting: () => true })
windowHandlers.close({ preventDefault: vi.fn() } as never)
expect(instance.hide).not.toHaveBeenCalled()
expect(webContents.send).toHaveBeenCalledWith('window:close-requested', {
isQuitting: true,
requestId: expect.any(Number)
})
})
it('does not hide when the renderer process is gone', () => {
setPlatform('win32')
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const { windowHandlers, instance } = setupCloseWindow()
const store = makeStore(true, true)
createMainWindow(store as never, { getIsQuitting: () => false })
windowHandlers['render-process-gone']?.(
{} as never,
{ reason: 'crashed', exitCode: 5 } as never
)
const preventDefault = vi.fn()
windowHandlers.close({ preventDefault } as never)
expect(instance.hide).not.toHaveBeenCalled()
expect(preventDefault).not.toHaveBeenCalled()
consoleError.mockRestore()
})
it('shows the first-run notification once and persists the flag', () => {
setPlatform('win32')
const { windowHandlers } = setupCloseWindow()
const store = makeStore(true, false)
createMainWindow(store as never, { getIsQuitting: () => false })
windowHandlers.close({ preventDefault: vi.fn() } as never)
expect(notificationMock).toHaveBeenCalledTimes(1)
expect(notificationShowMock).toHaveBeenCalledTimes(1)
expect(store.updateUI).toHaveBeenCalledWith({ trayMinimizeNoticeShown: true })
})
it('leaves the close handler unchanged off win32', () => {
setPlatform('darwin')
const { windowHandlers, webContents, instance } = setupCloseWindow()
const store = makeStore(true, true)
createMainWindow(store as never, { getIsQuitting: () => false })
windowHandlers.close({ preventDefault: vi.fn() } as never)
expect(instance.hide).not.toHaveBeenCalled()
expect(webContents.send).toHaveBeenCalledWith('window:close-requested', {
isQuitting: false,
requestId: expect.any(Number)
})
})
// Why: on Windows the renderer-drawn X routes through window:request-close,
// not the native close event — regression guard for the bug where the app
// quit instead of hiding because the guard only covered the native event.
function captureIpcHandlers(): Record<string, (...args: any[]) => void> {
const ipcHandlers: Record<string, (...args: any[]) => void> = {}
vi.mocked(ipcMain.on).mockImplementation((channel, handler) => {
ipcHandlers[channel] = handler as (...args: any[]) => void
return ipcMain
})
return ipcHandlers
}
it('hides to the tray when the renderer-drawn X requests close', () => {
setPlatform('win32')
const ipcHandlers = captureIpcHandlers()
const { webContents, instance } = setupCloseWindow()
const store = makeStore(true, true)
createMainWindow(store as never, { getIsQuitting: () => false })
ipcHandlers['window:request-close']?.()
expect(instance.hide).toHaveBeenCalledTimes(1)
expect(webContents.send).not.toHaveBeenCalledWith('window:close-requested', expect.anything())
})
it('forwards window:request-close to the renderer when the setting is off', () => {
setPlatform('win32')
const ipcHandlers = captureIpcHandlers()
const { webContents, instance } = setupCloseWindow()
const store = makeStore(false, true)
createMainWindow(store as never, { getIsQuitting: () => false })
ipcHandlers['window:request-close']?.()
expect(instance.hide).not.toHaveBeenCalled()
expect(webContents.send).toHaveBeenCalledWith('window:close-requested', {
isQuitting: false
})
})
})
})