veza/apps/web/src/components/ui/date-picker.test.tsx
senke 38eab5c205 refactor(ui): extract DatePicker into date-picker module
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 22:15:11 +01:00

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();
});
});
});