Files
orca/src/main/window/dashboard-popout-window.test.ts
T
mirror 34fa2cd5ca
Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
source snapshot 2026-09-02
2026-09-02 02:03:16 +08:00

478 lines
16 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
type Display = { workArea: { x: number; y: number; width: number; height: number } }
const {
instances,
BrowserWindowMock,
nativeThemeMock,
appOnMock,
appRemoveListenerMock,
getAllDisplaysMock,
installNavigationPolicyMock,
sendToTrustedUIRendererMock,
isMock
} = vi.hoisted(() => {
const created: FakeWindow[] = []
class FakeWindow {
options: Electron.BrowserWindowConstructorOptions
private handlers: Record<string, ((...args: unknown[]) => void)[]> = {}
private onceHandlers: Record<string, ((...args: unknown[]) => void)[]> = {}
destroyed = false
minimized = false
fullscreen = false
focused = false
zoomLevel = 0
private webContentsHandlers: Record<string, ((...args: unknown[]) => void)[]> = {}
webContents = {
id: created.length + 1,
send: vi.fn(),
isDestroyed: () => this.destroyed,
session: {
setPermissionRequestHandler: vi.fn(),
setPermissionCheckHandler: vi.fn()
},
on: (event: string, cb: (...args: unknown[]) => void) => {
;(this.webContentsHandlers[event] ||= []).push(cb)
},
setZoomLevel: vi.fn((level: number) => {
this.zoomLevel = level
}),
getZoomLevel: vi.fn(() => this.zoomLevel)
}
emitWebContents(event: string, ...args: unknown[]): void {
for (const cb of this.webContentsHandlers[event] ?? []) {
cb(...args)
}
}
bounds = { x: 100, y: 100, width: 960, height: 720 }
focus = vi.fn()
show = vi.fn()
restore = vi.fn(() => {
this.minimized = false
})
loadURL = vi.fn()
loadFile = vi.fn()
close = vi.fn(() => {
this.destroyed = true
this.emit('close')
this.emit('closed')
})
constructor(options: Electron.BrowserWindowConstructorOptions) {
this.options = options
created.push(this)
}
on(event: string, cb: (...args: unknown[]) => void): this {
;(this.handlers[event] ||= []).push(cb)
return this
}
once(event: string, cb: (...args: unknown[]) => void): this {
;(this.onceHandlers[event] ||= []).push(cb)
return this
}
emit(event: string, ...args: unknown[]): void {
for (const cb of this.handlers[event] ?? []) {
cb(...args)
}
for (const cb of this.onceHandlers[event] ?? []) {
cb(...args)
}
}
isDestroyed(): boolean {
return this.destroyed
}
isFocused(): boolean {
return this.focused
}
isMinimized(): boolean {
return this.minimized
}
isFullScreen(): boolean {
return this.fullscreen
}
getBounds(): { x: number; y: number; width: number; height: number } {
return this.bounds
}
}
return {
instances: created,
BrowserWindowMock: FakeWindow,
nativeThemeMock: { shouldUseDarkColors: true },
appOnMock: vi.fn(),
appRemoveListenerMock: vi.fn(),
getAllDisplaysMock: vi.fn((): Display[] => [
{ workArea: { x: 0, y: 0, width: 1920, height: 1080 } }
]),
installNavigationPolicyMock: vi.fn(),
sendToTrustedUIRendererMock: vi.fn(),
isMock: { dev: false } as { dev: boolean }
}
})
// Static helpers live on the constructor; broadcastPopoutOpenChanged uses
// BrowserWindow.getAllWindows(). Return the instances created in this test.
;(BrowserWindowMock as unknown as { getAllWindows: () => unknown[] }).getAllWindows = () =>
instances
vi.mock('electron', () => ({
app: { on: appOnMock, removeListener: appRemoveListenerMock },
BrowserWindow: BrowserWindowMock,
nativeTheme: nativeThemeMock,
screen: { getAllDisplays: getAllDisplaysMock }
}))
vi.mock('@electron-toolkit/utils', () => ({ is: isMock }))
vi.mock('../ipc/ui', () => ({ sendToTrustedUIRenderer: sendToTrustedUIRendererMock }))
vi.mock('./privileged-window-navigation', () => ({
installPrivilegedWindowNavigationPolicy: installNavigationPolicyMock
}))
import {
createOrFocusDashboardPopout,
closeDashboardPopout,
isDashboardPopoutRenderer,
zoomDashboardPopoutIfFocused
} from './dashboard-popout-window'
import { readBrowserClientHostIdArgument } from '../../shared/browser-client-host-id-argument'
type FakeWindow = InstanceType<typeof BrowserWindowMock>
function makeStore(ui: Record<string, unknown> = {}): {
getUI: () => Record<string, unknown>
updateUI: ReturnType<typeof vi.fn>
onUIChanged: ReturnType<typeof vi.fn>
emitUIChanged: (next: Record<string, unknown>) => void
uiChangeUnsubscribe: ReturnType<typeof vi.fn>
} {
const listeners: ((next: Record<string, unknown>) => void)[] = []
const uiChangeUnsubscribe = vi.fn()
return {
getUI: () => ui,
updateUI: vi.fn(),
onUIChanged: vi.fn((listener: (next: Record<string, unknown>) => void) => {
listeners.push(listener)
return uiChangeUnsubscribe
}),
emitUIChanged: (next) => {
for (const listener of listeners) {
listener(next)
}
},
uiChangeUnsubscribe
}
}
const RENDERER_URL = 'http://localhost:5173'
describe('createOrFocusDashboardPopout', () => {
beforeEach(() => {
instances.length = 0
isMock.dev = false
vi.stubEnv('ELECTRON_RENDERER_URL', '')
getAllDisplaysMock.mockReturnValue([{ workArea: { x: 0, y: 0, width: 1920, height: 1080 } }])
})
afterEach(() => {
// Reset the module-level singleton between tests.
closeDashboardPopout()
vi.unstubAllEnvs()
vi.clearAllMocks()
})
it('creates a native-frame window with the shared preload and no webview surface', () => {
createOrFocusDashboardPopout(makeStore() as never)
expect(instances).toHaveLength(1)
const opts = instances[0].options
expect(opts.title).toBe('Orca Agent Dashboard')
expect(opts.minWidth).toBe(480)
expect(opts.minHeight).toBe(360)
// Native frame: neither a custom titleBarStyle nor frame:false is set.
expect(opts.titleBarStyle).toBeUndefined()
expect(opts.frame).toBeUndefined()
expect(opts.backgroundColor).toBe('#0a0a0a') // dark theme mock
expect(opts.webPreferences?.sandbox).toBe(true)
expect(opts.webPreferences?.partition).toBe('orca-dashboard-popout')
expect(opts.webPreferences?.webviewTag).toBe(false)
expect(opts.webPreferences?.preload).toMatch(/preload[\\/]index\.js$/)
// Why unstamped: no guest of ours can run here, so this renderer hosts no client-placed page
// and must keep mirroring what the host publishes for every one of them.
expect(
readBrowserClientHostIdArgument(opts.webPreferences?.additionalArguments ?? [])
).toBeNull()
expect(installNavigationPolicyMock).toHaveBeenCalledWith(instances[0].webContents)
const { session } = instances[0].webContents
expect(session.setPermissionRequestHandler).toHaveBeenCalledTimes(1)
expect(session.setPermissionCheckHandler).toHaveBeenCalledTimes(1)
const permissionCallback = vi.fn()
session.setPermissionRequestHandler.mock.calls[0][0](null, 'notifications', permissionCallback)
expect(permissionCallback).toHaveBeenCalledWith(false)
expect(session.setPermissionCheckHandler.mock.calls[0][0]()).toBe(false)
expect(sendToTrustedUIRendererMock).toHaveBeenCalledWith('dashboard:popoutOpenChanged', true)
})
it('shows the window on ready-to-show', () => {
createOrFocusDashboardPopout(makeStore() as never)
const win = instances[0]
expect(win.show).not.toHaveBeenCalled()
win.emit('ready-to-show')
expect(win.show).toHaveBeenCalledTimes(1)
})
it('loads the prod file entry with the requested view', () => {
createOrFocusDashboardPopout(makeStore() as never, 'kanban')
const win = instances[0]
expect(win.loadURL).not.toHaveBeenCalled()
expect(win.loadFile).toHaveBeenCalledTimes(1)
const [file, options] = win.loadFile.mock.calls[0]
expect(String(file)).toMatch(/renderer[\\/]popout\.html$/)
expect(options).toEqual({ search: 'view=kanban' })
})
it('opens on the current dashboard view by default', () => {
createOrFocusDashboardPopout(makeStore() as never)
expect(instances[0].loadFile.mock.calls[0][1]).toEqual({ search: 'view=board' })
})
it('loads the dev server URL with the requested view when in dev', () => {
isMock.dev = true
vi.stubEnv('ELECTRON_RENDERER_URL', RENDERER_URL)
createOrFocusDashboardPopout(makeStore() as never, 'kanban')
const win = instances[0]
expect(win.loadFile).not.toHaveBeenCalled()
expect(win.loadURL).toHaveBeenCalledWith(`${RENDERER_URL}/popout.html?view=kanban`)
})
it('focuses the existing window instead of creating a second one', () => {
const store = makeStore()
const first = createOrFocusDashboardPopout(store as never)
const second = createOrFocusDashboardPopout(store as never)
expect(instances).toHaveLength(1)
expect(second).toBe(first)
expect(instances[0].focus).toHaveBeenCalledTimes(1)
})
it('switches an existing popout to an explicitly requested view', () => {
const store = makeStore()
createOrFocusDashboardPopout(store as never)
const win = instances[0]
createOrFocusDashboardPopout(store as never, 'map')
expect(win.webContents.send).toHaveBeenCalledWith('dashboard:viewRequested', 'map')
})
it('trusts only the live popout webContents', () => {
const win = createOrFocusDashboardPopout(makeStore() as never) as unknown as FakeWindow
expect(isDashboardPopoutRenderer(win.webContents as never)).toBe(true)
expect(isDashboardPopoutRenderer({ id: win.webContents.id } as never)).toBe(false)
win.destroyed = true
expect(isDashboardPopoutRenderer(win.webContents as never)).toBe(false)
})
it('restores a minimized window when re-requested', () => {
const store = makeStore()
const win = createOrFocusDashboardPopout(store as never) as unknown as FakeWindow
win.minimized = true
createOrFocusDashboardPopout(store as never)
expect(win.restore).toHaveBeenCalledTimes(1)
expect(win.focus).toHaveBeenCalledTimes(1)
})
it('restores valid persisted bounds', () => {
const store = makeStore({
dashboardPopoutBounds: { x: 200, y: 150, width: 1000, height: 800 }
})
createOrFocusDashboardPopout(store as never)
const opts = instances[0].options
expect(opts.x).toBe(200)
expect(opts.y).toBe(150)
expect(opts.width).toBe(1000)
expect(opts.height).toBe(800)
})
it('discards off-screen persisted bounds and falls back to defaults', () => {
// Display does not overlap the saved rect at all.
getAllDisplaysMock.mockReturnValue([{ workArea: { x: 0, y: 0, width: 800, height: 600 } }])
const store = makeStore({
dashboardPopoutBounds: { x: 5000, y: 5000, width: 1000, height: 800 }
})
createOrFocusDashboardPopout(store as never)
const opts = instances[0].options
expect(opts.x).toBeUndefined()
expect(opts.y).toBeUndefined()
expect(opts.width).toBe(960)
expect(opts.height).toBe(720)
})
it('persists bounds on resize after the debounce, guarding near-minimum sizes', () => {
vi.useFakeTimers()
try {
const store = makeStore()
const win = createOrFocusDashboardPopout(store as never) as unknown as FakeWindow
win.bounds = { x: 10, y: 10, width: 1200, height: 900 }
win.emit('resize')
vi.advanceTimersByTime(500)
expect(store.updateUI).toHaveBeenCalledWith({
dashboardPopoutBounds: { x: 10, y: 10, width: 1200, height: 900 }
})
store.updateUI.mockClear()
win.bounds = { x: 10, y: 10, width: 100, height: 100 } // below minimum
win.emit('resize')
vi.advanceTimersByTime(500)
expect(store.updateUI).not.toHaveBeenCalled()
} finally {
vi.useRealTimers()
}
})
it('closeDashboardPopout closes an open window', () => {
createOrFocusDashboardPopout(makeStore() as never)
const win = instances[0]
closeDashboardPopout()
expect(win.close).toHaveBeenCalledTimes(1)
})
it('applies the persisted app zoom level on dom-ready', () => {
const store = makeStore({ uiZoomLevel: 1.5 })
const win = createOrFocusDashboardPopout(store as never) as unknown as FakeWindow
expect(win.webContents.setZoomLevel).not.toHaveBeenCalled()
win.emitWebContents('dom-ready')
expect(win.webContents.setZoomLevel).toHaveBeenCalledWith(1.5)
})
it('follows app zoom changes while open and unsubscribes on close', () => {
const store = makeStore({ uiZoomLevel: 0 })
const win = createOrFocusDashboardPopout(store as never) as unknown as FakeWindow
store.emitUIChanged({ uiZoomLevel: 2 })
expect(win.webContents.setZoomLevel).toHaveBeenCalledWith(2)
// Unchanged level: no redundant reapply that would clobber a local zoom.
win.webContents.setZoomLevel.mockClear()
store.emitUIChanged({ uiZoomLevel: 2 })
expect(win.webContents.setZoomLevel).not.toHaveBeenCalled()
win.emit('closed')
expect(store.uiChangeUnsubscribe).toHaveBeenCalledTimes(1)
})
it('zoomDashboardPopoutIfFocused steps the popout zoom only while focused', () => {
expect(zoomDashboardPopoutIfFocused('in')).toBe(false)
const store = makeStore()
const win = createOrFocusDashboardPopout(store as never) as unknown as FakeWindow
expect(zoomDashboardPopoutIfFocused('in')).toBe(false)
expect(win.zoomLevel).toBe(0)
win.focused = true
expect(zoomDashboardPopoutIfFocused('in')).toBe(true)
expect(win.zoomLevel).toBe(0.5)
expect(zoomDashboardPopoutIfFocused('out')).toBe(true)
expect(win.zoomLevel).toBe(0)
win.zoomLevel = 5
expect(zoomDashboardPopoutIfFocused('in')).toBe(true)
expect(win.zoomLevel).toBe(5) // clamped at max
expect(zoomDashboardPopoutIfFocused('reset')).toBe(true)
expect(win.zoomLevel).toBe(0)
})
it('handles the zoom-in chord via before-input-event and ignores other keys', () => {
const store = makeStore()
const win = createOrFocusDashboardPopout(store as never) as unknown as FakeWindow
const mod =
process.platform === 'darwin'
? { meta: true, control: false }
: { meta: false, control: true }
const zoomEvent = { preventDefault: vi.fn() }
win.emitWebContents('before-input-event', zoomEvent, {
type: 'keyDown',
key: '=',
code: 'Equal',
alt: false,
shift: false,
...mod
})
expect(zoomEvent.preventDefault).toHaveBeenCalledTimes(1)
expect(win.zoomLevel).toBe(0.5)
const plainKeyEvent = { preventDefault: vi.fn() }
win.emitWebContents('before-input-event', plainKeyEvent, {
type: 'keyDown',
key: 'a',
code: 'KeyA',
alt: false,
shift: false,
meta: false,
control: false
})
expect(plainKeyEvent.preventDefault).not.toHaveBeenCalled()
expect(win.zoomLevel).toBe(0.5)
})
it('handles mouse-wheel zoom requests outside before-input-event', () => {
const win = createOrFocusDashboardPopout(makeStore() as never) as unknown as FakeWindow
const event = { preventDefault: vi.fn() }
win.emitWebContents('zoom-changed', event, 'in')
expect(event.preventDefault).toHaveBeenCalledTimes(1)
expect(win.zoomLevel).toBe(0.5)
win.emitWebContents('zoom-changed', event, 'out')
expect(event.preventDefault).toHaveBeenCalledTimes(2)
expect(win.zoomLevel).toBe(0)
})
it('respects zoom keybinding overrides for keyboard and mouse-wheel paths', () => {
const win = createOrFocusDashboardPopout(makeStore() as never, undefined, {
getKeybindings: () => ({
'zoom.in': ['Mod+Y'],
'zoom.out': []
})
}) as unknown as FakeWindow
const mod =
process.platform === 'darwin'
? { meta: true, control: false }
: { meta: false, control: true }
const defaultEvent = { preventDefault: vi.fn() }
win.emitWebContents('before-input-event', defaultEvent, {
type: 'keyDown',
key: '=',
code: 'Equal',
alt: false,
shift: false,
...mod
})
win.emitWebContents('zoom-changed', defaultEvent, 'out')
expect(defaultEvent.preventDefault).not.toHaveBeenCalled()
expect(win.zoomLevel).toBe(0)
const customEvent = { preventDefault: vi.fn() }
win.emitWebContents('before-input-event', customEvent, {
type: 'keyDown',
key: 'y',
code: 'KeyY',
alt: false,
shift: false,
...mod
})
expect(customEvent.preventDefault).toHaveBeenCalledTimes(1)
expect(win.zoomLevel).toBe(0.5)
})
})