Skip to content
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

simulate('click') on child component does not propagate to trigger blur on parent #1551

Closed
jamesopti opened this issue Mar 2, 2018 · 4 comments

Comments

@jamesopti
Copy link

jamesopti commented Mar 2, 2018

API

  • [ X ] shallow

Version

library version
Enzyme 3.0
React 16.0

Adapter

  • [ X ] enzyme-adapter-react-16

I have a simple dropdown component that I'm having trouble regression testing.
In Dropdown.js below, a bug was introduced which broke children's click handlers.

The onBlur event on the parent fires before the click event in the child, despite being triggered by the child click. This resulted in the child being destroyed and its clicks not being registered.
(the bug was removing the onMouseOver/onMouseLeave handlers, which set state that onBlur uses)

Enzyme tests didn't catch this because they triggered a click directly in one
of the children, circumventing the event propagation of the blur.

Is there a better way to test this using enzyme?

Dropdown.js

class Dropdown extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isOpen: false,
      overChildren: false,
    };
  }

  handleMouseOverChildren = () => {
    this.setState({
      overChildren: true,
    });
  }

  handleMouseLeavingChildren = () => {
    this.setState({
      overChildren: false,
    });
  }

  handleOnBlur = () => {
    // Only handle blur if we aren't hovered over the children,
    // which fires when the children themselves are clicked.
    if (!this.state.overChildren) {
      this.setState({
        isOpen: false,
      });
    }
  }

  handleToggle = (event) => {
    this.setState({
      isOpen: !this.state.isOpen,
      overChildren: false,
    });
  }

  render() {
    const {
      buttonContent,
      children
    } = this.props;

    return (
      <div>
        <button
          type='button'
          onClick={ this.handleToggle }
          onBlur={ this.handleOnBlur }>
          <div className='flex'>
            <div className='flex--1 truncate'>{ buttonContent }</div>
          </div>
        </button>
        <div
          onMouseOver={ this.handleMouseOverChildren }
          onMouseLeave={ this.handleMouseLeavingChildren }
          onClick={ this.handleToggle } >
          { this.state.isOpen && children }
        </div>
      </div>
    );
  }
}

test.js

/**
 * This test passes with or without the onMouseOver/onMouseLeave handlers.
 * In a real browser, onClickMock is not called without those handlers.
 */
const data = [
  {title: 'Option 1', description: 'a cool option'}
];
it('should invoke a childs onClick handler when the child is clicked', () => {
  const onClickMock = jest.fn();
  const component = shallow(
    <Dropdown
      buttonContent='Dropdown'>
      <ul>
        { data.map((item, index) => {
          return (
            <li
              key={ index }
              onClick={ onClickMock }
              data-test-section={ `dropdown-item-${index}` }>
              { item.title }
            </li>
          );
        })
      }
    </ul>
  </Dropdown>
);

   component.find('button').simulate('click');
   component.find('[data-test-section="dropdown-item-1"]').simulate('click');
   expect(onClickMock.mock.calls.length).toBe(1);
 });

Updated with minimally reproducible actual code

@ljharb
Copy link
Member

ljharb commented Mar 2, 2018

Because you've provided pseudocode, I can't be sure - can you provide the real code?

@jamesopti
Copy link
Author

@ljharb sure thing! Just updated with minimally reproducible example.

@jamesopti
Copy link
Author

@ljharb Any thoughts after seeing the real code?

@ljharb
Copy link
Member

ljharb commented Mar 7, 2018

@jamesopti yes. one problem is that you're using arrow functions in class properties, which you should never do for many reasons. Specifically here, you can't mock them out in tests (altho this might be a red herring).

Separately, I'd suggest avoiding simulate entirely. If you want to invoke the onClick prop, use .prop('onClick')().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants