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 { Modal } from './modal'; describe('Modal Component', () => { const mockOnClose = vi.fn(); beforeEach(() => { vi.clearAllMocks(); // Reset body overflow document.body.style.overflow = ''; }); it('renders nothing when open is false', () => { render(
Modal content
, ); expect(screen.queryByText('Modal content')).not.toBeInTheDocument(); }); it('renders modal when open is true', () => { render(
Modal content
, ); expect(screen.getByText('Modal content')).toBeInTheDocument(); }); it('displays title when provided', () => { render(
Modal content
, ); expect(screen.getByText('Test Modal')).toBeInTheDocument(); expect(screen.getByRole('heading')).toHaveAttribute('id', 'modal-title'); }); it('calls onClose when clicking overlay', async () => { const user = userEvent.setup(); render(
Modal content
, ); const overlay = screen.getByRole('dialog'); await user.click(overlay); expect(mockOnClose).toHaveBeenCalledTimes(1); }); it('does not call onClose when clicking inside modal', async () => { const user = userEvent.setup(); render(
Modal content
, ); const content = screen.getByText('Modal content'); await user.click(content); expect(mockOnClose).not.toHaveBeenCalled(); }); it('does not close when closeOnOverlayClick is false', async () => { const user = userEvent.setup(); render(
Modal content
, ); const overlay = screen.getByRole('dialog'); await user.click(overlay); expect(mockOnClose).not.toHaveBeenCalled(); }); it('calls onClose when pressing Escape', async () => { render(
Modal content
, ); fireEvent.keyDown(document, { key: 'Escape' }); await waitFor(() => { expect(mockOnClose).toHaveBeenCalledTimes(1); }); }); it('does not close when pressing Escape if closeOnEscape is false', async () => { render(
Modal content
, ); fireEvent.keyDown(document, { key: 'Escape' }); await waitFor( () => { expect(mockOnClose).not.toHaveBeenCalled(); }, { timeout: 100 }, ); }); it('renders close button when closeOnEscape is true', () => { render(
Modal content
, ); const closeButton = screen.getByLabelText('Fermer'); expect(closeButton).toBeInTheDocument(); }); it('calls onClose when clicking close button', async () => { const user = userEvent.setup(); render(
Modal content
, ); const closeButton = screen.getByLabelText('Fermer'); await user.click(closeButton); expect(mockOnClose).toHaveBeenCalledTimes(1); }); it('applies correct size classes', () => { const { rerender } = render(
Content
, ); let modal = screen.getByText('Content').closest('.max-w-sm'); expect(modal).toBeInTheDocument(); rerender(
Content
, ); modal = screen.getByText('Content').closest('.max-w-lg'); expect(modal).toBeInTheDocument(); }); it('applies custom className', () => { render(
Content
, ); const modal = screen.getByText('Content').closest('.custom-modal-class'); expect(modal).toBeInTheDocument(); }); it('prevents body scroll when open', () => { render(
Content
, ); expect(document.body.style.overflow).toBe('hidden'); }); it('restores body scroll when closed', () => { const { rerender } = render(
Content
, ); expect(document.body.style.overflow).toBe('hidden'); rerender(
Content
, ); expect(document.body.style.overflow).toBe(''); }); it('has correct ARIA attributes', () => { render(
Content
, ); const dialog = screen.getByRole('dialog'); expect(dialog).toHaveAttribute('aria-modal', 'true'); expect(dialog).toHaveAttribute('aria-labelledby', 'modal-title'); }); it('does not have aria-labelledby when title is not provided', () => { render(
Content
, ); const dialog = screen.getByRole('dialog'); expect(dialog).not.toHaveAttribute('aria-labelledby'); }); });