// @vitest-environment jsdom import { fireEvent } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { handleUiEditorKeyDown } from '../src/view/ui-editor/uiEditorKeyboardShortcuts'; describe('ui editor keyboard shortcuts', () => { afterEach(() => { document.body.replaceChildren(); }); it.each([ { key: 'z', ctrlKey: true }, { key: 'z', ctrlKey: true, shiftKey: true }, { key: 'y', ctrlKey: true }, ])('does not change history from inside a modal (%o)', (shortcut) => { const dialog = document.createElement('div'); dialog.setAttribute('role', 'dialog'); const button = document.createElement('button'); dialog.append(button); document.body.append(dialog); const historyUndo = vi.fn(() => true); const historyRedo = vi.fn(() => true); const listener = (event: KeyboardEvent) => handleUiEditorKeyDown(event, { selectedNodeId: null, activeImageId: null, deleteNode: vi.fn(), historyUndo, historyRedo, }); window.addEventListener('keydown', listener); fireEvent.keyDown(button, shortcut); expect(historyUndo).not.toHaveBeenCalled(); expect(historyRedo).not.toHaveBeenCalled(); window.removeEventListener('keydown', listener); }); it('keeps undo available from a non-modal button', () => { const button = document.createElement('button'); document.body.append(button); const historyUndo = vi.fn(() => true); const historyRedo = vi.fn(() => true); const listener = (event: KeyboardEvent) => handleUiEditorKeyDown(event, { selectedNodeId: null, activeImageId: null, deleteNode: vi.fn(), historyUndo, historyRedo, }); window.addEventListener('keydown', listener); fireEvent.keyDown(button, { key: 'z', ctrlKey: true }); expect(historyUndo).toHaveBeenCalledTimes(1); window.removeEventListener('keydown', listener); }); it.each(['Delete', 'Backspace'])( 'does not delete a node when the zoom slider has focus (%s)', (key) => { const slider = document.createElement('input'); slider.type = 'range'; document.body.append(slider); const deleteNode = vi.fn(() => ({ ok: true })); const listener = (event: KeyboardEvent) => handleUiEditorKeyDown(event, { selectedNodeId: 'node-1', activeImageId: 'page-1', deleteNode, historyUndo: vi.fn(() => true), historyRedo: vi.fn(() => true), }); window.addEventListener('keydown', listener); fireEvent.keyDown(slider, { key }); expect(deleteNode).not.toHaveBeenCalled(); window.removeEventListener('keydown', listener); }, ); it('keeps node deletion available after zoom button focus is released', () => { const zoomControls = document.createElement('div'); const zoomIn = document.createElement('button'); zoomControls.append(zoomIn); document.body.append(zoomControls); const deleteNode = vi.fn(() => ({ ok: true })); const listener = (event: KeyboardEvent) => handleUiEditorKeyDown(event, { selectedNodeId: 'node-1', activeImageId: 'page-1', deleteNode, historyUndo: vi.fn(() => true), historyRedo: vi.fn(() => true), }); window.addEventListener('keydown', listener); fireEvent.click(zoomIn); fireEvent.keyDown(window, { key: 'Delete' }); expect(deleteNode).toHaveBeenCalledWith('node-1', 'page-1'); window.removeEventListener('keydown', listener); }); });