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

enzyme@3: wrapper is not updated even if there was a new render #1153

Closed
deepsweet opened this issue Sep 26, 2017 · 48 comments
Closed

enzyme@3: wrapper is not updated even if there was a new render #1153

deepsweet opened this issue Sep 26, 2017 · 48 comments

Comments

@deepsweet
Copy link
Contributor

deepsweet commented Sep 26, 2017

Hi. Just faced this after updating to v3.

Given the following deps:

And the HOC:

import React, { Component } from 'react'

const testHOC = (Target) => class TestClass extends Component {
  constructor (...args) {
    super(...args)

    this.state = {}
    this.onTest = this.onTest.bind(this)
  }

  onTest () {
    this.setState({
      foo: true
    })
  }

  render () {
    console.log(this.state)

    return (
      <Target {...this.props} {...this.state} onTest={this.onTest} />
    )
  }
}

export default testHOC

And the test:

import React from 'react'
import { mount } from 'enzyme'
import testHOC from './testHOC'

describe('testHOC', () => {
  it('should work', () => {
    const Target = () => null
    const EnhancedTarget = testHOC(Target)
    const wrapper = mount(
      <EnhancedTarget />
    )

    wrapper.find(Target).prop('onTest')()

    console.log(
      wrapper.debug()
    )
  })
})

I see:

  console.log testHOC.js:18
    { foo: true }

  console.log testHOC.spec.js:15
    <TestClass>
      <Target onTest={[Function]} />
    </TestClass>

So the was a new render, but wrapper just didn't react to it. Works fine with v2.

Please correct me if I was wrong all this time and test should be written in another way.

@ljharb
Copy link
Member

ljharb commented Sep 26, 2017

Wrappers are immutable in enzyme 3; this is in the migration docs.

Try wrapper.update() after invoking your function.

@deepsweet
Copy link
Contributor Author

Already tried and now re-tried, same result.

@j4k
Copy link

j4k commented Sep 26, 2017

I'm also experiencing the same behaviour with the same deps on some of my component tests (non-HOC) where the state updates, but the wrapper does not, even after calling .update().

Placing console.log's in the component under tests render method show's things rendering correctly during the test, I just can't seem to assert properly after an .update(), .debug() outputs the initial component. .state() shows the correct state after an .update()

@yesmeck
Copy link
Contributor

yesmeck commented Sep 27, 2017

I'm also experiencing the similar behavior when using conditional rendering, here is a reproduction repo https://github.com/yesmeck/enzyme-set-props-bug.

@yesmeck
Copy link
Contributor

yesmeck commented Sep 27, 2017

I created a separate issue #1163.

@mx-scissortail
Copy link

Same issue. I can confirm that render is running appropriately, and I can even get the updated values from wrapper.state, but wrapper.update doesn't reflect the re-rendered component.

@iantanwx
Copy link

Same issue here.

@gziolo
Copy link

gziolo commented Sep 29, 2017

I did some heave debugging for WordPress/gutenberg#2813 and it looks like we are having the same kind of issues that are reported in this thread and here.

I tried to add recommended wrapper.update(); call after the click event is simulated, but it didn't help. It's quite interesting scenario because the part of the component that depends on the component's state (https://github.com/WordPress/gutenberg/blob/ad316ab1f8cfe60aeeb45ff27d45336bfb214c3c/editor/inserter/menu.js#L384) gets properly updated, but the other part (https://github.com/WordPress/gutenberg/blob/ad316ab1f8cfe60aeeb45ff27d45336bfb214c3c/editor/inserter/menu.js#L366) which also is conditionally rendered based on the components' state doesn't show up in the output of wrapper.debug() call. When I debugged it turned out that React component does everything properly behind the scenes. It looks like Enzyme gets out of sync.

@tleunen
Copy link

tleunen commented Sep 29, 2017

Confirmed what @gziolo said. The component is updated properly. .html() returns the right structure. But the ReactWrapper itself is out of sync.

