import { render, screen, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import userEvent from '@testing-library/user-event';
import { DatePicker } from './date-picker';
describe('DatePicker Component', () => {
const mockOnChange = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
});
it('renders date picker trigger correctly', () => {
render();
expect(screen.getByText('Select date...')).toBeInTheDocument();
});
it('uses custom placeholder', () => {
render();
expect(screen.getByText('Choose a date')).toBeInTheDocument();
});
it('displays selected date in single mode', () => {
const date = new Date(2024, 0, 15);
render();
expect(screen.getByText(date.toLocaleDateString())).toBeInTheDocument();
});
it('displays selected date range', () => {
const start = new Date(2024, 0, 15);
const end = new Date(2024, 0, 20);
render();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const displayText = `${start.toLocaleDateString()} - ${end.toLocaleDateString()}`;
expect(screen.getByText(new RegExp(start.toLocaleDateString().replace(/\//g, '/')))).toBeInTheDocument();
});
it('opens calendar when trigger is clicked', async () => {
const user = userEvent.setup();
render();
const triggers = screen.getAllByRole('button');
const trigger = triggers.find(btn => btn.textContent?.includes('Select date...')) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Today')).toBeInTheDocument();
});
});
it('displays current month by default', async () => {
const user = userEvent.setup();
const now = new Date();
render();
const triggers = screen.getAllByRole('button');
const trigger = triggers.find(btn => btn.textContent?.includes('Select date...')) || triggers[0];
await user.click(trigger);
await waitFor(() => {
const monthNames = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
const monthText = `${monthNames[now.getMonth()]} ${now.getFullYear()}`;
expect(screen.getByText(monthText)).toBeInTheDocument();
});
});
it('navigates to previous month', async () => {
const user = userEvent.setup();
const now = new Date();
const prevMonth = now.getMonth() === 0 ? 11 : now.getMonth() - 1;
const prevYear = now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear();
render();
const triggers = screen.getAllByRole('button');
const trigger = triggers.find(btn => btn.textContent?.includes('Select date...')) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Today')).toBeInTheDocument();
});
const prevButton = screen.getAllByRole('button').find(btn => {
const svg = btn.querySelector('svg');
return svg && svg.getAttribute('d')?.includes('m15 18-6-6 6-6');
});
if (prevButton) {
await user.click(prevButton);
await waitFor(() => {
const monthNames = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
const monthText = `${monthNames[prevMonth]} ${prevYear}`;
expect(screen.getByText(monthText)).toBeInTheDocument();
});
}
});
it('navigates to next month', async () => {
const user = userEvent.setup();
const now = new Date();
const nextMonth = now.getMonth() === 11 ? 0 : now.getMonth() + 1;
const nextYear = now.getMonth() === 11 ? now.getFullYear() + 1 : now.getFullYear();
render();
const triggers = screen.getAllByRole('button');
const trigger = triggers.find(btn => btn.textContent?.includes('Select date...')) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Today')).toBeInTheDocument();
});
const nextButtons = screen.getAllByRole('button');
const nextButton = nextButtons.find(btn => {
const svg = btn.querySelector('svg');
return svg && btn.getAttribute('aria-label') !== 'Fermer';
});
// Chercher le bouton avec ChevronRight
const chevronRightButtons = Array.from(document.querySelectorAll('svg')).filter(svg => {
const path = svg.querySelector('path');
return path && path.getAttribute('d')?.includes('m9 18 6-6-6-6');
});
if (chevronRightButtons.length > 0) {
const nextBtn = chevronRightButtons[0].closest('button');
if (nextBtn) {
await user.click(nextBtn);
await waitFor(() => {
const monthNames = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
const monthText = `${monthNames[nextMonth]} ${nextYear}`;
expect(screen.getByText(monthText)).toBeInTheDocument();
});
}
}
});
it('selects a date in single mode', async () => {
const user = userEvent.setup();
render();
const triggers = screen.getAllByRole('button');
const trigger = triggers.find(btn => btn.textContent?.includes('Select date...')) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Today')).toBeInTheDocument();
});
// Sélectionner le jour 15
const day15 = screen.getByText('15');
if (day15) {
await user.click(day15);
expect(mockOnChange).toHaveBeenCalled();
const selectedDate = mockOnChange.mock.calls[0][0];
expect(selectedDate).toBeInstanceOf(Date);
}
});
it('selects date range', async () => {
const user = userEvent.setup();
render();
const triggers = screen.getAllByRole('button');
const trigger = triggers.find(btn => btn.textContent?.includes('Select date range...')) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Today')).toBeInTheDocument();
});
// Sélectionner le jour 15
const day15 = screen.getByText('15');
if (day15) {
await user.click(day15);
expect(mockOnChange).toHaveBeenCalled();
const firstCall = mockOnChange.mock.calls[0][0];
expect(firstCall).toHaveProperty('start');
expect(firstCall).toHaveProperty('end');
}
});
it('completes date range selection', async () => {
const user = userEvent.setup();
const startDate = new Date(2024, 0, 15);
render(
);
const triggers = screen.getAllByRole('button');
const trigger = triggers.find(btn => btn.textContent?.includes('15')) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Today')).toBeInTheDocument();
});
// Sélectionner le jour 20 pour compléter la range
const day20 = screen.getByText('20');
if (day20) {
await user.click(day20);
expect(mockOnChange).toHaveBeenCalled();
const call = mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1][0];
expect(call).toHaveProperty('start');
expect(call).toHaveProperty('end');
expect(call.start).toBeInstanceOf(Date);
expect(call.end).toBeInstanceOf(Date);
}
});
it('disables dates before minDate', async () => {
const user = userEvent.setup();
const minDate = new Date(2024, 0, 15);
render();
const triggers = screen.getAllByRole('button');
const trigger = triggers.find(btn => btn.textContent?.includes('Select date...')) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Today')).toBeInTheDocument();
});
// Le jour 1 devrait être désactivé (avant le 15)
const day1 = screen.queryByText('1');
if (day1) {
const dayButton = day1.closest('button');
// Vérifier que le bouton est désactivé ou a les classes de désactivation
expect(dayButton?.classList.contains('opacity-50') || dayButton?.classList.contains('cursor-not-allowed') || dayButton?.hasAttribute('disabled')).toBe(true);
}
});
it('disables dates after maxDate', async () => {
const user = userEvent.setup();
const maxDate = new Date(2024, 0, 15);
render();
const triggers = screen.getAllByRole('button');
const trigger = triggers.find(btn => btn.textContent?.includes('Select date...')) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Today')).toBeInTheDocument();
});
// Le jour 31 devrait être désactivé si après maxDate
const day31 = screen.queryByText('31');
if (day31) {
const dayButton = day31.closest('button');
if (dayButton) {
expect(dayButton).toHaveClass('opacity-50');
}
}
});
it('clears selection when clear button is clicked', async () => {
const user = userEvent.setup();
const date = new Date(2024, 0, 15);
render();
const triggers = screen.getAllByRole('button');
const trigger = triggers.find(btn => btn.textContent?.includes('15'));
const clearButton = trigger?.querySelector('svg');
if (clearButton) {
await user.click(clearButton);
await waitFor(() => {
expect(mockOnChange).toHaveBeenCalled();
});
// Vérifier que onChange a été appelé avec undefined ou null
const lastCall = mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1];
expect(lastCall[0] === undefined || lastCall[0] === null).toBe(true);
}
});
it('selects today when Today button is clicked', async () => {
const user = userEvent.setup();
render();
const triggers = screen.getAllByRole('button');
const trigger = triggers.find(btn => btn.textContent?.includes('Select date...')) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Today')).toBeInTheDocument();
});
const todayButton = screen.getByText('Today');
await user.click(todayButton);
expect(mockOnChange).toHaveBeenCalled();
const selectedDate = mockOnChange.mock.calls[0][0];
expect(selectedDate).toBeInstanceOf(Date);
});
it('disables date picker when disabled prop is true', () => {
render();
const buttons = document.querySelectorAll('button');
const selectButton = Array.from(buttons).find(btn =>
btn.textContent?.includes('Select date...') && btn.disabled
);
expect(selectButton).toBeInTheDocument();
});
it('displays days of week correctly', async () => {
const user = userEvent.setup();
render();
const triggers = screen.getAllByRole('button');
const trigger = triggers.find(btn => btn.textContent?.includes('Select date...')) || triggers[0];
await user.click(trigger);
await waitFor(() => {
expect(screen.getByText('Mon')).toBeInTheDocument();
expect(screen.getByText('Tue')).toBeInTheDocument();
expect(screen.getByText('Wed')).toBeInTheDocument();
expect(screen.getByText('Thu')).toBeInTheDocument();
expect(screen.getByText('Fri')).toBeInTheDocument();
expect(screen.getByText('Sat')).toBeInTheDocument();
expect(screen.getByText('Sun')).toBeInTheDocument();
});
});
});