// 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 void> register: (accel: string, cb: () => void) => boolean unregister: (accel: string) => void press: (accel: string) => void } { const registered = new Map 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 shortcuts: ReturnType 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() }) })