Dom-testing-library: fireEvent.click() performs click on disabled input field

Created on 25 Aug 2018  ยท  19Comments  ยท  Source: testing-library/dom-testing-library

  • dom-testing-library version: 3.4.0
  • react version: _-NA-_
  • node version: 9.11.1
  • npm (or yarn) version: _-NA-_

Relevant code or config:

fireEvent.click() on disabled elements.

What you did:

I was expecting fireEvent.click() not to have dispatched click event on disabled elements.

What happened:

fireEvent is ignoring the disabled attribute on the element.

Reproduction:

The following test case events.js is failing.

describe('Disabled element', () => {
  it(`should not fire any event on disabled element`, () => {
    const node = document.createElement('input')
    const disabledAttr = document.createAttribute('disabled')
    node.setAttributeNode(disabledAttr);
    const spy = jest.fn()
    node.addEventListener('click', spy)
    fireEvent.click(node)
    expect(spy).toHaveBeenCalledTimes(0)
  })
});

Problem description:

Currently, the fireEvent is ignoring the disabled attribute in elements.

Suggested solution:

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.

โ€” The HTML Spec

fireEvent can check whether the element has disabled attribute and skip dispatching the event.

However I'm not sure whether to ignore all MouseEvents (like Chrome/Safari) or just ignore click events.

jsdom

Most helpful comment

I have encountered a form of this and almost thought it was a bug in jsdom, but it was here all along ๐Ÿ˜‡

The trick here is that _real_ user interaction clicks are required to always go into the user interaction task source first, and then it gets completely discarded if it turns out the target is a disabled form element; there's not even a capturing phase. Direct calls to .click() also respect the disabled property. However, .dispatchEvent() doesn't check it -- it never is supposed to be called to begin with!

The problem, though, is that there's no "user interaction task source" in a headless library like JSDOM. Perhaps it's something that can be done at the fireEvent / createEvent level by abusing the isTrusted flag.

JSDOM itself ignores it, but an option is to have createEvent (optionally?) set isTrusted to true ({ isTrusted: true } as the 2nd argument of new MouseEvent, but that's an implementation detail), and then fireEvent could check the target of isTrusted events for whether they are disabled form elements and drop the dispatchEvent if event.isTrusted && node.disabled. Or maybe testing-library's own symbol/weakmap to keep track of which event instances to treat as interactions and which events to treat as plain dispatches.

All 19 comments

Interesting. I wonder what the browser does and if this may be a bug in jsdom ๐Ÿค”

Can you try to run your code in the browser and see if dispatching a click event on a disabled button in the browser will call the click handler or not?

a quick scan in jsdom code, shows that they are handling this scenario here.

Now I'm puzzled why fireEvent went through on a disabled input in my case.

In browser (chrome, safari and ff) the click is not calling the handler.

Here is the code that I was trying. After spending some time now, I'm unclear about where the problem is.

Huh, weird. In addition, in codesandbox it's running in the browser rather than using jsdom... Hmm...

If anyone can come up with what's going on here and a solution to the problem then please write here and we can re-open, but I'm going to go ahead and close this for now.

I got the same problem.

const wrapper = render(<DumbComponent />);
const submitButtonElm = wrapper.getByTestId('submit-btn');
const handleClick = jest.fn();

console.log(submitButtonElm.disabled); // true
submitButtonElm.addEventListener('click', handleClick, false);
fireEvent.click(submitButtonElm);

expect(handleClick).not.toHaveBeenCalled();

I tried to run my code in the browser, the event handler is not called.

Can you please make a codesandbox of that?

@kentcdodds Thanks for your quickly response.
Here is the code I don't know why it works well on codesandbox. I have the same code as on the codesandbox, but it's failed (the same [email protected] version). So weird.

I have just pushed the code on the repo. Please take a few minutes to check it out. The same code on the Codesanbox, but it's failed.

@ngtan Updated your codesandbox https://codesandbox.io/s/naughty-smoke-ckqh6?fontsize=14

Removed the index.js and just used a pure test.js so it doesn't try to locate and render to a DOM element that doesn't exist.

The test shows the problem exists and disabled inputs can and will receive a click event!

I have encountered a form of this and almost thought it was a bug in jsdom, but it was here all along ๐Ÿ˜‡

The trick here is that _real_ user interaction clicks are required to always go into the user interaction task source first, and then it gets completely discarded if it turns out the target is a disabled form element; there's not even a capturing phase. Direct calls to .click() also respect the disabled property. However, .dispatchEvent() doesn't check it -- it never is supposed to be called to begin with!

The problem, though, is that there's no "user interaction task source" in a headless library like JSDOM. Perhaps it's something that can be done at the fireEvent / createEvent level by abusing the isTrusted flag.

JSDOM itself ignores it, but an option is to have createEvent (optionally?) set isTrusted to true ({ isTrusted: true } as the 2nd argument of new MouseEvent, but that's an implementation detail), and then fireEvent could check the target of isTrusted events for whether they are disabled form elements and drop the dispatchEvent if event.isTrusted && node.disabled. Or maybe testing-library's own symbol/weakmap to keep track of which event instances to treat as interactions and which events to treat as plain dispatches.

Thanks for your investigation @Jessidhia!

I'm hesitant to do much more in fireEvent than it's already doing, but this does violate the principle of least surprised (people are more surprised that _does_ call the click handler on a disabled input than if it were to _not_ call the handler). So I may be interested in a solution here.

Another option is to just encode this logic in user-event and recommend people use that for clicks ๐Ÿค”

Another option is to just encode this logic in user-event and recommend people use that for clicks ๐Ÿค”

Leaning towards that because of all the other focus handling and stuff that's enabled there

I'd like to hear more about why people want to write a text that clicks on a disabled input. What are you trying to test in this case? Is it not enough to simply assert that the input is disabled and trust the browser to do its job with that? I kinda think that trying to click it and asserting that the handler isn't called is basically testing the browser, not your code ๐Ÿค”

I suppose the disabled state could be considered an implementation detail - for example you could just remap the click handler to a no-op function, and unless there are significant accessibility issues with that, it's a valid approach.

unless there are significant accessibility issues with that

I believe there are. And if you remap the click handler to a no-op function then you actually _do_ want the click event to call it so you can test that :)

element.click() will not execute the click handler if the element is disabled in a jsdom environment. dispatchEvent will ignore disabled state. The former should be a sufficient workaround right now. The latter is filed against jsdom in https://github.com/jsdom/jsdom/issues/2665.

I'll check if this works if you use testing-library in a real browser. It should work as expected though. We can probably close this issue and fix it upstream.

Confirmed that a browser ignores click() as well as fireEvent.click on disabled elements: https://codesandbox.io/s/testing-library-disabled-input-ignores-click-owohf (Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36)

This must be handled by the environment. If you want to follow progress on this bug subscribe to https://github.com/jsdom/jsdom/issues/2665.

Was this page helpful?
0 / 5 - 0 ratings