Skip to content

onChange is called when checkbox is disabled #275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
rug1 opened this issue Jan 23, 2019 · 13 comments
Closed

onChange is called when checkbox is disabled #275

rug1 opened this issue Jan 23, 2019 · 13 comments
Labels
bug Something isn't working help wanted Extra attention is needed jsdom Issue with JSDOM environment needs investigation

Comments

@rug1
Copy link

rug1 commented Jan 23, 2019

Hi 👋 Thanks for the great library! Think I may have found a bug but if not I would love to know what I'm doing wrong here.

  • react-testing-library version: 5.4.4
  • react version: 16.7.0
  • node version: 10.15.0
  • npm (or yarn) version: yarn 1.10.1

Relevant code or config:

test('disabled checkbox cannot be selected', () => {
  const onChange = jest.fn()
  const {getByTestId} = render(
    <input
      type="checkbox"
      onChange={onChange}
      data-testid="checkbox"
      disabled
    />,
  )
  const checkbox = getByTestId('checkbox')
  fireEvent.click(checkbox)
  expect(onChange).not.toHaveBeenCalled() // this fails
})

What you did:

I wrote a test to check that a checkbox onChange function would not be called if the checkbox is disabled.

What happened:

The test failed saying that the onChange function had been called.

Reproduction:

https://codesandbox.io/s/91684p354

Problem description:

The test should pass. onChange functions should not be called in the test if the checkbox is disabled.

Suggested solution:

Not sure, haven't looked into the code yet.

@kentcdodds
Copy link
Member

Confirmed! This is pretty odd! I'm not sure what it could be. Anyone wanna dig into this one? Pretty good learning opportunity here :)

@kentcdodds kentcdodds added bug Something isn't working help wanted Extra attention is needed needs investigation labels Jan 23, 2019
@puemos
Copy link

puemos commented Jan 24, 2019

short answer

You should use onClick listener for checkbox inputs

example

https://codesandbox.io/s/j22y6pl21y

source

https://github.com/facebook/react/blob/c954efa70f44a44be9c33c60c57f87bea6f40a10/packages/react-dom/src/events/ChangeEventPlugin.js#L211

/**
 * SECTION: handle `click` event
 */
function shouldUseClickEvent(elem) {
  // Use the `click` event to detect changes to checkbox and radio inputs.
  // This approach works across all browsers, whereas `change` does not fire
  // until `blur` in IE8.
  var nodeName = elem.nodeName;
  return nodeName && nodeName.toLowerCase() === 'input' && (elem.type === 'checkbox' || elem.type === 'radio');
}

@kentcdodds
Copy link
Member

Thanks @puemos, but the fact is that the tests function differently from when you do this in an app. You definitely should be able to use onChange like this and fire a click event. Something else is up 🤔

@lourenci
Copy link

lourenci commented Jan 26, 2019

I don't know if this is a problem, but either for disabled or disabled={true}, render.debug() shows me <input disabled="" />. If I create a component in React in this way, the input is not disabled, so you can click on it.

Could it be a problem with the jsdom?

@Tolsee
Copy link

Tolsee commented Feb 10, 2019

@kentcdodds My feeling is there is a problem with firEvent of dom-testing-library somehow. Did not get the problem when a click is triggered in an element is self. Can you suggest me the best way to figure out the problem?

https://codesandbox.io/embed/yp996rqlyj

@kentcdodds
Copy link
Member

This is interesting. I created a vanilla JavaScript version of this:

https://codesandbox.io/s/q70qj31m46

const app = document.getElementById('app')

app.innerHTML = ''

const input = document.createElement('input')
input.setAttribute('type', 'checkbox')
input.setAttribute('disabled', 'true')
let clickCalled = false
input.addEventListener('click', () => {
  clickCalled = true
})
let changeCalled = false
input.addEventListener('change', () => {
  changeCalled = true
})
let inputCalled = false
input.addEventListener('input', () => {
  inputCalled = true
})

app.appendChild(input)

// this is basically what fireEvent.click does
const event = new MouseEvent('click', {
  bubbles: true,
  cancelable: true,
  button: 0,
})
input.dispatchEvent(event)

The result is that the input and change handlers are called, but the click handler is not called. And the input actually gets checked despite being disabled.

I'm guessing this has something to do with a strange specification or something. If someone wants to investigate this further then I'd welcome help there...

I'm afraid I don't have time to dedicate to this problem at the moment.

@ken-kenware
Copy link

It looks like this may be a case where the spec was followed a little too closely. According to the Spec:

A form control that is disabled must prevent any click events that are queued on the user interaction task source from being dispatched on the element.

In the vanilla JS example, we can see that it is in fact not dispatching the click event, but dispatching the remaining events.

My hypothesis is that browsers are doing some special handling for dispatching click events as opposed to other events in order to control ad clicking (I may be totally off basis here though I'm no expert). This is based entirely off of this method:
https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/core/dom/events/event_dispatcher.cc#L77

Which has this line that immediately returns if it's a form element that is disabled:
https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/core/dom/events/event_dispatcher.cc#L90

So yeah...my guess is that the right way to handle this is to make sure we are more closely simulating what the browser appears to be doing, and swallowing the click event if the element is disabled.

@kentcdodds
Copy link
Member

But that would be a change for react right? Not react-testing-library

@ken-kenware
Copy link

I don't think so. Since React is just proxying the event, they don't do anything around calling dispatchEvent to my knowledge.

My guess is that we need to do something here: https://github.com/kentcdodds/dom-testing-library/blob/master/src/events.js#L310

To account for the click event on ignored elements, and just swallow the event. This will mimic what the browser is doing under the hood (and I'm guessing the feature we would get if we could actually click a button rather than just dispatching the click event).

Thoughts?

@kentcdodds
Copy link
Member

I think it will make more sense when I see a pull request. Anyone willing to open that up?

@ken-kenware
Copy link

ken-kenware commented Feb 19, 2019 via email

@ken-kenware
Copy link

@kentcdodds
Copy link
Member

This is handled by user-event.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed jsdom Issue with JSDOM environment needs investigation
Projects
None yet
Development

No branches or pull requests

7 participants