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

API differences between render and mount/shallow #465

Closed
geowarin opened this issue Jun 21, 2016 · 11 comments
Closed

API differences between render and mount/shallow #465

geowarin opened this issue Jun 21, 2016 · 11 comments

Comments

@geowarin
Copy link

geowarin commented Jun 21, 2016

This is more of a question than an issue. I'm trying to see where you guys want to go with the render() function.
I like it but the API feels awkward sometimes.

For example, it is not possible to do this:

import {render} from 'enzyme';

const component = render(<MyComponent />);
const options = component.find('option').map(o => o.text());

Error:

TypeError: o.text is not a function

In cherrio's documenation, the pattern to map elements is:

$('li').map(function(i, el) {
  return $(this).text();
}).get().join(' ');

So the workaround seems to be:

import cheerio from 'cheerio';

component.find('option').map((i, el) => cheerio(el).text()).get()

which is kind of ugly and contrasts with the nice APIs of render() and mount().

A solution would be to return a wrapper in the render function instead of cheerio.load(html).root().

Something along the lines of:

class CheerioWrapper {

  constructor(root) {
    this.root = root;
  }

  map(mapper) {
    return this.root.map((i,e) => mapper(cheerio(e))).get();
  }

  find(what) {
    return new CheerioWrapper(this.root.find(what));
  }

  // etc...
}

function render(node) {
  const html = renderToStaticMarkup(node);
  return new CheerioWrapper(cheerio.load(html).root());
}

This looks like some work but it would have the advantage of proposing a unified API for mount(), shallow() and render().

So the question is: what is your roadmap for the render function ?

@geowarin geowarin changed the title Mapping cherrio elements with render API differences between render and mount/shallow Jun 21, 2016
@geowarin geowarin changed the title API differences between render and mount/shallow API differences between render and mount/shallow Jun 21, 2016
@aweary
Copy link
Collaborator

aweary commented Jun 21, 2016

Wouldn't this break most existing tests that user render? I see the convenience but I'm not sure it's worth disrupting backwards compatibility.

@ljharb
Copy link
Member

ljharb commented Jun 21, 2016

render is different than mount and shallow since we don't have any CheerioWrapper construct - it's just returning a cheerio instance. While we could do this, once you've rendered a component to HTML, you've kind of ejected yourself from the world of React - I kind of think that having the API be different is useful.

@geowarin
Copy link
Author

geowarin commented Jun 22, 2016

Thank you both for taking the time to anwser this.

@aweary Yes, this would be breaking the existing API, which I agree is an inconvenience

@ljharb Fair enough. The difference in API is assumed and it's OK.

However the docs say:

render returns a wrapper very similar to the other renderers in enzyme, mount and shallow; however, render uses a third party HTML parsing and traversal library Cheerio.

Which does not seem perfectly accurate (I'm a PITA I know 😄)
This comment in the code seems a bit more indicative of what render really does.

Playing with the different methods that enzyme provides, I have seen this:

Shallow

Real unit test (isolation, no children render)

Simple shallow

Calls:

  • constructor
  • render

Shallow + setProps

Calls:

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render

Shallow + unmount

Calls:

  • componentWillUnmount

Mount

The only way to test componentDidMount and componentDidUpdate.
Full rendering including child components.
Requires a DOM (jsdom, domino).
More constly in execution time.
If react is included before JSDOM, it can require some tricks:

require('fbjs/lib/ExecutionEnvironment').canUseDOM = true;

Simple mount

Calls:

  • constructor
  • render
  • componentDidMount

Mount + setProps

Calls:

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

Mount + unmount

Calls:

  • componentWillUnmount

Render

only calls render but renders all children.

So my rule of thumbs is:

  • Always begin with shallow
  • If componentDidMount or componentDidUpdate should be tested, use mount
  • If you want to test component lifecycle and children behavior, use mount
  • If you want to test children rendering with less overhead than mount and you are not interested in lifecycle methods, use render

There seems to be a very tiny use case for render.
I like it because it seems snappier than requiring jsdom but as @ljharb said, we cannot really test React internals with this.

I wonder if it would be possible to emulate lifecycle methods with the render method just like shallow ?
I would really appreciate if you could give me the use cases you have for render internally or what use cases you have seen in the wild.

I'm also curious to know why shallow does not call componentDidUpdate.

@aweary
Copy link
Collaborator

aweary commented Jun 28, 2016

Render is useful when you just want to assert on the DOM your component(s) render. This is a common case for static sites that use ReactDOMServer.renderToStaticMarkup. I don't think we want to introduce any lifecycle method support for render as shallow and mount cover the vast majority of use cases and there's no reason to have our APIs competing with each other.

I'm going to close as I don't think we'll be taking any action on this, but we really appreciate the issue/discussion @geowarin!

@aweary aweary closed this as completed Jun 28, 2016
@joncursi
Copy link

Is it possible to execute componentWillMount with shallow? Perhaps calling it manually in some way? I.e.

const wrapper = shallow(
      <Component
        {...minProps}
      />
    );

wrapper.componentWillMount();

expect...

Enzyme does not support mount in React Native, so I'm looking for a way to test some logic within componentWillMount. Thanks!

@joncursi
Copy link

Nevermind! It appears this was resolved in #318

@thom4parisot
Copy link

@geowarin hi! Thanks for the comment #465 (comment), it is great and helped me understand better the difference between shallow and render.

Also, sometimes Full DOM Rendering is called Full Rendering.

Do you think it could be beneficial to indicate, in the docs, the various lifecycle methods for each rendering methods, as well as their intrinsic features (like no-children rendering etc.)?

@dmoli
Copy link

dmoli commented Jul 28, 2017

@geowarin Thank you for the clarification about the diff between shallow, mount and render!

@nazreen
Copy link

nazreen commented Sep 8, 2017

@joncursi correct me if I'm wrong, but doesn't

const wrapper = shallow(
      <Component
        {...minProps}
      />
    );

already call componentWillMount once?

@MartinDawson
Copy link

For all readers now. LifeCycleExperimental which was previously an option that you had to manually set to true on shallow is now enabled by default because it is now stable.

This is much nicer than having to resort to mount when wanting to test lifecycles.

This means all lifecycle methods work with shallow now.

@ljharb
Copy link
Member

ljharb commented Mar 9, 2018

@Raju10100 please file a new issue and do not spam multiple closed issues with your comment. I’m deleting this one since it’s a duplicate.

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

8 participants