import { render, screen } from '@testing-library/react'; import { describe, it, expect } from 'vitest'; import { Timeline, TimelineItem } from './Timeline'; import { Calendar, CheckCircle } from 'lucide-react'; describe('Timeline Component', () => { const mockItems: TimelineItem[] = [ { id: '1', title: 'Événement 1', description: 'Description de l\'événement 1', date: new Date('2024-01-01'), }, { id: '2', title: 'Événement 2', description: 'Description de l\'événement 2', date: new Date('2024-01-15'), }, { id: '3', title: 'Événement 3', date: new Date('2024-02-01'), }, ]; describe('Rendering', () => { it('renders timeline with items', () => { render(); expect(screen.getByText('Événement 1')).toBeInTheDocument(); expect(screen.getByText('Événement 2')).toBeInTheDocument(); expect(screen.getByText('Événement 3')).toBeInTheDocument(); }); it('renders empty message when items is empty', () => { render(); expect(screen.getByText('Aucun événement à afficher')).toBeInTheDocument(); }); it('renders descriptions when provided', () => { render(); expect(screen.getByText('Description de l\'événement 1')).toBeInTheDocument(); expect(screen.getByText('Description de l\'événement 2')).toBeInTheDocument(); }); it('does not render description when not provided', () => { render(); const event3 = screen.getByText('Événement 3').closest('div'); expect(event3).toBeInTheDocument(); // L'événement 3 n'a pas de description }); it('renders dates', () => { render(); // Les dates sont formatées, on vérifie qu'elles sont présentes const date1 = formatDate(mockItems[0].date, 'short'); expect(screen.getByText(date1)).toBeInTheDocument(); }); it('renders dates with string format', () => { const itemsWithStringDates: TimelineItem[] = [ { id: '1', title: 'Événement', date: '2024-01-01', }, ]; render(); const date = formatDate(itemsWithStringDates[0].date, 'short'); expect(screen.getByText(date)).toBeInTheDocument(); }); }); describe('Icons', () => { it('renders custom icon when provided', () => { const itemsWithIcon: TimelineItem[] = [ { id: '1', title: 'Événement avec icône', date: new Date('2024-01-01'), icon: , }, ]; render(); expect(screen.getByTestId('calendar-icon')).toBeInTheDocument(); }); it('renders default dot when icon is not provided', () => { render(); // Vérifier que les icônes par défaut sont présentes (3 items = 3 dots) const icons = document.querySelectorAll('.h-2.w-2'); expect(icons.length).toBe(3); }); it('renders multiple icons correctly', () => { const itemsWithMultipleIcons: TimelineItem[] = [ { id: '1', title: 'Événement 1', date: new Date('2024-01-01'), icon: , }, { id: '2', title: 'Événement 2', date: new Date('2024-01-15'), icon: , }, { id: '3', title: 'Événement 3', date: new Date('2024-02-01'), }, ]; render(); expect(screen.getByTestId('icon-1')).toBeInTheDocument(); expect(screen.getByTestId('icon-2')).toBeInTheDocument(); }); }); describe('Orientation', () => { it('renders vertical timeline by default', () => { const { container } = render(); // Vérifier que la structure verticale est présente const timeline = container.firstChild; expect(timeline).toBeInTheDocument(); // Les lignes verticales doivent être présentes const verticalLines = container.querySelectorAll('.h-full.w-0\\.5'); expect(verticalLines.length).toBeGreaterThan(0); }); it('renders horizontal timeline when orientation is horizontal', () => { const { container } = render(); // Vérifier que la structure horizontale est présente const timeline = container.firstChild; expect(timeline).toBeInTheDocument(); // Les lignes horizontales doivent être présentes const horizontalLines = container.querySelectorAll('.h-0\\.5'); expect(horizontalLines.length).toBeGreaterThan(0); }); it('applies correct classes for horizontal orientation', () => { const { container } = render(); const flexContainer = container.querySelector('.flex.items-start'); expect(flexContainer).toBeInTheDocument(); }); }); describe('Connecting Lines', () => { it('renders connecting lines between items in vertical orientation', () => { const { container } = render(); // Il devrait y avoir 2 lignes de connexion pour 3 items (n-1) const verticalLines = container.querySelectorAll('.h-full.w-0\\.5.bg-border'); expect(verticalLines.length).toBe(2); }); it('renders connecting lines between items in horizontal orientation', () => { const { container } = render(); // Il devrait y avoir 2 lignes de connexion pour 3 items (n-1) const horizontalLines = container.querySelectorAll('.h-0\\.5.bg-border'); expect(horizontalLines.length).toBe(2); }); it('does not render connecting line for single item', () => { const singleItem: TimelineItem[] = [ { id: '1', title: 'Seul événement', date: new Date('2024-01-01'), }, ]; const { container } = render(); const lines = container.querySelectorAll('.bg-border'); expect(lines.length).toBe(0); }); it('does not render connecting line after last item', () => { const { container } = render(); // Vérifier que le dernier item n'a pas de ligne après lui const allLines = container.querySelectorAll('.bg-border'); expect(allLines.length).toBe(2); // Seulement 2 lignes pour 3 items }); }); describe('Custom className', () => { it('applies custom className', () => { const { container } = render(); const timeline = container.firstChild; expect(timeline).toHaveClass('custom-timeline'); }); }); describe('Accessibility', () => { it('renders items with proper structure', () => { render(); // Vérifier que chaque item a un titre expect(screen.getByText('Événement 1')).toBeInTheDocument(); expect(screen.getByText('Événement 2')).toBeInTheDocument(); expect(screen.getByText('Événement 3')).toBeInTheDocument(); }); it('maintains item order', () => { const { container } = render(); const items = container.querySelectorAll('[class*="flex"]'); expect(items.length).toBeGreaterThan(0); }); }); }); // Helper function pour formater les dates (copie de l'utilitaire) function formatDate(date: Date | string, format: 'short' | 'long' | 'relative' = 'short'): string { const d = new Date(date); if (isNaN(d.getTime())) { return 'Invalid date'; } switch (format) { case 'short': return d.toLocaleDateString(); case 'long': return d.toLocaleDateString('fr-FR', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', }); case 'relative': return 'relative'; default: return d.toLocaleDateString(); } }