410 lines
12 KiB
TypeScript
410 lines
12 KiB
TypeScript
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(<DatePicker onChange={mockOnChange} />);
|
|
|
|
expect(screen.getByText('Select date...')).toBeInTheDocument();
|
|
});
|
|
|
|
it('uses custom placeholder', () => {
|
|
render(<DatePicker onChange={mockOnChange} placeholder="Choose a date" />);
|
|
|
|
expect(screen.getByText('Choose a date')).toBeInTheDocument();
|
|
});
|
|
|
|
it('displays selected date in single mode', () => {
|
|
const date = new Date(2024, 0, 15);
|
|
render(<DatePicker value={date} onChange={mockOnChange} mode="single" />);
|
|
|
|
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(
|
|
<DatePicker
|
|
value={{ start, end }}
|
|
onChange={mockOnChange}
|
|
mode="range"
|
|
/>,
|
|
);
|
|
|
|
// 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(<DatePicker onChange={mockOnChange} />);
|
|
|
|
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(<DatePicker onChange={mockOnChange} />);
|
|
|
|
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(<DatePicker onChange={mockOnChange} />);
|
|
|
|
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(<DatePicker onChange={mockOnChange} />);
|
|
|
|
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(<DatePicker onChange={mockOnChange} mode="single" />);
|
|
|
|
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(<DatePicker onChange={mockOnChange} mode="range" />);
|
|
|
|
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(
|
|
<DatePicker
|
|
value={{ start: startDate, end: startDate }}
|
|
onChange={mockOnChange}
|
|
mode="range"
|
|
/>,
|
|
);
|
|
|
|
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(<DatePicker onChange={mockOnChange} minDate={minDate} />);
|
|
|
|
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(<DatePicker onChange={mockOnChange} maxDate={maxDate} />);
|
|
|
|
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(<DatePicker value={date} onChange={mockOnChange} mode="single" />);
|
|
|
|
const clearButton = screen.getByTestId('date-picker-clear');
|
|
await user.click(clearButton);
|
|
await waitFor(() => {
|
|
expect(mockOnChange).toHaveBeenCalled();
|
|
});
|
|
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(<DatePicker onChange={mockOnChange} />);
|
|
|
|
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(<DatePicker onChange={mockOnChange} disabled />);
|
|
|
|
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(<DatePicker onChange={mockOnChange} />);
|
|
|
|
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();
|
|
});
|
|
});
|
|
});
|