From 2fb07b9502488424da1dee821bedd668f3cb9ba5 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 13 Jan 2017 13:46:20 -0800 Subject: [PATCH] Added memoization test for interrupted low-priority renders Test added to verify that a high-priority update can reuse the children from an aborted low-priority update if shouldComponentUpdate returns false. --- scripts/fiber/tests-passing.txt | 1 + .../fiber/__tests__/ReactIncremental-test.js | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index b2952dfe3d383..22f7c05f6dc6f 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1174,6 +1174,7 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js * maintains the correct context when providers bail out due to low priority * maintains the correct context when unwinding due to an error in render * should not recreate masked context unless inputs have changed +* should reuse memoized work if pointers are updated before calling lifecycles src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js * catches render error in a boundary during full deferred mounting diff --git a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js index b2701d76bdba1..7c3783e74273a 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js @@ -2184,4 +2184,87 @@ describe('ReactIncremental', () => { 'componentDidUpdate', ]); }); + + it('should reuse memoized work if pointers are updated before calling lifecycles', () => { + let cduNextProps = []; + let cduPrevProps = []; + let scuNextProps = []; + let scuPrevProps = []; + let renderCounter = 0; + + function SecondChild(props) { + return {props.children}; + } + + class FirstChild extends React.Component { + componentDidUpdate(prevProps, prevState) { + cduNextProps.push(this.props); + cduPrevProps.push(prevProps); + } + shouldComponentUpdate(nextProps, nextState) { + scuNextProps.push(nextProps); + scuPrevProps.push(this.props); + return this.props.children !== nextProps.children; + } + render() { + renderCounter++; + return {this.props.children}; + } + } + + class Middle extends React.Component { + render() { + return ( +
+ {this.props.children} + {this.props.children} +
+ ); + } + } + + function Root(props) { + return ( + + ); + } + + // Initial render of the entire tree. + // Renders: Root, Middle, FirstChild, SecondChild + ReactNoop.render(A); + ReactNoop.flush(); + + expect(renderCounter).toBe(1); + + // Schedule low priority work to update children. + // Give it enough time to partially render. + // Renders: Root, Middle, FirstChild + ReactNoop.render(B); + ReactNoop.flushDeferredPri(20 + 30 + 5); + + // At this point our FirstChild component has rendered a second time, + // But since the render is not completed cDU should not be called yet. + expect(renderCounter).toBe(2); + expect(scuPrevProps).toEqual([{ children: 'A' }]); + expect(scuNextProps).toEqual([{ children: 'B' }]); + expect(cduPrevProps).toEqual([]); + expect(cduNextProps).toEqual([]); + + // Next interrupt the partial render with higher priority work. + // The in-progress child content will bailout. + // Renders: Root, Middle, FirstChild, SecondChild + ReactNoop.render(B); + ReactNoop.flush(); + + // At this point the higher priority render has completed. + // Since FirstChild props didn't change, sCU returned false. + // The previous memoized copy should be used. + expect(renderCounter).toBe(2); + expect(scuPrevProps).toEqual([{ children: 'A' }, { children: 'B' }]); + expect(scuNextProps).toEqual([{ children: 'B' }, { children: 'B' }]); + expect(cduPrevProps).toEqual([{ children: 'A' }]); + expect(cduNextProps).toEqual([{ children: 'B' }]); + }); });