diff --git a/src/renderers/noop/ReactNoopEntry.js b/src/renderers/noop/ReactNoopEntry.js index ddd89bcf0ae61..c785b3bc470c4 100644 --- a/src/renderers/noop/ReactNoopEntry.js +++ b/src/renderers/noop/ReactNoopEntry.js @@ -86,6 +86,8 @@ function removeChild( parentInstance.children.splice(index, 1); } +let elapsedTimeInMs = 0; + var NoopRenderer = ReactFiberReconciler({ getRootHostContext() { if (failInBeginPhase) { @@ -206,8 +208,7 @@ var NoopRenderer = ReactFiberReconciler({ resetAfterCommit(): void {}, now(): number { - // TODO: Add an API to advance time. - return 0; + return elapsedTimeInMs; }, }); @@ -344,6 +345,14 @@ var ReactNoop = { expect(actual).toEqual(expected); }, + expire(ms: number): void { + elapsedTimeInMs += ms; + }, + + flushExpired(): Array { + return ReactNoop.flushUnitsOfWork(0); + }, + yield(value: mixed) { if (yieldedValues === null) { yieldedValues = [value]; diff --git a/src/renderers/shared/fiber/__tests__/ReactExpiration-test.js b/src/renderers/shared/fiber/__tests__/ReactExpiration-test.js new file mode 100644 index 0000000000000..087735a38880b --- /dev/null +++ b/src/renderers/shared/fiber/__tests__/ReactExpiration-test.js @@ -0,0 +1,129 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +'use strict'; + +var React; +var ReactNoop; + +describe('ReactExpiration', () => { + beforeEach(() => { + jest.resetModules(); + React = require('react'); + ReactNoop = require('react-noop-renderer'); + }); + + function span(prop) { + return {type: 'span', children: [], prop}; + } + + it('increases priority of updates as time progresses', () => { + ReactNoop.render(); + + expect(ReactNoop.getChildren()).toEqual([]); + + // Nothing has expired yet because time hasn't advanced. + ReactNoop.flushExpired(); + expect(ReactNoop.getChildren()).toEqual([]); + + // Advance by 300ms, not enough to expire the low pri update. + ReactNoop.expire(300); + ReactNoop.flushExpired(); + expect(ReactNoop.getChildren()).toEqual([]); + + // Advance by another second. Now the update should expire and flush. + ReactNoop.expire(1000); + ReactNoop.flushExpired(); + expect(ReactNoop.getChildren()).toEqual([span('done')]); + }); + + it('coalesces updates to the same component', () => { + const foos = []; + class Foo extends React.Component { + constructor() { + super(); + this.state = {step: 0}; + foos.push(this); + } + render() { + return ; + } + } + + ReactNoop.render([, ]); + ReactNoop.flush(); + const [a, b] = foos; + + a.setState({step: 1}); + + // Advance time by 500ms. + ReactNoop.expire(500); + + // Update A again. This update should coalesce with the previous update. + a.setState({step: 2}); + // Update B. This is the first update, so it has nothing to coalesce with. + b.setState({step: 1}); + + // Advance time. This should be enough to flush both updates to A, but not + // the update to B. If only the first update to A flushes, but not the + // second, then it wasn't coalesced properly. + ReactNoop.expire(500); + ReactNoop.flushExpired(); + expect(ReactNoop.getChildren()).toEqual([span(2), span(0)]); + + // Now expire the update to B. + ReactNoop.expire(500); + ReactNoop.flushExpired(); + expect(ReactNoop.getChildren()).toEqual([span(2), span(1)]); + }); + + it('stops coalescing after a certain threshold', () => { + let instance; + class Foo extends React.Component { + state = {step: 0}; + render() { + instance = this; + return ; + } + } + + ReactNoop.render(); + ReactNoop.flush(); + + instance.setState({step: 1}); + + // Advance time by 500 ms. + ReactNoop.expire(500); + + // Update again. This update should coalesce with the previous update. + instance.setState({step: 2}); + + // Advance time by 480ms. Not enough to expire the updates. + ReactNoop.expire(480); + ReactNoop.flushExpired(); + expect(ReactNoop.getChildren()).toEqual([span(0)]); + + // Update again. This update should NOT be coalesced, because the + // previous updates have almost expired. + instance.setState({step: 3}); + + // Advance time. This should expire the first two updates, + // but not the third. + ReactNoop.expire(500); + ReactNoop.flushExpired(); + expect(ReactNoop.getChildren()).toEqual([span(2)]); + + // Now expire the remaining update. + ReactNoop.expire(1000); + ReactNoop.flushExpired(); + expect(ReactNoop.getChildren()).toEqual([span(3)]); + }); +});