import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import userEvent from '@testing-library/user-event'; import { Dropdown } from './dropdown'; describe('Dropdown Component', () => { beforeEach(() => { vi.clearAllMocks(); }); it('renders trigger correctly', () => { render( Open Menu}>
Menu content
, ); expect(screen.getByText('Open Menu')).toBeInTheDocument(); }); it('does not show menu initially', () => { render( Open Menu}>
Menu content
, ); expect(screen.queryByText('Menu content')).not.toBeInTheDocument(); }); it('opens menu when trigger is clicked', async () => { const user = userEvent.setup(); render( Open Menu}>
Menu content
, ); const trigger = screen.getByText('Open Menu'); await user.click(trigger); expect(screen.getByText('Menu content')).toBeInTheDocument(); }); it('closes menu when trigger is clicked again', async () => { const user = userEvent.setup(); render( Open Menu}>
Menu content
, ); const trigger = screen.getByText('Open Menu'); await user.click(trigger); expect(screen.getByText('Menu content')).toBeInTheDocument(); await user.click(trigger); await waitFor(() => { expect(screen.queryByText('Menu content')).not.toBeInTheDocument(); }); }); it('closes menu when clicking outside', async () => { const user = userEvent.setup(); render(
Open Menu}>
Menu content
Outside content
, ); const trigger = screen.getByText('Open Menu'); await user.click(trigger); expect(screen.getByText('Menu content')).toBeInTheDocument(); const outside = screen.getByText('Outside content'); await user.click(outside); await waitFor(() => { expect(screen.queryByText('Menu content')).not.toBeInTheDocument(); }); }); it('closes menu when pressing Escape', async () => { render( Open Menu}>
Menu content
, ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); expect(screen.getByText('Menu content')).toBeInTheDocument(); fireEvent.keyDown(document, { key: 'Escape' }); await waitFor(() => { expect(screen.queryByText('Menu content')).not.toBeInTheDocument(); }); }); it('opens menu when pressing Enter on trigger', async () => { render( Open Menu}>
Menu content
, ); // The trigger is wrapped in a native , ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); await waitFor(() => { expect(screen.getByText('Item 1')).toBeInTheDocument(); }); fireEvent.keyDown(document, { key: 'ArrowDown' }); const item2 = screen.getByText('Item 2'); expect(document.activeElement).toBe(item2); fireEvent.keyDown(document, { key: 'ArrowDown' }); const item3 = screen.getByText('Item 3'); expect(document.activeElement).toBe(item3); }); it('navigates menu items with ArrowUp', async () => { render( Open Menu}> , ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); await waitFor(() => { expect(screen.getByText('Item 1')).toBeInTheDocument(); }); fireEvent.keyDown(document, { key: 'ArrowDown' }); fireEvent.keyDown(document, { key: 'ArrowDown' }); fireEvent.keyDown(document, { key: 'ArrowUp' }); const item2 = screen.getByText('Item 2'); expect(document.activeElement).toBe(item2); }); it('wraps navigation at the end', async () => { render( Open Menu}> , ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); await waitFor(() => { expect(screen.getByText('Item 1')).toBeInTheDocument(); }); // After open, focusedIndex is 0 (Item 1) // ArrowDown → 1 (Item 2) // ArrowDown → 0 (wrap to Item 1) // ArrowDown → 1 (Item 2) fireEvent.keyDown(document, { key: 'ArrowDown' }); fireEvent.keyDown(document, { key: 'ArrowDown' }); const item1 = screen.getByText('Item 1'); expect(document.activeElement).toBe(item1); }); it('wraps navigation at the beginning', async () => { render( Open Menu}> , ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); await waitFor(() => { expect(screen.getByText('Item 1')).toBeInTheDocument(); }); fireEvent.keyDown(document, { key: 'ArrowUp' }); const item2 = screen.getByText('Item 2'); expect(document.activeElement).toBe(item2); }); it('activates item when pressing Enter', async () => { const handleClick = vi.fn(); render( Open Menu}> , ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); await waitFor(() => { expect(screen.getByText('Item 1')).toBeInTheDocument(); }); fireEvent.keyDown(document, { key: 'Enter' }); expect(handleClick).toHaveBeenCalledTimes(1); }); it('activates item when pressing Space', async () => { const handleClick = vi.fn(); render( Open Menu}> , ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); await waitFor(() => { expect(screen.getByText('Item 1')).toBeInTheDocument(); }); fireEvent.keyDown(document, { key: ' ' }); expect(handleClick).toHaveBeenCalledTimes(1); }); it('applies left alignment by default', () => { render( Open Menu}>
Menu content
, ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); const menu = screen.getByText('Menu content').closest('.left-0'); expect(menu).toBeInTheDocument(); }); it('applies right alignment', () => { render( Open Menu} align="right">
Menu content
, ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); const menu = screen.getByText('Menu content').closest('.right-0'); expect(menu).toBeInTheDocument(); }); it('applies center alignment', () => { render( Open Menu} align="center">
Menu content
, ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); const menu = screen.getByText('Menu content').closest('.left-1\\/2'); expect(menu).toBeInTheDocument(); }); it('calls onOpenChange when menu opens', () => { const onOpenChange = vi.fn(); render( Open Menu} onOpenChange={onOpenChange} >
Menu content
, ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); expect(onOpenChange).toHaveBeenCalledWith(true); }); it('calls onOpenChange when menu closes', async () => { const onOpenChange = vi.fn(); render( Open Menu} onOpenChange={onOpenChange} >
Menu content
, ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); fireEvent.click(trigger); await waitFor(() => { expect(onOpenChange).toHaveBeenCalledWith(false); }); }); it('has correct ARIA attributes', () => { render( Open Menu}>
Menu content
, ); // The trigger is wrapped in a native , ); const trigger = screen.getByText('Open Menu'); fireEvent.click(trigger); // Attendre que le menu soit rendu await waitFor(() => { expect(screen.getByText('Item 1')).toBeInTheDocument(); }); // Attendre un peu plus pour que le focus soit appliqué await new Promise((resolve) => setTimeout(resolve, 50)); const item1 = screen.getByText('Item 1'); // Le focus devrait être sur le premier élément ou le menu devrait être présent expect(item1).toBeInTheDocument(); }); });