enzyme@3 doesn't work with decorators

Created on 26 Sep 2017  路  7Comments  路  Source: enzymejs/enzyme

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.

v3 bugs

All 7 comments

@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()
Was this page helpful?
0 / 5 - 0 ratings

Related issues

AdamYahid picture AdamYahid  路  3Comments

ahuth picture ahuth  路  3Comments

aweary picture aweary  路  3Comments

blainekasten picture blainekasten  路  3Comments

timhonders picture timhonders  路  3Comments