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

Comments

Projects
None yet
8 participants
@geowarin

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 from Mapping cherrio elements with `render` to API differences between `render` and `mount`/`shallow` Jun 21, 2016

@geowarin geowarin changed the title from API differences between `render` and `mount`/`shallow` to API differences between render and mount/shallow Jun 21, 2016

@aweary

This comment has been minimized.

Show comment
Hide comment
@aweary

aweary Jun 21, 2016

Collaborator

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.

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.

@aweary aweary added the Render label Jun 21, 2016

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Jun 21, 2016

Member

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.

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

This comment has been minimized.

Show comment
Hide comment
@geowarin

geowarin 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.

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

This comment has been minimized.

Show comment
Hide comment
@aweary

aweary Jun 28, 2016

Collaborator

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!

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 Jun 28, 2016

@joncursi

This comment has been minimized.

Show comment
Hide comment
@joncursi

joncursi Aug 26, 2016

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!

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

This comment has been minimized.

Show comment
Hide comment
@joncursi

joncursi Aug 26, 2016

Nevermind! It appears this was resolved in #318

Nevermind! It appears this was resolved in #318

@oncletom

This comment has been minimized.

Show comment
Hide comment
@oncletom

oncletom Feb 28, 2017

@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.)?

@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.)?

@kaizenberg

This comment has been minimized.

Show comment
Hide comment
@kaizenberg

kaizenberg Jul 28, 2017

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

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

@aaron7 aaron7 referenced this issue Sep 8, 2017

Merged

Alert component #45

@nxmohamad

This comment has been minimized.

Show comment
Hide comment
@nxmohamad

nxmohamad Sep 8, 2017

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

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

already call componentWillMount once?

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

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

already call componentWillMount once?

@MartinDawson

This comment has been minimized.

Show comment
Hide comment
@MartinDawson

MartinDawson Nov 16, 2017

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.

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

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Mar 9, 2018

Member

@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.

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