mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-28 18:19:28 +00:00
240 lines
9 KiB
TypeScript
240 lines
9 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
import {
|
|
createQuickEntryShortcut,
|
|
DEFAULT_QUICK_ENTRY_SHORTCUT,
|
|
type GlobalShortcutLike,
|
|
parseQuickEntryShortcut,
|
|
quickEntryWindowBounds,
|
|
sanitizeQuickEntrySettings
|
|
} from './quick-entry'
|
|
|
|
function fakeGlobalShortcut(options: { register?: boolean; taken?: string[] } = {}) {
|
|
const held = new Set(options.taken ?? [])
|
|
|
|
const globalShortcut: GlobalShortcutLike = {
|
|
isRegistered: vi.fn((accelerator: string) => held.has(accelerator)),
|
|
register: vi.fn((accelerator: string) => {
|
|
if (options.register === false) {
|
|
return false
|
|
}
|
|
|
|
held.add(accelerator)
|
|
|
|
return true
|
|
}),
|
|
unregister: vi.fn((accelerator: string) => void held.delete(accelerator))
|
|
}
|
|
|
|
return { globalShortcut, held }
|
|
}
|
|
|
|
describe('parseQuickEntryShortcut', () => {
|
|
it('normalizes casing, aliases, and modifier order', () => {
|
|
expect(parseQuickEntryShortcut('cmdorctrl+shift+space')).toEqual({
|
|
accelerator: 'CommandOrControl+Shift+Space',
|
|
ok: true
|
|
})
|
|
expect(parseQuickEntryShortcut(' Shift + CTRL + k ')).toEqual({ accelerator: 'Control+Shift+K', ok: true })
|
|
expect(parseQuickEntryShortcut('Alt+f5')).toEqual({ accelerator: 'Alt+F5', ok: true })
|
|
expect(parseQuickEntryShortcut('Meta+/')).toEqual({ accelerator: 'Super+/', ok: true })
|
|
})
|
|
|
|
it('collapses duplicate modifiers', () => {
|
|
expect(parseQuickEntryShortcut('Ctrl+Control+Shift+J')).toEqual({ accelerator: 'Control+Shift+J', ok: true })
|
|
})
|
|
|
|
it('requires a modifier so a global bind cannot swallow a bare key', () => {
|
|
expect(parseQuickEntryShortcut('K')).toEqual({ ok: false, reason: 'no-modifier' })
|
|
expect(parseQuickEntryShortcut('Space')).toEqual({ ok: false, reason: 'no-modifier' })
|
|
})
|
|
|
|
it('requires exactly one non-modifier key', () => {
|
|
expect(parseQuickEntryShortcut('Shift+Control')).toEqual({ ok: false, reason: 'no-key' })
|
|
expect(parseQuickEntryShortcut('Shift+A+B')).toEqual({ ok: false, reason: 'invalid-key' })
|
|
expect(parseQuickEntryShortcut('A+Shift')).toEqual({ ok: false, reason: 'invalid-modifier' })
|
|
})
|
|
|
|
it('rejects empty, junk, and the reserved Escape key', () => {
|
|
expect(parseQuickEntryShortcut('')).toEqual({ ok: false, reason: 'empty' })
|
|
expect(parseQuickEntryShortcut(' ')).toEqual({ ok: false, reason: 'empty' })
|
|
expect(parseQuickEntryShortcut(null)).toEqual({ ok: false, reason: 'empty' })
|
|
expect(parseQuickEntryShortcut('Ctrl+NotAKey')).toEqual({ ok: false, reason: 'invalid-key' })
|
|
// Escape hides the window; binding it globally would make it un-toggleable.
|
|
expect(parseQuickEntryShortcut('Ctrl+Escape')).toEqual({ ok: false, reason: 'reserved' })
|
|
})
|
|
|
|
it('accepts the shipped default unchanged', () => {
|
|
expect(parseQuickEntryShortcut(DEFAULT_QUICK_ENTRY_SHORTCUT)).toEqual({
|
|
accelerator: DEFAULT_QUICK_ENTRY_SHORTCUT,
|
|
ok: true
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('sanitizeQuickEntrySettings', () => {
|
|
it('defaults to enabled with the default shortcut', () => {
|
|
expect(sanitizeQuickEntrySettings(undefined)).toEqual({ enabled: true, shortcut: DEFAULT_QUICK_ENTRY_SHORTCUT })
|
|
expect(sanitizeQuickEntrySettings('not an object')).toEqual({
|
|
enabled: true,
|
|
shortcut: DEFAULT_QUICK_ENTRY_SHORTCUT
|
|
})
|
|
})
|
|
|
|
it('keeps an explicit disable and normalizes a stored shortcut', () => {
|
|
expect(sanitizeQuickEntrySettings({ enabled: false, shortcut: 'alt+j' })).toEqual({
|
|
enabled: false,
|
|
shortcut: 'Alt+J'
|
|
})
|
|
})
|
|
|
|
it('falls back to the default when the stored shortcut is unusable', () => {
|
|
expect(sanitizeQuickEntrySettings({ enabled: true, shortcut: 'Q' })).toEqual({
|
|
enabled: true,
|
|
shortcut: DEFAULT_QUICK_ENTRY_SHORTCUT
|
|
})
|
|
})
|
|
|
|
it('treats a non-boolean enabled as off (only `true` opts in once present)', () => {
|
|
expect(sanitizeQuickEntrySettings({ enabled: 'yes' }).enabled).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('createQuickEntryShortcut', () => {
|
|
it('registers the normalized accelerator when enabled', () => {
|
|
const { globalShortcut } = fakeGlobalShortcut()
|
|
const onTrigger = vi.fn()
|
|
const controller = createQuickEntryShortcut(globalShortcut, onTrigger)
|
|
|
|
const state = controller.apply({ enabled: true, shortcut: 'cmdorctrl+shift+space' })
|
|
|
|
expect(state).toEqual({ error: null, registered: true, shortcut: 'CommandOrControl+Shift+Space' })
|
|
expect(globalShortcut.register).toHaveBeenCalledWith('CommandOrControl+Shift+Space', onTrigger)
|
|
expect(controller.current()).toEqual(state)
|
|
})
|
|
|
|
it('never registers while the setting is disabled', () => {
|
|
const { globalShortcut } = fakeGlobalShortcut()
|
|
const controller = createQuickEntryShortcut(globalShortcut, vi.fn())
|
|
|
|
const state = controller.apply({ enabled: false, shortcut: DEFAULT_QUICK_ENTRY_SHORTCUT })
|
|
|
|
expect(globalShortcut.register).not.toHaveBeenCalled()
|
|
expect(state).toEqual({ error: null, registered: false, shortcut: DEFAULT_QUICK_ENTRY_SHORTCUT })
|
|
})
|
|
|
|
it('releases the old accelerator before registering a new one', () => {
|
|
const { globalShortcut, held } = fakeGlobalShortcut()
|
|
const controller = createQuickEntryShortcut(globalShortcut, vi.fn())
|
|
|
|
controller.apply({ enabled: true, shortcut: 'Alt+J' })
|
|
controller.apply({ enabled: true, shortcut: 'Alt+K' })
|
|
|
|
expect(globalShortcut.unregister).toHaveBeenCalledWith('Alt+J')
|
|
expect(held.has('Alt+J')).toBe(false)
|
|
expect(held.has('Alt+K')).toBe(true)
|
|
})
|
|
|
|
it('turning the feature off releases the live accelerator', () => {
|
|
const { globalShortcut, held } = fakeGlobalShortcut()
|
|
const controller = createQuickEntryShortcut(globalShortcut, vi.fn())
|
|
|
|
controller.apply({ enabled: true, shortcut: 'Alt+J' })
|
|
const off = controller.apply({ enabled: false, shortcut: 'Alt+J' })
|
|
|
|
expect(globalShortcut.unregister).toHaveBeenCalledWith('Alt+J')
|
|
expect(held.size).toBe(0)
|
|
expect(off.registered).toBe(false)
|
|
expect(off.error).toBeNull()
|
|
})
|
|
|
|
it("surfaces 'taken' when another app already owns the chord", () => {
|
|
const { globalShortcut } = fakeGlobalShortcut({ taken: ['Alt+J'] })
|
|
const controller = createQuickEntryShortcut(globalShortcut, vi.fn())
|
|
|
|
const state = controller.apply({ enabled: true, shortcut: 'alt+j' })
|
|
|
|
expect(globalShortcut.register).not.toHaveBeenCalled()
|
|
expect(state).toEqual({ error: 'taken', registered: false, shortcut: 'Alt+J' })
|
|
})
|
|
|
|
it("surfaces 'taken' when the OS refuses the registration", () => {
|
|
const { globalShortcut } = fakeGlobalShortcut({ register: false })
|
|
const controller = createQuickEntryShortcut(globalShortcut, vi.fn())
|
|
|
|
expect(controller.apply({ enabled: true, shortcut: 'Alt+J' })).toEqual({
|
|
error: 'taken',
|
|
registered: false,
|
|
shortcut: 'Alt+J'
|
|
})
|
|
})
|
|
|
|
it("surfaces 'invalid' for an unusable shortcut without asking the OS", () => {
|
|
const { globalShortcut } = fakeGlobalShortcut()
|
|
const controller = createQuickEntryShortcut(globalShortcut, vi.fn())
|
|
|
|
expect(controller.apply({ enabled: true, shortcut: 'J' })).toEqual({
|
|
error: 'invalid',
|
|
registered: false,
|
|
shortcut: 'J'
|
|
})
|
|
expect(globalShortcut.register).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('survives a throwing globalShortcut', () => {
|
|
const globalShortcut: GlobalShortcutLike = {
|
|
isRegistered: () => false,
|
|
register: () => {
|
|
throw new Error('x11 grab failed')
|
|
},
|
|
unregister: () => {}
|
|
}
|
|
|
|
const controller = createQuickEntryShortcut(globalShortcut, vi.fn())
|
|
|
|
expect(controller.apply({ enabled: true, shortcut: 'Alt+J' }).error).toBe('taken')
|
|
})
|
|
|
|
it('dispose releases the accelerator and is idempotent', () => {
|
|
const { globalShortcut, held } = fakeGlobalShortcut()
|
|
const controller = createQuickEntryShortcut(globalShortcut, vi.fn())
|
|
|
|
controller.apply({ enabled: true, shortcut: 'Alt+J' })
|
|
controller.dispose()
|
|
controller.dispose()
|
|
|
|
expect(globalShortcut.unregister).toHaveBeenCalledTimes(1)
|
|
expect(held.size).toBe(0)
|
|
expect(controller.current().registered).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('quickEntryWindowBounds', () => {
|
|
it('centers horizontally and sits below the top edge of the work area', () => {
|
|
const bounds = quickEntryWindowBounds({ height: 1000, width: 1600, x: 0, y: 0 })
|
|
|
|
expect(bounds.width).toBe(640)
|
|
expect(bounds.x).toBe((1600 - 640) / 2)
|
|
expect(bounds.y).toBeGreaterThan(0)
|
|
expect(bounds.y + bounds.height).toBeLessThanOrEqual(1000)
|
|
})
|
|
|
|
it('respects a display origin offset (second monitor)', () => {
|
|
const bounds = quickEntryWindowBounds({ height: 900, width: 1440, x: 1600, y: -200 })
|
|
|
|
expect(bounds.x).toBe(1600 + (1440 - 640) / 2)
|
|
expect(bounds.y).toBeGreaterThanOrEqual(-200)
|
|
})
|
|
|
|
it('stays inside a tiny work area', () => {
|
|
const bounds = quickEntryWindowBounds({ height: 120, width: 320, x: 0, y: 0 })
|
|
|
|
expect(bounds.width).toBeLessThanOrEqual(320)
|
|
expect(bounds.height).toBeLessThanOrEqual(120)
|
|
expect(bounds.y + bounds.height).toBeLessThanOrEqual(120)
|
|
})
|
|
|
|
it('falls back to the origin without a work area', () => {
|
|
expect(quickEntryWindowBounds()).toEqual({ height: 168, width: 640, x: 0, y: 0 })
|
|
})
|
|
})
|