Skip to content

Using a customRender method will break rerender usage #218

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

Closed
jannisg opened this issue Nov 7, 2018 · 13 comments
Closed

Using a customRender method will break rerender usage #218

jannisg opened this issue Nov 7, 2018 · 13 comments
Labels
documentation A docs improvement is needed. good first issue Good for newcomers help wanted Extra attention is needed

Comments

@jannisg
Copy link

jannisg commented Nov 7, 2018

  • react-testing-library version: ^5.2.1
  • react version: ^16.5.2
  • node version: v8.11.1
  • npm (or yarn) version: [email protected]

Relevant code or config:

https://github.com/kentcdodds/react-testing-library/blob/master/src/index.js#L27

import { render } from '../custom-render';

const { rerender } = render(<MyComponent prop={1} />);

// breaks because rerender calls the original (non-custom) render method
rerender(<MyComponent prop={2} />);

What you did:

I am wrapping and re-exporting the render method according to the docs: https://github.com/kentcdodds/react-testing-library#custom-render

My implementation:

import React from "react";
import { render } from "react-testing-library";
import { Provider } from "react-redux";
import { ThemeProvider } from "styled-components/macro";
import { MemoryRouter } from "react-router-dom";
import theme from "../theme";
import { store } from "../redux/store";

// NOTE: https://github.com/kentcdodds/react-testing-library#custom-render
const customRender = (node, ...options) => {
  return render(
    <Provider store={store}>
      <ThemeProvider theme={theme}>
        <MemoryRouter>{node}</MemoryRouter>
      </ThemeProvider>
    </Provider>,
    ...options,
  );
};

export * from "react-testing-library";
export { customRender as render };

What happened / Problem description:

When I now call rerender(...) in any of my tests the internal (non-custom) render method is called which breaks with error messages about my store not being present for example since my <Provider>...</Provider> from react-redux is not used on the rerender.

I think the issue is here: https://github.com/kentcdodds/react-testing-library/blob/master/src/index.js#L27 where the original render is called.

Suggested solution:

Perhaps rerender could be defined as a top-level method that could be overwritten?
Not sure how that would work though since the const { rerender } = ... usage might not work nicely that way.

@alexkrolick
Copy link
Collaborator

I don't think the library needs to change - rerender should be implemented the same way custom queries would be, by changing the return value of the customRender. The example should be updated to reflect this.

@kentcdodds
Copy link
Member

I agree with @alexkrolick 👍

@kentcdodds kentcdodds added help wanted Extra attention is needed good first issue Good for newcomers documentation A docs improvement is needed. labels Nov 7, 2018
@good-idea
Copy link

Is this possible given the current way of customizing the render function? I don't see how the internal usage of render could access the customized one.

Is there any issue with adding something like a WrapperComponent to the render options?

const MyWrapper = ({ children }) => <MemoryRouter>{children}</MemoryRouter>

const customRender = (node, options) =>
  render(
    node,
    {
      WrapperComponent: MyWrapper,
      ...options
    }
  )

export * from 'react-testing-library'
export { customRender as render }

This way, WrapperComponent can be passed back to the 'real' render function with the other options.

@alexkrolick
Copy link
Collaborator

alexkrolick commented Nov 7, 2018

I think WrapperComponent would be a reasonable thing to add to render options since it it very common.

You can still fix the issue by returning something like this from customRender:

function customRender(...args) {
  const rendered = rtl.render(...args)
  return {
    ...rendered, 
    rerender: customRender.bind(this, ...args)
  }
}

@good-idea
Copy link

@alexkrolick yes, that works!

I guess the question is which option is more user-friendly?

@alexkrolick
Copy link
Collaborator

Let's update the docs for now (want to make a PR? 😉), and make a separate issue for adding the wrapper option.

@good-idea
Copy link

Actually, digging into this a little more, that customRender setup doesn't work, because it doesn't keep a consistent baseElement - instead, it creates a new one. So, all of the utilities are still querying the result from the initial render (I think).

The second and third test fail:

import * as React from 'react'
import { render } from 'react-testing-library'

function customRender(node, options) {
	const rendered = render(<div>{node}</div>, options)
	return {
		...rendered,
		rerender: customRender
    }
}

function customRenderBound(...args) {
	const [node, ...options] = args
	const rendered = render(<div>{node}</div>, options)
	return {
		...rendered,
		rerender: customRender.bind(this),
	}
}