@lelandrichardson
Copy link
Collaborator

This is hopefully fixed in [email protected]. Please upgrade and let us know if it does not fix your issue. Thanks!

@mrbinky3000
Copy link

This seems to still be an issue in [email protected] wrapper.update() does not work. Yet console logs in the source code show that state is being updated. .html() shows the correct structure. I can point you to a repo with the failing test if that helps.

@ljharb
Copy link
Member

ljharb commented Nov 28, 2017

@mrbinky3000 .html() isn't as useful as .debug(), what does that show?

@mrbinky3000
Copy link

mrbinky3000 commented Nov 28, 2017

ImageWithZoom has a state property named "hovering". It starts off false as expected.

  console.log src/ImageWithZoom/ImageWithZoom.jsx:89
    ImageWithZoom.render() state { isImageLoading: false,
      hovering: false,
      style: {},
      x: null,
      y: null }

This is a debug() dump of the wrapper before the mouseover event.

  console.log src/ImageWithZoom/__tests__/ImageWithZoom.test.jsx:38
    before mouseover debug:
     <ImageWithZoom src="bob.jpg" tag="div">
      <div className="container">
        <Wrapper className="image carousel__zoom-image" tag="div" src="bob.jpg" isBgImage={true} onLoad={[Function]} onError={[Function]}>
          <Image hasMasterSpinner={false} orientation="horizontal" className="image carousel__zoom-image" tag="div" src="bob.jpg" isBgImage={true} onLoad={[Function]} onError={[Function]} carouselStore={{...}} alt="" height={{...}} renderError={{...}} renderLoading={{...}} style={{...}}>
            <div className="image carousel__image carousel__image--with-background carousel__image--success image carousel__zoom-image" style={{...}} orientation="horizontal" height={{...}} />
          </Image>
        </Wrapper>
        <Wrapper className="overlay carousel__zoom-image-overlay" tag="div" src="bob.jpg" style={{...}} isBgImage={true} onMouseOver={[Function]} onMouseOut={[Function]} onMouseMove={[Function]}>
          <Image hasMasterSpinner={false} orientation="horizontal" className="overlay carousel__zoom-image-overlay" tag="div" src="bob.jpg" style={{...}} isBgImage={true} onMouseOver={[Function]} onMouseOut={[Function]} onMouseMove={[Function]} carouselStore={{...}} alt="" height={{...}} onError={{...}} onLoad={{...}} renderError={{...}} renderLoading={{...}}>
            <div className="image carousel__image carousel__image--with-background carousel__image--success overlay carousel__zoom-image-overlay" style={{...}} orientation="horizontal" onMouseOver={[Function]} onMouseOut={[Function]} onMouseMove={[Function]} height={{...}} />
          </Image>
        </Wrapper>
      </div>
    </ImageWithZoom>

This is the .html() of the wrapper before the mouseover event is triggered.

  console.log src/ImageWithZoom/__tests__/ImageWithZoom.test.jsx:39
    before mouseover html:
     <div class="container"><div class="image carousel__image carousel__image--with-background carousel__image--success image carousel__zoom-image" orientation="horizontal" style="background-image: url(bob.jpg); background-size: cover;"></div><div class="image carousel__image carousel__image--with-background carousel__image--success overlay carousel__zoom-image-overlay" orientation="horizontal" style="background-image: url(bob.jpg); background-size: cover;"></div></div>

This is a console log in the ImageWithZoom component to acknowledge that our mouseover method was called. It sets the state property "hovering" to true.

  console.log src/ImageWithZoom/ImageWithZoom.jsx:41
    ImageWithZoom.handleOnMouseOver()

This console log is from the ImageWithZoom render method. It shows that the "hovering" state property is now true, as expected.

  console.log src/ImageWithZoom/ImageWithZoom.jsx:89
    ImageWithZoom.render() state { isImageLoading: false,
      hovering: true,
      style: {},
      x: null,
      y: null }

