diff --git a/packages/data/src/components/use-select/test/index.js b/packages/data/src/components/use-select/test/index.js index 7e507e4a4b2b78..4f7ab0e2589316 100644 --- a/packages/data/src/components/use-select/test/index.js +++ b/packages/data/src/components/use-select/test/index.js @@ -118,8 +118,7 @@ describe( 'useSelect', () => { expect( screen.getByRole( 'status' ) ).toHaveTextContent( 'bar' ); } ); - // TODO: this might be impossible to pull off in React 18 without `useSyncExternalStore` - it( 'avoid calling nested listener after unmounted', async () => { + it( 'does not rerender a nested component that is to be unmounted', () => { registry.registerStore( 'toggler', { reducer: ( state = false, action ) => action.type === 'TOGGLE' ? ! state : state, @@ -134,16 +133,16 @@ describe( 'useSelect', () => { const mapSelect = ( select ) => select( 'toggler' ).get(); const mapSelectChild = jest.fn( mapSelect ); - function Child() { + const Child = jest.fn( () => { const show = useSelect( mapSelectChild, [] ); return show ? 'yes' : 'no'; - } + } ); const mapSelectParent = jest.fn( mapSelect ); - function Parent() { + const Parent = jest.fn( () => { const show = useSelect( mapSelectParent, [] ); return show ? : 'none'; - } + } ); render( @@ -155,14 +154,10 @@ describe( 'useSelect', () => { expect( screen.getByText( 'none' ) ).toBeInTheDocument(); expect( mapSelectParent ).toHaveBeenCalledTimes( 2 ); expect( mapSelectChild ).toHaveBeenCalledTimes( 0 ); + expect( Parent ).toHaveBeenCalledTimes( 1 ); + expect( Child ).toHaveBeenCalledTimes( 0 ); - // act() does batched updates internally, i.e., any scheduled setStates or effects - // will be executed only after the dispatch finishes. But we want to opt out of - // batched updates here. We want all the setStates to be done synchronously, as the - // store listeners are called. The async/await code is a trick to do it: do the - // dispatch in a different event loop tick, where the batched updates are no longer active. - await act( async () => { - await Promise.resolve(); + act( () => { registry.dispatch( 'toggler' ).toggle(); } ); @@ -170,18 +165,21 @@ describe( 'useSelect', () => { expect( screen.getByText( 'yes' ) ).toBeInTheDocument(); expect( mapSelectParent ).toHaveBeenCalledTimes( 3 ); expect( mapSelectChild ).toHaveBeenCalledTimes( 2 ); + expect( Parent ).toHaveBeenCalledTimes( 2 ); + expect( Child ).toHaveBeenCalledTimes( 1 ); - await act( async () => { - await Promise.resolve(); + act( () => { registry.dispatch( 'toggler' ).toggle(); } ); // Check that child was unmounted without any extra state update being performed on it. - // I.e., `mapSelectChild` was never called again, and no "state update on an unmounted - // component" warning was triggered. + // I.e., `mapSelectChild` was called again, and state update was scheduled, we cannot + // avoid that, but the state update is never executed and doesn't do a rerender. expect( screen.getByText( 'none' ) ).toBeInTheDocument(); expect( mapSelectParent ).toHaveBeenCalledTimes( 4 ); - expect( mapSelectChild ).toHaveBeenCalledTimes( 2 ); + expect( mapSelectChild ).toHaveBeenCalledTimes( 3 ); + expect( Parent ).toHaveBeenCalledTimes( 3 ); + expect( Child ).toHaveBeenCalledTimes( 1 ); } ); describe( 'rerenders as expected with various mapSelect return types', () => {