After upgrading to enzyme@3 I'm having an issue using decorators. I've created a tiny sample repo to reproduce the issue here: https://github.com/EvHaus/enzyme-decorator
The problem is when you try to mount() a component like this:
class MyComponent extends PureComponent {
static displayName = 'MyComponent';
constructor (props) {
super(props);
this._handleClick = this._handleClick.bind(this);
}
render () {
return <div onClick={this._handleClick} />;
}
// NOTE: Commenting out the next line fixes the issue
@keydown('enter')
_handleClick () {
console.log('do something')
}
}
You get a failure like this:
Expected the function not to throw an error.
Instead, it threw:
Invariant Violation: getNodeFromInstance: Invalid argument.
at invariant (node_modules/fbjs/lib/invariant.js:42:15)
at Object.getNodeFromInstance (node_modules/react-dom/lib/ReactDOMComponentTree.js:160:77)
at Object.findDOMNode (node_modules/react-dom/lib/findDOMNode.js:47:41)
at Object.bindFocusables (node_modules/react-keydown/dist/lib/dom_helpers.js:34:35)
at onMount (node_modules/react-keydown/dist/event_handlers.js:104:25)
at MyComponent.target.componentDidMount (node_modules/react-keydown/dist/decorators/method_decorator.js:59:35)
at node_modules/enzyme/build/ShallowWrapper.js:123:20
at ReactDefaultBatchingStrategyTransaction.perform (node_modules/react-test-renderer/lib/shallow/Transaction.js:141:20)
at Object.batchedUpdates (node_modules/react-test-renderer/lib/shallow/ReactDefaultBatchingStrategy.js:60:26)
at Object.batchedUpdates (node_modules/react-test-renderer/lib/shallow/ReactUpdates.js:95:27)
at ReactShallowRenderer.unstable_batchedUpdates (node_modules/react-test-renderer/lib/shallow/ReactShallowRenderer.js:128:25)
at node_modules/enzyme-adapter-react-15/build/ReactFifteenAdapter.js:289:33
at withSetStateAllowed (node_modules/enzyme-adapter-utils/build/Utils.js:95:16)
at Object.batchedUpdates (node_modules/enzyme-adapter-react-15/build/ReactFifteenAdapter.js:288:66)
at new ShallowWrapper (node_modules/enzyme/build/ShallowWrapper.js:122:24)
at shallow (node_modules/enzyme/build/shallow.js:19:10)
at expect (__tests__/index.js:32:24)
at Object.<anonymous> (node_modules/expect/build/to_throw_matchers.js:43:5)
at Object.throwingMatcher [as toThrow] (node_modules/expect/build/index.js:199:24)
at Object.it (__tests__/index.js:33:10)
at Promise (<anonymous>)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
at Object.it (__tests__/index.js:33:10)
at Promise (<anonymous>)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
This works fine when using enzyme@2.
@EvHaus thanks for providing a full repro! From the stacktrace, it's not really clear to me what the issue is. Will take a look.
What does keydown look like?
@EvHaus the problem here is that you're using the shallow renderer with a component that is calling ReactDOM.findDOMNode, which doesn't work with the shallow renderer. The react-keydown package monkey patches the component's componentDidMount method to initialize the listener, which eventually calls findDOMNode.
You didn't see this issue in Enzyme 2 because by default the shallow renderer doesn't call componentDidMount. If you used the lifecycleExperimental option, you would see a failure in version 2 as well.
shallow(<MyComponent />, { lifecycleExperimental: true });
This is expected behavior. If you're testing something that uses a DOM-specific API like findDOMNode you should use mount, which I verified works with the example you provided.
I hope that clears things up!
Note that this change is outlined here and that you can fix your test by using:
const wrapper = shallow(<Component />, { disableLifecycleMethods: true });
Thanks for the investigation @aweary!
const wrapper = shallow(<Component />, { disableLifecycleMethods: true }); did the trick.
Thank you all for the awesome work on Enzyme and congrats on the 3.0 release. It looks amazing!
Hi, I still have an issue with that. I mount component, and want to testing something that uses a DOM-specific API like findDOMNode , but id does not work for me.
beforeEach(() => {
alarmsListComponent = mount(
<Alarms
init={init = sinon.spy()}
dispose={dispose = sinon.spy()}
invalidateAlarms={invalidateAlarms = sinon.spy()}
alarms={alarms}
hasNextPage={true}
getNextPage={getNextPage = sinon.spy()}
/>, mountContext)
})
describe('componentDidMount', () => {
const clientHeight = 800
beforeEach(() => sinon.stub(ReactDOM, 'findDOMNode').returns({parentNode: { clientHeight: clientHeight }}))
afterEach(() => ReactDOM.findDOMNode.restore())
it.only('should set containerHeight', () => {
alarmsListComponent.find(AlarmsList).props().containerHeight.should.be.equal(clientHeight)
})
The component
```
export class Alarms extends Component {
static propTypes = {
.....
}
state = {containerHeight: 400}
componentWillMount = () => this.props.init()
componentDidMount = () => {
console.log('componentDidMount')
this.resize()
window.addEventListener('resize', this.resize)
}
componentWillUnmount = () => {
this.props.dispose()
window.removeEventListener('resize', this.resize)
this.props.invalidateAlarms()
}
resize = () => this.setState({containerHeight: ReactDOM.findDOMNode(this).parentNode.clientHeight})
render = () => {
return (
<AlarmsList
containerHeight={this.state.containerHeight}
.......
/>)
}
}
error:
AssertionError: expected 0 to equal 800
After I tried like unmount/mount, the test was passsed, but why?
alarmsListComponent.unmount()
alarmsListComponent.mount()