import { DateInput, DateInputProps, Field } from '..';
import { mockMatchMedia, mockResizeObserver, render, userEvent, screen } from '../test-utils';
mockMatchMedia();
mockResizeObserver();
describe('Date Input Component', () => {
const props: DateInputProps = {
onChange: jest.fn(),
onFocus: jest.fn(),
onBlur: jest.fn(),
};
afterEach(jest.clearAllMocks);
describe('when initialised without a model', () => {
it('sets day field to empty', () => {
render();
const dayInput = screen.getByRole('textbox', { name: /day/i });
expect(dayInput).toHaveValue('');
});
it('sets month field to empty', () => {
render();
const monthSelect = screen.getByRole('combobox', { name: /month/i });
expect(monthSelect).toHaveValue('');
});
it('sets year field to empty', () => {
render();
const yearInput = screen.getByRole('textbox', { name: /year/i });
expect(yearInput).toHaveValue('');
});
});
describe('when initialised with a model', () => {
it('sets values correctly with a valid Date instance', () => {
render();
expect(screen.getByRole('textbox', { name: /day/i })).toHaveValue('1');
expect(screen.getByRole('combobox', { name: /month/i })).toHaveTextContent('August');
expect(screen.getByRole('textbox', { name: /year/i })).toHaveValue('1971');
});
it('sets values correctly with a valid short ISO8601 string', () => {
render();
expect(screen.getByRole('textbox', { name: /day/i })).toHaveValue('22');
expect(screen.getByRole('combobox', { name: /month/i })).toHaveTextContent('August');
expect(screen.getByRole('textbox', { name: /year/i })).toHaveValue('1990');
});
it('sets values correctly with a valid short ISO8601 string with year and month only', () => {
render();
expect(screen.getByRole('textbox', { name: /day/i })).toHaveValue('');
expect(screen.getByRole('combobox', { name: /month/i })).toHaveTextContent('August');
expect(screen.getByRole('textbox', { name: /year/i })).toHaveValue('1990');
});
it('sets values correctly with a valid long ISO8601 string', () => {
render();
expect(screen.getByRole('textbox', { name: /day/i })).toHaveValue('28');
expect(screen.getByRole('combobox', { name: /month/i })).toHaveTextContent('February');
expect(screen.getByRole('textbox', { name: /year/i })).toHaveValue('1990');
});
it('sets values to disabled when disabled is set to true', () => {
render();
expect(screen.getByRole('textbox', { name: /day/i })).toBeDisabled();
expect(screen.getByRole('combobox', { name: /month/i })).toBeDisabled();
expect(screen.getByRole('textbox', { name: /year/i })).toBeDisabled();
});
it("doesn't set values to disabled when disabled is set to false", () => {
render();
expect(screen.getByRole('textbox', { name: /day/i })).toBeEnabled();
expect(screen.getByRole('combobox', { name: /month/i })).toBeEnabled();
expect(screen.getByRole('textbox', { name: /year/i })).toBeEnabled();
});
});
describe('when initialised', () => {
it(`doesn't call the onChange callback without an initial value`, () => {
render();
expect(props.onChange).not.toHaveBeenCalled();
});
it(`doesn't call the onChange callback with an initial value`, () => {
render();
expect(props.onChange).not.toHaveBeenCalled();
});
});
describe('when interacting', () => {
it('calls the onChange callback with null when changing the day but nothing else is filled out', async () => {
render();
await userEvent.type(screen.getByRole('textbox', { name: /day/i }), '4');
expect(props.onChange).toHaveBeenCalledWith(null);
});
it('calls the onChange callback with null when changing the month but nothing else is filled out', async () => {
render();
const monthSelect = screen.getByLabelText(/month/i);
await userEvent.click(monthSelect);
await userEvent.click(screen.getByRole('option', { name: /January/ }));
expect(props.onChange).toHaveBeenCalledWith(null);
}, 10_000);
it('calls the onChange callback with null when changing the year but nothing else is filled out', async () => {
render();
await userEvent.type(screen.getByRole('textbox', { name: /day/i }), '4444');
expect(props.onChange).toHaveBeenCalledWith(null);
});
it('calls the onChange callback with the correct value when changing the day', async () => {
render();
const dayInput = screen.getByRole('textbox', { name: /day/i });
// add 8 to the 1 that's already there
await userEvent.type(dayInput, '8');
expect(props.onChange).toHaveBeenCalledWith('2022-12-18');
});
it('calls the onChange callback with the correct value when changing the month', async () => {
render();
const monthSelect = screen.getByRole('combobox', { name: /month/i });
await userEvent.click(monthSelect);
await userEvent.click(screen.getByRole('option', { name: /January/ }));
expect(props.onChange).toHaveBeenCalledWith('2022-01-01');
}, 10_000);
it('calls the onChange callback with the correct value when changing the year', async () => {
render();
const yearInput = screen.getByRole('textbox', { name: /year/i });
// 0122 ends up being 122 in the box when it's supplied as the starting value
await userEvent.type(yearInput, '2');
expect(props.onChange).toHaveBeenCalledWith('1222-12-01');
});
it('calls the onChange callback with the null when not having a 4 digit year', async () => {
render();
const yearInput = screen.getByRole('textbox', { name: /year/i });
await userEvent.type(yearInput, '123');
expect(props.onChange).toHaveBeenCalledWith(null);
});
it('calls the onChange callback with the null when changing the day to a day that is higher than max value', async () => {
render();
const dayInput = screen.getByRole('textbox', { name: /day/i });
// add 1 to the 3 that's already there
await userEvent.type(dayInput, '1');
expect(props.onChange).toHaveBeenCalledWith(null);
});
it('does not overflow very high day values to the next month', async () => {
render();
const dayInput = screen.getByRole('textbox', { name: /day/i });
// add 6 to the 6 that's already there
await userEvent.type(dayInput, '6');
expect(props.onChange).toHaveBeenCalledWith(null);
});
it('only pastes in first 6 digits of a long number for year', async () => {
render();
const yearInput = screen.getByRole('textbox', { name: /year/i });
yearInput.focus();
await userEvent.type(yearInput, '{backspace}{backspace}{backspace}{backspace}');
await userEvent.paste('12345678');
expect(props.onChange).toHaveBeenCalledWith('123456-01-01');
});
it('only pastes in first 2 digits of a long number for day', async () => {
render();
const yearInput = screen.getByRole('textbox', { name: /day/i });
yearInput.focus();
await userEvent.type(yearInput, '{backspace}{backspace}');
await userEvent.paste('123456');
expect(props.onChange).toHaveBeenCalledWith('2010-01-12');
});
});
describe('propagating focus and blur callbacks', () => {
it('should propagate if focusing from or blurring to external component', async () => {
render(
<>
>,
);
await userEvent.tab();
await userEvent.tab();
await userEvent.tab({ shift: true });
expect(props.onFocus).toHaveBeenCalledTimes(1);
expect(props.onBlur).toHaveBeenCalledTimes(1);
});
it('should not propagate if switching between internals', async () => {
render(
<>
>,
);
const externalButton = screen.getByRole('button', { name: 'external button' });
const day = screen.getByRole('textbox', { name: /day/i });
const month = screen.getByRole('combobox', { name: /month/i });
const year = screen.getByRole('textbox', { name: /year/i });
await userEvent.click(externalButton);
await userEvent.click(day);
expect(props.onFocus).toHaveBeenCalledTimes(1);
expect(props.onBlur).toHaveBeenCalledTimes(0);
await userEvent.click(month);
// selectinput really likes to refocus, apparently.
expect(props.onFocus).toHaveBeenCalledTimes(4);
expect(props.onBlur).toHaveBeenCalledTimes(0);
await userEvent.click(year);
expect(props.onFocus).toHaveBeenCalledTimes(5);
expect(props.onBlur).toHaveBeenCalledTimes(0);
await userEvent.click(externalButton);
expect(props.onFocus).toHaveBeenCalledTimes(5);
expect(props.onBlur).toHaveBeenCalledTimes(1);
});
});
describe('when selectProps is provided', () => {
it('renders Select component with expected props', () => {
render(
,
);
const monthSelect = screen.getByRole('combobox', { name: /month/i });
expect(monthSelect).toHaveAttribute('id', 'mock-id');
});
});
it('supports `Field` for labeling', () => {
render(
{}} />
,
);
expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Date of birth/);
});
it('focuses day input when `Field` label is clicked', async () => {
const label = 'Date of birth';
render(
{}} />
,
);
const dayInput = screen.getByRole('textbox', { name: /day/i });
await userEvent.click(screen.getByText(label, { selector: 'label' })); // Have to use `getByText` due to the way `Field` handles group labelling
expect(dayInput).toHaveFocus();
});
it('focuses month input when `Field` label is clicked and day is not present', async () => {
const label = 'Date of birth';
render(
{}} />
,
);
const monthTrigger = screen.getByRole('combobox');
await userEvent.click(screen.getByText(label, { selector: 'label' })); // Have to use `getByText` due to the way `Field` handles group labelling
expect(monthTrigger).toHaveAttribute('aria-expanded', 'true');
});
});