API differences between render and mount/shallow #465

Closed
geowarin opened this Issue Jun 21, 2016 · 6 comments

Projects

None yet

4 participants

@geowarin
geowarin commented Jun 21, 2016 edited

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
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
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
geowarin commented Jun 22, 2016 edited

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

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

Nevermind! It appears this was resolved in #318

@franmolmedo franmolmedo referenced this issue in MasterLemon2016/LeanMood Nov 24, 2016
Merged

Feature/unitest infraestructure #21

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