Remove ReactNoop.flushDeferredPri and flushUnitsOfWork (#14934)

* Remove ReactNoop.flushDeferredPri and flushUnitsOfWork

Some of our older tests worked by counting how many times React checked
whether it should yield to the main thread, instead of something
publicly observable like how many times a component is rendered.

Our newer tests have converged on a style where we push into a log and
make assertions on the log. This pattern is less coupled to the
implementation while still being sufficient to test performance
optimizations, like resuming (whenever we add that back).

This commit removes flushDeferredPri and flushUnitsOfWork and upgrades
the affected tests.

* Remove shouldYieldToRenderer indirection

This wrapper is no longer necessary.
This commit is contained in:
Andrew Clark
2019-02-22 17:27:30 -08:00
committed by GitHub
parent 920b0bbb3c
commit ba708fa79b
13 changed files with 317 additions and 394 deletions
+18 -21
View File
@@ -747,36 +747,27 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
return NoopRenderer.findHostInstance(component);
},
flushDeferredPri(timeout: number = Infinity): Array<mixed> {
// The legacy version of this function decremented the timeout before
// returning the new time.
// TODO: Convert tests to use flushUnitsOfWork or flushAndYield instead.
const n = timeout / 5 - 1;
let values = [];
flush(): Array<mixed> {
let values = yieldedValues || [];
yieldedValues = null;
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const value of flushUnitsOfWork(n)) {
for (const value of flushUnitsOfWork(Infinity)) {
values.push(...value);
}
return values;
},
flush(): Array<mixed> {
return ReactNoop.flushUnitsOfWork(Infinity);
},
flushAndYield(
unitsOfWork: number = Infinity,
): Generator<Array<mixed>, void, void> {
return flushUnitsOfWork(unitsOfWork);
},
flushUnitsOfWork(n: number): Array<mixed> {
// TODO: Should only be used via a Jest plugin (like we do with the
// test renderer).
unstable_flushNumberOfYields(n: number): Array<mixed> {
let values = yieldedValues || [];
yieldedValues = null;
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const value of flushUnitsOfWork(n)) {
for (const value of flushUnitsOfWork(Infinity)) {
values.push(...value);
if (values.length >= n) {
break;
}
}
return values;
},
@@ -847,7 +838,13 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
},
flushExpired(): Array<mixed> {
return ReactNoop.flushUnitsOfWork(0);
let values = yieldedValues || [];
yieldedValues = null;
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const value of flushUnitsOfWork(0)) {
values.push(...value);
}
return values;
},
yield(value: mixed) {
+19 -38
View File
@@ -1214,7 +1214,7 @@ function workLoop(isYieldy) {
}
} else {
// Flush asynchronous work until there's a higher priority event
while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
@@ -2007,7 +2007,7 @@ function onSuspend(
msUntilTimeout: number,
): void {
root.expirationTime = rootExpirationTime;
if (msUntilTimeout === 0 && !shouldYieldToRenderer()) {
if (msUntilTimeout === 0 && !shouldYield()) {
// Don't wait an additional tick. Commit the tree immediately.
root.pendingCommitExpirationTime = suspendedExpirationTime;
root.finishedWork = finishedWork;
@@ -2204,43 +2204,24 @@ function findHighestPriorityRoot() {
nextFlushedExpirationTime = highestPriorityWork;
}
// TODO: This wrapper exists because many of the older tests (the ones that use
// flushDeferredPri) rely on the number of times `shouldYield` is called. We
// should get rid of it.
let didYield: boolean = false;
function shouldYieldToRenderer() {
if (didYield) {
return true;
}
if (shouldYield()) {
didYield = true;
return true;
}
return false;
}
function performAsyncWork(didTimeout) {
try {
if (didTimeout) {
// The callback timed out. That means at least one update has expired.
// Iterate through the root schedule. If they contain expired work, set
// the next render expiration time to the current time. This has the effect
// of flushing all expired work in a single batch, instead of flushing each
// level one at a time.
if (firstScheduledRoot !== null) {
recomputeCurrentRendererTime();
let root: FiberRoot = firstScheduledRoot;
do {
didExpireAtExpirationTime(root, currentRendererTime);
// The root schedule is circular, so this is never null.
root = (root.nextScheduledRoot: any);
} while (root !== firstScheduledRoot);
}
if (didTimeout) {
// The callback timed out. That means at least one update has expired.
// Iterate through the root schedule. If they contain expired work, set
// the next render expiration time to the current time. This has the effect
// of flushing all expired work in a single batch, instead of flushing each
// level one at a time.
if (firstScheduledRoot !== null) {
recomputeCurrentRendererTime();
let root: FiberRoot = firstScheduledRoot;
do {
didExpireAtExpirationTime(root, currentRendererTime);
// The root schedule is circular, so this is never null.
root = (root.nextScheduledRoot: any);
} while (root !== firstScheduledRoot);
}
performWork(NoWork, true);
} finally {
didYield = false;
}
performWork(NoWork, true);
}
function performSyncWork() {
@@ -2266,7 +2247,7 @@ function performWork(minExpirationTime: ExpirationTime, isYieldy: boolean) {
nextFlushedRoot !== null &&
nextFlushedExpirationTime !== NoWork &&
minExpirationTime <= nextFlushedExpirationTime &&
!(didYield && currentRendererTime > nextFlushedExpirationTime)
!(shouldYield() && currentRendererTime > nextFlushedExpirationTime)
) {
performWorkOnRoot(
nextFlushedRoot,
@@ -2414,7 +2395,7 @@ function performWorkOnRoot(
if (finishedWork !== null) {
// We've completed the root. Check the if we should yield one more time
// before committing.
if (!shouldYieldToRenderer()) {
if (!shouldYield()) {
// Still time left. Commit the root.
completeRoot(root, finishedWork, expirationTime);
} else {
@@ -39,10 +39,8 @@ describe('ReactIncremental', () => {
});
it('should render a simple component, in steps if needed', () => {
let renderCallbackCalled = false;
let barCalled = false;
function Bar() {
barCalled = true;
ReactNoop.yield('Bar');
return (
<span>
<div>Hello World</div>
@@ -50,26 +48,17 @@ describe('ReactIncremental', () => {
);
}
let fooCalled = false;
function Foo() {
fooCalled = true;
ReactNoop.yield('Foo');
return [<Bar key="a" isBar={true} />, <Bar key="b" isBar={true} />];
}
ReactNoop.render(<Foo />, () => (renderCallbackCalled = true));
expect(fooCalled).toBe(false);
expect(barCalled).toBe(false);
expect(renderCallbackCalled).toBe(false);
ReactNoop.render(<Foo />, () => ReactNoop.yield('callback'));
// Do one step of work.
ReactNoop.flushDeferredPri(7 + 5);
expect(fooCalled).toBe(true);
expect(barCalled).toBe(false);
expect(renderCallbackCalled).toBe(false);
expect(ReactNoop.flushNextYield()).toEqual(['Foo']);
// Do the rest of the work.
ReactNoop.flushDeferredPri(50);
expect(fooCalled).toBe(true);
expect(barCalled).toBe(true);
expect(renderCallbackCalled).toBe(true);
expect(ReactNoop.flush()).toEqual(['Bar', 'Bar', 'callback']);
});
it('updates a previous render', () => {
@@ -140,15 +129,13 @@ describe('ReactIncremental', () => {
});
it('can cancel partially rendered work and restart', () => {
let ops = [];
function Bar(props) {
ops.push('Bar');
ReactNoop.yield('Bar');
return <div>{props.children}</div>;
}
function Foo(props) {
ops.push('Foo');
ReactNoop.yield('Foo');
return (
<div>
<Bar>{props.text}</Bar>
@@ -161,34 +148,22 @@ describe('ReactIncremental', () => {
ReactNoop.render(<Foo text="foo" />);
ReactNoop.flush();
ops = [];
ReactNoop.render(<Foo text="bar" />);
// Flush part of the work
ReactNoop.flushDeferredPri(20 + 5);
expect(ops).toEqual(['Foo', 'Bar']);
ops = [];
ReactNoop.flushThrough(['Foo', 'Bar']);
// This will abort the previous work and restart
ReactNoop.flushSync(() => ReactNoop.render(null));
ReactNoop.render(<Foo text="baz" />);
ReactNoop.clearYields();
// Flush part of the new work
ReactNoop.flushDeferredPri(20 + 5);
expect(ops).toEqual(['Foo', 'Bar']);
ReactNoop.flushThrough(['Foo', 'Bar']);
// Flush the rest of the work which now includes the low priority
ReactNoop.flush(20);
expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
expect(ReactNoop.flush()).toEqual(['Bar']);
});
it('should call callbacks even if updates are aborted', () => {
const ops = [];
let inst;
class Foo extends React.Component {
@@ -215,32 +190,27 @@ describe('ReactIncremental', () => {
inst.setState(
() => {
ops.push('setState1');
ReactNoop.yield('setState1');
return {text: 'bar'};
},
() => ops.push('callback1'),
() => ReactNoop.yield('callback1'),
);
// Flush part of the work
ReactNoop.flushDeferredPri(20 + 5);
expect(ops).toEqual(['setState1']);
ReactNoop.flushThrough(['setState1']);
// This will abort the previous work and restart
ReactNoop.flushSync(() => ReactNoop.render(<Foo />));
inst.setState(
() => {
ops.push('setState2');
ReactNoop.yield('setState2');
return {text2: 'baz'};
},
() => ops.push('callback2'),
() => ReactNoop.yield('callback2'),
);
// Flush the rest of the work which now includes the low priority
ReactNoop.flush();
expect(ops).toEqual([
'setState1',
expect(ReactNoop.flush()).toEqual([
'setState1',
'setState2',
'callback1',
@@ -250,20 +220,18 @@ describe('ReactIncremental', () => {
});
it('can deprioritize unfinished work and resume it later', () => {
let ops = [];
function Bar(props) {
ops.push('Bar');
ReactNoop.yield('Bar');
return <div>{props.children}</div>;
}
function Middle(props) {
ops.push('Middle');
ReactNoop.yield('Middle');
return <span>{props.children}</span>;
}
function Foo(props) {
ops.push('Foo');
ReactNoop.yield('Foo');
return (
<div>
<Bar>{props.text}</Bar>
@@ -280,25 +248,20 @@ describe('ReactIncremental', () => {
// Init
ReactNoop.render(<Foo text="foo" />);
ReactNoop.flush();
expect(ops).toEqual(['Foo', 'Bar', 'Bar', 'Middle', 'Middle']);
ops = [];
expect(ReactNoop.flush()).toEqual([
'Foo',
'Bar',
'Bar',
'Middle',
'Middle',
]);
// Render part of the work. This should be enough to flush everything except
// the middle which has lower priority.
ReactNoop.render(<Foo text="bar" />);
ReactNoop.flushDeferredPri(40);
expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
ops = [];
ReactNoop.flushThrough(['Foo', 'Bar', 'Bar']);
// Flush only the remaining work
ReactNoop.flush();
expect(ops).toEqual(['Middle', 'Middle']);
expect(ReactNoop.flush()).toEqual(['Middle', 'Middle']);
});
it('can deprioritize a tree from without dropping work', () => {
@@ -1844,8 +1807,6 @@ describe('ReactIncremental', () => {
});
it('merges and masks context', () => {
const ops = [];
class Intl extends React.Component {
static childContextTypes = {
locale: PropTypes.string,
@@ -1856,7 +1817,7 @@ describe('ReactIncremental', () => {
};
}
render() {
ops.push('Intl ' + JSON.stringify(this.context));
ReactNoop.yield('Intl ' + JSON.stringify(this.context));
return this.props.children;
}
}
@@ -1871,7 +1832,7 @@ describe('ReactIncremental', () => {
};
}
render() {
ops.push('Router ' + JSON.stringify(this.context));
ReactNoop.yield('Router ' + JSON.stringify(this.context));
return this.props.children;
}
}
@@ -1881,7 +1842,7 @@ describe('ReactIncremental', () => {
locale: PropTypes.string,
};
render() {
ops.push('ShowLocale ' + JSON.stringify(this.context));
ReactNoop.yield('ShowLocale ' + JSON.stringify(this.context));
return this.context.locale;
}
}
@@ -1891,13 +1852,13 @@ describe('ReactIncremental', () => {
route: PropTypes.string,
};
render() {
ops.push('ShowRoute ' + JSON.stringify(this.context));
ReactNoop.yield('ShowRoute ' + JSON.stringify(this.context));
return this.context.route;
}
}
function ShowBoth(props, context) {
ops.push('ShowBoth ' + JSON.stringify(context));
ReactNoop.yield('ShowBoth ' + JSON.stringify(context));
return `${context.route} in ${context.locale}`;
}
ShowBoth.contextTypes = {
@@ -1907,14 +1868,14 @@ describe('ReactIncremental', () => {
class ShowNeither extends React.Component {
render() {
ops.push('ShowNeither ' + JSON.stringify(this.context));
ReactNoop.yield('ShowNeither ' + JSON.stringify(this.context));
return null;
}
}
class Indirection extends React.Component {
render() {
ops.push('Indirection ' + JSON.stringify(this.context));
ReactNoop.yield('Indirection ' + JSON.stringify(this.context));
return [
<ShowLocale key="a" />,
<ShowRoute key="b" />,
@@ -1927,7 +1888,6 @@ describe('ReactIncremental', () => {
}
}
ops.length = 0;
ReactNoop.render(
<Intl locale="fr">
<ShowLocale />
@@ -1936,18 +1896,18 @@ describe('ReactIncremental', () => {
</div>
</Intl>,
);
expect(ReactNoop.flush).toWarnDev(
expect(() =>
expect(ReactNoop.flush()).toEqual([
'Intl {}',
'ShowLocale {"locale":"fr"}',
'ShowBoth {"locale":"fr"}',
]),
).toWarnDev(
'Legacy context API has been detected within a strict-mode tree: \n\n' +
'Please update the following components: Intl, ShowBoth, ShowLocale',
{withoutStack: true},
);
expect(ops).toEqual([
'Intl {}',
'ShowLocale {"locale":"fr"}',
'ShowBoth {"locale":"fr"}',
]);
ops.length = 0;
ReactNoop.render(
<Intl locale="de">
<ShowLocale />
@@ -1956,14 +1916,12 @@ describe('ReactIncremental', () => {
</div>
</Intl>,
);
ReactNoop.flush();
expect(ops).toEqual([
expect(ReactNoop.flush()).toEqual([
'Intl {}',
'ShowLocale {"locale":"de"}',
'ShowBoth {"locale":"de"}',
]);
ops.length = 0;
ReactNoop.render(
<Intl locale="sv">
<ShowLocale />
@@ -1972,10 +1930,8 @@ describe('ReactIncremental', () => {
</div>
</Intl>,
);
ReactNoop.flushDeferredPri(15);
expect(ops).toEqual(['Intl {}']);
ReactNoop.flushThrough(['Intl {}']);
ops.length = 0;
ReactNoop.render(
<Intl locale="en">
<ShowLocale />
@@ -1985,26 +1941,27 @@ describe('ReactIncremental', () => {
<ShowBoth />
</Intl>,
);
expect(ReactNoop.flush).toWarnDev(
expect(() =>
expect(ReactNoop.flush()).toEqual([
'ShowLocale {"locale":"sv"}',
'ShowBoth {"locale":"sv"}',
'Intl {}',
'ShowLocale {"locale":"en"}',
'Router {}',
'Indirection {}',
'ShowLocale {"locale":"en"}',
'ShowRoute {"route":"/about"}',
'ShowNeither {}',
'Intl {}',
'ShowBoth {"locale":"ru","route":"/about"}',
'ShowBoth {"locale":"en","route":"/about"}',
'ShowBoth {"locale":"en"}',
]),
).toWarnDev(
'Legacy context API has been detected within a strict-mode tree: \n\n' +
'Please update the following components: Router, ShowRoute',
{withoutStack: true},
);
expect(ops).toEqual([
'ShowLocale {"locale":"sv"}',
'ShowBoth {"locale":"sv"}',
'Intl {}',
'ShowLocale {"locale":"en"}',
'Router {}',
'Indirection {}',
'ShowLocale {"locale":"en"}',
'ShowRoute {"route":"/about"}',
'ShowNeither {}',
'Intl {}',
'ShowBoth {"locale":"ru","route":"/about"}',
'ShowBoth {"locale":"en","route":"/about"}',
'ShowBoth {"locale":"en"}',
]);
});
it('does not leak own context into context provider', () => {
@@ -2080,8 +2037,6 @@ describe('ReactIncremental', () => {
});
it('provides context when reusing work', () => {
const ops = [];
class Intl extends React.Component {
static childContextTypes = {
locale: PropTypes.string,
@@ -2092,7 +2047,7 @@ describe('ReactIncremental', () => {
};
}
render() {
ops.push('Intl ' + JSON.stringify(this.context));
ReactNoop.yield('Intl ' + JSON.stringify(this.context));
return this.props.children;
}
}
@@ -2102,12 +2057,11 @@ describe('ReactIncremental', () => {
locale: PropTypes.string,
};
render() {
ops.push('ShowLocale ' + JSON.stringify(this.context));
ReactNoop.yield('ShowLocale ' + JSON.stringify(this.context));
return this.context.locale;
}
}
ops.length = 0;
ReactNoop.render(
<Intl locale="fr">
<ShowLocale />
@@ -2120,24 +2074,23 @@ describe('ReactIncremental', () => {
<ShowLocale />
</Intl>,
);
ReactNoop.flushDeferredPri(40);
expect(ops).toEqual([
ReactNoop.flushThrough([
'Intl {}',
'ShowLocale {"locale":"fr"}',
'ShowLocale {"locale":"fr"}',
]);
ops.length = 0;
expect(ReactNoop.flush).toWarnDev(
expect(() =>
expect(ReactNoop.flush()).toEqual([
'ShowLocale {"locale":"fr"}',
'Intl {}',
'ShowLocale {"locale":"ru"}',
]),
).toWarnDev(
'Legacy context API has been detected within a strict-mode tree: \n\n' +
'Please update the following components: Intl, ShowLocale',
{withoutStack: true},
);
expect(ops).toEqual([
'ShowLocale {"locale":"fr"}',
'Intl {}',
'ShowLocale {"locale":"ru"}',
]);
});
it('reads context when setState is below the provider', () => {
@@ -318,7 +318,7 @@ describe('ReactIncrementalErrorHandling', () => {
);
}
ReactNoop.render(<Parent />);
ReactNoop.render(<Parent />, () => ReactNoop.yield('commit'));
// Render the bad component asynchronously
ReactNoop.flushThrough(['Parent', 'BadRender']);
@@ -326,10 +326,9 @@ describe('ReactIncrementalErrorHandling', () => {
// Finish the rest of the async work
ReactNoop.flushThrough(['Sibling']);
// Rendering two more units of work should be enough to trigger the retry
// and synchronously throw an error.
// React retries once, synchronously, before throwing.
ops = [];
expect(() => ReactNoop.flushUnitsOfWork(2)).toThrow('oops');
expect(() => ReactNoop.flushNextYield()).toThrow('oops');
expect(ops).toEqual(['Parent', 'BadRender', 'Sibling']);
});
@@ -409,7 +408,7 @@ describe('ReactIncrementalErrorHandling', () => {
<BrokenRender />
</ErrorBoundary>,
);
ReactNoop.flushDeferredPri();
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello.')]);
});
@@ -588,20 +587,19 @@ describe('ReactIncrementalErrorHandling', () => {
});
it('propagates an error from a noop error boundary during partial deferred mounting', () => {
const ops = [];
class RethrowErrorBoundary extends React.Component {
componentDidCatch(error) {
ops.push('RethrowErrorBoundary componentDidCatch');
ReactNoop.yield('RethrowErrorBoundary componentDidCatch');
throw error;
}
render() {
ops.push('RethrowErrorBoundary render');
ReactNoop.yield('RethrowErrorBoundary render');
return this.props.children;
}
}
function BrokenRender() {
ops.push('BrokenRender');
ReactNoop.yield('BrokenRender');
throw new Error('Hello');
}
@@ -611,16 +609,12 @@ describe('ReactIncrementalErrorHandling', () => {
</RethrowErrorBoundary>,
);
ReactNoop.flushDeferredPri(15);
expect(ops).toEqual(['RethrowErrorBoundary render']);
ReactNoop.flushThrough(['RethrowErrorBoundary render']);
ops.length = 0;
expect(() => {
ReactNoop.flush();
}).toThrow('Hello');
expect(ops).toEqual([
'BrokenRender',
expect(ReactNoop.clearYields()).toEqual([
// React retries one more time
'RethrowErrorBoundary render',
'BrokenRender',
@@ -1526,7 +1520,7 @@ describe('ReactIncrementalErrorHandling', () => {
<BrokenRender />
</ErrorBoundary>,
);
ReactNoop.flushDeferredPri();
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([
span(
'Caught an error:\n' +
@@ -1562,7 +1556,7 @@ describe('ReactIncrementalErrorHandling', () => {
<BrokenRender />
</ErrorBoundary>,
);
ReactNoop.flushDeferredPri();
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello')]);
});
@@ -50,7 +50,7 @@ describe('ReactIncrementalErrorLogging', () => {
</span>
</div>,
);
expect(ReactNoop.flushDeferredPri).toThrowError('constructor error');
expect(() => ReactNoop.flush()).toThrowError('constructor error');
expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith(
__DEV__
@@ -86,7 +86,7 @@ describe('ReactIncrementalErrorLogging', () => {
</span>
</div>,
);
expect(ReactNoop.flushDeferredPri).toThrowError('componentDidMount error');
expect(() => ReactNoop.flush()).toThrowError('componentDidMount error');
expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith(
__DEV__
@@ -125,7 +125,7 @@ describe('ReactIncrementalErrorLogging', () => {
</span>
</div>,
);
expect(ReactNoop.flushDeferredPri).toThrow('render error');
expect(() => ReactNoop.flush()).toThrow('render error');
expect(logCapturedErrorCalls.length).toBe(1);
expect(logCapturedErrorCalls[0]).toEqual(
__DEV__
@@ -117,6 +117,7 @@ describe('ReactDebugFiberPerf', () => {
require('shared/ReactFeatureFlags').enableUserTimingAPI = true;
require('shared/ReactFeatureFlags').enableProfilerTimer = false;
require('shared/ReactFeatureFlags').replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
require('shared/ReactFeatureFlags').debugRenderPhaseSideEffectsForStrictMode = false;
// Import after the polyfill is set up:
React = require('react');
@@ -397,12 +398,21 @@ describe('ReactDebugFiberPerf', () => {
it('measures deferred work in chunks', () => {
class A extends React.Component {
render() {
ReactNoop.yield('A');
return <div>{this.props.children}</div>;
}
}
class B extends React.Component {
render() {
ReactNoop.yield('B');
return <div>{this.props.children}</div>;
}
}
class C extends React.Component {
render() {
ReactNoop.yield('C');
return <div>{this.props.children}</div>;
}
}
@@ -415,14 +425,15 @@ describe('ReactDebugFiberPerf', () => {
<B>
<Child />
</B>
<C>
<Child />
</C>
</Parent>,
);
addComment('Start mounting Parent and A');
ReactNoop.flushDeferredPri(40);
addComment('Mount B just a little (but not enough to memoize)');
ReactNoop.flushDeferredPri(10);
addComment('Complete B and Parent');
ReactNoop.flushDeferredPri();
addComment('Start rendering through B');
ReactNoop.flushThrough(['A', 'B']);
addComment('Complete the rest');
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
@@ -632,11 +643,12 @@ describe('ReactDebugFiberPerf', () => {
it('warns if an in-progress update is interrupted', () => {
function Foo() {
ReactNoop.yield('Foo');
return <span />;
}
ReactNoop.render(<Foo />);
ReactNoop.flushUnitsOfWork(2);
ReactNoop.flushNextYield();
ReactNoop.flushSync(() => {
ReactNoop.render(<Foo />);
});
@@ -24,8 +24,6 @@ describe('ReactIncrementalReflection', () => {
});
it('handles isMounted even when the initial render is deferred', () => {
let ops = [];
const instances = [];
class Component extends React.Component {
@@ -36,10 +34,10 @@ describe('ReactIncrementalReflection', () => {
}
UNSAFE_componentWillMount() {
instances.push(this);
ops.push('componentWillMount', this._isMounted());
ReactNoop.yield('componentWillMount: ' + this._isMounted());
}
componentDidMount() {
ops.push('componentDidMount', this._isMounted());
ReactNoop.yield('componentDidMount: ' + this._isMounted());
}
render() {
return <span />;
@@ -53,29 +51,23 @@ describe('ReactIncrementalReflection', () => {
ReactNoop.render(<Foo />);
// Render part way through but don't yet commit the updates.
ReactNoop.flushDeferredPri(20);
expect(ops).toEqual(['componentWillMount', false]);
ReactNoop.flushThrough(['componentWillMount: false']);
expect(instances[0]._isMounted()).toBe(false);
ops = [];
// Render the rest and commit the updates.
expect(ReactNoop.flush).toWarnDev(
expect(() =>
expect(ReactNoop.flush()).toEqual(['componentDidMount: true']),
).toWarnDev(
'componentWillMount: Please update the following components ' +
'to use componentDidMount instead: Component',
{withoutStack: true},
);
expect(ops).toEqual(['componentDidMount', true]);
expect(instances[0]._isMounted()).toBe(true);
});
it('handles isMounted when an unmount is deferred', () => {
let ops = [];
const instances = [];
class Component extends React.Component {
@@ -86,16 +78,16 @@ describe('ReactIncrementalReflection', () => {
instances.push(this);
}
componentWillUnmount() {
ops.push('componentWillUnmount', this._isMounted());
ReactNoop.yield('componentWillUnmount: ' + this._isMounted());
}
render() {
ops.push('Component');
ReactNoop.yield('Component');
return <span />;
}
}
function Other() {
ops.push('Other');
ReactNoop.yield('Other');
return <span />;
}
@@ -104,38 +96,28 @@ describe('ReactIncrementalReflection', () => {
}
ReactNoop.render(<Foo mount={true} />);
expect(ReactNoop.flush).toWarnDev(
expect(() => expect(ReactNoop.flush()).toEqual(['Component'])).toWarnDev(
'componentWillMount: Please update the following components ' +
'to use componentDidMount instead: Component',
{withoutStack: true},
);
expect(ops).toEqual(['Component']);
ops = [];
expect(instances[0]._isMounted()).toBe(true);
ReactNoop.render(<Foo mount={false} />);
// Render part way through but don't yet commit the updates so it is not
// fully unmounted yet.
ReactNoop.flushDeferredPri(20);
expect(ops).toEqual(['Other']);
ops = [];
ReactNoop.flushThrough(['Other']);
expect(instances[0]._isMounted()).toBe(true);
// Finish flushing the unmount.
ReactNoop.flush();
expect(ops).toEqual(['componentWillUnmount', true]);
expect(ReactNoop.flush()).toEqual(['componentWillUnmount: true']);
expect(instances[0]._isMounted()).toBe(false);
});
it('finds no node before insertion and correct node before deletion', () => {
let ops = [];
let classInstance = null;
function findInstance(inst) {
@@ -154,22 +136,22 @@ describe('ReactIncrementalReflection', () => {
class Component extends React.Component {
UNSAFE_componentWillMount() {
classInstance = this;
ops.push('componentWillMount', findInstance(this));
ReactNoop.yield(['componentWillMount', findInstance(this)]);
}
componentDidMount() {
ops.push('componentDidMount', findInstance(this));
ReactNoop.yield(['componentDidMount', findInstance(this)]);
}
UNSAFE_componentWillUpdate() {
ops.push('componentWillUpdate', findInstance(this));
ReactNoop.yield(['componentWillUpdate', findInstance(this)]);
}
componentDidUpdate() {
ops.push('componentDidUpdate', findInstance(this));
ReactNoop.yield(['componentDidUpdate', findInstance(this)]);
}
componentWillUnmount() {
ops.push('componentWillUnmount', findInstance(this));
ReactNoop.yield(['componentWillUnmount', findInstance(this)]);
}
render() {
ops.push('render');
ReactNoop.yield('render');
return this.props.step < 2 ? (
<span ref={ref => (this.span = ref)} />
) : this.props.step === 2 ? (
@@ -182,7 +164,7 @@ describe('ReactIncrementalReflection', () => {
function Sibling() {
// Sibling is used to assert that we've rendered past the first component.
ops.push('render sibling');
ReactNoop.yield('render sibling');
return <span />;
}
@@ -192,23 +174,22 @@ describe('ReactIncrementalReflection', () => {
ReactNoop.render(<Foo step={0} />);
// Flush past Component but don't complete rendering everything yet.
ReactNoop.flushDeferredPri(30);
expect(ops).toEqual([
'componentWillMount',
null,
ReactNoop.flushThrough([
['componentWillMount', null],
'render',
'render sibling',
]);
ops = [];
expect(classInstance).toBeDefined();
// The instance has been complete but is still not committed so it should
// not find any host nodes in it.
expect(findInstance(classInstance)).toBe(null);
expect(ReactNoop.flush).toWarnDev(
expect(() =>
expect(ReactNoop.flush()).toEqual([
['componentDidMount', classInstance.span],
]),
).toWarnDev(
'componentWillMount: Please update the following components ' +
'to use componentDidMount instead: Component' +
'\n\ncomponentWillUpdate: Please update the following components ' +
@@ -221,79 +202,54 @@ describe('ReactIncrementalReflection', () => {
expect(findInstance(classInstance)).toBe(hostSpan);
expect(ops).toEqual(['componentDidMount', hostSpan]);
ops = [];
// Flush next step which will cause an update but not yet render a new host
// node.
ReactNoop.render(<Foo step={1} />);
ReactNoop.flush();
expect(ops).toEqual([
'componentWillUpdate',
hostSpan,
expect(ReactNoop.flush()).toEqual([
['componentWillUpdate', hostSpan],
'render',
'render sibling',
'componentDidUpdate',
hostSpan,
['componentDidUpdate', hostSpan],
]);
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
ops = [];
// The next step will render a new host node but won't get committed yet.
// We expect this to mutate the original Fiber.
ReactNoop.render(<Foo step={2} />);
ReactNoop.flushDeferredPri(30);
expect(ops).toEqual([
'componentWillUpdate',
hostSpan,
ReactNoop.flushThrough([
['componentWillUpdate', hostSpan],
'render',
'render sibling',
]);
ops = [];
// This should still be the host span.
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
// When we finally flush the tree it will get committed.
ReactNoop.flush();
expect(ReactNoop.flush()).toEqual([
['componentDidUpdate', classInstance.div],
]);
const hostDiv = classInstance.div;
expect(hostDiv).toBeDefined();
expect(hostSpan).not.toBe(hostDiv);
expect(ops).toEqual(['componentDidUpdate', hostDiv]);
ops = [];
// We should now find the new host node.
expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
// Render to null but don't commit it yet.
ReactNoop.render(<Foo step={3} />);
ReactNoop.flushDeferredPri(25);
expect(ops).toEqual([
'componentWillUpdate',
hostDiv,
ReactNoop.flushThrough([
['componentWillUpdate', hostDiv],
'render',
'render sibling',
]);
ops = [];
// This should still be the host div since the deletion is not committed.
expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
ReactNoop.flush();
expect(ops).toEqual(['componentDidUpdate', null]);
expect(ReactNoop.flush()).toEqual([['componentDidUpdate', null]]);
// This should still be the host div since the deletion is not committed.
expect(ReactNoop.findInstance(classInstance)).toBe(null);
@@ -302,11 +258,8 @@ describe('ReactIncrementalReflection', () => {
ReactNoop.render(<Foo step={4} />);
ReactNoop.flush();
ops = [];
// Unmount the component.
ReactNoop.render([]);
ReactNoop.flush();
expect(ops).toEqual(['componentWillUnmount', hostDiv]);
expect(ReactNoop.flush()).toEqual([['componentWillUnmount', hostDiv]]);
});
});
@@ -31,7 +31,7 @@ describe('ReactIncrementalScheduling', () => {
ReactNoop.render(<span prop="1" />);
expect(ReactNoop.getChildren()).toEqual([]);
ReactNoop.flushDeferredPri();
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span('1')]);
});
@@ -86,71 +86,99 @@ describe('ReactIncrementalScheduling', () => {
});
it('works on deferred roots in the order they were scheduled', () => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
const {useEffect} = React;
function Text({text}) {
useEffect(
() => {
ReactNoop.yield(text);
},
[text],
);
return text;
}
ReactNoop.act(() => {
ReactNoop.renderToRootWithID(<Text text="a:1" />, 'a');
ReactNoop.renderToRootWithID(<Text text="b:1" />, 'b');
ReactNoop.renderToRootWithID(<Text text="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')]);
expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1');
expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:1');
// Schedule deferred work in the reverse order
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
ReactNoop.act(() => {
ReactNoop.renderToRootWithID(<Text text="c:2" />, 'c');
ReactNoop.renderToRootWithID(<Text text="b:2" />, 'b');
});
// Ensure it starts in the order it was scheduled
ReactNoop.flushDeferredPri(15 + 5);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
ReactNoop.flushThrough(['c:2']);
expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1');
expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
// Schedule last bit of work, it will get processed the last
ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
ReactNoop.act(() => {
ReactNoop.renderToRootWithID(<Text text="a:2" />, 'a');
});
// Keep performing work in the order it was scheduled
ReactNoop.flushDeferredPri(15 + 5);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
ReactNoop.flushDeferredPri(15 + 5);
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
ReactNoop.flushThrough(['b:2']);
expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2');
expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
ReactNoop.flushThrough(['a:2']);
expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:2');
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2');
expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
});
it('schedules sync updates when inside componentDidMount/Update', () => {
let instance;
let ops = [];
class Foo extends React.Component {
state = {tick: 0};
componentDidMount() {
ops.push('componentDidMount (before setState): ' + this.state.tick);
ReactNoop.yield(
'componentDidMount (before setState): ' + this.state.tick,
);
this.setState({tick: 1});
// We're in a batch. Update hasn't flushed yet.
ops.push('componentDidMount (after setState): ' + this.state.tick);
ReactNoop.yield(
'componentDidMount (after setState): ' + this.state.tick,
);
}
componentDidUpdate() {
ops.push('componentDidUpdate: ' + this.state.tick);
ReactNoop.yield('componentDidUpdate: ' + this.state.tick);
if (this.state.tick === 2) {
ops.push('componentDidUpdate (before setState): ' + this.state.tick);
ReactNoop.yield(
'componentDidUpdate (before setState): ' + this.state.tick,
);
this.setState({tick: 3});
ops.push('componentDidUpdate (after setState): ' + this.state.tick);
ReactNoop.yield(
'componentDidUpdate (after setState): ' + this.state.tick,
);
// We're in a batch. Update hasn't flushed yet.
}
}
render() {
ops.push('render: ' + this.state.tick);
ReactNoop.yield('render: ' + this.state.tick);
instance = this;
return <span prop={this.state.tick} />;
}
}
ReactNoop.render(<Foo />);
// Render without committing
ReactNoop.flushThrough(['render: 0']);
ReactNoop.flushDeferredPri(20 + 5);
expect(ops).toEqual([
'render: 0',
// Do one more unit of work to commit
expect(ReactNoop.flushNextYield()).toEqual([
'componentDidMount (before setState): 0',
'componentDidMount (after setState): 0',
// If the setState inside componentDidMount were deferred, there would be
@@ -159,12 +187,9 @@ describe('ReactIncrementalScheduling', () => {
'componentDidUpdate: 1',
]);
ops = [];
instance.setState({tick: 2});
ReactNoop.flushDeferredPri(20 + 5);
expect(ops).toEqual([
'render: 2',
ReactNoop.flushThrough(['render: 2']);
expect(ReactNoop.flushNextYield()).toEqual([
'componentDidUpdate: 2',
'componentDidUpdate (before setState): 2',
'componentDidUpdate (after setState): 2',
@@ -254,17 +279,18 @@ describe('ReactIncrementalScheduling', () => {
});
}
render() {
ReactNoop.yield('Foo');
return <span prop={this.state.step} />;
}
}
ReactNoop.render(<Foo />);
// This should be just enough to complete all the work, but not enough to
// commit it.
ReactNoop.flushDeferredPri(20);
ReactNoop.flushThrough(['Foo']);
expect(ReactNoop.getChildren()).toEqual([]);
// Do one more unit of work.
ReactNoop.flushDeferredPri(10);
ReactNoop.flushNextYield();
// The updates should all be flushed with Task priority
expect(ReactNoop.getChildren()).toEqual([span(5)]);
});
@@ -373,10 +373,12 @@ describe('ReactIncrementalSideEffects', () => {
it('does not update child nodes if a flush is aborted', () => {
function Bar(props) {
ReactNoop.yield('Bar');
return <span prop={props.text} />;
}
function Foo(props) {
ReactNoop.yield('Foo');
return (
<div>
<div>
@@ -395,7 +397,9 @@ describe('ReactIncrementalSideEffects', () => {
]);
ReactNoop.render(<Foo text="World" />);
ReactNoop.flushDeferredPri(35);
// Flush some of the work without committing
ReactNoop.flushThrough(['Foo', 'Bar']);
expect(ReactNoop.getChildren()).toEqual([
div(div(span('Hello'), span('Hello')), span('Yo')),
]);
@@ -403,10 +407,12 @@ describe('ReactIncrementalSideEffects', () => {
it('preserves a previously rendered node when deprioritized', () => {
function Middle(props) {
ReactNoop.yield('Middle');
return <span prop={props.children} />;
}
function Foo(props) {
ReactNoop.yield('Foo');
return (
<div>
<div hidden={true}>
@@ -427,9 +433,8 @@ describe('ReactIncrementalSideEffects', () => {
</div>,
);
ReactNoop.render(<Foo text="bar" />);
ReactNoop.flushDeferredPri(20);
ReactNoop.render(<Foo text="bar" />, () => ReactNoop.yield('commit'));
ReactNoop.flushThrough(['Foo', 'commit']);
expect(ReactNoop.getChildrenAsJSX()).toEqual(
<div>
<div hidden={true}>
@@ -437,8 +442,8 @@ describe('ReactIncrementalSideEffects', () => {
</div>
</div>,
);
ReactNoop.flush();
ReactNoop.flush();
expect(ReactNoop.getChildrenAsJSX()).toEqual(
<div>
<div hidden={true}>
@@ -450,6 +455,7 @@ describe('ReactIncrementalSideEffects', () => {
it('can reuse side-effects after being preempted', () => {
function Bar(props) {
ReactNoop.yield('Bar');
return <span prop={props.children} />;
}
@@ -461,6 +467,7 @@ describe('ReactIncrementalSideEffects', () => {
);
function Foo(props) {
ReactNoop.yield('Foo');
return (
<div hidden={true}>
{props.step === 0 ? (
@@ -490,8 +497,10 @@ describe('ReactIncrementalSideEffects', () => {
// Make a quick update which will schedule low priority work to
// update the middle content.
ReactNoop.render(<Foo text="bar" step={1} />);
ReactNoop.flushDeferredPri(30);
ReactNoop.render(<Foo text="bar" step={1} />, () =>
ReactNoop.yield('commit'),
);
ReactNoop.flushThrough(['Foo', 'commit', 'Bar']);
// The tree remains unchanged.
expect(ReactNoop.getChildrenAsJSX()).toEqual(
@@ -529,6 +538,7 @@ describe('ReactIncrementalSideEffects', () => {
return this.props.children !== nextProps.children;
}
render() {
ReactNoop.yield('Bar');
return <span prop={this.props.children} />;
}
}
@@ -538,6 +548,7 @@ describe('ReactIncrementalSideEffects', () => {
return this.props.step !== nextProps.step;
}
render() {
ReactNoop.yield('Content');
return (
<div>
<Bar>{this.props.step === 0 ? 'Hi' : 'Hello'}</Bar>
@@ -548,6 +559,7 @@ describe('ReactIncrementalSideEffects', () => {
}
function Foo(props) {
ReactNoop.yield('Foo');
return (
<div hidden={true}>
<Content step={props.step} text={props.text} />
@@ -571,7 +583,7 @@ describe('ReactIncrementalSideEffects', () => {
// Make a quick update which will schedule low priority work to
// update the middle content.
ReactNoop.render(<Foo text="bar" step={1} />);
ReactNoop.flushDeferredPri(35);
ReactNoop.flushThrough(['Foo', 'Content', 'Bar']);
// The tree remains unchanged.
expect(ReactNoop.getChildrenAsJSX()).toEqual(
@@ -587,7 +599,7 @@ describe('ReactIncrementalSideEffects', () => {
// render some higher priority work. The middle content will bailout so
// it remains untouched which means that it should reuse it next time.
ReactNoop.render(<Foo text="foo" step={1} />);
ReactNoop.flush(30);
ReactNoop.flush();
// Since we did nothing to the middle subtree during the interruption,
// we should be able to reuse the reconciliation work that we already did
@@ -605,34 +617,34 @@ describe('ReactIncrementalSideEffects', () => {
it('can update a completed tree before it has a chance to commit', () => {
function Foo(props) {
ReactNoop.yield('Foo');
return <span prop={props.step} />;
}
ReactNoop.render(<Foo step={1} />);
// This should be just enough to complete the tree without committing it
ReactNoop.flushDeferredPri(20);
expect(ReactNoop.getChildren()).toEqual([]);
// To confirm, perform one more unit of work. The tree should now be flushed.
// (ReactNoop decrements the time remaining by 5 *before* returning it from
// the deadline, so to perform n units of work, you need to give it 5n + 5.
// TODO: This is confusing. Decrement it after.)
ReactNoop.flushDeferredPri(10);
expect(ReactNoop.getChildren()).toEqual([span(1)]);
ReactNoop.flushThrough(['Foo']);
expect(ReactNoop.getChildrenAsJSX()).toEqual(null);
// To confirm, perform one more unit of work. The tree should now
// be flushed.
ReactNoop.flushNextYield();
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
ReactNoop.render(<Foo step={2} />);
// This should be just enough to complete the tree without committing it
ReactNoop.flushDeferredPri(20);
expect(ReactNoop.getChildren()).toEqual([span(1)]);
ReactNoop.flushThrough(['Foo']);
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
// This time, before we commit the tree, we update the root component with
// new props
ReactNoop.render(<Foo step={3} />);
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
// Now let's commit. We already had a commit that was pending, which will
// render 2.
ReactNoop.flushDeferredPri(10);
expect(ReactNoop.getChildren()).toEqual([span(2)]);
ReactNoop.flushNextYield();
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={2} />);
// If we flush the rest of the work, we should get another commit that
// renders 3. If it renders 2 again, that means an update was dropped.
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(3)]);
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={3} />);
});
it('updates a child even though the old props is empty', () => {
@@ -875,8 +887,6 @@ describe('ReactIncrementalSideEffects', () => {
});
it('deprioritizes setStates that happens within a deprioritized tree', () => {
let ops = [];
const barInstances = [];
class Bar extends React.Component {
@@ -889,12 +899,12 @@ describe('ReactIncrementalSideEffects', () => {
this.setState({active: true});
}
render() {
ops.push('Bar');
ReactNoop.yield('Bar');
return <span prop={this.state.active ? 'X' : this.props.idx} />;
}
}
function Foo(props) {
ops.push('Foo');
ReactNoop.yield('Foo');
return (
<div>
<span prop={props.tick} />
@@ -907,7 +917,7 @@ describe('ReactIncrementalSideEffects', () => {
);
}
ReactNoop.render(<Foo tick={0} idx={0} />);
ReactNoop.flush();
expect(ReactNoop.flush()).toEqual(['Foo', 'Bar', 'Bar', 'Bar']);
expect(ReactNoop.getChildrenAsJSX()).toEqual(
<div>
<span prop={0} />
@@ -919,12 +929,8 @@ describe('ReactIncrementalSideEffects', () => {
</div>,
);
expect(ops).toEqual(['Foo', 'Bar', 'Bar', 'Bar']);
ops = [];
ReactNoop.render(<Foo tick={1} idx={1} />);
ReactNoop.flushDeferredPri(70 + 5);
ReactNoop.flushThrough(['Foo', 'Bar', 'Bar']);
expect(ReactNoop.getChildrenAsJSX()).toEqual(
<div>
{/* Updated */}
@@ -937,16 +943,12 @@ describe('ReactIncrementalSideEffects', () => {
</div>,
);
expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
ops = [];
barInstances[0].activate();
// This should not be enough time to render the content of all the hidden
// items. Including the set state since that is deprioritized.
// TODO: The cycles it takes to do this could be lowered with further
// optimizations.
ReactNoop.flushDeferredPri(35);
// ReactNoop.flushDeferredPri(35);
ReactNoop.flushThrough(['Bar']);
expect(ReactNoop.getChildrenAsJSX()).toEqual(
<div>
{/* Updated */}
@@ -960,12 +962,9 @@ describe('ReactIncrementalSideEffects', () => {
</div>,
);
expect(ops).toEqual(['Bar']);
ops = [];
// However, once we render fully, we will have enough time to finish it all
// at once.
ReactNoop.flush();
expect(ReactNoop.flush()).toEqual(['Bar', 'Bar', 'Bar']);
expect(ReactNoop.getChildrenAsJSX()).toEqual(
<div>
<span prop={1} />
@@ -977,8 +976,6 @@ describe('ReactIncrementalSideEffects', () => {
</div>
</div>,
);
expect(ops).toEqual(['Bar', 'Bar', 'Bar']);
});
// TODO: Test that side-effects are not cut off when a work in progress node
// moves to "current" without flushing due to having lower priority. Does this
@@ -385,7 +385,7 @@ describe('ReactIncrementalTriangle', () => {
ReactNoop.flushSync(() => {
switch (action.type) {
case FLUSH:
ReactNoop.flushUnitsOfWork(action.unitsOfWork);
ReactNoop.unstable_flushNumberOfYields(action.unitsOfWork);
break;
case FLUSH_ALL:
ReactNoop.flush();
@@ -32,6 +32,7 @@ describe('ReactIncrementalUpdates', () => {
class Foo extends React.Component {
state = {};
componentDidMount() {
ReactNoop.yield('commit');
ReactNoop.deferredUpdates(() => {
// Has low priority
this.setState({b: 'b'});
@@ -47,7 +48,7 @@ describe('ReactIncrementalUpdates', () => {
}
ReactNoop.render(<Foo />);
ReactNoop.flushDeferredPri(25);
ReactNoop.flushThrough(['commit']);
expect(state).toEqual({a: 'a'});
ReactNoop.flush();
expect(state).toEqual({a: 'a', b: 'b', c: 'c'});
@@ -1691,6 +1691,7 @@ describe('ReactNewContext', () => {
return false;
}
render() {
ReactNoop.yield();
if (this.props.depth >= this.props.maxDepth) {
return null;
}
@@ -1771,7 +1772,7 @@ describe('ReactNewContext', () => {
ReactNoop.flush();
break;
case FLUSH:
ReactNoop.flushUnitsOfWork(action.unitsOfWork);
ReactNoop.unstable_flushNumberOfYields(action.unitsOfWork);
break;
case UPDATE:
finalExpectedValues = {
@@ -211,25 +211,33 @@ exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = `
exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
// Start mounting Parent and A
// Start rendering through B
⚛ (React Tree Reconciliation: Yielded)
⚛ Parent [mount]
⚛ A [mount]
⚛ (Waiting for async callback... will force flush in 5250 ms)
⚛ (React Tree Reconciliation: Yielded)
⚛ Parent [mount]
⚛ A [mount]
⚛ Child [mount]
⚛ B [mount]
⚛ (Waiting for async callback... will force flush in 5250 ms)
// Mount B just a little (but not enough to memoize)
// Complete the rest
⚛ (React Tree Reconciliation: Yielded)
⚛ Parent [mount]
⚛ B [mount]
⚛ Child [mount]
⚛ C [mount]
⚛ (Waiting for async callback... will force flush in 5250 ms)
// Complete B and Parent
⚛ (React Tree Reconciliation: Completed Root)
⚛ Parent [mount]
B [mount]
C [mount]
⚛ Child [mount]
⚛ (Committing Changes)
@@ -396,20 +404,6 @@ exports[`ReactDebugFiberPerf supports Suspense and lazy 2`] = `
"
`;
exports[`ReactDebugFiberPerf supports portals 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
⚛ (React Tree Reconciliation: Completed Root)
⚛ Parent [mount]
⚛ Child [mount]
⚛ (Committing Changes)
⚛ (Committing Snapshot Effects: 0 Total)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
exports[`ReactDebugFiberPerf supports memo 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
@@ -424,6 +418,20 @@ exports[`ReactDebugFiberPerf supports memo 1`] = `
"
`;
exports[`ReactDebugFiberPerf supports portals 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
⚛ (React Tree Reconciliation: Completed Root)
⚛ Parent [mount]
⚛ Child [mount]
⚛ (Committing Changes)
⚛ (Committing Snapshot Effects: 0 Total)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
exports[`ReactDebugFiberPerf warns if an in-progress update is interrupted 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)