/** * Tests for services/keyboard.js * * Tests the keyboard shortcut matching logic and service API. * We can't test the full initialization (which binds to document), * but we can test the shortcut matching and public API. */ // Stub URLS needed by keyboard.js handlers window.URLS = { PAGES: { HOME: '/', HISTORY: '/history/', METRICS: '/metrics/', SETTINGS: '/settings/', } }; import '@js/services/keyboard.js'; const KS = window.KeyboardService; describe('KeyboardService', () => { describe('shortcuts registry invariants', () => { it('every registered shortcut has a callable handler', () => { // Catches accidental refactors that drop the handler field. const shortcuts = KS.shortcuts(); for (const [name, shortcut] of Object.entries(shortcuts)) { expect(shortcut.handler, `${name} missing handler`).toBeTypeOf('function'); expect(shortcut.keys, `${name} missing keys`).toBeInstanceOf(Array); expect(shortcut.keys.length, `${name} has empty keys`).toBeGreaterThan(0); } }); }); describe('addShortcut / removeShortcut', () => { afterEach(() => { KS.removeShortcut('testShortcut'); }); it('adds a custom shortcut', () => { KS.addShortcut('testShortcut', { keys: ['ctrl+t'], description: 'Test shortcut', handler: () => {} }); const shortcuts = KS.shortcuts(); expect(shortcuts.testShortcut).toBeDefined(); expect(shortcuts.testShortcut.keys).toEqual(['ctrl+t']); }); it('removes a custom shortcut', () => { KS.addShortcut('testShortcut', { keys: ['ctrl+t'], description: 'Test', handler: () => {} }); KS.removeShortcut('testShortcut'); const shortcuts = KS.shortcuts(); expect(shortcuts.testShortcut).toBeUndefined(); }); }); describe('keyboard event handling', () => { it('Escape key triggers newSearch shortcut', () => { // The shortcut sets window.location.href, which we can spy on const originalHref = window.location.href; // Create a keydown event for Escape const event = new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', bubbles: true, }); // We can verify the event doesn't throw expect(() => document.dispatchEvent(event)).not.toThrow(); }); it('ignores shortcuts when typing in input fields', () => { const input = document.createElement('input'); input.type = 'text'; document.body.appendChild(input); input.focus(); const event = new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', bubbles: true, }); // Should not throw or cause navigation when typing expect(() => input.dispatchEvent(event)).not.toThrow(); input.remove(); }); it('allows Ctrl+Shift navigation shortcuts even in input', () => { const input = document.createElement('input'); input.type = 'text'; document.body.appendChild(input); input.focus(); const event = new KeyboardEvent('keydown', { key: '1', code: 'Digit1', ctrlKey: true, shiftKey: true, bubbles: true, }); // Should not throw expect(() => input.dispatchEvent(event)).not.toThrow(); input.remove(); }); }); });