276 lines
8.4 KiB
TypeScript
276 lines
8.4 KiB
TypeScript
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();
|
|
}
|
|
}
|