Files
orca/src/main/window/createMainWindow-markdown-editor-focus.test.ts
T

575 lines
17 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('electron', async () =>
(await import('./createMainWindow-test-harness')).electronModuleMock()
)
vi.mock('./macos-tahoe-release', async () =>
(await import('./createMainWindow-test-harness')).macosTahoeReleaseMock()
)
vi.mock('../app-icon', async () => (await import('./createMainWindow-test-harness')).appIconMock())
vi.mock('../browser/browser-manager', async () =>
(await import('./createMainWindow-test-harness')).browserManagerMock()
)
import { createMainWindow } from './createMainWindow'
import { ipcMain } from 'electron'
import { resetExpectedTeardownStateForTest } from '../crash-reporting/expected-teardown-state'
import {
browserWindowMock,
buildFromTemplateMock,
menuPopupMock,
resetMainWindowMocks
} from './createMainWindow-test-harness'
function openMainWindowForContextMenu() {
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(() => Promise.resolve()),
loadURL: vi.fn(() => Promise.resolve())
}
browserWindowMock.mockImplementation(function () {
return browserWindowInstance
})
createMainWindow(null)
return { webContents, windowHandlers, browserWindowInstance }
}
describe('createMainWindow', () => {
beforeEach(() => {
resetMainWindowMocks()
resetExpectedTeardownStateForTest()
vi.useRealTimers()
})
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(() => Promise.resolve()),
loadURL: vi.fn(() => Promise.resolve())
}
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(() => Promise.resolve()),
loadURL: vi.fn(() => Promise.resolve())
}
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(() => Promise.resolve()),
loadURL: vi.fn(() => Promise.resolve())
}
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(() => Promise.resolve()),
loadURL: vi.fn(() => Promise.resolve())
}
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(() => Promise.resolve()),
loadURL: vi.fn(() => Promise.resolve())
}
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(() => Promise.resolve()),
loadURL: vi.fn(() => Promise.resolve())
}
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(() => Promise.resolve()),
loadURL: vi.fn(() => Promise.resolve())
}
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('opens a table-aware context menu synchronously without a renderer query', () => {
const { webContents, windowHandlers, browserWindowInstance } = openMainWindowForContextMenu()
const tableTargetListener = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'rich-markdown:context-target')?.[1]
tableTargetListener?.({ sender: webContents } as never, {
cellType: 'body',
targetId: 'table-target',
x: 42,
y: 84
})
windowHandlers['context-menu'](
{} as never,
{
x: 42,
y: 84,
isEditable: true,
formControlType: 'none',
spellcheckEnabled: true,
dictionarySuggestions: ['reference'],
misspelledWord: 'refrence'
} as Electron.ContextMenuParams
)
expect(buildFromTemplateMock).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({ label: 'reference' }),
expect.objectContaining({ label: 'Table' })
])
)
expect(menuPopupMock).toHaveBeenCalledWith({ window: browserWindowInstance, x: 42, y: 84 })
})
it('builds the plain edit menu for the one right-click the renderer marks as plain text', () => {
const { webContents, windowHandlers } = openMainWindowForContextMenu()
const params = {
x: 10,
y: 20,
isEditable: true,
formControlType: 'none',
dictionarySuggestions: ['reference'],
misspelledWord: 'refrence'
}
const plainTextListener: ((...args: any[]) => void) | undefined = vi
.mocked(ipcMain.on)
.mock.calls.find(([channel]) => channel === 'editable-context-menu:plain-text-target')?.[1]
plainTextListener?.({ sender: webContents })
windowHandlers['context-menu']({}, params)
windowHandlers['context-menu']({}, params)
expect(buildFromTemplateMock).toHaveBeenNthCalledWith(1, [
expect.objectContaining({ label: 'reference' }),
{ type: 'separator' },
expect.objectContaining({ label: 'Add to dictionary' }),
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
expect.objectContaining({ label: 'Paste' }),
{ role: 'selectAll' }
])
expect(buildFromTemplateMock).toHaveBeenNthCalledWith(
2,
expect.arrayContaining([expect.objectContaining({ label: 'Format' })])
)
})
})