import { render, screen, act } from '@testing-library/react' import { fireEvent } from '@testing-library/react' import { renderHook } from '@testing-library/react' import { CommandPaletteProvider, useCommandPalette } from '../command-palette-context' // Wrapper that exposes the context value through data attributes so we can // assert on it without tying tests to component internals. function ContextProbe() { const { isOpen, open, close, toggle } = useCommandPalette() return (
) } function renderWithProvider() { return render( ) } function probe() { return screen.getByTestId('probe') } describe('CommandPaletteProvider / useCommandPalette', () => { describe('initial state', () => { it('starts with isOpen = false', () => { renderWithProvider() expect(probe().dataset.open).toBe('false') }) }) describe('open()', () => { it('sets isOpen to true', () => { renderWithProvider() fireEvent.click(screen.getByTestId('btn-open')) expect(probe().dataset.open).toBe('true') }) it('is idempotent — calling open twice leaves isOpen true', () => { renderWithProvider() fireEvent.click(screen.getByTestId('btn-open')) fireEvent.click(screen.getByTestId('btn-open')) expect(probe().dataset.open).toBe('true') }) }) describe('close()', () => { it('sets isOpen to false', () => { renderWithProvider() fireEvent.click(screen.getByTestId('btn-open')) fireEvent.click(screen.getByTestId('btn-close')) expect(probe().dataset.open).toBe('false') }) it('is idempotent — calling close when already closed keeps isOpen false', () => { renderWithProvider() fireEvent.click(screen.getByTestId('btn-close')) expect(probe().dataset.open).toBe('false') }) }) describe('toggle()', () => { it('opens when currently closed', () => { renderWithProvider() fireEvent.click(screen.getByTestId('btn-toggle')) expect(probe().dataset.open).toBe('true') }) it('closes when currently open', () => { renderWithProvider() fireEvent.click(screen.getByTestId('btn-open')) fireEvent.click(screen.getByTestId('btn-toggle')) expect(probe().dataset.open).toBe('false') }) it('toggles back and forth correctly', () => { renderWithProvider() const btn = screen.getByTestId('btn-toggle') fireEvent.click(btn) expect(probe().dataset.open).toBe('true') fireEvent.click(btn) expect(probe().dataset.open).toBe('false') fireEvent.click(btn) expect(probe().dataset.open).toBe('true') }) }) describe('Cmd+K / Ctrl+K keyboard shortcut', () => { it('toggles isOpen on Cmd+K (metaKey)', () => { renderWithProvider() act(() => { fireEvent.keyDown(document, { key: 'k', metaKey: true }) }) expect(probe().dataset.open).toBe('true') }) it('toggles isOpen on Ctrl+K (ctrlKey)', () => { renderWithProvider() act(() => { fireEvent.keyDown(document, { key: 'k', ctrlKey: true }) }) expect(probe().dataset.open).toBe('true') }) it('toggles closed on second Cmd+K', () => { renderWithProvider() act(() => { fireEvent.keyDown(document, { key: 'k', metaKey: true }) }) act(() => { fireEvent.keyDown(document, { key: 'k', metaKey: true }) }) expect(probe().dataset.open).toBe('false') }) it('does not toggle on plain k keypress', () => { renderWithProvider() act(() => { fireEvent.keyDown(document, { key: 'k' }) }) expect(probe().dataset.open).toBe('false') }) it('removes the global keydown listener on unmount (no state update after unmount)', () => { const { unmount } = renderWithProvider() unmount() // Should not throw when dispatching after unmount expect(() => { act(() => { fireEvent.keyDown(document, { key: 'k', metaKey: true }) }) }).not.toThrow() }) }) describe('Escape key', () => { it('closes the palette when open', () => { renderWithProvider() // Open it first fireEvent.click(screen.getByTestId('btn-open')) expect(probe().dataset.open).toBe('true') act(() => { fireEvent.keyDown(document, { key: 'Escape' }) }) expect(probe().dataset.open).toBe('false') }) it('does nothing when palette is already closed', () => { renderWithProvider() act(() => { fireEvent.keyDown(document, { key: 'Escape' }) }) expect(probe().dataset.open).toBe('false') }) }) describe('useCommandPalette — usage outside provider', () => { it('throws when used outside CommandPaletteProvider', () => { // Suppress the expected React error boundary console output const spy = jest.spyOn(console, 'error').mockImplementation(() => {}) expect(() => { renderHook(() => useCommandPalette()) }).toThrow('useCommandPalette must be used within a CommandPaletteProvider') spy.mockRestore() }) }) })