Files
tolaria/src/hooks/useAppKeyboard.test.ts
Luca Rossi 384cffec48 feat: zoom in/out keyboard shortcuts (Cmd+=, Cmd+-, Cmd+0) (#134)
* feat: zoom in/out keyboard shortcuts (Cmd+=, Cmd+-, Cmd+0)

Add zoom control with keyboard shortcuts, native menu items, command
palette integration, and StatusBar zoom indicator with localStorage
persistence (80-150%, 10% step).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add zoom-shortcuts design file with StatusBar zoom indicator

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* design: zoom-shortcuts — StatusBar zoom indicator + Command Palette entries

---------

Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 12:41:57 +01:00

178 lines
5.6 KiB
TypeScript

import { describe, it, expect, vi, afterEach } from 'vitest'
import { renderHook } from '@testing-library/react'
import { useAppKeyboard } from './useAppKeyboard'
function fireKey(key: string, mods: { altKey?: boolean; metaKey?: boolean; ctrlKey?: boolean; shiftKey?: boolean } = {}) {
const event = new KeyboardEvent('keydown', {
key,
altKey: mods.altKey ?? false,
metaKey: mods.metaKey ?? false,
ctrlKey: mods.ctrlKey ?? false,
shiftKey: mods.shiftKey ?? false,
bubbles: true,
cancelable: true,
})
window.dispatchEvent(event)
}
function makeActions() {
return {
onQuickOpen: vi.fn(),
onCommandPalette: vi.fn(),
onSearch: vi.fn(),
onCreateNote: vi.fn(),
onSave: vi.fn(),
onOpenSettings: vi.fn(),
onTrashNote: vi.fn(),
onArchiveNote: vi.fn(),
onSetViewMode: vi.fn(),
onZoomIn: vi.fn(),
onZoomOut: vi.fn(),
onZoomReset: vi.fn(),
activeTabPathRef: { current: '/vault/test.md' } as React.MutableRefObject<string | null>,
handleCloseTabRef: { current: vi.fn() } as React.MutableRefObject<(path: string) => void>,
}
}
describe('useAppKeyboard', () => {
afterEach(() => vi.restoreAllMocks())
it('Cmd+1 sets view mode to editor-only', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('1', { metaKey: true })
expect(actions.onSetViewMode).toHaveBeenCalledWith('editor-only')
})
it('Cmd+2 sets view mode to editor-list', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('2', { metaKey: true })
expect(actions.onSetViewMode).toHaveBeenCalledWith('editor-list')
})
it('Cmd+3 sets view mode to all', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('3', { metaKey: true })
expect(actions.onSetViewMode).toHaveBeenCalledWith('all')
})
it('does not fire view mode when Cmd+Alt pressed', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('1', { metaKey: true, altKey: true })
expect(actions.onSetViewMode).not.toHaveBeenCalled()
})
it('Cmd+P triggers quick open', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('p', { metaKey: true })
expect(actions.onQuickOpen).toHaveBeenCalled()
})
it('Cmd+N triggers create note', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('n', { metaKey: true })
expect(actions.onCreateNote).toHaveBeenCalled()
})
it('Cmd+W closes the active tab', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('w', { metaKey: true })
expect(actions.handleCloseTabRef.current).toHaveBeenCalledWith('/vault/test.md')
})
it('Alt+4 does not trigger any view mode', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('4', { altKey: true })
expect(actions.onSetViewMode).not.toHaveBeenCalled()
})
it('Cmd+K triggers command palette', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('k', { metaKey: true })
expect(actions.onCommandPalette).toHaveBeenCalled()
})
it('Cmd+Shift+F triggers search', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('f', { metaKey: true, shiftKey: true })
expect(actions.onSearch).toHaveBeenCalled()
})
it('Cmd+Shift+F does not trigger other shortcuts', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('f', { metaKey: true, shiftKey: true })
expect(actions.onQuickOpen).not.toHaveBeenCalled()
expect(actions.onCreateNote).not.toHaveBeenCalled()
})
function withFocusedInput(fn: () => void) {
const input = document.createElement('input')
document.body.appendChild(input)
input.focus()
try { fn() } finally { document.body.removeChild(input) }
}
it('Cmd+Backspace does not trash note when text input is focused', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
withFocusedInput(() => {
fireKey('Backspace', { metaKey: true })
expect(actions.onTrashNote).not.toHaveBeenCalled()
})
})
it('Cmd+Backspace trashes note when no text input is focused', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('Backspace', { metaKey: true })
expect(actions.onTrashNote).toHaveBeenCalledWith('/vault/test.md')
})
it('Cmd+K still works when text input is focused', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
withFocusedInput(() => {
fireKey('k', { metaKey: true })
expect(actions.onCommandPalette).toHaveBeenCalled()
})
})
it('Cmd+= triggers zoom in', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('=', { metaKey: true })
expect(actions.onZoomIn).toHaveBeenCalled()
})
it('Cmd++ triggers zoom in', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('+', { metaKey: true })
expect(actions.onZoomIn).toHaveBeenCalled()
})
it('Cmd+- triggers zoom out', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('-', { metaKey: true })
expect(actions.onZoomOut).toHaveBeenCalled()
})
it('Cmd+0 triggers zoom reset', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('0', { metaKey: true })
expect(actions.onZoomReset).toHaveBeenCalled()
})
})