UNPKG

qnce-engine

Version:

Core QNCE (Quantum Narrative Convergence Engine) - Framework agnostic narrative engine with performance optimization

374 lines 16.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const react_1 = require("@testing-library/react"); const useKeyboardShortcuts_1 = require("../hooks/useKeyboardShortcuts"); const core_1 = require("../../engine/core"); const demo_story_1 = require("../../engine/demo-story"); // Mock the global addEventListener/removeEventListener const mockAddEventListener = jest.fn(); const mockRemoveEventListener = jest.fn(); describe('useKeyboardShortcuts', () => { let engine; let mockKeyboardEvent; beforeEach(() => { // Mock document.addEventListener/removeEventListener since the hook uses document as default target Object.defineProperty(document, 'addEventListener', { value: mockAddEventListener, writable: true, }); Object.defineProperty(document, 'removeEventListener', { value: mockRemoveEventListener, writable: true, }); engine = (0, core_1.createQNCEEngine)(demo_story_1.DEMO_STORY); // Mock engine methods that the hook expects engine.undo = jest.fn().mockResolvedValue({ success: true }); engine.redo = jest.fn().mockResolvedValue({ success: true }); engine.canUndo = jest.fn().mockReturnValue(true); engine.canRedo = jest.fn().mockReturnValue(true); engine.manualAutosave = jest.fn().mockResolvedValue({}); engine.resetNarrative = jest.fn(); // Mock window.confirm for reset functionality Object.defineProperty(window, 'confirm', { value: jest.fn().mockReturnValue(true), writable: true, }); mockKeyboardEvent = { key: '', ctrlKey: false, metaKey: false, shiftKey: false, altKey: false, preventDefault: jest.fn(), stopPropagation: jest.fn() }; jest.clearAllMocks(); mockAddEventListener.mockClear(); mockRemoveEventListener.mockClear(); }); describe('Hook Initialization', () => { it('registers keyboard event listeners when enabled', () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); expect(mockAddEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); }); it('does not register listeners when disabled', () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: false })); expect(mockAddEventListener).not.toHaveBeenCalled(); }); it('removes event listeners on unmount', () => { const { unmount } = (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); unmount(); expect(mockRemoveEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); }); }); describe('Default Keyboard Shortcuts', () => { it('triggers undo on Ctrl+Z', async () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'z', ctrlKey: true }); }); expect(engine.undo).toHaveBeenCalledTimes(1); }); it('triggers undo on Cmd+Z (Mac)', async () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'z', metaKey: true }); }); expect(engine.undo).toHaveBeenCalledTimes(1); }); it('triggers redo on Ctrl+Y', async () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'y', ctrlKey: true }); }); expect(engine.redo).toHaveBeenCalledTimes(1); }); it('triggers redo on Ctrl+Shift+Z', async () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'z', ctrlKey: true, shiftKey: true }); }); expect(engine.redo).toHaveBeenCalledTimes(1); }); it('triggers save on Ctrl+S', async () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 's', ctrlKey: true }); }); expect(engine.manualAutosave).toHaveBeenCalledTimes(1); }); }); describe('Custom Keyboard Bindings', () => { it('uses custom bindings when provided', async () => { const customBindings = { undo: ['ctrl+u'], redo: ['ctrl+r'], save: ['ctrl+shift+s'], reset: ['ctrl+alt+r'] }; (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true, bindings: customBindings })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; // Test custom undo binding await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'u', ctrlKey: true }); }); expect(engine.undo).toHaveBeenCalledTimes(1); }); it('supports multiple bindings for the same action', async () => { const customBindings = { undo: ['ctrl+z', 'ctrl+u', 'cmd+z'] }; (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true, bindings: customBindings })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; // Test first binding await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'z', ctrlKey: true }); }); // Test second binding await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'u', ctrlKey: true }); }); // Test third binding await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'z', metaKey: true }); }); expect(engine.undo).toHaveBeenCalledTimes(3); }); }); describe('Callback Functions', () => { it('executes undo action when undo is triggered', async () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'z', ctrlKey: true }); }); expect(engine.undo).toHaveBeenCalledTimes(1); }); it('executes redo action when redo is triggered', async () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'y', ctrlKey: true }); }); expect(engine.redo).toHaveBeenCalledTimes(1); }); it('executes save action when save is triggered', async () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 's', ctrlKey: true }); }); expect(engine.manualAutosave).toHaveBeenCalledTimes(1); }); it('executes reset action when reset is triggered', async () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true, bindings: { reset: ['ctrl+r'] } })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'r', ctrlKey: true }); }); expect(engine.resetNarrative).toHaveBeenCalledTimes(1); }); }); describe('Event Prevention', () => { it('prevents default behavior for recognized shortcuts', async () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; const event = { ...mockKeyboardEvent, key: 'z', ctrlKey: true }; await (0, react_1.act)(async () => { keydownHandler(event); }); expect(event.preventDefault).toHaveBeenCalledTimes(1); }); it('does not prevent default for unrecognized key combinations', async () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; const event = { ...mockKeyboardEvent, key: 'x', ctrlKey: true }; await (0, react_1.act)(async () => { keydownHandler(event); }); expect(event.preventDefault).not.toHaveBeenCalled(); }); it('prevents default behavior when preventDefault is enabled', async () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true, preventDefault: true })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; const event = { ...mockKeyboardEvent, key: 'z', ctrlKey: true }; await (0, react_1.act)(async () => { keydownHandler(event); }); expect(event.preventDefault).toHaveBeenCalledTimes(1); }); }); describe('Accessibility Features', () => { it('works with different target elements', () => { const targetElement = document.createElement('div'); (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true, target: targetElement })); // Should not throw an error with custom target expect(true).toBe(true); }); it('provides consistent keyboard navigation patterns', () => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); // Hook should work consistently expect(true).toBe(true); }); }); describe('Error Handling', () => { it('handles engine method failures gracefully', async () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { }); engine.undo.mockImplementation(() => { throw new Error('Undo failed'); }); (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true })); const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; // Should not throw when engine method fails expect(() => { keydownHandler({ ...mockKeyboardEvent, key: 'z', ctrlKey: true }); }).not.toThrow(); consoleSpy.mockRestore(); }); it('handles null/undefined engine gracefully', () => { expect(() => { (0, react_1.renderHook)(() => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(null, { enabled: true })); }).not.toThrow(); }); }); describe('Dynamic Configuration Updates', () => { it('updates listeners when configuration changes', () => { const { rerender } = (0, react_1.renderHook)(({ config }) => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, config), { initialProps: { config: { enabled: false } } }); expect(mockAddEventListener).not.toHaveBeenCalled(); rerender({ config: { enabled: true } }); expect(mockAddEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); }); it('updates bindings when they change', async () => { const { rerender } = (0, react_1.renderHook)(({ bindings }) => (0, useKeyboardShortcuts_1.useKeyboardShortcuts)(engine, { enabled: true, bindings }), { initialProps: { bindings: { undo: ['ctrl+z'] } } }); // Test original binding const keydownHandler = mockAddEventListener.mock.calls.find(call => call[0] === 'keydown')[1]; await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'z', ctrlKey: true }); }); expect(engine.undo).toHaveBeenCalledTimes(1); // Update bindings rerender({ bindings: { undo: ['ctrl+u'] } }); // Old binding should not work await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'z', ctrlKey: true }); }); // New binding should work await (0, react_1.act)(async () => { keydownHandler({ ...mockKeyboardEvent, key: 'u', ctrlKey: true }); }); expect(engine.undo).toHaveBeenCalledTimes(2); }); }); }); //# sourceMappingURL=useKeyboardShortcuts.test.js.map