162 lines
5.3 KiB
TypeScript
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()
|
|
})
|
|
})
|