import React from 'react';
import { describe, it, expect, vi } from 'vitest';
import { render, fireEvent } from '@testing-library/react';
import { PixelDateRangePicker } from '../../forms/PixelDateRangePicker';
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('PixelDateRangePicker', () => {
it('renders placeholder when no value', () => {
const { getByTestId } = render(
,
);
const trigger = getByTestId('trigger');
expect(trigger.textContent).toContain('Pick a range');
});
it('clicking trigger opens calendars', () => {
const { getByTestId, queryAllByRole } = render(
,
);
expect(queryAllByRole('grid').length).toBe(0);
fireEvent.click(getByTestId('trigger'));
// Default numberOfMonths=2 → two grids visible.
expect(queryAllByRole('grid').length).toBe(2);
});
it('two clicks select a range (from and to)', () => {
const onChange = vi.fn();
const initial = { from: new Date(2026, 5, 10), to: new Date(2026, 5, 10) };
const { getByTestId, getAllByLabelText } = render(
,
);
fireEvent.click(getByTestId('trigger'));
// First click: pick June 12 → resets to { from: 12 }
const day12 = getAllByLabelText(/June 12, 2026/i)[0];
fireEvent.click(day12);
// Second click: pick June 20 → completes range
const day20 = getAllByLabelText(/June 20, 2026/i)[0];
fireEvent.click(day20);
// onChange called at least twice (once per click)
expect(onChange).toHaveBeenCalled();
const last = onChange.mock.calls[onChange.mock.calls.length - 1][0] as {
from?: Date;
to?: Date;
};
expect(last.from).toBeInstanceOf(Date);
expect(last.to).toBeInstanceOf(Date);
expect(last.from!.getDate()).toBe(12);
expect(last.to!.getDate()).toBe(20);
});
it('preset selects predefined range', () => {
const onChange = vi.fn();
const presetFrom = new Date(2026, 0, 1);
const presetTo = new Date(2026, 0, 7);
const { getByTestId, getByText } = render(
,
);
fireEvent.click(getByTestId('trigger'));
fireEvent.click(getByText('First Week'));
expect(onChange).toHaveBeenCalledTimes(1);
const arg = onChange.mock.calls[0][0] as { from?: Date; to?: Date };
expect(arg.from!.getDate()).toBe(1);
expect(arg.to!.getDate()).toBe(7);
});
it('numberOfMonths=1 renders single calendar', () => {
const { getByTestId, queryAllByRole } = render(
,
);
fireEvent.click(getByTestId('trigger'));
expect(queryAllByRole('grid').length).toBe(1);
});
it('clearable clears the value', () => {
const onChange = vi.fn();
const initial = {
from: new Date(2026, 5, 10),
to: new Date(2026, 5, 20),
};
const { getByTestId, getByText } = render(
,
);
fireEvent.click(getByTestId('trigger'));
fireEvent.click(getByText(/clear/i));
expect(onChange).toHaveBeenCalled();
const arg = onChange.mock.calls[onChange.mock.calls.length - 1][0] as {
from?: Date;
to?: Date;
};
expect(arg.from).toBeUndefined();
expect(arg.to).toBeUndefined();
});
it('hidden inputs serialize from/to ISO dates', () => {
const value = {
from: new Date(2026, 5, 10),
to: new Date(2026, 5, 20),
};
const { container } = render(
,
);
const from = container.querySelector(
'input[type="hidden"][name="range.from"]',
) as HTMLInputElement | null;
const to = container.querySelector(
'input[type="hidden"][name="range.to"]',
) as HTMLInputElement | null;
expect(from).not.toBeNull();
expect(to).not.toBeNull();
expect(from!.value).toBe(toISO(value.from));
expect(to!.value).toBe(toISO(value.to));
});
});