diff --git a/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue b/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue index 7881a0d25..3da526fc0 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue @@ -589,11 +589,11 @@ function isCmdPlusEnterToSendEnabled() { useKeyboardEvents({ 'Alt+KeyP': { action: focusEditorInputField, - allowOnFocusedInput: true, + allowOnFocusedInput: false, }, 'Alt+KeyL': { action: focusEditorInputField, - allowOnFocusedInput: true, + allowOnFocusedInput: false, }, }); diff --git a/app/javascript/dashboard/components/widgets/WootWriter/ReplyTopPanel.vue b/app/javascript/dashboard/components/widgets/WootWriter/ReplyTopPanel.vue index a2929f7f8..bf45cdc1c 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/ReplyTopPanel.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/ReplyTopPanel.vue @@ -105,11 +105,11 @@ export default { const keyboardEvents = { 'Alt+KeyP': { action: () => handleNoteClick(), - allowOnFocusedInput: true, + allowOnFocusedInput: false, }, 'Alt+KeyL': { action: () => handleReplyClick(), - allowOnFocusedInput: true, + allowOnFocusedInput: false, }, }; useKeyboardEvents(keyboardEvents); diff --git a/app/javascript/dashboard/composables/spec/useKeyboardEvents.spec.js b/app/javascript/dashboard/composables/spec/useKeyboardEvents.spec.js index d20babeaa..995a486b4 100644 --- a/app/javascript/dashboard/composables/spec/useKeyboardEvents.spec.js +++ b/app/javascript/dashboard/composables/spec/useKeyboardEvents.spec.js @@ -1,6 +1,47 @@ +const keyboardEventsMock = vi.hoisted(() => ({ + mountedCallbacks: [], + unmountedCallbacks: [], + registeredKeybindings: {}, +})); + +vi.mock('vue', async importOriginal => ({ + ...(await importOriginal()), + onMounted: vi.fn(callback => + keyboardEventsMock.mountedCallbacks.push(callback) + ), + onUnmounted: vi.fn(callback => + keyboardEventsMock.unmountedCallbacks.push(callback) + ), +})); + +vi.mock('dashboard/composables/useDetectKeyboardLayout', () => ({ + useDetectKeyboardLayout: vi.fn(() => Promise.resolve('QWERTY')), +})); + +vi.mock('tinykeys', () => ({ + createKeybindingsHandler: vi.fn(keybindings => { + keyboardEventsMock.registeredKeybindings = keybindings; + return event => keybindings[event.shortcut]?.(event); + }), +})); + +import { useDetectKeyboardLayout } from 'dashboard/composables/useDetectKeyboardLayout'; import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; +import { LAYOUT_QWERTZ } from 'shared/helpers/KeyboardHelpers'; + +const mount = async events => { + useKeyboardEvents(events); + await keyboardEventsMock.mountedCallbacks[0](); + return keyboardEventsMock.registeredKeybindings; +}; describe('useKeyboardEvents', () => { + beforeEach(() => { + keyboardEventsMock.mountedCallbacks = []; + keyboardEventsMock.unmountedCallbacks = []; + keyboardEventsMock.registeredKeybindings = {}; + }); + it('should be defined', () => { expect(useKeyboardEvents).toBeDefined(); }); @@ -9,18 +50,46 @@ describe('useKeyboardEvents', () => { expect(useKeyboardEvents).toBeInstanceOf(Function); }); - it('should set up listeners on mount and remove them on unmount', async () => { - const events = { - 'ALT+KeyL': () => {}, - }; + it('registers keybindings on mount and removes the listener on unmount', async () => { + const addEventListener = vi.spyOn(document, 'addEventListener'); - const mountedMock = vi.fn(); - const unmountedMock = vi.fn(); - useKeyboardEvents(events); - mountedMock(); - unmountedMock(); + const keybindings = await mount({ 'ALT+KeyL': () => {} }); + expect(Object.keys(keybindings)).toEqual(['ALT+KeyL']); - expect(mountedMock).toHaveBeenCalled(); - expect(unmountedMock).toHaveBeenCalled(); + const { signal } = addEventListener.mock.calls[0][2]; + expect(signal.aborted).toBe(false); + + keyboardEventsMock.unmountedCallbacks[0](); + expect(signal.aborted).toBe(true); + }); + + it('prefixes Shift on QWERTZ layouts for the affected keys', async () => { + useDetectKeyboardLayout.mockResolvedValueOnce(LAYOUT_QWERTZ); + + const keybindings = await mount({ 'Alt+KeyL': () => {} }); + + expect(Object.keys(keybindings)).toEqual(['Shift+Alt+KeyL']); + }); + + it('ignores shortcuts on focused typeable elements by default', async () => { + const action = vi.fn(); + const keybindings = await mount({ + 'Alt+KeyL': { action, allowOnFocusedInput: false }, + }); + + keybindings['Alt+KeyL']({ target: document.createElement('textarea') }); + + expect(action).not.toHaveBeenCalled(); + }); + + it('runs shortcuts on focused typeable elements when allowed', async () => { + const action = vi.fn(); + const keybindings = await mount({ + 'Alt+KeyL': { action, allowOnFocusedInput: true }, + }); + + keybindings['Alt+KeyL']({ target: document.createElement('textarea') }); + + expect(action).toHaveBeenCalledTimes(1); }); });