This .html() dump shows that the component has added the class 'carousel__zoom-image-overlay--hovering' in response to the state property "hovering" now being true.

  console.log src/ImageWithZoom/__tests__/ImageWithZoom.test.jsx:41
    after mouseover html:
     <div class="container"><div class="image carousel__image carousel__image--with-background carousel__image--success image carousel__zoom-image" orientation="horizontal" style="background-image: url(bob.jpg); background-size: cover;"></div><div class="image carousel__image carousel__image--with-background carousel__image--success overlay carousel__zoom-image-overlay hover carousel__zoom-image-overlay--hovering" orientation="horizontal" style="background-image: url(bob.jpg); background-size: cover;"></div></div>

However, Enzyme has not been updated to reflect the state change. The css class "carousel__zoom-image-overlay--hovering" is missing. Hence, my tests fail.

  console.log src/ImageWithZoom/__tests__/ImageWithZoom.test.jsx:42
    after mouseover debug
     <ImageWithZoom src="bob.jpg" tag="div">
      <div className="container">
        <Wrapper className="image carousel__zoom-image" tag="div" src="bob.jpg" isBgImage={true} onLoad={[Function]} onError={[Function]}>
          <Image hasMasterSpinner={false} orientation="horizontal" className="image carousel__zoom-image" tag="div" src="bob.jpg" isBgImage={true} onLoad={[Function]} onError={[Function]} carouselStore={{...}} alt="" height={{...}} renderError={{...}} renderLoading={{...}} style={{...}}>
            <div className="image carousel__image carousel__image--with-background carousel__image--success image carousel__zoom-image" style={{...}} orientation="horizontal" height={{...}} />
          </Image>
        </Wrapper>
        <Wrapper className="overlay carousel__zoom-image-overlay" tag="div" src="bob.jpg" style={{...}} isBgImage={true} onMouseOver={[Function]} onMouseOut={[Function]} onMouseMove={[Function]}>
          <Image hasMasterSpinner={false} orientation="horizontal" className="overlay carousel__zoom-image-overlay" tag="div" src="bob.jpg" style={{...}} isBgImage={true} onMouseOver={[Function]} onMouseOut={[Function]} onMouseMove={[Function]} carouselStore={{...}} alt="" height={{...}} onError={{...}} onLoad={{...}} renderError={{...}} renderLoading={{...}}>
            <div className="image carousel__image carousel__image--with-background carousel__image--success overlay carousel__zoom-image-overlay" style={{...}} orientation="horizontal" onMouseOver={[Function]} onMouseOut={[Function]} onMouseMove={[Function]} height={{...}} />
          </Image>
        </Wrapper>
      </div>
    </ImageWithZoom>

Here is the test.

  it('should add hovering classes to the overlay when mouse is hovering', () => {
    expect(imageWithZoom.find('div.overlay').hasClass('hover')).toBe(false);
    expect(imageWithZoom.find('div.overlay').hasClass('carousel__zoom-image-overlay--hovering')).toBe(false);

    console.log('before mouseover debug:\n', imageWithZoom.debug());
    console.log('before mouseover html:\n', imageWithZoom.html());
    imageWithZoom.find('Wrapper.overlay').simulate('mouseover');
    wrapper.update(); // doesn't matter if this is here or not, it still fails the test.
    console.log('after mouseover html:\n', imageWithZoom.html());
    console.log('after mouseover debug\n', imageWithZoom.debug());

    expect(imageWithZoom.find('div.overlay').hasClass('hover')).toBe(true);
    expect(imageWithZoom.find('div.overlay').hasClass('carousel__zoom-image-overlay--hovering')).toBe(true);
  });

I made a branch in my repo that you can check out if you need it.
https://github.com/express-labs/pure-react-carousel/tree/share/sharing-with-enzyme-author

@ljharb
Copy link
Member

ljharb commented Nov 29, 2017

@mrbinky3000 thanks; can you file this separately?

@mrbinky3000
Copy link

@ljharb I created issue #1400

Thanks for looking into this.

@mrt123
Copy link

