mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
The update is scheduled as if the current processing update has already been processed; if it has the same or higher priority, it will be flushed in the same batch. We also print a warning.
312 lines
8.7 KiB
JavaScript
312 lines
8.7 KiB
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
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var React;
|
|
var ReactNoop;
|
|
|
|
describe('ReactIncrementalUpdates', () => {
|
|
beforeEach(() => {
|
|
jest.resetModuleRegistry();
|
|
React = require('React');
|
|
ReactNoop = require('ReactNoop');
|
|
});
|
|
|
|
function div(...children) {
|
|
children = children.map(c => typeof c === 'string' ? { text: c } : c);
|
|
return { type: 'div', children, prop: undefined };
|
|
}
|
|
|
|
function span(prop) {
|
|
return { type: 'span', children: [], prop };
|
|
}
|
|
|
|
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.flush();
|
|
expect(Object.keys(state)).toEqual(['a', 'b', '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(Object.keys(state)).toEqual(['a', 'b', 'c']);
|
|
});
|
|
|
|
it('only drops updates with equal or lesser priority when replaceState is called', () => {
|
|
let instance;
|
|
const Foo = React.createClass({
|
|
getInitialState() {
|
|
return {};
|
|
},
|
|
render() {
|
|
instance = this;
|
|
return (
|
|
<span prop={Object.keys(this.state).join('')} />
|
|
);
|
|
},
|
|
});
|
|
|
|
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(ReactNoop.getChildren()).toEqual([span('ab')]);
|
|
|
|
ReactNoop.flush();
|
|
// Now the rest of the updates are flushed.
|
|
expect(ReactNoop.getChildren()).toEqual([span('cd')]);
|
|
});
|
|
|
|
it('can abort an update, schedule additional updates, and resume', () => {
|
|
let instance;
|
|
class Foo extends React.Component {
|
|
state = {};
|
|
render() {
|
|
instance = this;
|
|
return (
|
|
<span prop={Object.keys(this.state).join('')} />
|
|
);
|
|
}
|
|
}
|
|
|
|
ReactNoop.render(<Foo />);
|
|
ReactNoop.flush();
|
|
|
|
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(ReactNoop.getChildren()).toEqual([span('')]);
|
|
expect(progressedUpdates).toEqual(['a', 'b', 'c']);
|
|
|
|
instance.setState(createUpdate('f'));
|
|
ReactNoop.performAnimationWork(() => {
|
|
instance.setState(createUpdate('d'));
|
|
instance.setState(createUpdate('e'));
|
|
});
|
|
instance.setState(createUpdate('g'));
|
|
|
|
// Updates a, b, and c were aborted, so they should be applied first even
|
|
// though they have low priority. Update f was scheduled after the render
|
|
// was aborted, so it should come after d and e, which have higher priority.
|
|
ReactNoop.flush();
|
|
expect(ReactNoop.getChildren()).toEqual([span('abcdefg')]);
|
|
expect(progressedUpdates).toEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g']);
|
|
});
|
|
|
|
it('can abort an update, schedule a replaceState, and resume', () => {
|
|
let instance;
|
|
const Foo = React.createClass({
|
|
getInitialState() {
|
|
return {};
|
|
},
|
|
render() {
|
|
instance = this;
|
|
return (
|
|
<span prop={Object.keys(this.state).join('')} />
|
|
);
|
|
},
|
|
});
|
|
|
|
ReactNoop.render(<Foo />);
|
|
ReactNoop.flush();
|
|
|
|
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(ReactNoop.getChildren()).toEqual([span('')]);
|
|
expect(progressedUpdates).toEqual(['a', 'b', 'c']);
|
|
|
|
progressedUpdates = [];
|
|
|
|
instance.setState(createUpdate('f'));
|
|
ReactNoop.performAnimationWork(() => {
|
|
instance.setState(createUpdate('d'));
|
|
instance.replaceState(createUpdate('e'));
|
|
});
|
|
instance.setState(createUpdate('g'));
|
|
|
|
// Updates a, b, and c were aborted, so they should be applied first even
|
|
// though they have low priority. Update f was scheduled after the render
|
|
// was aborted, so it should come after d and e, which have higher priority.
|
|
// Because e is a replaceState, d gets dropped.
|
|
ReactNoop.flush();
|
|
expect(ReactNoop.getChildren()).toEqual([span('efg')]);
|
|
// Ensure that updater function d is never called.
|
|
expect(progressedUpdates).toEqual(['e', 'f', 'g']);
|
|
});
|
|
|
|
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('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 = {};
|
|
componentDidMount() {
|
|
ops.push('componentDidMount');
|
|
this.setState(function a() {
|
|
// Force update b to have Task priority
|
|
ReactNoop.syncUpdates(() => {
|
|
this.setState({ b: 'b' });
|
|
});
|
|
return { a: 'a' };
|
|
});
|
|
}
|
|
render() {
|
|
ops.push('render');
|
|
instance = this;
|
|
return <span prop={Object.keys(this.state).join('')} />;
|
|
}
|
|
}
|
|
|
|
ReactNoop.render(<Foo />);
|
|
ReactNoop.flush();
|
|
expectDev(console.error.calls.count()).toBe(1);
|
|
expect(ReactNoop.getChildren()).toEqual([span('ab')]);
|
|
expect(ops).toEqual([
|
|
// Initial render
|
|
'render',
|
|
'componentDidMount',
|
|
// Updates a and b both have Task priority. Update b is enqueued while
|
|
// update a is being processed, but it should be inserted into the queue
|
|
// as if update a is already processed. Then processing continues. Because
|
|
// they have the same priority, update b is processed in the same batch.
|
|
// So there should only be a single render below.
|
|
'render',
|
|
]);
|
|
|
|
ops = [];
|
|
|
|
ReactNoop.performAnimationWork(() => {
|
|
instance.setState(function c() {
|
|
// Update d happens during the begin phase, so it has low priority.
|
|
this.setState({ d: 'd' });
|
|
return { c: 'c' };
|
|
});
|
|
});
|
|
|
|
ReactNoop.flush();
|
|
expect(ReactNoop.getChildren()).toEqual([span('abcd')]);
|
|
expect(ops).toEqual([
|
|
// Update c has animation priority. Update d is enqueued while c is being
|
|
// processed with animation priority. Because d is low priority, it is not
|
|
// processed until the next render. So there should be two renders below.
|
|
'render',
|
|
'render',
|
|
]);
|
|
|
|
expectDev(console.error.calls.count()).toBe(2);
|
|
console.error.calls.reset();
|
|
});
|
|
});
|