From 3294a5e3b22adb7526b58797eebb652ac5ab76e4 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 5 Apr 2022 17:45:31 +0100 Subject: [PATCH] Add a pair of tests for removing the parent --- .../src/__tests__/ReactLazy-test.internal.js | 120 +++++++++++++++++- 1 file changed, 118 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js index 3ed6e4da23440..6ddf0f0bbe993 100644 --- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js @@ -1632,7 +1632,7 @@ describe('ReactLazy', () => { expect(root).toMatchRenderedOutput('ba'); }); - it('does not destroy layout effects twice', async () => { + it('does not destroy layout effects twice when hidden child is removed', async () => { function ChildA({label}) { React.useLayoutEffect(() => { Scheduler.unstable_yieldValue('Did mount: ' + label); @@ -1686,7 +1686,7 @@ describe('ReactLazy', () => { expect(root).toMatchRenderedOutput('B'); }); - it('does not call componentWillUnmount twice', async () => { + it('does not call componentWillUnmount twice when hidden child is removed', async () => { class ChildA extends React.Component { componentDidMount() { Scheduler.unstable_yieldValue('Did mount: ' + this.props.label); @@ -1743,4 +1743,120 @@ describe('ReactLazy', () => { expect(Scheduler).toFlushAndYield(['B', 'Did mount: B']); expect(root).toMatchRenderedOutput('B'); }); + + it('does not destroy layout effects twice when parent suspense is removed', async () => { + function ChildA({label}) { + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('Did mount: ' + label); + return () => { + Scheduler.unstable_yieldValue('Will unmount: ' + label); + }; + }, []); + return ; + } + function ChildB({label}) { + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('Did mount: ' + label); + return () => { + Scheduler.unstable_yieldValue('Will unmount: ' + label); + }; + }, []); + return ; + } + const LazyChildA = lazy(() => fakeImport(ChildA)); + const LazyChildB = lazy(() => fakeImport(ChildB)); + + function Parent({swap}) { + return ( + }> + {swap ? : } + + ); + } + + const root = ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + + expect(Scheduler).toFlushAndYield(['Loading...']); + + await LazyChildA; + expect(Scheduler).toFlushAndYield(['A', 'Did mount: A']); + expect(root).toMatchRenderedOutput('A'); + + // Swap the position of A and B + root.unstable_flushSync(() => { + root.update(); + }); + expect(Scheduler).toHaveYielded(['Loading...', 'Will unmount: A']); + expect(root).toMatchRenderedOutput('Loading...'); + + // Destroy the whole tree, including the hidden A + root.unstable_flushSync(() => { + root.update(

Hello

); + }); + expect(Scheduler).toFlushAndYield([]); + expect(root).toMatchRenderedOutput(

Hello

); + }); + + it('does not call componentWillUnmount twice when parent suspense is removed', async () => { + class ChildA extends React.Component { + componentDidMount() { + Scheduler.unstable_yieldValue('Did mount: ' + this.props.label); + } + componentWillUnmount() { + Scheduler.unstable_yieldValue('Will unmount: ' + this.props.label); + } + render() { + return ; + } + } + + class ChildB extends React.Component { + componentDidMount() { + Scheduler.unstable_yieldValue('Did mount: ' + this.props.label); + } + componentWillUnmount() { + Scheduler.unstable_yieldValue('Will unmount: ' + this.props.label); + } + render() { + return ; + } + } + + const LazyChildA = lazy(() => fakeImport(ChildA)); + const LazyChildB = lazy(() => fakeImport(ChildB)); + + function Parent({swap}) { + return ( + }> + {swap ? : } + + ); + } + + const root = ReactTestRenderer.create(, { + unstable_isConcurrent: true, + }); + + expect(Scheduler).toFlushAndYield(['Loading...']); + + await LazyChildA; + expect(Scheduler).toFlushAndYield(['A', 'Did mount: A']); + expect(root).toMatchRenderedOutput('A'); + + // Swap the position of A and B + root.unstable_flushSync(() => { + root.update(); + }); + expect(Scheduler).toHaveYielded(['Loading...', 'Will unmount: A']); + expect(root).toMatchRenderedOutput('Loading...'); + + // Destroy the whole tree, including the hidden A + root.unstable_flushSync(() => { + root.update(

Hello

); + }); + expect(Scheduler).toFlushAndYield(['']); + expect(root).toMatchRenderedOutput(

Hello

); + }); });