fix(menu): restore paste in macOS native dialogs (#12645)

* fix(menu): restore paste in macOS native dialogs

* test(menu): pin platform for paste routing coverage

CI runs unit shards on ubuntu-latest only, so the unpinned paste-routing
test asserted "no native paste" vacuously: a double-route regression
passed green on Linux. Cover darwin/linux/win32 explicitly so the
exactly-once contract holds on every platform, and assert the item exists
in the negative-only cases so a rename cannot pass them silently.

Also document why the native first-responder fallback exists.

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
This commit is contained in:
pstanton237 2026-08-06 15:17:34 +09:00 committed by GitHub
parent a30e3b9f61
commit ca187c7307
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 74 additions and 16 deletions

View File

@ -1,9 +1,15 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const { buildFromTemplateMock, setApplicationMenuMock, getFocusedWindowMock } = vi.hoisted(() => ({
const {
buildFromTemplateMock,
setApplicationMenuMock,
getFocusedWindowMock,
sendActionToFirstResponderMock
} = vi.hoisted(() => ({
buildFromTemplateMock: vi.fn(),
setApplicationMenuMock: vi.fn(),
getFocusedWindowMock: vi.fn()
getFocusedWindowMock: vi.fn(),
sendActionToFirstResponderMock: vi.fn()
}))
vi.mock('electron', () => ({
@ -12,7 +18,8 @@ vi.mock('electron', () => ({
},
Menu: {
buildFromTemplate: buildFromTemplateMock,
setApplicationMenu: setApplicationMenuMock
setApplicationMenu: setApplicationMenuMock,
sendActionToFirstResponder: sendActionToFirstResponderMock
},
app: {
name: 'Orca'
@ -70,9 +77,14 @@ describe('registerAppMenu', () => {
buildFromTemplateMock.mockReset()
setApplicationMenuMock.mockReset()
getFocusedWindowMock.mockReset()
sendActionToFirstResponderMock.mockReset()
buildFromTemplateMock.mockImplementation((template) => ({ template }))
})
afterEach(() => {
vi.restoreAllMocks()
})
it('shows reload shortcuts as policy-routed menu hints', () => {
registerAppMenu(buildMenuOptions())
@ -214,23 +226,59 @@ describe('registerAppMenu', () => {
expect(paletteItem?.accelerator).toBeUndefined()
})
it('routes Edit > Paste through Orca coordinated paste ownership', () => {
const send = vi.fn()
getFocusedWindowMock.mockReturnValue({ webContents: { send } })
// Why: pin the platform on every case — CI runs this suite on Linux only, so an
// unpinned test leaves the other platforms' branches entirely uncovered.
it.each(['darwin', 'linux', 'win32'] as const)(
'routes Edit > Paste through Orca coordinated paste ownership on %s',
(platform) => {
vi.spyOn(process, 'platform', 'get').mockReturnValue(platform)
const send = vi.fn()
getFocusedWindowMock.mockReturnValue({ webContents: { send } })
registerAppMenu(buildMenuOptions())
const editSubmenu = getSubmenu(getTemplate(), 'Edit')
const pasteItem = editSubmenu.find((item) => item.label === 'Paste')
expect(pasteItem).toBeDefined()
expect(pasteItem?.role).toBeUndefined()
expect(pasteItem?.accelerator).toBe('CmdOrCtrl+V')
pasteItem?.click?.({} as never, {} as never, {} as never)
expect(send).toHaveBeenCalledOnce()
expect(send).toHaveBeenCalledWith('ui:appMenuPaste')
expect(sendActionToFirstResponderMock).not.toHaveBeenCalled()
}
)
it('routes Edit > Paste to the native first responder once on macOS without a focused window', () => {
vi.spyOn(process, 'platform', 'get').mockReturnValue('darwin')
getFocusedWindowMock.mockReturnValue(null)
registerAppMenu(buildMenuOptions())
const editSubmenu = getSubmenu(getTemplate(), 'Edit')
const pasteItem = editSubmenu.find((item) => item.label === 'Paste')
expect(pasteItem).toBeDefined()
expect(pasteItem?.role).toBeUndefined()
expect(pasteItem?.accelerator).toBe('CmdOrCtrl+V')
const pasteItem = getSubmenu(getTemplate(), 'Edit').find((item) => item.label === 'Paste')
pasteItem?.click?.({} as never, {} as never, {} as never)
expect(send).toHaveBeenCalledWith('ui:appMenuPaste')
expect(sendActionToFirstResponderMock).toHaveBeenCalledOnce()
expect(sendActionToFirstResponderMock).toHaveBeenCalledWith('paste:')
})
it.each(['linux', 'win32'] as const)(
'does not invoke the native paste responder on %s without a focused window',
(platform) => {
vi.spyOn(process, 'platform', 'get').mockReturnValue(platform)
getFocusedWindowMock.mockReturnValue(null)
registerAppMenu(buildMenuOptions())
const pasteItem = getSubmenu(getTemplate(), 'Edit').find((item) => item.label === 'Paste')
// Why: this case asserts only a negative, so it would pass green if the item vanished.
expect(pasteItem).toBeDefined()
pasteItem?.click?.({} as never, {} as never, {} as never)
expect(sendActionToFirstResponderMock).not.toHaveBeenCalled()
}
)
it.runIf(!isMac)('puts Settings and Exit under File on Windows/Linux', () => {
registerAppMenu(buildMenuOptions())

View File

@ -180,7 +180,17 @@ function buildAndApplyMenu(options: RegisterAppMenuOptions): void {
click: () => {
// Why: a focused terminal/native-chat pane is not a native editable
// control, so raw Electron paste cannot know which Orca surface owns it.
BrowserWindow.getFocusedWindow()?.webContents.send('ui:appMenuPaste')
const focusedWindow = BrowserWindow.getFocusedWindow()
if (focusedWindow) {
focusedWindow.webContents.send('ui:appMenuPaste')
return
}
// Why: a macOS native panel (open/save, Go to Folder) leaves no focused
// BrowserWindow, so overriding the paste role would strand Cmd+V as a no-op.
if (isMac) {
Menu.sendActionToFirstResponder('paste:')
}
}
},
{ role: 'selectAll' }