Enzyme: simulate('click') on child component does not propagate to trigger blur on parent

Created on 2 Mar 2018  路  4Comments  路  Source: enzymejs/enzyme

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

Most helpful comment

@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')().

All 4 comments

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

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

@ljharb Any thoughts after seeing the real code?

@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')().

Was this page helpful?
0 / 5 - 0 ratings

Related issues

blainekasten picture blainekasten  路  3Comments

abe903 picture abe903  路  3Comments

aweary picture aweary  路  3Comments

timhonders picture timhonders  路  3Comments

ivanbtrujillo picture ivanbtrujillo  路  3Comments