testing-library / react-testing-library

🐐 Simple and complete React DOM testing utilities that encourage good testing practices.
https://testing-library.com/react
MIT License
18.84k stars 1.09k forks source link

`rerender` does not reset state #1331

Closed saiNaruUFL closed 1 month ago

saiNaruUFL commented 1 month ago

Relevant code or config:

Renderer Code

export const renderWithProviders = ( 
   ui: ReactElement, 
   extendedRenderOptions: ExtendedRenderOptions = {}, 
 ) => { 
   const { 
     preloadedState = {}, 
     // Automatically create a store instance if no store was passed in 
     store = makeStore(preloadedState), 
     ...renderOptions 
   } = extendedRenderOptions 

   const Wrapper = ({ children }: PropsWithChildren) => ( 
     <Provider store={store}>{children}</Provider> 
   ) 

   const rendered = render(<div>{ui}</div>, { 
     ...renderOptions, 
     wrapper: Wrapper, 
   }) 

   // Return an object with the store and all of RTL's query functions 
   return { 
     ...rendered, 
     store, 
     user: userEvent.setup(), 
     rerender: (ui: ReactElement) => { 
       store: makeStore(preloadedState), 
         renderWithProviders(ui, { 
           wrapper: Wrapper, 
           container: rendered.container, 
           ...renderOptions, 
         }) 
     }, 
   } 
 }

Test Code

//Search Test 
 test("Search Bar should mount, have no initial value, and re-render properly", async () => { 
   const { store, user, rerender } = renderWithProviders(<App />) 

   //Find search-bar and ensrue that initial value is empty 
   expect(screen.getByRole("textbox")).toBeInTheDocument() 
   expect(screen.getByRole("textbox")).toHaveValue("") 

   //get the ui-component and emulate user typing 
   const inputUI = screen.getByRole("textbox") 
   await user.type(inputUI, "Bowjeet is King") 

   //screen debug info after typing 
   screen.debug() 
   console.log(store.getState()) 

   // Rerender the component (issue: the store does not clear) 
   rerender(<App />) 

   // Screen debug after re-render 
   screen.debug() 

   // Ensure the value is reset (issue: the component clears but the store does not) 
   expect(screen.getByRole("textbox")).toHaveValue("") 
   console.log(store.getState()) 
 }) 

What you did:

Called rerender after initial call to renderWithProviders to test resetting redux state. renderWithProviders is a custom wrapper to include redux store

What happened:

After calling rerender, the redux store still persists state from initial render call. The screenshot below shows that the screen rerenders appropriately, but the redux store does not reset.

slkjsd

Reproduction:

https://github.com/Agriculture-Intelligence/react-test-library-redux-example/tree/master

Related to: https://github.com/testing-library/react-testing-library/issues/218#issuecomment-436730757 https://github.com/testing-library/react-testing-library/issues/950 https://github.com/testing-library/testing-library-docs/issues/1156 ?

Problem description:

rerender should reset the wrapper as well. Our understanding of rerender is that it should work similar to a browser refresh.

Ultimately, we want to test our components using Gherkin syntax and reset the component for every scenario as such

describeFeature(feature, (f) => {
  let viRender = renderWithProviders(<InventoryTab />, {
        preloadedState,
      })

  f.AfterEachScenario(() => {
    viRender.rerender();
  })
  f.Scenario(...);

This Gherkin syntax testing is not included in the reproduction to simplify the issue we are having, this testing style comes from https://github.com/amiceli/vitest-cucumber

Suggested solution:

saiNaruUFL commented 1 month ago

CC:@MatanBobi

eps1lon commented 1 month ago

rerender is intended to persist state just like calling root.render() in React would persist state.

You want to call render from React Testing Library again to discard the previous Redux state.

MatanBobi commented 1 month ago

Adding to what @eps1lon wrote, regarding what you wrote here:

Our understanding of rerender is that it should work similar to a browser refresh.

That's not a good mental model. rerender is in fact a component re-render, it's not equal to an unmount + mount again which is what a full page refresh causes.

charlieforward9 commented 1 month ago

@MatanBobi thank you for clarifying.

@eps1lon Thank you for the response as well. Regarding our use case:

describeFeature(feature, (f) => {
  let viRender = renderWithProviders(<InventoryTab />, {
        preloadedState,
      })

  f.AfterEachScenario(() => {
    viRender.rerender();
  })
  f.Scenario(...);

Are you implying that we should call a new render within each Scenario? I was hoping to avoid repetitive calls but this seems like the only approach hat makes sense.

charlieforward9 commented 1 month ago

@eps1lon @MatanBobi we are still having an issue with state idempotency. We found a nearly identical issue that has not been resolved on Stack Overflow.

We could open a new issue, but all of the details are here so hoping to continue on this thread.

MatanBobi commented 1 week ago

@charlieforward9 I recommend opening a new issue with the use case you're experiencing. As you can see, commenting on closed issues usually gets lost in the notifications mess 😅