/** * Tests for useKeyboardShortcuts hook * * Verifies keyboard shortcut handling for navigation and actions. */ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { renderHook } from "@testing-library/react"; import { useKeyboardShortcuts, type ShortcutMap } from "@/hooks/useKeyboardShortcuts"; describe("useKeyboardShortcuts", () => { // Helper to dispatch keyboard events function dispatchKeyEvent( key: string, opts: { metaKey?: boolean; ctrlKey?: boolean; shiftKey?: boolean } = {} ): void { const event = new KeyboardEvent("keydown", { key, metaKey: opts.metaKey ?? false, ctrlKey: opts.ctrlKey ?? false, shiftKey: opts.shiftKey ?? false, bubbles: true, }); document.dispatchEvent(event); } it("calls handler when shortcut is pressed (meta key on Mac)", () => { const handler = vi.fn(); const shortcuts: ShortcutMap = { "mod+1": handler }; renderHook(() => useKeyboardShortcuts(shortcuts)); dispatchKeyEvent("1", { metaKey: true }); expect(handler).toHaveBeenCalledTimes(1); }); it("calls handler when shortcut is pressed (ctrl key fallback)", () => { const handler = vi.fn(); const shortcuts: ShortcutMap = { "mod+2": handler }; renderHook(() => useKeyboardShortcuts(shortcuts)); dispatchKeyEvent("2", { ctrlKey: true }); expect(handler).toHaveBeenCalledTimes(1); }); it("does not call handler when wrong key is pressed", () => { const handler = vi.fn(); const shortcuts: ShortcutMap = { "mod+1": handler }; renderHook(() => useKeyboardShortcuts(shortcuts)); dispatchKeyEvent("2", { metaKey: true }); expect(handler).not.toHaveBeenCalled(); }); it("does not call handler when modifier is missing", () => { const handler = vi.fn(); const shortcuts: ShortcutMap = { "mod+1": handler }; renderHook(() => useKeyboardShortcuts(shortcuts)); dispatchKeyEvent("1"); // No modifier expect(handler).not.toHaveBeenCalled(); }); it("handles comma shortcut (mod+,)", () => { const handler = vi.fn(); const shortcuts: ShortcutMap = { "mod+,": handler }; renderHook(() => useKeyboardShortcuts(shortcuts)); dispatchKeyEvent(",", { metaKey: true }); expect(handler).toHaveBeenCalledTimes(1); }); it("supports multiple shortcuts", () => { const handler1 = vi.fn(); const handler2 = vi.fn(); const handler3 = vi.fn(); const shortcuts: ShortcutMap = { "mod+1": handler1, "mod+2": handler2, "mod+3": handler3, }; renderHook(() => useKeyboardShortcuts(shortcuts)); dispatchKeyEvent("1", { metaKey: true }); dispatchKeyEvent("3", { metaKey: true }); expect(handler1).toHaveBeenCalledTimes(1); expect(handler2).not.toHaveBeenCalled(); expect(handler3).toHaveBeenCalledTimes(1); }); it("removes listeners on unmount", () => { const handler = vi.fn(); const shortcuts: ShortcutMap = { "mod+1": handler }; const { unmount } = renderHook(() => useKeyboardShortcuts(shortcuts)); unmount(); dispatchKeyEvent("1", { metaKey: true }); expect(handler).not.toHaveBeenCalled(); }); it("ignores shortcuts when typing in input fields", () => { const handler = vi.fn(); const shortcuts: ShortcutMap = { "mod+1": handler }; renderHook(() => useKeyboardShortcuts(shortcuts)); // Create and focus an input element const input = document.createElement("input"); document.body.appendChild(input); input.focus(); // Dispatch from the input const event = new KeyboardEvent("keydown", { key: "1", metaKey: true, bubbles: true, }); input.dispatchEvent(event); expect(handler).not.toHaveBeenCalled(); // Cleanup document.body.removeChild(input); }); it("ignores shortcuts when typing in textarea", () => { const handler = vi.fn(); const shortcuts: ShortcutMap = { "mod+2": handler }; renderHook(() => useKeyboardShortcuts(shortcuts)); const textarea = document.createElement("textarea"); document.body.appendChild(textarea); textarea.focus(); const event = new KeyboardEvent("keydown", { key: "2", metaKey: true, bubbles: true, }); textarea.dispatchEvent(event); expect(handler).not.toHaveBeenCalled(); document.body.removeChild(textarea); }); it("ignores shortcuts when contenteditable is focused", () => { const handler = vi.fn(); const shortcuts: ShortcutMap = { "mod+3": handler }; renderHook(() => useKeyboardShortcuts(shortcuts)); const div = document.createElement("div"); div.contentEditable = "true"; document.body.appendChild(div); div.focus(); const event = new KeyboardEvent("keydown", { key: "3", metaKey: true, bubbles: true, }); div.dispatchEvent(event); expect(handler).not.toHaveBeenCalled(); document.body.removeChild(div); }); it("prevents default behavior when shortcut matches", () => { const handler = vi.fn(); const shortcuts: ShortcutMap = { "mod+1": handler }; renderHook(() => useKeyboardShortcuts(shortcuts)); const event = new KeyboardEvent("keydown", { key: "1", metaKey: true, bubbles: true, cancelable: true, }); const preventDefaultSpy = vi.spyOn(event, "preventDefault"); document.dispatchEvent(event); expect(preventDefaultSpy).toHaveBeenCalled(); }); });