enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.95k stars 2.01k forks source link

When shallow rendering, .find, .findWhere, etc only go one node deep #388

Closed dclowd9901 closed 8 years ago

dclowd9901 commented 8 years ago

I'm finding that even though I can output all content with wrapper.html(), when I try to dig through a rendered shallow component with .find and .findWhere, they stop at the top level.

In this example:

wrapper.findWhere(function(el){ console.log(el); });

for instance, only runs once, even for a component with deeply nested inline elements.

aweary commented 8 years ago

That is what shallow rendering does:

https://facebook.github.io/react/docs/test-utils.html#shallow-rendering

Shallow rendering is an experimental feature that lets you render a component "one level deep" and assert facts about what its render method returns, without worrying about the behavior of child components, which are not instantiated or rendered. This does not require a DOM

If you want to assert on the fully rendered DOM, you can use mount.

dclowd9901 commented 8 years ago

Sorry, I think I didn't make myself clear:

class Component extends React.Component {
    inline() {
        return (<div id="i-am-inline"></div>);
    }

    render() {
        return (<div id="i-am-the-parent">{this.inline()}</div>);
    }
}

var wrapper = Enzyme.shallow(<Component />);

console.log(wrapper.html());
// outputs
// <div id="i-am-the-parent"><div id="i-am-inline"></div></div>

console.log(wrapper.find('#i-am-inline').length);
// outputs 0

If I perform wrapper.find('#i-am-inline') it won't be found, despite the fact that it is apparently being rendered according to .html().

aweary commented 8 years ago

@dclowd9901 I'll reopen this for now until I can reproduce and clarify what the expected behavior here should be.

dclowd9901 commented 8 years ago

Thanks; it may just be my misunderstanding of shallow render. My understanding was always that it will render an entire component (depth notwithstanding), just not other components that might inhabit it.

brad-decker commented 8 years ago

the find method seems to be working only against the nodes that are compiled via shallow, or mount, and in the case of shallow only the root node will be found. The html() method will render the full html but the find method is not finding against the html output. Ran into this issue earlier today and just using mount fixed the problem.

ljharb commented 8 years ago

That's intentional - html() just does a full render, but .find operates on the shallow tree.

brad-decker commented 8 years ago

@ljharb yeah i agree with you. I think this issue can be closed.

aweary commented 8 years ago

Then I will close this out again 😄

jwbay commented 8 years ago

@dclowd9901 your example was bugging me (as in everything looked right) so I threw this in enzyme's test suite and both assertions pass. Just a heads up.

it.only('test', () => {
  class Component extends React.Component {
    inline() {
      return (<div id="i-am-inline"></div>);
    }

    render() {
      return (<div id="i-am-the-parent">{this.inline()}</div>);
    }
  }

  var wrapper = shallow(<Component />);
  expect(wrapper.find('#i-am-inline').length).to.equal(1);

  var count = 0;
  wrapper.findWhere(w => count++);
  expect(count).to.equal(2);
});
conlanpatrek commented 7 years ago

@dclowd9901 I know this is pretty old, but I just ran across this symptom, and I thought I would share my experience.

I hit this point in my testing where I would shallow render a component, but I couldn't find() any child within the node. It would always return an empty result set. Well, I had failed to notice that I was actually wrapping the component in a higher order component just before exporting. In my case it was a react-intl wrapper.

So in this instance, I was shallow rendering the react-intl component and stopping short of rendering the component I was actually trying to test. Obviously this isn't a problem with Enzyme. It's more of a gotcha to be aware of. Hope this is helpful to someone.

ljharb commented 7 years ago

@conlanpatrek for that use case, try .dive()

conlanpatrek commented 7 years ago

@ljharb Oooh, I've never noticed .dive() before. Thanks for that.

smoholkar commented 7 years ago

@ljharb awesome .... thanks for that

smoholkar commented 7 years ago

@ljharb .. If I have this structure . How should I use dive() to get the value of the input field in wrapper3.jsx when I shallow render one.jsx? What's the right way to do this ?

I tried : comp.find('Wrapper2').dive('.abc') comp.find('Wrapper2').find('Wrapper3').dive('.abc') .. but the wrapper3 lenght is always 0.

``
**one.jsx**
<Wrapper1>
 <Wrapper2 />
</Wrapper1>

**two.jsx**
<Wrapper3/>

**wrapper3.jsx**
<input class='abc'/>
ljharb commented 7 years ago

dive does not take a selector - it only works when there is a single custom component. You want shallow(<Wrapper1 />).dive().dive().find('input').

smoholkar commented 7 years ago

hi @ljharb thanks for replying. The above doesn't work. shallow().dive() returns length 1 but if I dive more it says

It always says dive() can only be called on components.

ljharb commented 7 years ago

@smoholkar if it doesn't render another Component, then it's not just a wrapper, conceptually, so you'd need to use .find and then .shallow.

smoholkar commented 7 years ago

@ljharb cool thanks for the prompt response 👍 . I ended up using mount & then .find() ==> that also worked.

smoholkar commented 7 years ago

@ljharb I have one more question, might be unrelated to this thread. Apologies if I've put it in wrong thread. I can open up a new one if necessary but :

I'm trying to check if onChange handler was called on keyDown simulation.

``
**one.jsx**
<Wrapper1>
 <Wrapper2 handleKeyDown={this.handleKeyDown}/>
</Wrapper1>

**two.jsx**
<Wrapper3 handleKeyDown={props.handleKeyDown}/>

**wrapper3.jsx**
<input class='abc' onKeyDown={props.handleKeyDown}/>

I'm trying to check if onChange handler was called on keyDown simulation. The simulate seems to be fired but the onChange doesn't seem to be called. Is there another way to test this behavior using enzyme?

test:

const component = mount(<One {...properties} />);
const instance = component.instance();
instance.onChangeHandler = jest.fn();
const input = component.find('input');
input.simulate('keydown', { key: 's', keyCode: 83 });
expect(instance.onChangeHandler).toBeCalled();
ljharb commented 7 years ago

All you need to assert is that the input has onKeyDown. You don't need to (and shouldn't) be testing React's event wiring itself.

smoholkar commented 7 years ago

@ljharb yes I agree but for certain keys I am prevent the event. Which is why I was trying to test if onChange fired when I pressed that particular key. How would I test that?

ljharb commented 7 years ago

Directly call the function with different faked event objects, and see what happens?

ljharb commented 7 years ago

onChangeHandler = jest.fn(); changes your local variable, it doesn't change anything about the instance.

guidosreis commented 6 years ago

I was facing the same issue, and using shallow().find(Child).dive() worked just fine. Thanks a lot!