Search code examples
javascriptreactjsreact-testing-library

Cannot change value of <input type="date"> in react test


I tried countless different ways to interact with the date input but nothing worked. Sometimes the default date just stayed the same, and sometimes it turned into the empty string. I was never able to change the value to a new date. I tried among other things:

  const startDate = screen.getByLabelText('from');
  act(() => {
    fireEvent.change(startDate, {target: {value: '2019-10-08'}});
  });

this turned it into empty string.

  act(() => {
    userEvent.type(startDate, '08102019');  // and all variations with dashes or slashes and with the year first
  });

this had no effect on the value (using userEvent==13.5.2).

  const user = userEvent.setup();
  await act(async () => {
    await user.type(startDate, '08102019');
  });

No effect (using the latest userEvent, 14+).

And various other combinations using fireEffect to click or userEvent to click or userEvent.keyboard for example.

Here's a minimal version of my code reproducing the error

DateComp.test.tsx

import { render, screen, fireEvent } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import DateComp from './DateComp';
import '@testing-library/jest-dom';

test('changing date value works', async () => {
  render(<DateInput onChange={()=>{return;}} className="" />);

  const startDate = screen.getByLabelText('from');
  const endDate = screen.getByLabelText('to');
  act(() => {
    fireEvent.change(startDate, {target: {value: '2019-10-08'}});
  });
  act(() => {
    fireEvent.change(endDate, {target: {value: '2020-10-08'}});
  });
  expect(startDate.getAttribute('value')).toEqual('2019-10-08');
  expect(endDate.getAttribute('value')).toEqual('2020-10-08');
});

DateComp.tsx

import { useState, useEffect } from 'react';

interface IProps {
  onChange: CallableFunction,
  className: string,
}

const DateComp = ({ onChange, className }: IProps): JSX.Element => {
  // Default dates
  const dateFrom = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().substring(0, 10);  // 7 days ago
  const dateTo = new Date().toISOString().substring(0, 10);  // today

  const [selectedFrom, setSelectedFrom] = useState(dateFrom);
  const [selectedTo, setSelectedTo] = useState(dateTo);

  const handleFrom = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSelectedFrom(e.target.value);
  };
  const handleTo = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSelectedTo(e.target.value);
  };

  return (
    <div className={className}>
      <label htmlFor='date_inputs'>Period</label>
      <div id='date_inputs' className='date-inputs'>
        <label htmlFor='date_from' className='screen-reader-only'>from</label>
        <input id='date_from' type='date' defaultValue={dateFrom} onChange={handleFrom} />
        <label htmlFor='date_to'>to</label>
        <input id='date_to' type='date' defaultValue={dateTo} onChange={handleTo} />
      </div>
    </div>
  );
};

export default DateComp;

package.json

{
  ...,
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "typescript": "^4.9.5",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "./node_modules/.bin/react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  ...,
  "devDependencies": {
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^14.4.3",
    "@types/jest": "^27.5.2",
    "@types/node": "^16.18.24",
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.0",
    "msw": "^1.2.3",
    "react-scripts": "5.0.1"
  },
  "jest": {
    "transformIgnorePatterns": [
      "node_modules/(?!@ngrx|(?!deck.gl)|ng-dynamic)"
    ]
  }
}

Solution

  • Turns out my problem wasn't with the test, but with my React code. I had an uncontrolled <input type="date"> element because I was using defaultValue instead of value.

    Changing to value and using the state instead of the default dates (thereby making the element controlled I believe) fixed this. Working code:

    DateComp.tsx

    <label htmlFor='date_from' className='screen-reader-only'>from</label>
    <input id='date_from' type='date' value={selectedFrom} onChange={handleFrom} />
    <label htmlFor='date_to'>to</label>
    <input id='date_to' type='date' value={selectedTo} onChange={handleTo} />
    

    DateComp.test.tsx

    const startDate = screen.getByLabelText('from');
    act(() => {
      fireEvent.change(startDate, {target: {value: '2015-12-10'}});
    });
    expect(startDate.getAttribute('value')).toEqual('2019-10-08');
    

    P.S. using any combination of user/userEvent still doesn't work. If anyone figures that out let me know. Maybe the input type="date" element is still too new to be able to interact with nicely through the tests?

    P.P.S it's also interesting to me the date component worked totally fine from the users perspective using defaultValue, only the test wasn't able to modify the value.