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