import { render, screen } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import userEvent from '@testing-library/user-event'; import { Tooltip } from './tooltip'; describe('Tooltip Component', () => { beforeEach(() => { vi.clearAllMocks(); vi.useFakeTimers(); }); afterEach(() => { vi.runOnlyPendingTimers(); vi.useRealTimers(); }); it('renders children correctly', () => { render( ); expect(screen.getByText('Hover me')).toBeInTheDocument(); }); it('does not show tooltip initially', () => { render( ); expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument(); }); it('shows tooltip on hover after delay', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); // Avancer le temps pour déclencher le délai (300ms) et le setTimeout imbriqué (10ms) vi.advanceTimersByTime(310); expect(screen.getByText('Tooltip text')).toBeInTheDocument(); }); it('hides tooltip when mouse leaves', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); // Avancer pour le setTimeout(..., 0) vi.advanceTimersByTime(10); expect(screen.getByText('Tooltip text')).toBeInTheDocument(); fireEvent.mouseLeave(button); // Avancer pour la fin de l'animation (200ms) vi.advanceTimersByTime(250); // Le tooltip peut être monté mais invisible, vérifier qu'il n'est pas visible const tooltip = screen.queryByText('Tooltip text'); if (tooltip) { expect(tooltip).toHaveClass('opacity-0'); } }); it('shows tooltip immediately when delay is 0', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); // Avancer pour le setTimeout(..., 0) vi.advanceTimersByTime(10); expect(screen.getByText('Tooltip text')).toBeInTheDocument(); }); it('applies top position by default', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); vi.advanceTimersByTime(10); const tooltip = screen.getByText('Tooltip text'); expect(tooltip.closest('.bottom-full')).toBeInTheDocument(); }); it('applies bottom position', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); vi.advanceTimersByTime(10); const tooltip = screen.getByText('Tooltip text'); expect(tooltip.closest('.top-full')).toBeInTheDocument(); }); it('applies left position', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); vi.advanceTimersByTime(10); const tooltip = screen.getByText('Tooltip text'); expect(tooltip.closest('.right-full')).toBeInTheDocument(); }); it('applies right position', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); vi.advanceTimersByTime(10); const tooltip = screen.getByText('Tooltip text'); expect(tooltip.closest('.left-full')).toBeInTheDocument(); }); it('does not show tooltip when disabled', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); vi.advanceTimersByTime(10); expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument(); }); it('renders React node as content', () => { render( Custom content} delay={0}> ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); vi.advanceTimersByTime(10); expect(screen.getByText('Custom content')).toBeInTheDocument(); }); it('applies custom className', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); vi.advanceTimersByTime(10); const tooltip = screen.getByText('Tooltip text'); expect(tooltip.closest('.custom-tooltip')).toBeInTheDocument(); }); it('has correct ARIA role', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); vi.advanceTimersByTime(10); const tooltip = screen.getByRole('tooltip'); expect(tooltip).toBeInTheDocument(); }); it('cancels tooltip display if mouse leaves before delay', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); // Avancer le temps mais pas assez pour déclencher l'affichage vi.advanceTimersByTime(200); fireEvent.mouseLeave(button); // Avancer le reste du temps vi.advanceTimersByTime(200); expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument(); }); describe('Triggers', () => { it('shows tooltip on click when trigger is click', async () => { const user = userEvent.setup({ delay: null }); render( ); const button = screen.getByText('Click me'); await user.click(button); vi.advanceTimersByTime(10); expect(screen.getByText('Click tooltip')).toBeInTheDocument(); }); it('toggles tooltip on click when trigger is click', async () => { const user = userEvent.setup({ delay: null }); render( ); const button = screen.getByText('Click me'); // Premier clic await user.click(button); vi.advanceTimersByTime(10); expect(screen.getByText('Click tooltip')).toBeInTheDocument(); // Deuxième clic pour fermer await user.click(button); vi.advanceTimersByTime(300); // Le tooltip est monté mais invisible, vérifier qu'il n'est pas visible const tooltip = screen.queryByText('Click tooltip'); // Le tooltip peut être monté mais invisible, ou complètement démonté // On vérifie qu'il n'est pas visible (opacity-0 ou pas présent) if (tooltip) { expect(tooltip).toHaveClass('opacity-0'); } }); it('shows tooltip on focus when trigger is focus', () => { render( ); const input = screen.getByPlaceholderText('Focus me'); fireEvent.focus(input); vi.advanceTimersByTime(10); expect(screen.getByText('Focus tooltip')).toBeInTheDocument(); }); it('hides tooltip on blur when trigger is focus', () => { render( ); const input = screen.getByPlaceholderText('Focus me'); fireEvent.focus(input); vi.advanceTimersByTime(10); expect(screen.getByText('Focus tooltip')).toBeInTheDocument(); fireEvent.blur(input); vi.advanceTimersByTime(300); // Le tooltip est monté mais invisible, vérifier qu'il n'est pas visible const tooltip = screen.queryByText('Focus tooltip'); // Le tooltip peut être monté mais invisible, ou complètement démonté // On vérifie qu'il n'est pas visible (opacity-0 ou pas présent) if (tooltip) { expect(tooltip).toHaveClass('opacity-0'); } }); }); describe('Advanced features', () => { it('shows arrow when showArrow is true', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); vi.advanceTimersByTime(10); const tooltip = screen.getByText('With arrow'); const arrow = tooltip.parentElement?.querySelector('.border-4'); expect(arrow).toBeInTheDocument(); }); it('hides arrow when showArrow is false', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); vi.advanceTimersByTime(10); const tooltip = screen.getByText('No arrow'); const arrow = tooltip.parentElement?.querySelector('.border-4'); expect(arrow).not.toBeInTheDocument(); }); it('applies maxWidth style', () => { render( ); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); vi.advanceTimersByTime(10); const tooltip = screen.getByText('Limited width'); expect(tooltip).toHaveStyle({ maxWidth: '200px' }); }); it('renders rich content with HTML elements', () => { render( Rich content

With multiple elements

} delay={0} >
); const button = screen.getByText('Hover me'); fireEvent.mouseEnter(button); vi.advanceTimersByTime(10); expect(screen.getByText('Rich content')).toBeInTheDocument(); expect(screen.getByText('With multiple elements')).toBeInTheDocument(); }); }); });