diff --git a/src/renderers/noop/ReactNoop.js b/src/renderers/noop/ReactNoop.js index e6ec61cf4f..44bf8f430d 100644 --- a/src/renderers/noop/ReactNoop.js +++ b/src/renderers/noop/ReactNoop.js @@ -34,7 +34,7 @@ var scheduledDeferredCallback = null; const TERMINAL_TAG = 99; const TEXT_TAG = 98; -type Container = { rootID: number, children: Array }; +type Container = { rootID: string, children: Array }; type Props = { prop: any }; type Instance = { tag: 99, type: string, id: number, children: Array, prop: any }; type TextInstance = { tag: 98, text: string }; @@ -145,19 +145,46 @@ var NoopRenderer = ReactFiberReconciler({ }); -var rootContainer = { rootID: 0, children: [] }; - -var root = null; +var rootContainers = new Map(); +var roots = new Map(); +var DEFAULT_ROOT_ID = ''; var ReactNoop = { - root: rootContainer, - - render(element : ReactElement, callback: ?Function) { - if (!root) { - root = NoopRenderer.mountContainer(element, rootContainer, callback); + getChildren(rootID : string = DEFAULT_ROOT_ID) { + const container = rootContainers.get(rootID); + if (container) { + return container.children; } else { - NoopRenderer.updateContainer(element, root, callback); + return null; + } + }, + + // Shortcut for testing a single root + render(element : ReactElement, callback: ?Function) { + ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback); + }, + + renderToRootWithID(element : ReactElement, rootID : string, callback : ?Function) { + if (!roots.has(rootID)) { + const container = { rootID: rootID, children: [] }; + rootContainers.set(rootID, container); + const root = NoopRenderer.mountContainer(element, container, callback); + roots.set(rootID, root); + } else { + const root = roots.get(rootID); + if (root) { + NoopRenderer.updateContainer(element, root, callback); + } + } + }, + + unmountRootWithID(rootID : string) { + const root = roots.get(rootID); + roots.delete(rootID); + rootContainers.delete(rootID); + if (root) { + NoopRenderer.unmountContainer(root); } }, @@ -211,8 +238,10 @@ var ReactNoop = { }, // Logs the current state of the tree. - dumpTree() { - if (!root) { + dumpTree(rootID : string = DEFAULT_ROOT_ID) { + const root = roots.get(rootID); + const rootContainer = rootContainers.get(rootID); + if (!root || !rootContainer) { console.log('Nothing rendered yet.'); return; } diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js new file mode 100644 index 0000000000..4987ff390e --- /dev/null +++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js @@ -0,0 +1,103 @@ +/** + * 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('ReactIncrementalScheduling', () => { + beforeEach(() => { + React = require('React'); + ReactNoop = require('ReactNoop'); + }); + + function span(prop) { + return { type: 'span', children: [], prop }; + } + + it('schedules multiple roots', () => { + // Schedule one root + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.flushDeferredPri(15); + expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]); + + // Schedule two roots + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'b'); + // First scheduled one gets processed first + ReactNoop.flushDeferredPri(15); + expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]); + expect(ReactNoop.getChildren('b')).toEqual([]); + expect(ReactNoop.getChildren('c')).toEqual(null); + // Then the second one gets processed + ReactNoop.flushDeferredPri(15); + expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]); + expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]); + expect(ReactNoop.getChildren('c')).toEqual(null); + + // Schedule three roots + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'b'); + ReactNoop.renderToRootWithID(, 'c'); + // They get processed in the order they were scheduled + ReactNoop.flushDeferredPri(15); + expect(ReactNoop.getChildren('a')).toEqual([span('a:3')]); + expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]); + expect(ReactNoop.getChildren('c')).toEqual([]); + ReactNoop.flushDeferredPri(15); + expect(ReactNoop.getChildren('a')).toEqual([span('a:3')]); + expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]); + expect(ReactNoop.getChildren('c')).toEqual([]); + ReactNoop.flushDeferredPri(15); + expect(ReactNoop.getChildren('a')).toEqual([span('a:3')]); + expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]); + expect(ReactNoop.getChildren('c')).toEqual([span('c:3')]); + + // Schedule one root many times + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.flushDeferredPri(15); + expect(ReactNoop.getChildren('a')).toEqual([span('a:6')]); + }); + + it('works on roots with animation priority before deferred work', () => { + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'b'); + ReactNoop.renderToRootWithID(, 'c'); + ReactNoop.flush(); + expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]); + expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]); + expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]); + + // Schedule both deferred and animation work + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.performAnimationWork(() => { + ReactNoop.renderToRootWithID(, 'b'); + ReactNoop.renderToRootWithID(, 'c'); + }); + // Handle roots with animation work first + ReactNoop.flushDeferredPri(15); + expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]); + expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]); + expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]); + ReactNoop.flushDeferredPri(15); + expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]); + expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]); + expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]); + // Then handle deferred root + ReactNoop.flushDeferredPri(15); + expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]); + expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]); + expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]); + }); +}); diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js index 5116d0c5ec..1bb2088b96 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js @@ -46,13 +46,13 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(span()), ]); ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(span(), span()), ]); @@ -82,19 +82,19 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(span(), span('test')), ]); ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(span(), span(), div(), span('test')), ]); ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(span(), div(), span(), span('test')), ]); @@ -120,13 +120,13 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div('Hello'), ]); ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div('World', 'World', '!'), ]); @@ -152,13 +152,13 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(div(), span('Hello'), 'World'), ]); ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(), ]); @@ -199,7 +199,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(span('Class'), 'Trail'), ]); @@ -207,7 +207,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(span('Function'), 'Trail'), ]); @@ -215,13 +215,13 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div('Text', 'Trail'), ]); ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div('Trail'), ]); @@ -260,7 +260,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(span('Class'), 'Trail'), ]); @@ -268,7 +268,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(span('Function'), 'Trail'), ]); @@ -276,7 +276,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div('Trail'), ]); @@ -302,13 +302,13 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(div(span('Hello'), span('Hello')), span('Yo')), ]); ReactNoop.render(); ReactNoop.flushDeferredPri(35); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(div(span('Hello'), span('Hello')), span('Yo')), ]); @@ -333,20 +333,20 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(div(span('foo'))), ]); ReactNoop.render(); ReactNoop.flushDeferredPri(20); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(div(span('foo'))), ]); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(div(span('bar'))), ]); @@ -384,7 +384,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(div(span('Hi'), span('foo'))), ]); @@ -394,7 +394,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.flushDeferredPri(30); // The tree remains unchanged. - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(div(span('Hi'), span('foo'))), ]); @@ -408,7 +408,7 @@ describe('ReactIncrementalSideEffects', () => { // we should be able to reuse the reconciliation work that we already did // without restarting. The side-effects should still be replayed. - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(div(span('Hello'), span('World'))), ]); }); @@ -450,7 +450,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(div(span('Hi'), span('foo'))), ]); @@ -460,7 +460,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.flushDeferredPri(35); // The tree remains unchanged. - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(div(span('Hi'), span('foo'))), ]); @@ -474,7 +474,7 @@ describe('ReactIncrementalSideEffects', () => { // we should be able to reuse the reconciliation work that we already did // without restarting. The side-effects should still be replayed. - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(div(span('Hello'), span('World'))), ]); }); @@ -490,7 +490,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div(span(1)), ]); }); @@ -517,7 +517,7 @@ describe('ReactIncrementalSideEffects', () => { } ReactNoop.render(); ReactNoop.flushDeferredPri(40 + 25); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(0), div(/*the spans are down-prioritized and not rendered yet*/) @@ -525,14 +525,14 @@ describe('ReactIncrementalSideEffects', () => { ]); ReactNoop.render(); ReactNoop.flushDeferredPri(35 + 25); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(1), div(/*still not rendered yet*/) ), ]); ReactNoop.flushDeferredPri(30 + 25); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(1), div( @@ -542,10 +542,10 @@ describe('ReactIncrementalSideEffects', () => { ) ), ]); - var innerSpanA = ReactNoop.root.children[0].children[1].children[1]; + var innerSpanA = ReactNoop.getChildren()[0].children[1].children[1]; ReactNoop.render(); ReactNoop.flushDeferredPri(30 + 25); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(2), div( @@ -557,7 +557,7 @@ describe('ReactIncrementalSideEffects', () => { ]); ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(3), div( @@ -568,7 +568,7 @@ describe('ReactIncrementalSideEffects', () => { ), ]); - var innerSpanB = ReactNoop.root.children[0].children[1].children[1]; + var innerSpanB = ReactNoop.getChildren()[0].children[1].children[1]; // This should have been an update to an existing instance, not recreation. // We verify that by ensuring that the child instance was the same as // before. @@ -611,7 +611,7 @@ describe('ReactIncrementalSideEffects', () => { } ReactNoop.render(); ReactNoop.flushDeferredPri(65); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(0), div(/*the spans are down-prioritized and not rendered yet*/) @@ -623,7 +623,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flushDeferredPri(70); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(1), div(/*still not rendered yet*/) @@ -634,7 +634,7 @@ describe('ReactIncrementalSideEffects', () => { ops = []; ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(1), div( @@ -656,7 +656,7 @@ describe('ReactIncrementalSideEffects', () => { // way through. ReactNoop.render(); ReactNoop.flushDeferredPri(95); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(2), div( @@ -680,7 +680,7 @@ describe('ReactIncrementalSideEffects', () => { // way through. ReactNoop.render(); ReactNoop.flushDeferredPri(50); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(3), div( @@ -701,7 +701,7 @@ describe('ReactIncrementalSideEffects', () => { // We should now be able to reuse some of the work we've already done // and replay those side-effects. ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(3), div( @@ -753,7 +753,7 @@ describe('ReactIncrementalSideEffects', () => { } ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(0), div( @@ -770,7 +770,7 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flushDeferredPri(70); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( // Updated. span(1), @@ -793,7 +793,7 @@ describe('ReactIncrementalSideEffects', () => { // TODO: The cycles it takes to do this could be lowered with further // optimizations. ReactNoop.flushDeferredPri(60); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( // Updated. span(1), @@ -812,7 +812,7 @@ describe('ReactIncrementalSideEffects', () => { // However, once we render fully, we will have enough time to finish it all // at once. ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ div( span(1), div( @@ -845,12 +845,12 @@ describe('ReactIncrementalSideEffects', () => { ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ span('foo'), ]); let called = false; instance.setState({ text: 'bar' }, () => { - expect(ReactNoop.root.children).toEqual([ + expect(ReactNoop.getChildren()).toEqual([ span('bar'), ]); called = true; diff --git a/src/renderers/shared/fiber/__tests__/ReactTopLevelText-test.js b/src/renderers/shared/fiber/__tests__/ReactTopLevelText-test.js index 50092b8691..3382b8ddc6 100644 --- a/src/renderers/shared/fiber/__tests__/ReactTopLevelText-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactTopLevelText-test.js @@ -26,14 +26,14 @@ describe('ReactTopLevelText', () => { const Text = ({value}) => value; ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([{text: 'foo'}]); + expect(ReactNoop.getChildren()).toEqual([{text: 'foo'}]); }); it('should render a component returning numbers directly from render', () => { const Text = ({value}) => value; ReactNoop.render(); ReactNoop.flush(); - expect(ReactNoop.root.children).toEqual([{text: '10'}]); + expect(ReactNoop.getChildren()).toEqual([{text: '10'}]); }); });