import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import '@testing-library/jest-dom/vitest'; import { ThemeProvider } from '@mui/material/styles'; import { lightTheme } from '@services/theme/defaultTheme'; import { KeyboardShortcutRegister } from '../KeyboardShortcutRegister'; const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( {children} ); describe('KeyboardShortcutRegister Component', () => { let mockOnChange: ReturnType; beforeEach(() => { vi.clearAllMocks(); mockOnChange = vi.fn(); // Mock window.service.context.get Object.defineProperty(window.service.context, 'get', { value: vi.fn().mockImplementation((key: string) => { if (key === 'platform') { // Return platform based on process.platform for testing return Promise.resolve(process.platform === 'darwin' ? 'darwin' : 'win32'); } return Promise.resolve('win32'); }), writable: true, }); }); const renderComponent = (overrides: { value?: string; onChange?: (value: string) => void; label?: string; disabled?: boolean; } = {}) => { const defaultProps = { value: '', onChange: mockOnChange, label: 'Test Shortcut', disabled: false, ...overrides, }; return render( , ); }; describe('Initial state and rendering', () => { it('should display button with label and current shortcut value', () => { renderComponent({ value: 'Ctrl+Shift+T', label: 'Toggle Window', }); const button = screen.getByTestId('shortcut-register-button'); expect(button).toBeInTheDocument(); expect(button).toHaveTextContent('Toggle Window'); expect(button).toHaveTextContent('Ctrl+Shift+T'); }); it('should display "None" when no shortcut is set', () => { renderComponent({ value: '', label: 'Toggle Window', }); const button = screen.getByTestId('shortcut-register-button'); expect(button).toHaveTextContent('KeyboardShortcut.None'); }); it('should respect disabled state', () => { renderComponent({ disabled: true, }); const button = screen.getByTestId('shortcut-register-button'); expect(button).toBeDisabled(); }); it('should not show dialog initially', () => { renderComponent(); expect(screen.queryByTestId('shortcut-dialog')).not.toBeInTheDocument(); }); }); describe('Dialog interaction', () => { it('should open dialog when button is clicked', async () => { const user = userEvent.setup(); renderComponent(); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); expect(screen.getByText('KeyboardShortcut.RegisterShortcut')).toBeInTheDocument(); }); it('should display current shortcut in dialog initially', async () => { const user = userEvent.setup(); renderComponent({ value: 'Ctrl+A', }); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { const display = screen.getByTestId('shortcut-display'); expect(display).toHaveTextContent('Ctrl+A'); }); }); it('should close dialog when cancel button is clicked', async () => { const user = userEvent.setup(); renderComponent(); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); // Press ESC to close dialog (MUI Dialog default behavior) fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' }); await waitFor(() => { expect(screen.queryByTestId('shortcut-dialog')).not.toBeInTheDocument(); }, { timeout: 500 }); }); }); describe('Keyboard shortcut capture', () => { it('should capture Ctrl key combination', async () => { const user = userEvent.setup(); renderComponent(); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); const dialogContent = screen.getByTestId('shortcut-dialog-content'); // Simulate keyboard event with Ctrl+Shift+T fireEvent.keyDown(dialogContent, { key: 'T', ctrlKey: true, shiftKey: true, bubbles: true, }); await waitFor(() => { const display = screen.getByTestId('shortcut-display'); expect(display).toHaveTextContent('Ctrl+Shift+T'); }); }); it('should capture Cmd key combination on macOS', async () => { const originalPlatform = process.platform; Object.defineProperty(process, 'platform', { value: 'darwin', writable: true, configurable: true, }); const user = userEvent.setup(); renderComponent(); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); // Simulate Cmd+K key press on document fireEvent.keyDown(document, { key: 'K', metaKey: true, bubbles: true, }); await waitFor(() => { const display = screen.getByTestId('shortcut-display'); expect(display).toHaveTextContent('Cmd+K'); }); Object.defineProperty(process, 'platform', { value: originalPlatform, writable: true, configurable: true, }); }); it('should capture Alt key combination', async () => { const user = userEvent.setup(); renderComponent(); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); const dialogContent = screen.getByTestId('shortcut-dialog-content'); fireEvent.keyDown(dialogContent, { key: 'F4', altKey: true, bubbles: true, }); await waitFor(() => { const display = screen.getByTestId('shortcut-display'); expect(display).toHaveTextContent('Alt+F4'); }); }); it('should ignore modifier keys pressed alone', async () => { const user = userEvent.setup(); renderComponent(); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); const dialogContent = screen.getByTestId('shortcut-dialog-content'); const display = screen.getByTestId('shortcut-display'); // Should show initial "Press keys" message expect(display).toHaveTextContent('KeyboardShortcut.PressKeys'); const modifierKeys = ['Shift', 'Control', 'Alt', 'Meta']; for (const key of modifierKeys) { fireEvent.keyDown(dialogContent, { key, bubbles: true, }); } // Should still show "Press keys" message expect(display).toHaveTextContent('KeyboardShortcut.PressKeys'); }); it('should update display when keys are pressed', async () => { const user = userEvent.setup(); renderComponent(); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); const dialogContent = screen.getByTestId('shortcut-dialog-content'); // Press first combination fireEvent.keyDown(dialogContent, { key: 'A', ctrlKey: true, bubbles: true, }); await waitFor(() => { const display = screen.getByTestId('shortcut-display'); expect(display).toHaveTextContent('Ctrl+A'); }); // Press second combination - should replace fireEvent.keyDown(dialogContent, { key: 'B', ctrlKey: true, shiftKey: true, bubbles: true, }); await waitFor(() => { const display = screen.getByTestId('shortcut-display'); expect(display).toHaveTextContent('Ctrl+Shift+B'); }); }); }); describe('Clear functionality', () => { it('should clear current shortcut when clear button is clicked', async () => { const user = userEvent.setup(); renderComponent({ value: 'Ctrl+T', }); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); // Initially should show the current value const display = screen.getByTestId('shortcut-display'); expect(display).toHaveTextContent('Ctrl+T'); // Verify clear button exists and is enabled const clearButton = screen.getByTestId('shortcut-clear-button'); expect(clearButton).toBeInTheDocument(); expect(clearButton).toBeEnabled(); }); it('should call onChange with empty string when cleared and confirmed', async () => { const user = userEvent.setup(); renderComponent({ value: '', // Start with empty to test setting empty and confirming }); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); // Don't set any keys, just press Enter to confirm empty fireEvent.keyDown(document, { key: 'Enter', code: 'Enter' }); await waitFor(() => { expect(mockOnChange).toHaveBeenCalledWith(''); }); }); }); describe('Confirm functionality', () => { it('should call onChange with new shortcut on confirm', async () => { const user = userEvent.setup(); renderComponent({ value: '', }); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); const dialogContent = screen.getByTestId('shortcut-dialog-content'); fireEvent.keyDown(dialogContent, { key: 'N', ctrlKey: true, bubbles: true, }); await waitFor(() => { const display = screen.getByTestId('shortcut-display'); expect(display).toHaveTextContent('Ctrl+N'); }); // Press Enter to confirm fireEvent.keyDown(document, { key: 'Enter', code: 'Enter' }); await waitFor(() => { expect(mockOnChange).toHaveBeenCalledWith('Ctrl+N'); }); }); it('should not call onChange when cancel is clicked', async () => { const user = userEvent.setup(); renderComponent({ value: 'Ctrl+A', }); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); const dialogContent = screen.getByTestId('shortcut-dialog-content'); fireEvent.keyDown(dialogContent, { key: 'B', ctrlKey: true, bubbles: true, }); await waitFor(() => { const display = screen.getByTestId('shortcut-display'); expect(display).toHaveTextContent('Ctrl+B'); }); // Press ESC to cancel without saving fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' }); await waitFor(() => { expect(mockOnChange).not.toHaveBeenCalled(); }); await waitFor(() => { expect(screen.queryByTestId('shortcut-dialog')).not.toBeInTheDocument(); }, { timeout: 500 }); }); it('should close dialog after confirm', async () => { const user = userEvent.setup(); renderComponent(); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); const dialogContent = screen.getByTestId('shortcut-dialog-content'); fireEvent.keyDown(dialogContent, { key: 'T', ctrlKey: true, bubbles: true, }); // Press Enter to confirm and close dialog fireEvent.keyDown(document, { key: 'Enter', code: 'Enter' }); await waitFor(() => { expect(screen.queryByTestId('shortcut-dialog')).not.toBeInTheDocument(); }, { timeout: 500 }); }); }); describe('Help text', () => { it('should display help text in dialog', async () => { const user = userEvent.setup(); renderComponent(); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByText('KeyboardShortcut.HelpText')).toBeInTheDocument(); }); }); }); describe('Props validation', () => { it('should use custom label', () => { renderComponent({ label: 'Custom Label' }); const button = screen.getByTestId('shortcut-register-button'); expect(button).toHaveTextContent('Custom Label'); }); it('should handle onChange callback', async () => { const customOnChange = vi.fn(); const user = userEvent.setup(); renderComponent({ onChange: customOnChange }); const button = screen.getByTestId('shortcut-register-button'); await user.click(button); await waitFor(() => { expect(screen.getByTestId('shortcut-dialog')).toBeInTheDocument(); }); // Simulate Ctrl+X key press on document fireEvent.keyDown(document, { key: 'X', ctrlKey: true, bubbles: true, }); // Wait for the key combination to be processed await waitFor(() => { expect(screen.getByText('Ctrl+X')).toBeInTheDocument(); }); // Press Enter to confirm fireEvent.keyDown(document, { key: 'Enter', code: 'Enter' }); await waitFor(() => { expect(customOnChange).toHaveBeenCalledWith('Ctrl+X'); }); }); }); });