// useCmdEnterSave attaches a capture-phase keydown listener while // enabled=true and fires onSave on Cmd/Ctrl+Enter. Tests render a host // component via @testing-library/react so the React effect lifecycle // (mount / unmount / dep-change cleanup) drives the listener // attach/detach instead of being mocked. import { useCmdEnterSave } from '@quick-edit/lib/cmd-enter-save'; import { render } from '@testing-library/react'; const Host = ({ onSave, enabled }) => { useCmdEnterSave(onSave, enabled); return null; }; const fireKeydown = (init) => { const event = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...init, }); const preventSpy = jest.spyOn(event, 'preventDefault'); const stopSpy = jest.spyOn(event, 'stopPropagation'); window.dispatchEvent(event); return { event, preventSpy, stopSpy }; }; describe('useCmdEnterSave — modifier matrix', () => { const cases = [ ['Cmd+Enter (metaKey)', { key: 'Enter', metaKey: true }, true], ['Ctrl+Enter (ctrlKey)', { key: 'Enter', ctrlKey: true }, true], ['plain Enter', { key: 'Enter' }, false], ['Cmd+A', { key: 'a', metaKey: true }, false], ['Cmd+Shift+Enter', { key: 'Enter', metaKey: true, shiftKey: true }, true], ]; for (const [name, init, shouldFire] of cases) { it(`${shouldFire ? 'fires' : 'ignores'} ${name}`, () => { const onSave = jest.fn(); render(); fireKeydown(init); expect(onSave).toHaveBeenCalledTimes(shouldFire ? 1 : 0); }); } }); describe('useCmdEnterSave — event prevention', () => { it('preventsDefault and stopsPropagation on a match', () => { const onSave = jest.fn(); render(); const { preventSpy, stopSpy } = fireKeydown({ key: 'Enter', metaKey: true, }); expect(preventSpy).toHaveBeenCalled(); expect(stopSpy).toHaveBeenCalled(); }); it('does not preventDefault on a miss', () => { const onSave = jest.fn(); render(); const { preventSpy } = fireKeydown({ key: 'Enter' }); expect(preventSpy).not.toHaveBeenCalled(); }); }); describe('useCmdEnterSave — enabled gate', () => { it('does not attach the listener when enabled=false', () => { const onSave = jest.fn(); render(); fireKeydown({ key: 'Enter', metaKey: true }); expect(onSave).not.toHaveBeenCalled(); }); it('defaults enabled=true when the second arg is omitted', () => { const Default = ({ onSave }) => { useCmdEnterSave(onSave); return null; }; const onSave = jest.fn(); render(); fireKeydown({ key: 'Enter', metaKey: true }); expect(onSave).toHaveBeenCalledTimes(1); }); it('re-attaches when enabled flips false → true', () => { const onSave = jest.fn(); const { rerender } = render(); fireKeydown({ key: 'Enter', metaKey: true }); expect(onSave).not.toHaveBeenCalled(); rerender(); fireKeydown({ key: 'Enter', metaKey: true }); expect(onSave).toHaveBeenCalledTimes(1); }); }); describe('useCmdEnterSave — cleanup', () => { it('removes the listener on unmount', () => { const onSave = jest.fn(); const { unmount } = render(); unmount(); fireKeydown({ key: 'Enter', metaKey: true }); expect(onSave).not.toHaveBeenCalled(); }); it('removes the prior listener when onSave changes (no duplicate fires)', () => { const first = jest.fn(); const second = jest.fn(); const { rerender } = render(); rerender(); fireKeydown({ key: 'Enter', metaKey: true }); expect(first).not.toHaveBeenCalled(); expect(second).toHaveBeenCalledTimes(1); }); }); describe('useCmdEnterSave — onSave null-safety', () => { it('does not throw when onSave is null and the shortcut fires', () => { render(); expect(() => fireKeydown({ key: 'Enter', metaKey: true })).not.toThrow(); }); });