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

3.4.3 breaks tests with "ShallowWrapper::setState() can only be called on root" #1765

Closed
monken opened this issue Aug 17, 2018 · 6 comments
Closed

Comments

@monken
Copy link

monken commented Aug 17, 2018

Describe the bug
Not sure why, but the 3.4.3 release broke my tests. I receive Error: ShallowWrapper::setState() can only be called on the root errors all over the place. Seems to be related to #1756.

The relevant test is attached. I was able to figure out that componentDidMount is called twice with the 3.4.3 release, which was not the case before.

To Reproduce

  it('<MyComponent />', async () => {
    fetch.mockResponse(JSON.stringify(jsonData));
    const wrapper = shallow(<MyComponent />, { disableLifecycleMethods: true });
    await wrapper.instance().componentDidMount();
    wrapper.update();
    expect(fetch.mock.calls.length).toEqual(1);
    expect(wrapper.find(Table).length).toEqual(1);
  });

The second call to componentDidMount has the following stack trace:

  console.error console.js:253
        at MyComponent.componentDidMount (/src/components/MyComponent.jsx:42:13)
        at /node_modules/enzyme/build/ShallowWrapper.js:204:20
        at Object.batchedUpdates (/node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:342:22)
        at new ShallowWrapper (/node_modules/enzyme/build/ShallowWrapper.js:203:24)
        at ShallowWrapper.wrap (/node_modules/enzyme/build/ShallowWrapper.js:1763:16)
        at ShallowWrapper.find (/node_modules/enzyme/build/ShallowWrapper.js:815:21)
        at ShallowWrapper.exists (/node_modules/enzyme/build/ShallowWrapper.js:1711:52)
        at Object._callee$ (/src/components/tests/MyComponent.spec.jsx:38:20)
        at tryCatch (/node_modules/regenerator-runtime/runtime.js:62:40)
        at Generator.invoke [as _invoke] (/node_modules/regenerator-runtime/runtime.js:296:22)
@koba04
Copy link
Contributor

koba04 commented Aug 17, 2018

@monken Could you create a minimal test case? I can't try your code.

The following test is passed with v3.4.3, which is an expected behavior.

      it('should call `componentDidUpdate` when component’s `setState` is called2', () => {
        class Foo extends React.Component {
          constructor(props) {
            super(props);
            this.state = {
              foo: 'init',
            };
          }

          componentDidMount() {
            this.setState({ foo: 'update' });
          }

          render() {
            return <div>{this.state.foo}</div>;
          }
        }
        const spy = sinon.spy(Foo.prototype, 'componentDidMount');

        const wrapper = shallow(<Foo />, { disableLifecycleMethods: true });
        expect(spy).to.have.property('callCount', 0);
        wrapper.instance().componentDidMount();
        wrapper.update();
        expect(spy).to.have.property('callCount', 1);
      });

@ljharb
Copy link
Member

ljharb commented Aug 17, 2018

@monken can you share the code for MyComponent?

awaiting the result of componentDidMount seems strange, since react itself won't await it and it's not intended to be an async function.

@monken
Copy link
Author

monken commented Aug 17, 2018

import React, { Fragment, Component } from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';

class Table extends Component {
  render() {
    return (<table />);
  }
}

class MyComponent extends Component {

  state = {
    showTable: false,
  }

  componentDidMount() {
    this.setState({ showTable: true });
  }

  render() {
    const { showTable } = this.state;
    return (<Fragment>{showTable ? <Table /> : null}</Fragment>);
  }
}


describe('<MyContainer />', () => {
  it('should call `componentDidUpdate` when component’s `setState` is called', () => {
    const wrapper = shallow(<MyComponent />, { disableLifecycleMethods: true });
    expect(wrapper.find(Table).length).toEqual(0);
    wrapper.instance().componentDidMount();
    wrapper.update();
    expect(wrapper.find(Table).length).toEqual(1);
  });
});

Output:

 FAIL  src/components/tests/foo.spec.jsx
  <MyContainer />
    ✕ should call `componentDidUpdate` when component’s `setState` is called (11ms)

  ● <MyContainer /> › should call `componentDidUpdate` when component’s `setState` is called

    ShallowWrapper::setState() can only be called on the root

@koba04
Copy link
Contributor

koba04 commented Aug 17, 2018

This will be fixed by #1768

koba04 added a commit to koba04/enzyme that referenced this issue Aug 17, 2018
@xitter
Copy link

xitter commented Aug 17, 2018

When is this going to npm?

@monken
Copy link
Author

monken commented Aug 17, 2018

@koba04 thanks for the quick turn-around on this!

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

No branches or pull requests

4 participants