d3ro-voice/apps/desktop/tests/main/windows/popup-navigation.test.ts

162 lines
5.3 KiB
TypeScript

// tests/main/windows/popup-navigation.test.ts
// 히스토리 팝업을 연 채 명령어 팝업을 열면 Enter 가 히스토리 항목을 삽입하던 버그의 회귀 테스트.
import { describe, it, expect, vi } from 'vitest'
import {
PopupNavigationController,
POPUP_NAV_KEYS,
HISTORY_INSERT_DELAY_MS,
type PopupKind,
type PopupWindowPort,
} from '../../../src/main/windows/popup-navigation'
interface FakeWindow extends PopupWindowPort {
visible: boolean
keys: string[]
}
function fakeWindow(): FakeWindow {
const win: FakeWindow = {
visible: false,
keys: [],
show: () => {
win.visible = true
},
hide: () => {
win.visible = false
},
isVisible: () => win.visible,
sendKey: (key) => {
win.keys.push(key)
},
}
return win
}
/** Electron globalShortcut 처럼 이미 등록된 가속기의 재등록은 false 로 거부한다 */
function fakeShortcuts(): {
registered: Map<string, () => void>
register: (accel: string, cb: () => void) => boolean
unregister: (accel: string) => void
press: (accel: string) => void
} {
const registered = new Map<string, () => void>()
return {
registered,
register: (accel, cb) => {
if (registered.has(accel)) return false
registered.set(accel, cb)
return true
},
unregister: (accel) => {
registered.delete(accel)
},
press: (accel) => registered.get(accel)?.(),
}
}
function setup(): {
controller: PopupNavigationController
windows: Record<PopupKind, FakeWindow>
shortcuts: ReturnType<typeof fakeShortcuts>
inserted: string[]
scheduled: Array<{ fn: () => void; ms: number }>
selections: Array<{ id: string; name: string }>
} {
const windows = { history: fakeWindow(), command: fakeWindow() }
const shortcuts = fakeShortcuts()
const inserted: string[] = []
const scheduled: Array<{ fn: () => void; ms: number }> = []
const selections: Array<{ id: string; name: string }> = []
const controller = new PopupNavigationController({
windows,
shortcuts,
insertText: async (text) => {
inserted.push(text)
},
applyCommandSelection: (sel) => selections.push(sel),
schedule: (fn, ms) => scheduled.push({ fn, ms }),
})
return { controller, windows, shortcuts, inserted, scheduled, selections }
}
describe('PopupNavigationController', () => {
it('히스토리 → 명령어 전환 시 히스토리를 닫고 Enter 는 명령어 팝업으로 간다', () => {
const { controller, windows, shortcuts } = setup()
controller.toggle('history')
controller.toggle('command')
expect(windows.history.visible).toBe(false)
expect(windows.command.visible).toBe(true)
expect(controller.owner).toBe('command')
shortcuts.press('Return')
expect(windows.command.keys).toEqual(['Enter'])
expect(windows.history.keys).toEqual([])
})
it('소유자가 아닌 팝업을 닫아도 소유자의 키를 뺏지 않는다', () => {
const { controller, windows, shortcuts } = setup()
controller.show('command')
controller.dismiss('history')
expect(shortcuts.registered.size).toBe(POPUP_NAV_KEYS.length)
shortcuts.press('Down')
expect(windows.command.keys).toEqual(['ArrowDown'])
})
it('소유 팝업을 닫으면 전역 내비게이션 키를 모두 해제한다', () => {
const { controller, shortcuts } = setup()
controller.toggle('history')
expect(shortcuts.registered.size).toBe(POPUP_NAV_KEYS.length)
controller.toggle('history')
expect(shortcuts.registered.size).toBe(0)
expect(controller.owner).toBeNull()
})
it('히스토리 선택은 팝업을 닫고 지연 뒤 삽입한다', async () => {
const { controller, windows, inserted, scheduled, shortcuts } = setup()
controller.show('history')
controller.selectHistoryItem('붙여넣을 텍스트')
expect(windows.history.visible).toBe(false)
expect(shortcuts.registered.size).toBe(0)
expect(scheduled).toHaveLength(1)
expect(scheduled[0].ms).toBe(HISTORY_INSERT_DELAY_MS)
expect(inserted).toEqual([])
scheduled[0].fn()
await Promise.resolve()
expect(inserted).toEqual(['붙여넣을 텍스트'])
})
it('명령어 선택은 팝업을 닫고 선택을 반영한다', () => {
const { controller, windows, selections } = setup()
controller.show('command')
controller.selectCommand({ id: 'cmd-1', name: '번역' })
expect(windows.command.visible).toBe(false)
expect(selections).toEqual([{ id: 'cmd-1', name: '번역' }])
})
it('팝업이 다른 경로로 사라졌으면 다음 키에서 전역 키를 놓는다', () => {
const { controller, windows, shortcuts } = setup()
controller.show('history')
windows.history.visible = false
shortcuts.press('Return')
expect(shortcuts.registered.size).toBe(0)
expect(windows.history.keys).toEqual([])
})
it('등록 실패(다른 앱이 점유)를 경고한다', () => {
const warn = vi.fn()
const windows = { history: fakeWindow(), command: fakeWindow() }
const controller = new PopupNavigationController({
windows,
shortcuts: { register: () => false, unregister: () => undefined },
insertText: async () => undefined,
applyCommandSelection: () => undefined,
warn,
})
controller.show('history')
expect(warn).toHaveBeenCalled()
})
})