React-testing-library: Onchange event not being fired when using fireEvent.change

Created on 15 Nov 2019  路  17Comments  路  Source: testing-library/react-testing-library

When trying to use fireEvent.change on an input The onChange event isn't triggered. I am seeing the onBlur event is triggered for fireEvent.blur

  • "jest": "24.9.0",
  • "jest-environment-jsdom-fourteen": "^0.1.0",
  • "jest-localstorage-mock": "2.4.0",
  • "jest-serializer-enzyme": "1.0.0",
  • "jest-skipped-reporter": "0.0.4",
  • "jest-trx-results-processor": "0.2.0",
  • "jest-watch-typeahead": "0.3.0",
  • "@testing-library/react": "^9.3.0",
  • "react-hooks-testing-library": "0.4.0",
  • "ts-jest": "24.1.0",
  • "react": "16.9",
  • "react-dom": "16.9",
  • node version: v8.16.1
  • yarn "1.17.3"

What you did:

when I use fireEvent.change(input, {target: {value: 'bob'}}
OnChange isn't triggered
When I use fireEvent.blur(input)
Blur is triggered
When I use simulate for react-dom/test-utils it works.

Things I've tried

using "jest-environment-jsdom-fourteen": "^0.1.0",
checking to make sure input fields have type set.

example test

it.only('should map errors', async () => {
        const { container, getByLabelText, getByText } = await renderWithState();
        const routingNumber = getByLabelText('Routing number') as HTMLInputElement;
        const accountNumber = getByLabelText('Account number') as HTMLInputElement;
        const accountConfirm = getByLabelText('Confirm account number') as HTMLInputElement;
        fireEvent.change(routingNumber, { target: { value: '110000000', name: EBankingFieldNames.routingNumber } });
        fireEvent.change(accountNumber, { target: { value: '000444444440', name: EBankingFieldNames.accountNumber } });
        fireEvent.change(accountConfirm, {
            target: { value: '000444444440', name: EBankingFieldNames.accountNumberConfirm },
        });
        await waitForDomChange({ container });
        fireEvent.click(getByText('Save'));
        await waitForDomChange({ container });
        expect(getByText('Please enter a valid routing number'));
    });
bug needs investigation

Most helpful comment

I did some digging into this and it looks like the root cause is JSDOM not defaulting invalid input types to "text" as per the spec:

The missing value default and the invalid value default are the Text state.

I tracked it down to React only creating change events when the input has a valid type:
https://github.com/facebook/react/blob/ab1a4f249e61045d523ddbbfb840e868afbbf785/packages/react-dom/src/events/ChangeEventPlugin.js#L275
https://github.com/facebook/react/blob/ab1a4f249e61045d523ddbbfb840e868afbbf785/packages/shared/isTextInputElement.js#L13-L29

I've opened a PR with JSDOM that should address the issue, however I think Jest is using JSDOM 14 so it might be some time for this change to filter through the right packages.

All 17 comments

Hi @jamesBennett,

I'm not sure whether it will make a difference, but I noticed that you're not adding await on your waitForDomChange calls there. Those are async so you'll need to await those.

We could help a lot more if you post a codesandbox reproduction of this.

Hi @jamesBennett,

I'm not sure whether it will make a difference, but I noticed that you're not adding await on your waitForDomChange calls there. Those are async so you'll need to await those.

Yeah doesn't change anything. Even if I had the await the onChange should be called before hitting the waitForDomChange

We could help a lot more if you post a codesandbox reproduction of this.

My app is really large. That seems like it would take a significant amount of time. Also, I have enzyme in here as well. Could that be anything?

I doubt that enzyme is causing a problem. Without a reproduction of the problem it's impossible for us to help because this works for everyone else.

@kentcdodds So the problem is that if you don't set the type or the type is invalid for an input it will not trigger an onChange.

Although this is definitely my error it seems odd that onBlur would still work. Is this a known bug? Seems like the query functions should throw an error on this.

if you don't set the type or the type is invalid for an input it will not trigger an onChange.

I think there's something else about your set up because this test passes:

import React from 'react'
import {render, fireEvent} from '@testing-library/react'

test('calls change handler for an input', () => {
  const handleChange = jest.fn()
  const {container} = render(<input onChange={handleChange} />)
  const input = container.firstChild
  fireEvent.change(input, {target: {value: 'a'}})
  expect(handleChange).toHaveBeenCalledTimes(1)
})

https://codesandbox.io/s/rtl-calls-change-handler-dh3gs?fontsize=14&hidenavigation=1&module=%2Fsrc%2F__tests__%2Findex.js&previewwindow=tests&theme=dark

This is why having a reproduction is a great deal of help.

I think there's something else about your set up because this test passes:

But this version fails.

import React from 'react'
import {render, fireEvent} from '@testing-library/react'

test('calls change handler for an input', () => {
  const handleChange = jest.fn()
  const {container} = render(
    <input onChange={handleChange} type="invalidType" />,
  )
  const input = container.firstChild
  fireEvent.change(input, {target: {value: 'a'}})
  expect(handleChange).toHaveBeenCalledTimes(1)
})

notice the type

Yeah, that's a bug. I'm not sure what the problem is here and it will need some investigation. I wonder whether it's a limitation for jsdom.

Anyone's invited to do some investigation as to why an onChange handler is working when rendering it to the app, but not in the test. Here's a codesandbox example of this issue:

https://codesandbox.io/s/rtl-change-handler-with-invalid-type-yipu3

it's a limitation for jsdom.

@kentcdodds I don't think so because it works fine with reacts simulate.change
https://codesandbox.io/s/rtl-calls-change-handler-pfnks?fontsize=14&hidenavigation=1&theme=dark

Simulate doesn't fire events. Simulate is cheating, which is why we don't use it.

I did some digging into this and it looks like the root cause is JSDOM not defaulting invalid input types to "text" as per the spec:

The missing value default and the invalid value default are the Text state.

I tracked it down to React only creating change events when the input has a valid type:
https://github.com/facebook/react/blob/ab1a4f249e61045d523ddbbfb840e868afbbf785/packages/react-dom/src/events/ChangeEventPlugin.js#L275
https://github.com/facebook/react/blob/ab1a4f249e61045d523ddbbfb840e868afbbf785/packages/shared/isTextInputElement.js#L13-L29

I've opened a PR with JSDOM that should address the issue, however I think Jest is using JSDOM 14 so it might be some time for this change to filter through the right packages.

Super! Thank you for doing that! Glad to get this taken care of 馃憤

Doesn't seem to be only for invalid types. The following doesnt work:

test('calls change handler for an input', () => {
    const handleChange = jest.fn()
    const { container } = render(<input type="radio" onChange={handleChange} />)
    const input = container.firstChild
    fireEvent.change(input, { target: { value: 'a' } })
    expect(handleChange).toHaveBeenCalledTimes(1)
})

@RobertSigma, you can't change the value of a radio button to "a"... that doesn't make sense. For radio and checkboxes you "click" them.

@RobertSigma, you can't change the value of a radio button to "a"... that doesn't make sense. For radio and checkboxes you "click" them.

I'm starting to think we should add some warnings to fireEvent.change in dom-testing-library. It feels like we get a lot of reports that come down to wrong usage of fireEvent.change with certain input types (e.g. type="date" and non iso values).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

joshvillahermosa picture joshvillahermosa  路  3Comments

julienw picture julienw  路  4Comments

NiGhTTraX picture NiGhTTraX  路  3Comments

jalvarado91 picture jalvarado91  路  3Comments

drwpow picture drwpow  路  4Comments