import { mock, mockReset } from 'jest-mock-extended'; import { Chooser, Hotkey, KeymapContext, KeymapEventListener, Scope } from 'obsidian'; import { Keymap } from 'src/switcherPlus'; import { AnySuggestion, Mode } from 'src/types'; describe('keymap', () => { const mockScope = mock<Scope>(); const mockChooser = mock<Chooser<AnySuggestion>>(); const mockModalContainer = mock<HTMLElement>(); describe('isOpen property', () => { let sut: Keymap; beforeAll(() => { sut = new Keymap(mockScope, mockChooser, mockModalContainer); }); it('should save the value provided for isOpen', () => { sut.isOpen = true; const result = sut.isOpen; expect(result).toBe(true); }); }); describe('Next/Previous keyboard navigation', () => { beforeEach(() => { mockReset(mockScope); mockReset(mockChooser); }); it('should register ctrl-n/p for navigating forward/backward', () => { new Keymap(mockScope, null, null); expect(mockScope.register).toHaveBeenCalledWith( expect.arrayContaining(['Ctrl']), 'n', expect.anything(), ); expect(mockScope.register).toHaveBeenCalledWith( expect.arrayContaining(['Ctrl']), 'p', expect.anything(), ); }); test('when Open, it should change the selected item with ctrl-n/p keyboard navigation', () => { const selectedIndex = 1; const navPairs: Record<string, KeymapEventListener> = { n: null, p: null, }; mockScope.register.mockImplementation((_m, key, func) => { if (key in navPairs) { navPairs[key] = func; } return null; }); mockChooser.selectedItem = selectedIndex; const sut = new Keymap(mockScope, mockChooser, null); sut.isOpen = true; // here navPairs.n(mock<KeyboardEvent>(), mock<KeymapContext>({ key: 'n' })); navPairs.p(mock<KeyboardEvent>(), mock<KeymapContext>({ key: 'p' })); expect(mockChooser.setSelectedItem).toHaveBeenCalledWith(selectedIndex + 1, true); expect(mockChooser.setSelectedItem).toHaveBeenCalledWith(selectedIndex - 1, true); }); test('when not Open, it should not change the selected item with ctrl-n/p keyboard navigation', () => { const selectedIndex = 1; const navPairs: Record<string, KeymapEventListener> = { n: null, p: null, }; mockScope.register.mockImplementation((_m, key, func) => { if (key in navPairs) { navPairs[key] = func; } return null; }); mockChooser.selectedItem = selectedIndex; const sut = new Keymap(mockScope, mockChooser, null); sut.isOpen = false; // here navPairs.n(mock<KeyboardEvent>(), mock<KeymapContext>({ key: 'n' })); navPairs.p(mock<KeyboardEvent>(), mock<KeymapContext>({ key: 'p' })); expect(mockChooser.setSelectedItem).not.toHaveBeenCalled(); expect(mockChooser.setSelectedItem).not.toHaveBeenCalled(); }); }); describe('updateKeymapForMode', () => { const selector = '.prompt-instructions'; const mockInstructionsEl = mock<HTMLElement>(); const mockMetaEnter = mock<Hotkey>({ modifiers: ['Meta'], key: 'Enter', }); const mockShiftEnter = mock<Hotkey>({ modifiers: ['Shift'], key: 'Enter', }); beforeEach(() => { mockReset(mockScope); mockReset(mockModalContainer); }); it('should do nothing if the helper text (prompt instructions) element is not found', () => { const mockQuerySelector = mockModalContainer.querySelector.mockReturnValueOnce(null); const sut = new Keymap(mockScope, mockChooser, mockModalContainer); expect(() => sut.updateKeymapForMode(Mode.Standard)).not.toThrow(); expect(mockQuerySelector).toHaveBeenCalledWith(selector); }); it('should hide the helper text (prompt instructions) in non-standard modes', () => { const mockQuerySelector = mockModalContainer.querySelector.mockReturnValueOnce(mockInstructionsEl); const sut = new Keymap(mockScope, mockChooser, mockModalContainer); sut.updateKeymapForMode(Mode.EditorList); expect(mockQuerySelector).toHaveBeenCalledWith(selector); expect(mockInstructionsEl.style.display).toBe('none'); }); it('should show the helper text (prompt instructions) in standard modes', () => { const mockQuerySelector = mockModalContainer.querySelector.mockReturnValueOnce(mockInstructionsEl); const sut = new Keymap(mockScope, mockChooser, mockModalContainer); sut.updateKeymapForMode(Mode.Standard); expect(mockQuerySelector).toHaveBeenCalledWith(selector); expect(mockInstructionsEl.style.display).toBe(''); }); it('should not remove Enter hotkey without shift/meta modifier', () => { const mockEnter = mock<Hotkey>({ modifiers: [], key: 'Enter', }); mockScope.keys = [mockEnter]; const sut = new Keymap(mockScope, null, mockModalContainer); sut.updateKeymapForMode(Mode.EditorList); expect(mockScope.keys).toContain(mockEnter); }); it('should remove the shift-enter hotkey in non-standard modes', () => { mockScope.keys = [mockMetaEnter, mockShiftEnter]; const sut = new Keymap(mockScope, null, mockModalContainer); sut.updateKeymapForMode(Mode.EditorList); expect(mockScope.keys).toHaveLength(1); }); it('should keep the meta-enter hotkey registered in non-standard modes', () => { mockScope.keys = [mockMetaEnter, mockShiftEnter]; const sut = new Keymap(mockScope, null, mockModalContainer); sut.updateKeymapForMode(Mode.StarredList); expect(mockScope.keys).toHaveLength(1); expect(mockScope.keys).toContain(mockMetaEnter); }); it('should restore the shift/meta hotkey in standard mode', () => { mockScope.keys = [mockMetaEnter, mockShiftEnter]; const sut = new Keymap(mockScope, null, mockModalContainer); // should first remove shift-enter in non-standard mode sut.updateKeymapForMode(Mode.EditorList); const extendedModeKeyCount = mockScope.keys.length; // should restore all hotkeys in standard mode sut.updateKeymapForMode(Mode.Standard); expect(extendedModeKeyCount).toBe(1); expect(mockScope.keys).toContain(mockMetaEnter); expect(mockScope.keys).toContain(mockShiftEnter); }); }); });