mrt123 commented Jan 11, 2018

This is (actually isn't ... see update below) still an issue in:
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"react-test-renderer": "^16.2.0",

In my case, component triggers a fetch in componentDidMount, which is mocked and confirmed to get triggered. Also tested component successfully re-renders during test.

wrapper.debug() still returns outdated components, and assertions fail.

 jest.spyOn(global, 'fetch').mockImplementation(() => Promise.resolve({
		json: () => myData,
}));

it('renders Dashboard with Loader active', () => {
	const wrapper = mount(<Dashboard />);
	wrapper.update();
	// console.log(wrapper.debug());
	expect(wrapper.find('Dashboard')).toHaveLength(1);
	expect(wrapper.find('RouteContentLoader')).toHaveLength(0);  // This still returns 1
	expect(wrapper.find(SpinnerSvg1)).toHaveLength(0);    // This still returns 1
});

----- UPDATE:
This does not appear to be an issue anymore. Problem was due to my own implementation. In order to update the wrapper I had to ensure promise provided in the fetch mock is fully resolved (which depending on implementation may require multiple then callbacks):

const metrics = { myStuff: 1 };

const dataPromise = Promise.resolve({
	json: () => metrics,
});
jest.spyOn(global, 'fetch')
	.mockImplementation(() => dataPromise);
//.....

// in test
	return dataPromise.then().then(() => {
		wrapper.update();  // will only work if called in last then callback
		//expect(wrapper....);
	});

@mkermani144
Copy link

mkermani144 commented Jan 28, 2018

This no longer seems to be an issue for me in the following versions:

enzyme: 3.3.0
enzyme-adapter-react-16: 1.1.1
react-test-renderer: 16.2.0

I had exactly the same issue with shallow and wrapper.update() solved the problem. The problem was that I was updating state by calling some props of the wrapper children and enzyme didn't know that some state changes may happen, so it didn't update automatically (as stated here).

@Duskfall
Copy link

Same problem here.
setState is inside the method I am trying to test.
So when calling the method from the test and even when I do a wrapper.update the state of the component isn't updated. I have tried both with mount and shallow

test.js

const wrapper = shallow(<Panel {...props} />);
const appInstance = wrapper.instance();

appInstance.handleChange(data);

panel.js

handleChange(data){
   this.setState({
       test: data
   })
}

@maloguertin
Copy link

Is this supposed to be fixed?

I have this code:

const wrapper = mount(<Component/>)
// simulate event
console.log(wrapper.html())
console.log('length', wrapper.find('.edit-choice').length)

results:

//html()
//includes element with className edit-choice

//wrapper.find()
//length 0

@ljharb
Copy link
Member

ljharb commented Aug 7, 2018

@maloguertin it's pretty hard to tell when you elide parts of the code; however it's likely that you need a wrapper.update() after you simulate the event. Separately, use .debug(), not .html(), since the latter does a full render.

@ljharb
Copy link
Member

ljharb commented Sep 1, 2018

This is an old issue that's very long, and there's been numerous updates since all of the comments.

I'm going to close this, since it's not clear to me what's still broken.

If you're still having trouble, please do file a new issue, with a clear repro step, filling out the template - and we'll get it addressed ASAP.

@ljharb ljharb closed this as completed Sep 1, 2018
@lucasdeassis
Copy link

Tried all suggestions here and didn't work. At the end updating to "enzyme-adapter-react-16": "^1.3.1" worked.

@guofei0723
Copy link

guofei0723 commented Dec 14, 2018

"react": "^16.4.1",
"enzyme": "^3.7.0",
"enzyme-adapter-react-16": "^1.7.0",

same issue

update
it is not the same issue in my case. enzyme does not output the error message which happened in render() of my component, and that makes looks like does not re-render

@ljharb
Copy link
Member

ljharb commented Dec 14, 2018

@guofei0723 please file a new issue if you're having trouble.

@pvanny1124

This comment has been minimized.

@ljharb

This comment has been minimized.

@pvanny1124
Copy link

pvanny1124 commented Jan 6, 2019 via email

@pvanny1124
Copy link

pvanny1124 commented Jan 6, 2019 via email

@ljharb
Copy link
Member

ljharb commented Jan 6, 2019

Whatever async stuff you’re doing in your component, you have to intercept in your tests so that you can reliably await that.

If you have further questions, please file a new issue.

@Finesse
Copy link

Finesse commented Feb 10, 2019

I had a similar issue. I fixed it by the following replacement:

  const wrapper = mount(<Component />);
  const button = wrapper.find('.button');
  // Do some stuff with the button...
- button.update();
+ wrapper.update();
- expect(button.prop('type')).toEqual('foo');
+ expect(wrapper.find('.button').prop('type')).toEqual('foo');

The .update() method must be called on the root wrapper, not on a child wrapper. The method mutates the root wrapper but doesn't mutate any child wrapper so I have to find the button again.

@ljharb
Copy link
Member

ljharb commented Feb 10, 2019

@Finesse yes, that's part of v3 - you always have to re-find from the root.

@Jlevett
Copy link

Jlevett commented Mar 11, 2019

I found something that works.
Add these middle two lines.

wrapper .find(...Your thing) .simulate('click'); //Click event on wrapper
await wrapper.instance().forceUpdate(); //THIS ONE
wrapper.update();  //AND THIS ONE
expect(....).toHaveLength(1); //Your assertion here

@Finesse
Copy link

Finesse commented Mar 11, 2019

@Jlevett Calling forceUpdate can spoil your test because React may skip updates in some cases and you need to check that React has or hasn't skipped the update.

@RaiVaibhav
Copy link

RaiVaibhav commented Sep 19, 2019

@Finesse Thanks for the explanation. It's mention in the guide but getting a conclusion like this
The method mutates the root wrapper but doesn't mutate any child wrapper so I have to find the button again was pretty tough for me, It was blocking me entirely

@brendangibson
Copy link

FWIW I was having the same issue. The I added the done parameter to the it function and called it at the end, and that fixed my problem.

it('does something', done => { .... done()})

@guruusingh2033
Copy link

guruusingh2033 commented Feb 27, 2020

I am having issue. could you help me please.
componentDidMount() updating the state vale and then a Array.map() renders list of dropdown ,that means new render should be generated but i am unable to get that updated wrapper .
I want to get those dropdown items to simulate click and test method of react component .

"enzyme": "^3.11.0",

"enzyme-adapter-react-16": "^1.15.2",

"react": "^16.8.6",

"react-router-dom": "^4.2.2",

"react-test-render": "^1.1.2"

@ljharb
Copy link
Member

ljharb commented Feb 27, 2020

@guruusingh2033 i'd suggest avoiding simulate entirely; but please file a new issue with more detail if you're still having trouble.

@guruusingh2033
Copy link

guruusingh2033 commented Feb 28, 2020

@ljharb thanks for reading
Here is the wrapper

const wrapper = mount(
    <Provider store={configureStore()}>
      <IntlProvider
        locale={currentAppLocale.locale}
        messages={currentAppLocale.messages}
      >
        <MemoryRouter initialEntries={["/list"]}>
          <ListPage match={{ url: "localhost:3000", path: "list" }} location={{ state: undefined }} />
        </MemoryRouter>
      </IntlProvider>
    </Provider>
);

How can I test async method of ListPage component/wrapper while I am not getting updated wrapper

@ljharb
Copy link
Member

ljharb commented Feb 28, 2020

@guruusingh2033 for one, pass the Providers and MemoryRouter using wrappingComponent, so that you're wrapping ListPage directly.

@guruusingh2033
Copy link

@guruusingh2033 for one, pass the Providers and MemoryRouter using wrappingComponent, so that you're wrapping ListPage directly.

Could you share any reference/link please ?

@ljharb
Copy link
Member

ljharb commented Feb 28, 2020

See the API docs.

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