react-native
Version:
A framework for building native apps using React
376 lines (330 loc) • 9.65 kB
JavaScript
/**
* 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
*/
;
var React;
var ReactNoop;
var ReactFeatureFlags;
describe('ReactIncrementalUpdates', () => {
beforeEach(() => {
jest.resetModuleRegistry();
React = require('React');
ReactNoop = require('ReactNoop');
ReactFeatureFlags = require('ReactFeatureFlags');
ReactFeatureFlags.disableNewFiberFeatures = false;
});
it('applies updates in order of priority', () => {
let state;
class Foo extends React.Component {
state = {};
componentDidMount() {
ReactNoop.performAnimationWork(() => {
// Has Animation priority
this.setState({ b: 'b' });
this.setState({ c: 'c' });
});
// Has Task priority
this.setState({ a: 'a' });
}
render() {
state = this.state;
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flushDeferredPri(25);
expect(state).toEqual({ a: 'a' });
ReactNoop.flush();
expect(state).toEqual({ a: 'a', b: 'b', c: 'c' });
});
it('applies updates with equal priority in insertion order', () => {
let state;
class Foo extends React.Component {
state = {};
componentDidMount() {
// All have Task priority
this.setState({ a: 'a' });
this.setState({ b: 'b' });
this.setState({ c: 'c' });
}
render() {
state = this.state;
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
expect(state).toEqual({ a: 'a', b: 'b', c: 'c' });
});
it('only drops updates with equal or lesser priority when replaceState is called', () => {
let instance;
let ops = [];
const Foo = React.createClass({
getInitialState() {
return {};
},
componentDidMount() {
ops.push('componentDidMount');
},
componentDidUpdate() {
ops.push('componentDidUpdate');
},
render() {
ops.push('render');
instance = this;
return <div />;
},
});
ReactNoop.render(<Foo />);
ReactNoop.flush();
instance.setState({ x: 'x' });
instance.setState({ y: 'y' });
ReactNoop.performAnimationWork(() => {
instance.setState({ a: 'a' });
instance.setState({ b: 'b' });
});
instance.replaceState({ c: 'c' });
instance.setState({ d: 'd' });
ReactNoop.flushAnimationPri();
// Even though a replaceState has been already scheduled, it hasn't been
// flushed yet because it has low priority.
expect(instance.state).toEqual({ a: 'a', b: 'b' });
expect(ops).toEqual([
'render',
'componentDidMount',
'render',
'componentDidUpdate',
]);
ops = [];
ReactNoop.flush();
// Now the rest of the updates are flushed.
expect(instance.state).toEqual({ c: 'c', d: 'd' });
expect(ops).toEqual([
'render',
'componentDidUpdate',
]);
});
it('can abort an update, schedule additional updates, and resume', () => {
let instance;
let ops = [];
class Foo extends React.Component {
state = {};
componentDidUpdate() {
ops.push('componentDidUpdate');
}
render() {
ops.push('render');
instance = this;
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
ops = [];
let progressedUpdates = [];
function createUpdate(letter) {
return () => {
progressedUpdates.push(letter);
return {
[letter]: letter,
};
};
}
instance.setState(createUpdate('a'));
instance.setState(createUpdate('b'));
instance.setState(createUpdate('c'));
// Do just enough work to begin the update but not enough to flush it
ReactNoop.flushDeferredPri(15);
// expect(ReactNoop.getChildren()).toEqual([span('')]);
expect(ops).toEqual(['render']);
expect(progressedUpdates).toEqual(['a', 'b', 'c']);
expect(instance.state).toEqual({ a: 'a', b: 'b', c: 'c' });
ops = [];
progressedUpdates = [];
instance.setState(createUpdate('f'));
ReactNoop.performAnimationWork(() => {
instance.setState(createUpdate('d'));
instance.setState(createUpdate('e'));
});
instance.setState(createUpdate('g'));
ReactNoop.flushAnimationPri();
expect(ops).toEqual([
// Flushes animation work (d and e)
'render',
'componentDidUpdate',
]);
ops = [];
ReactNoop.flush();
expect(ops).toEqual([
// Flushes deferred work (f and g)
'render',
'componentDidUpdate',
]);
expect(progressedUpdates).toEqual(['d', 'e', 'a', 'b', 'c', 'f', 'g']);
expect(instance.state).toEqual({ a: 'a', b: 'b', c: 'c', d: 'd', e: 'e', f: 'f', g: 'g' });
});
it('can abort an update, schedule a replaceState, and resume', () => {
let instance;
let ops = [];
const Foo = React.createClass({
getInitialState() {
return {};
},
componentDidUpdate() {
ops.push('componentDidUpdate');
},
render() {
ops.push('render');
instance = this;
return <span />;
},
});
ReactNoop.render(<Foo />);
ReactNoop.flush();
ops = [];
let progressedUpdates = [];
function createUpdate(letter) {
return () => {
progressedUpdates.push(letter);
return {
[letter]: letter,
};
};
}
instance.setState(createUpdate('a'));
instance.setState(createUpdate('b'));
instance.setState(createUpdate('c'));
// Do just enough work to begin the update but not enough to flush it
ReactNoop.flushDeferredPri(20);
expect(ops).toEqual(['render']);
expect(progressedUpdates).toEqual(['a', 'b', 'c']);
expect(instance.state).toEqual({ a: 'a', b: 'b', c: 'c' });
ops = [];
progressedUpdates = [];
instance.setState(createUpdate('f'));
ReactNoop.performAnimationWork(() => {
instance.setState(createUpdate('d'));
instance.replaceState(createUpdate('e'));
});
instance.setState(createUpdate('g'));
ReactNoop.flush();
expect(progressedUpdates).toEqual(['d', 'e', 'f', 'g']);
expect(instance.state).toEqual({ e: 'e', f: 'f', g: 'g' });
});
it('passes accumulation of previous updates to replaceState updater function', () => {
let instance;
const Foo = React.createClass({
getInitialState() {
return {};
},
render() {
instance = this;
return <span />;
},
});
ReactNoop.render(<Foo />);
ReactNoop.flush();
instance.setState({ a: 'a' });
instance.setState({ b: 'b' });
instance.replaceState(previousState => ({ previousState }));
ReactNoop.flush();
expect(instance.state).toEqual({ previousState: { a: 'a', b : 'b' } });
});
it('does not call callbacks that are scheduled by another callback until a later commit', () => {
let ops = [];
class Foo extends React.Component {
state = {};
componentDidMount() {
ops.push('did mount');
this.setState({ a: 'a' }, () => {
ops.push('callback a');
this.setState({ b: 'b' }, () => {
ops.push('callback b');
});
});
}
render() {
ops.push('render');
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
expect(ops).toEqual([
'render',
'did mount',
'render',
'callback a',
'render',
'callback b',
]);
});
it('gives setState during reconciliation the same priority as whatever level is currently reconciling', () => {
let instance;
let ops = [];
class Foo extends React.Component {
state = {};
componentWillReceiveProps() {
ops.push('componentWillReceiveProps');
this.setState({ b: 'b' });
}
render() {
ops.push('render');
instance = this;
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
ops = [];
ReactNoop.performAnimationWork(() => {
instance.setState({ a: 'a' });
ReactNoop.render(<Foo />); // Trigger componentWillReceiveProps
});
ReactNoop.flush();
expect(instance.state).toEqual({ a: 'a', b: 'b' });
expect(ops).toEqual([
'componentWillReceiveProps',
'render',
]);
});
it('enqueues setState inside an updater function as if the in-progress update is progressed (and warns)', () => {
spyOn(console, 'error');
let instance;
let ops = [];
class Foo extends React.Component {
state = {};
render() {
ops.push('render');
instance = this;
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
instance.setState(function a() {
ops.push('setState updater');
this.setState({ b: 'b' });
return { a: 'a' };
});
ReactNoop.flush();
expect(ops).toEqual([
// Initial render
'render',
'setState updater',
// Update b is enqueued with the same priority as update a, so it should
// be flushed in the same commit.
'render',
]);
expect(instance.state).toEqual({ a: 'a', b: 'b' });
expectDev(console.error.calls.count()).toBe(1);
console.error.calls.reset();
});
});