const MyComponent = (props) => (
	<p data-testid="text-content">{props.active ? 'active' : 'nope'}</p>
)

it('[RTL] getByTestId should work after rerender', async () => {
	const { getByTestId, rerender } = render(<MyComponent />)
	expect(getByTestId('text-content').textContent).toBe('nope')

	rerender(<MyComponent active />)

	expect(getByTestId('text-content').textContent).toBe('active')
})

it('[custom] getByTestId should work after rerender', async () => {
	const { getByTestId, rerender } = customRender(<MyComponent />)
	expect(getByTestId('text-content').textContent).toBe('nope')

	rerender(<MyComponent active />)

	expect(getByTestId('text-content').textContent).toBe('active')
})

it('[customBound] getByTestId should work after rerender', async () => {
	const { getByTestId, rerender } = customRenderBound(<MyComponent />)
	expect(getByTestId('text-content').textContent).toBe('nope')

	rerender(<MyComponent active />)

	expect(getByTestId('text-content').textContent).toBe('active')
})

image

Also, normally, rerender doesn't return anything at all, so the other problem here is that people will have weird issues when they try to use the utilities returned from rerender - when it shouldn't return anything at all:

https://github.com/kentcdodds/react-testing-library/blob/03fa4b99e4b81dc9788a53330b0770e36c208461/src/index.js#L28-L29

@kentcdodds
Copy link
Member

You need to make sure that the element is rendered to the same container. This should work:

function customRender(node, options) {
	const rendered = render(<div>{node}</div>, options)
	return {
		...rendered,
		rerender: (ui, options) => customRender(ui, {container: rendered.container, ...options})
    }
}

@good-idea
Copy link

Works for me!

@EstebanMarin
Copy link
Contributor

I created a PR for this addition into the Docs. Happy to get feeedback

kentcdodds pushed a commit that referenced this issue Nov 9, 2018
<!--
Thanks for your interest in the project. Bugs filed and PRs submitted are appreciated!

Please make sure that you are familiar with and follow the Code of Conduct for
this project (found in the CODE_OF_CONDUCT.md file).

Also, please make sure you're familiar with and follow the instructions in the
contributing guidelines (found in the CONTRIBUTING.md file).

If you're new to contributing to open source projects, you might find this free
video course helpful: http://kcd.im/pull-request

Please fill out the information below to expedite the review and (hopefully)
merge of your pull request!
-->

<!-- What changes are being made? (What feature/bug is being fixed here?) -->

**What**: issue(#218) Adding to documentation the customRender method when having a provider

<!-- Why are these changes necessary? -->

**Why**: Its still not an option, and the option is not evident

<!-- How were these changes implemented? -->

**How**:  N/A

<!-- Have you done all of these things?  -->

**Checklist**: Yes

<!-- add "N/A" to the end of each line that's irrelevant to your changes -->

<!-- to check an item, place an "x" in the box like so: "- [x] Documentation" -->

- [x] Documentation
- [x] Tests
- [x] Ready to be merged
      <!-- In your opinion, is this ready to be merged as soon as it's reviewed? -->
- [x] Added myself to contributors table
      <!-- this is optional, see the contributing guidelines for instructions -->

<!-- feel free to add additional comments -->
@kentcdodds
Copy link
Member

Resolved in #219. Thanks @EstebanMarin!

@ArjobanSingh
Copy link

ArjobanSingh commented Jun 21, 2021

You need to make sure that the element is rendered to the same container. This should work:

function customRender(node, options) {
	const rendered = render(<div>{node}</div>, options)
	return {
		...rendered,
		rerender: (ui, options) => customRender(ui, {container: rendered.container, ...options})
    }
}

But basically this thing is un-mounting and than remounting the component, which makes all the mount/unmount side-effects to re-run, which we won't want in re-render.
what about the following approach, as it explicitly uses re-render provided by Rtl.

function customRender(node, options, renderFn = render) {
	const rendered = render(<div>{node}</div>, options)
	return {
		...rendered,
		rerender: (ui, options) => customRender(ui, options, rendered.rerender)
    }
}

@kentcdodds
Copy link
Member

But basically this thing is un-mounting and than remounting the component

My code example will not do this. If that's what you're experiencing it's because of something else. Please prepare a reproduction and open a new issue or take it to our support channels: https://kcd.im/rtl-help

@testing-library testing-library locked as resolved and limited conversation to collaborators Jun 21, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation A docs improvement is needed. good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

6 participants