| library | version
| ---------------- | -------
| Enzyme | 3.0
| React | 16.0
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
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')().
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
simulateentirely. If you want to invoke the onClick prop, use.prop('onClick')().