import React from 'react'; import { describe, it, expect, vi } from 'vitest'; import { render, fireEvent } from '@testing-library/react'; import { PixelDatePicker } from '../../forms/PixelDatePicker'; function toISO(d: Date): string { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, '0'); const dd = String(d.getDate()).padStart(2, '0'); return `${y}-${m}-${dd}`; } describe('PixelDatePicker', () => { it('renders placeholder when no value', () => { const { getByTestId } = render( , ); const trigger = getByTestId('trigger'); expect(trigger.textContent).toContain('Pick a date'); }); it('clicking trigger opens calendar', () => { const { getByTestId, queryByRole } = render( , ); expect(queryByRole('grid')).toBeNull(); fireEvent.click(getByTestId('trigger')); expect(queryByRole('grid')).not.toBeNull(); }); it('clicking a day calls onChange with that date and closes', () => { const onChange = vi.fn(); const initial = new Date(2026, 5, 15); // June 15, 2026 const { getByTestId, queryByRole, getByLabelText } = render( , ); fireEvent.click(getByTestId('trigger')); expect(queryByRole('grid')).not.toBeNull(); // Click on day 20 in the current month (June 2026) const day20 = getByLabelText(/June 20, 2026/i); fireEvent.click(day20); expect(onChange).toHaveBeenCalledTimes(1); const arg = onChange.mock.calls[0][0] as Date; expect(arg).toBeInstanceOf(Date); expect(arg.getFullYear()).toBe(2026); expect(arg.getMonth()).toBe(5); expect(arg.getDate()).toBe(20); // popover should be closed expect(queryByRole('grid')).toBeNull(); }); it('next-month arrow advances calendar month', () => { const initial = new Date(2026, 5, 15); // June 2026 const { getByTestId, getByLabelText, getByText } = render( , ); fireEvent.click(getByTestId('trigger')); // The month label should read June 2026 expect(getByText(/June 2026/i)).toBeTruthy(); fireEvent.click(getByLabelText(/next month/i)); expect(getByText(/July 2026/i)).toBeTruthy(); }); it('min/max bounds disable out-of-range days', () => { const initial = new Date(2026, 5, 15); const min = new Date(2026, 5, 10); const max = new Date(2026, 5, 20); const onChange = vi.fn(); const { getByTestId, getByLabelText } = render( , ); fireEvent.click(getByTestId('trigger')); // Day 5 should be disabled (before min) const day5 = getByLabelText(/June 5, 2026/i) as HTMLButtonElement; expect(day5.disabled).toBe(true); fireEvent.click(day5); expect(onChange).not.toHaveBeenCalled(); // Day 25 should be disabled (after max) const day25 = getByLabelText(/June 25, 2026/i) as HTMLButtonElement; expect(day25.disabled).toBe(true); // Day 15 should be enabled const day15 = getByLabelText(/June 15, 2026/i) as HTMLButtonElement; expect(day15.disabled).toBe(false); }); it('preset click selects preset value', () => { const onChange = vi.fn(); const preset = new Date(2026, 0, 1); const { getByTestId, getByText } = render( , ); fireEvent.click(getByTestId('trigger')); fireEvent.click(getByText('New Year')); expect(onChange).toHaveBeenCalledTimes(1); const arg = onChange.mock.calls[0][0] as Date; expect(arg.getFullYear()).toBe(2026); expect(arg.getMonth()).toBe(0); expect(arg.getDate()).toBe(1); }); it('hidden input serializes ISO date', () => { const value = new Date(2026, 5, 15); const { container } = render( , ); const hidden = container.querySelector( 'input[type="hidden"][name="birthday"]', ) as HTMLInputElement | null; expect(hidden).not.toBeNull(); expect(hidden!.value).toBe(toISO(value)); }); });