veza/apps/web/src/components/data/Timeline.test.tsx

259 lines
8.2 KiB
TypeScript
Raw Normal View History

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(<Timeline items={mockItems} />);
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(<Timeline items={[]} />);
expect(screen.getByText('Aucun événement à afficher')).toBeInTheDocument();
});
it('renders descriptions when provided', () => {
render(<Timeline items={mockItems} />);
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(<Timeline items={mockItems} />);
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(<Timeline items={mockItems} />);
// 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(<Timeline items={itemsWithStringDates} />);
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: <Calendar data-testid="calendar-icon" />,
},
];
render(<Timeline items={itemsWithIcon} />);
expect(screen.getByTestId('calendar-icon')).toBeInTheDocument();
});
it('renders default dot when icon is not provided', () => {
render(<Timeline items={mockItems} />);
// 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: <Calendar data-testid="icon-1" />,
},
{
id: '2',
title: 'Événement 2',
date: new Date('2024-01-15'),
icon: <CheckCircle data-testid="icon-2" />,
},
{
id: '3',
title: 'Événement 3',
date: new Date('2024-02-01'),
},
];
render(<Timeline items={itemsWithMultipleIcons} />);
expect(screen.getByTestId('icon-1')).toBeInTheDocument();
expect(screen.getByTestId('icon-2')).toBeInTheDocument();
});
});
describe('Orientation', () => {
it('renders vertical timeline by default', () => {
const { container } = render(<Timeline items={mockItems} />);
// 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(<Timeline items={mockItems} orientation="horizontal" />);
// 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(<Timeline items={mockItems} orientation="horizontal" />);
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(<Timeline items={mockItems} />);
// 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(<Timeline items={mockItems} orientation="horizontal" />);
// 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(<Timeline items={singleItem} />);
const lines = container.querySelectorAll('.bg-border');
expect(lines.length).toBe(0);
});
it('does not render connecting line after last item', () => {
const { container } = render(<Timeline items={mockItems} />);
// 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(<Timeline items={mockItems} className="custom-timeline" />);
const timeline = container.firstChild;
expect(timeline).toHaveClass('custom-timeline');
});
});
describe('Accessibility', () => {
it('renders items with proper structure', () => {
render(<Timeline items={mockItems} />);
// 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(<Timeline items={mockItems} />);
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();
}
}