enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.95k stars 2.01k forks source link

Simulating click does not honor the disabled attribute #386

Open djskinner opened 8 years ago

djskinner commented 8 years ago

There seems to be a difference in behaviour between shallow/mount and react-addons-test-utils when it comes to simulating a click on an element with the disabled attribute. React and react-addons-test-utils does not call the onClick handler when the disabled attribute is present but shallow/mount does.

Component under test:

export const DisabledButton = React.createClass({
  getInitialState (){
    return {
      clicks: 0
    }
  },
  handleClick () {
    console.log(`the click was registered`)
    this.setState({
      clicks: ++this.state.clicks
    })
  },
  render () {
    return (
      <section>
        <p>Clicks: {this.state.clicks}</p>
        <button onClick={this.handleClick} disabled={true}>Click Me!</button>
      </section>
    )
  }
})

All tests pass using react-addons-test-utils:

import TestUtils from 'react-addons-test-utils'
import ReactDOM from 'react-dom'

describe('<DisabledButton /> TestUtils', function () {

  it('should start with zero clicks', () => {
    const component = TestUtils.renderIntoDocument(
      <DisabledButton />
    )
    const p = ReactDOM.findDOMNode(TestUtils.findRenderedDOMComponentWithTag(component, 'p'))
    expect(p.textContent).toContain('0')
  })

  it('should not do anything when clicked because the button is disabled', () => {
    const component = TestUtils.renderIntoDocument(
      <DisabledButton />
    )
    TestUtils.Simulate.click(TestUtils.findRenderedDOMComponentWithTag(component, 'button'))

    const p = ReactDOM.findDOMNode(TestUtils.findRenderedDOMComponentWithTag(component, 'p'))
    expect(p.textContent).toContain('0')
  })
})

Using shallow the second test will fail:

import { shallow } from 'enzyme'

describe('<DisabledButton /> shallow', function () {

  it('should start with zero clicks', () => {
    const wrapper = shallow(
      <DisabledButton />
    )
    expect(wrapper.find('p').text()).toContain('0')
  })

  it('should not do anything when clicked because the button is disabled', () => {
    const wrapper = shallow(
      <DisabledButton />
    )
    wrapper.find('button').simulate('click')
    expect(wrapper.find('p').text()).toContain('0')
  })
})

Using mount the second test will fail:

import { mount } from 'enzyme'

describe('<DisabledButton /> mount', function () {

  it('should start with zero clicks', () => {
    const wrapper = mount(
      <DisabledButton />
    )
    expect(wrapper.find('p').text()).toContain('0')
  })

  it('should not do anything when clicked because the button is disabled', () => {
    const wrapper = shallow(
      <DisabledButton />
    )
    wrapper.find('button').simulate('click')
    expect(wrapper.find('p').text()).toContain('0')
  })
})
aweary commented 8 years ago

@djskinner your second test uses shallow in the failing test case when it should use mount. Can you verify it actually fails with mount? I wouldn't expect it to, as simulate is a thin wrapper around ReactTestUtils.Simulate. shallow on the other hand doesn't have any guard against this AFAIK right now.

djskinner commented 8 years ago

My bad. mount does indeed work as expected.

aweary commented 8 years ago

Thank for verifying!

M1r1k commented 8 years ago

I think it is again active :) https://github.com/facebook/react/blob/master/CHANGELOG.md#react-test-utils https://github.com/facebook/react/issues/8305

MartinEden commented 7 years ago

I can confirm this.

AlexChipev commented 6 years ago

Any chance this is reproducible in v16.2.0.? When I simulate click on disabled button I`m expecting (fn.mock.calls.length).toBe(0); but with shallow I get 2, with mount - 1. Seems it still calls the function.

zachfejes commented 6 years ago

I'm noticing this bug with react 15.5.4 + enzyme 3.1.0, specifically with a shallow component. For reference, the component has 3 buttons in it, I'm finding them with wrapper.find('button') with has a length 3, and then attempting to simulate a click with:

   const fooMock = jest.fn();
   const wrapper = shallow(<MyComponent foo={fooMock} />); // the button, when clicked, should call foo();

   console.log(wrapper.find('button').at(0).getProps().disabled); // outputs 'disabled', as expected

   wrapper.find('button').at(0).simulate('click');

   expect(fooMock).toHaveBeenCalledTimes(0);

And the test fails, because fooMock was called once - despite the button being disabled. Is there any way around this?

ljharb commented 6 years ago

@zachfejes react 15 only goes up to 15.6; there's no 15.9?

simulate should be avoided; it doesn't faithfully simulate anything. If you want to invoke the onClick function, extract it from props and invoke it.

zachfejes commented 6 years ago

@ljharb thanks for the response - I've corrected the version number in my comment to clarify, react 15.5.4.

Bartuz commented 6 years ago

if someone decided to copy-paste code from @zachfejes here is working getProps() line:

console.log(wrapper.find('button').at(0).props().disabled); // outputs 'disabled', as expected

getProps() -> props()

dao-heart commented 5 years ago

Hi, I am facing the same issue as @zachfejes.

const onSubmitSpy = sinon.spy();

const mountedWrapper = mount(
    <Provider store={store}>
      <reduxForm {...props} handleSubmit={onSubmitSpy} />
    </Provider>
  );

mountedWrapper.find("button").prop("onClick")();

expect(mountedWrapper.find("button").prop("disabled")).toBe(true); //true

 expect(onSubmitSpy.called).toBe(true); // true
goodwin64 commented 5 years ago

Hi! Any updates on this issue?

mattvalli commented 5 years ago

Looks like the issue still exists for version 3.10.0. Is there a plan to address this issue in the near future?

ljharb commented 5 years ago

@mattvalli the plan is in v4, to remove simulate entirely. It's a bad API, and it doesn't faithfully simulate anything.

Nobody should be using simulate in their tests.

omril1 commented 4 years ago

@ljharb So how do you simulate a click event (or any event) in v4?

ljharb commented 4 years ago

You don't simulate a click event - that'd be testing react itself, or the browser, and that's not your purview.

You assert that the right thing ended up in onClick, and you invoke the onClick prop manually, and assert that the right stuff happened.

butterywombat commented 3 years ago

You assert that the right thing ended up in onClick

@ljharb can you show an example of this? I can do the latter part, but not sure how to assert that if the onClick fn is just a local fn in my functional component. I guess I'd have to expose overriding it as a prop

ljharb commented 3 years ago

@butterywombat in your example, what does that local function do? You can extract it with .prop(), and then invoke it, and then test that it did the thing you expect.

danielo515 commented 3 years ago

So what's the issue about nor? The disabled not being honoured on shallow, on mount or on both? It works on mount for me but not for shallow, not sure if that still is the desired case.

ljharb commented 3 years ago

If there's a difference between shallow and mount, we should fix shallow to match mount whenever possible.

If someone can provide a PR, or a link to a branch/commit, that includes a test case that passes for mount and fails for shallow, that would make it much easier to work on a fix.