[Fiber] Add some scheduling tests (#8171)

* Extend ReactNoop to handle multiple roots

This lets us test scheduling between the roots.

* Add a few first tests for scheduler
This commit is contained in:
Dan Abramov authored and GitHub committed 2016-11-01 14:35:01 +00:00
1 parent 08b4cc53b8
commit ab02a03cad
4 files changed
+193 -61

No files matched your search

+41 -12
View File
@@ -34,7 +34,7 @@ var scheduledDeferredCallback = null;
const TERMINAL_TAG = 99;
const TEXT_TAG = 98;
type Container = { rootID: number, children: Array<Instance | TextInstance> };
type Container = { rootID: string, children: Array<Instance | TextInstance> };
type Props = { prop: any };
type Instance = { tag: 99, type: string, id: number, children: Array<Instance | TextInstance>, 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 = '<default>';
var ReactNoop = {
root: rootContainer,
render(element : ReactElement<any>, 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<any>, callback: ?Function) {
ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback);
},
renderToRootWithID(element : ReactElement<any>, 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;
}
@@ -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(<span prop="a:1" />, 'a');
ReactNoop.flushDeferredPri(15);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
// Schedule two roots
ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:2" />, '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(<span prop="a:3" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:3" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:3" />, '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(<span prop="a:4" />, 'a');
ReactNoop.renderToRootWithID(<span prop="a:5" />, 'a');
ReactNoop.renderToRootWithID(<span prop="a:6" />, 'a');
ReactNoop.flushDeferredPri(15);
expect(ReactNoop.getChildren('a')).toEqual([span('a:6')]);
});
it('works on roots with animation priority before deferred work', () => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:1" />, '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(<span prop="a:2" />, 'a');
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:2" />, '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')]);
});
});
@@ -46,13 +46,13 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop.render(<Foo text="Hello" />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(span()),
]);
ReactNoop.render(<Foo text="World" />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(span(), span()),
]);
@@ -82,19 +82,19 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop.render(<Foo text="Hello" />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(span(), span('test')),
]);
ReactNoop.render(<Foo text="World" />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(span(), span(), div(), span('test')),
]);
ReactNoop.render(<Foo text="Hi" />);
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(<Foo text="Hello" />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div('Hello'),
]);
ReactNoop.render(<Foo text="World" />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div('World', 'World', '!'),
]);
@@ -152,13 +152,13 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop.render(<Foo show={true} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(div(), span('Hello'), 'World'),
]);
ReactNoop.render(<Foo show={false} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(),
]);
@@ -199,7 +199,7 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop.render(<Foo useClass={true} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(span('Class'), 'Trail'),
]);
@@ -207,7 +207,7 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop.render(<Foo useFunction={true} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(span('Function'), 'Trail'),
]);
@@ -215,13 +215,13 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop.render(<Foo useText={true} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div('Text', 'Trail'),
]);
ReactNoop.render(<Foo />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div('Trail'),
]);
@@ -260,7 +260,7 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop.render(<Foo useClass={true} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(span('Class'), 'Trail'),
]);
@@ -268,7 +268,7 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop.render(<Foo useFunction={true} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(span('Function'), 'Trail'),
]);
@@ -276,7 +276,7 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop.render(<Foo />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div('Trail'),
]);
@@ -302,13 +302,13 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop.render(<Foo text="Hello" />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(div(span('Hello'), span('Hello')), span('Yo')),
]);
ReactNoop.render(<Foo text="World" />);
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(<Foo text="foo" />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(div(span('foo'))),
]);
ReactNoop.render(<Foo text="bar" />);
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(<Foo text="foo" step={0} />);
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(<Foo text="foo" step={0} />);
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(<Foo />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(span(1)),
]);
});
@@ -517,7 +517,7 @@ describe('ReactIncrementalSideEffects', () => {
}
ReactNoop.render(<Foo tick={0} idx={0} />);
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(<Foo tick={1} idx={0} />);
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(<Foo tick={2} idx={1} />);
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(<Foo tick={3} idx={1} />);
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(<Foo tick={0} idx={0} />);
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(<Foo tick={1} idx={0} />);
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(<Foo tick={2} idx={1} />);
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(<Foo tick={3} idx={1} />);
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(<Foo tick={0} idx={0} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([
expect(ReactNoop.getChildren()).toEqual([
div(
span(0),
div(
@@ -770,7 +770,7 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop.render(<Foo tick={1} idx={1} />);
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(<Foo />);
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;
@@ -26,14 +26,14 @@ describe('ReactTopLevelText', () => {
const Text = ({value}) => value;
ReactNoop.render(<Text value="foo" />);
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(<Text value={10} />);
ReactNoop.flush();
expect(ReactNoop.root.children).toEqual([{text: '10'}]);
expect(ReactNoop.getChildren()).toEqual([{text: '10'}]);
});
});