Codemod tests to waitFor pattern (4/?) (#26302)
This converts some of our test suite to use the `waitFor` test pattern, instead of the `expect(Scheduler).toFlushAndYield` pattern. Most of these changes are automated with jscodeshift, with some slight manual cleanup in certain cases. See #26285 for full context.
This commit is contained in:
parent
06460b6fb5
commit
faacefb4d0
|
@ -18,6 +18,8 @@ describe('SimpleEventPlugin', function () {
|
||||||
|
|
||||||
let onClick;
|
let onClick;
|
||||||
let container;
|
let container;
|
||||||
|
let assertLog;
|
||||||
|
let waitForAll;
|
||||||
|
|
||||||
function expectClickThru(element) {
|
function expectClickThru(element) {
|
||||||
element.click();
|
element.click();
|
||||||
|
@ -43,6 +45,10 @@ describe('SimpleEventPlugin', function () {
|
||||||
ReactDOMClient = require('react-dom/client');
|
ReactDOMClient = require('react-dom/client');
|
||||||
Scheduler = require('scheduler');
|
Scheduler = require('scheduler');
|
||||||
|
|
||||||
|
const InternalTestUtils = require('internal-test-utils');
|
||||||
|
assertLog = InternalTestUtils.assertLog;
|
||||||
|
waitForAll = InternalTestUtils.waitForAll;
|
||||||
|
|
||||||
onClick = jest.fn();
|
onClick = jest.fn();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -222,12 +228,12 @@ describe('SimpleEventPlugin', function () {
|
||||||
|
|
||||||
ReactDOM.render(<Button />, container);
|
ReactDOM.render(<Button />, container);
|
||||||
expect(button.textContent).toEqual('Count: 0');
|
expect(button.textContent).toEqual('Count: 0');
|
||||||
expect(Scheduler).toHaveYielded([]);
|
assertLog([]);
|
||||||
|
|
||||||
click();
|
click();
|
||||||
|
|
||||||
// There should be exactly one update.
|
// There should be exactly one update.
|
||||||
expect(Scheduler).toHaveYielded(['didUpdate - Count: 3']);
|
assertLog(['didUpdate - Count: 3']);
|
||||||
expect(button.textContent).toEqual('Count: 3');
|
expect(button.textContent).toEqual('Count: 3');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -240,6 +246,10 @@ describe('SimpleEventPlugin', function () {
|
||||||
ReactDOMClient = require('react-dom/client');
|
ReactDOMClient = require('react-dom/client');
|
||||||
Scheduler = require('scheduler');
|
Scheduler = require('scheduler');
|
||||||
|
|
||||||
|
const InternalTestUtils = require('internal-test-utils');
|
||||||
|
assertLog = InternalTestUtils.assertLog;
|
||||||
|
waitForAll = InternalTestUtils.waitForAll;
|
||||||
|
|
||||||
act = require('jest-react').act;
|
act = require('jest-react').act;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -274,10 +284,10 @@ describe('SimpleEventPlugin', function () {
|
||||||
// Initial mount
|
// Initial mount
|
||||||
root.render(<Button />);
|
root.render(<Button />);
|
||||||
// Should not have flushed yet because it's async
|
// Should not have flushed yet because it's async
|
||||||
expect(Scheduler).toHaveYielded([]);
|
assertLog([]);
|
||||||
expect(button).toBe(undefined);
|
expect(button).toBe(undefined);
|
||||||
// Flush async work
|
// Flush async work
|
||||||
expect(Scheduler).toFlushAndYield(['render button: enabled']);
|
await waitForAll(['render button: enabled']);
|
||||||
|
|
||||||
function click() {
|
function click() {
|
||||||
const event = new MouseEvent('click', {
|
const event = new MouseEvent('click', {
|
||||||
|
@ -292,7 +302,7 @@ describe('SimpleEventPlugin', function () {
|
||||||
|
|
||||||
// Click the button to trigger the side-effect
|
// Click the button to trigger the side-effect
|
||||||
await act(async () => click());
|
await act(async () => click());
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
// The handler fired
|
// The handler fired
|
||||||
'Side-effect',
|
'Side-effect',
|
||||||
// The component re-rendered synchronously, even in concurrent mode.
|
// The component re-rendered synchronously, even in concurrent mode.
|
||||||
|
@ -301,7 +311,7 @@ describe('SimpleEventPlugin', function () {
|
||||||
|
|
||||||
// Click the button again
|
// Click the button again
|
||||||
click();
|
click();
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
// The event handler was removed from the button, so there's no effect.
|
// The event handler was removed from the button, so there's no effect.
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -312,7 +322,7 @@ describe('SimpleEventPlugin', function () {
|
||||||
click();
|
click();
|
||||||
click();
|
click();
|
||||||
click();
|
click();
|
||||||
expect(Scheduler).toFlushAndYield([]);
|
await waitForAll([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: This test was written for the old behavior of discrete updates,
|
// NOTE: This test was written for the old behavior of discrete updates,
|
||||||
|
@ -345,7 +355,7 @@ describe('SimpleEventPlugin', function () {
|
||||||
// Should not have flushed yet because it's async
|
// Should not have flushed yet because it's async
|
||||||
expect(button).toBe(undefined);
|
expect(button).toBe(undefined);
|
||||||
// Flush async work
|
// Flush async work
|
||||||
Scheduler.unstable_flushAll();
|
await waitForAll([]);
|
||||||
expect(button.textContent).toEqual('Count: 0');
|
expect(button.textContent).toEqual('Count: 0');
|
||||||
|
|
||||||
function click() {
|
function click() {
|
||||||
|
@ -373,7 +383,7 @@ describe('SimpleEventPlugin', function () {
|
||||||
await act(async () => click());
|
await act(async () => click());
|
||||||
|
|
||||||
// Flush the remaining work
|
// Flush the remaining work
|
||||||
Scheduler.unstable_flushAll();
|
await waitForAll([]);
|
||||||
// The counter should equal the total number of clicks
|
// The counter should equal the total number of clicks
|
||||||
expect(button.textContent).toEqual('Count: 7');
|
expect(button.textContent).toEqual('Count: 7');
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,6 +18,8 @@ let ReactTestRenderer;
|
||||||
let Scheduler;
|
let Scheduler;
|
||||||
let ReactDOMServer;
|
let ReactDOMServer;
|
||||||
let act;
|
let act;
|
||||||
|
let assertLog;
|
||||||
|
let waitForAll;
|
||||||
|
|
||||||
describe('ReactHooks', () => {
|
describe('ReactHooks', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -30,6 +32,10 @@ describe('ReactHooks', () => {
|
||||||
Scheduler = require('scheduler');
|
Scheduler = require('scheduler');
|
||||||
ReactDOMServer = require('react-dom/server');
|
ReactDOMServer = require('react-dom/server');
|
||||||
act = require('jest-react').act;
|
act = require('jest-react').act;
|
||||||
|
|
||||||
|
const InternalTestUtils = require('internal-test-utils');
|
||||||
|
assertLog = InternalTestUtils.assertLog;
|
||||||
|
waitForAll = InternalTestUtils.waitForAll;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
|
@ -54,7 +60,7 @@ describe('ReactHooks', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
it('bails out in the render phase if all of the state is the same', () => {
|
it('bails out in the render phase if all of the state is the same', async () => {
|
||||||
const {useState, useLayoutEffect} = React;
|
const {useState, useLayoutEffect} = React;
|
||||||
|
|
||||||
function Child({text}) {
|
function Child({text}) {
|
||||||
|
@ -80,11 +86,7 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
||||||
root.update(<Parent />);
|
root.update(<Parent />);
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll(['Parent: 0, 0', 'Child: 0, 0', 'Effect: 0, 0']);
|
||||||
'Parent: 0, 0',
|
|
||||||
'Child: 0, 0',
|
|
||||||
'Effect: 0, 0',
|
|
||||||
]);
|
|
||||||
expect(root).toMatchRenderedOutput('0, 0');
|
expect(root).toMatchRenderedOutput('0, 0');
|
||||||
|
|
||||||
// Normal update
|
// Normal update
|
||||||
|
@ -93,15 +95,11 @@ describe('ReactHooks', () => {
|
||||||
setCounter2(1);
|
setCounter2(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog(['Parent: 1, 1', 'Child: 1, 1', 'Effect: 1, 1']);
|
||||||
'Parent: 1, 1',
|
|
||||||
'Child: 1, 1',
|
|
||||||
'Effect: 1, 1',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Update that bails out.
|
// Update that bails out.
|
||||||
act(() => setCounter1(1));
|
act(() => setCounter1(1));
|
||||||
expect(Scheduler).toHaveYielded(['Parent: 1, 1']);
|
assertLog(['Parent: 1, 1']);
|
||||||
|
|
||||||
// This time, one of the state updates but the other one doesn't. So we
|
// This time, one of the state updates but the other one doesn't. So we
|
||||||
// can't bail out.
|
// can't bail out.
|
||||||
|
@ -110,11 +108,7 @@ describe('ReactHooks', () => {
|
||||||
setCounter2(2);
|
setCounter2(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog(['Parent: 1, 2', 'Child: 1, 2', 'Effect: 1, 2']);
|
||||||
'Parent: 1, 2',
|
|
||||||
'Child: 1, 2',
|
|
||||||
'Effect: 1, 2',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Lots of updates that eventually resolve to the current values.
|
// Lots of updates that eventually resolve to the current values.
|
||||||
act(() => {
|
act(() => {
|
||||||
|
@ -128,7 +122,7 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
// Because the final values are the same as the current values, the
|
// Because the final values are the same as the current values, the
|
||||||
// component bails out.
|
// component bails out.
|
||||||
expect(Scheduler).toHaveYielded(['Parent: 1, 2']);
|
assertLog(['Parent: 1, 2']);
|
||||||
|
|
||||||
// prepare to check SameValue
|
// prepare to check SameValue
|
||||||
act(() => {
|
act(() => {
|
||||||
|
@ -136,11 +130,7 @@ describe('ReactHooks', () => {
|
||||||
setCounter2(NaN);
|
setCounter2(NaN);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog(['Parent: 0, NaN', 'Child: 0, NaN', 'Effect: 0, NaN']);
|
||||||
'Parent: 0, NaN',
|
|
||||||
'Child: 0, NaN',
|
|
||||||
'Effect: 0, NaN',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// check if re-setting to negative 0 / NaN still bails out
|
// check if re-setting to negative 0 / NaN still bails out
|
||||||
act(() => {
|
act(() => {
|
||||||
|
@ -150,20 +140,16 @@ describe('ReactHooks', () => {
|
||||||
setCounter2(NaN);
|
setCounter2(NaN);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded(['Parent: 0, NaN']);
|
assertLog(['Parent: 0, NaN']);
|
||||||
|
|
||||||
// check if changing negative 0 to positive 0 does not bail out
|
// check if changing negative 0 to positive 0 does not bail out
|
||||||
act(() => {
|
act(() => {
|
||||||
setCounter1(0);
|
setCounter1(0);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog(['Parent: 0, NaN', 'Child: 0, NaN', 'Effect: 0, NaN']);
|
||||||
'Parent: 0, NaN',
|
|
||||||
'Child: 0, NaN',
|
|
||||||
'Effect: 0, NaN',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bails out in render phase if all the state is the same and props bail out with memo', () => {
|
it('bails out in render phase if all the state is the same and props bail out with memo', async () => {
|
||||||
const {useState, memo} = React;
|
const {useState, memo} = React;
|
||||||
|
|
||||||
function Child({text}) {
|
function Child({text}) {
|
||||||
|
@ -188,10 +174,7 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
||||||
root.update(<Parent theme="light" />);
|
root.update(<Parent theme="light" />);
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll(['Parent: 0, 0 (light)', 'Child: 0, 0 (light)']);
|
||||||
'Parent: 0, 0 (light)',
|
|
||||||
'Child: 0, 0 (light)',
|
|
||||||
]);
|
|
||||||
expect(root).toMatchRenderedOutput('0, 0 (light)');
|
expect(root).toMatchRenderedOutput('0, 0 (light)');
|
||||||
|
|
||||||
// Normal update
|
// Normal update
|
||||||
|
@ -200,14 +183,11 @@ describe('ReactHooks', () => {
|
||||||
setCounter2(1);
|
setCounter2(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog(['Parent: 1, 1 (light)', 'Child: 1, 1 (light)']);
|
||||||
'Parent: 1, 1 (light)',
|
|
||||||
'Child: 1, 1 (light)',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Update that bails out.
|
// Update that bails out.
|
||||||
act(() => setCounter1(1));
|
act(() => setCounter1(1));
|
||||||
expect(Scheduler).toHaveYielded(['Parent: 1, 1 (light)']);
|
assertLog(['Parent: 1, 1 (light)']);
|
||||||
|
|
||||||
// This time, one of the state updates but the other one doesn't. So we
|
// This time, one of the state updates but the other one doesn't. So we
|
||||||
// can't bail out.
|
// can't bail out.
|
||||||
|
@ -216,10 +196,7 @@ describe('ReactHooks', () => {
|
||||||
setCounter2(2);
|
setCounter2(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog(['Parent: 1, 2 (light)', 'Child: 1, 2 (light)']);
|
||||||
'Parent: 1, 2 (light)',
|
|
||||||
'Child: 1, 2 (light)',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Updates bail out, but component still renders because props
|
// Updates bail out, but component still renders because props
|
||||||
// have changed
|
// have changed
|
||||||
|
@ -229,10 +206,7 @@ describe('ReactHooks', () => {
|
||||||
root.update(<Parent theme="dark" />);
|
root.update(<Parent theme="dark" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog(['Parent: 1, 2 (dark)', 'Child: 1, 2 (dark)']);
|
||||||
'Parent: 1, 2 (dark)',
|
|
||||||
'Child: 1, 2 (dark)',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Both props and state bail out
|
// Both props and state bail out
|
||||||
act(() => {
|
act(() => {
|
||||||
|
@ -241,10 +215,10 @@ describe('ReactHooks', () => {
|
||||||
root.update(<Parent theme="dark" />);
|
root.update(<Parent theme="dark" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded(['Parent: 1, 2 (dark)']);
|
assertLog(['Parent: 1, 2 (dark)']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warns about setState second argument', () => {
|
it('warns about setState second argument', async () => {
|
||||||
const {useState} = React;
|
const {useState} = React;
|
||||||
|
|
||||||
let setCounter;
|
let setCounter;
|
||||||
|
@ -258,7 +232,7 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
||||||
root.update(<Counter />);
|
root.update(<Counter />);
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 0']);
|
await waitForAll(['Count: 0']);
|
||||||
expect(root).toMatchRenderedOutput('0');
|
expect(root).toMatchRenderedOutput('0');
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
|
@ -274,11 +248,11 @@ describe('ReactHooks', () => {
|
||||||
'declare it in the component body with useEffect().',
|
'declare it in the component body with useEffect().',
|
||||||
{withoutStack: true},
|
{withoutStack: true},
|
||||||
);
|
);
|
||||||
expect(Scheduler).toHaveYielded(['Count: 1']);
|
assertLog(['Count: 1']);
|
||||||
expect(root).toMatchRenderedOutput('1');
|
expect(root).toMatchRenderedOutput('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warns about dispatch second argument', () => {
|
it('warns about dispatch second argument', async () => {
|
||||||
const {useReducer} = React;
|
const {useReducer} = React;
|
||||||
|
|
||||||
let dispatch;
|
let dispatch;
|
||||||
|
@ -292,7 +266,7 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
||||||
root.update(<Counter />);
|
root.update(<Counter />);
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 0']);
|
await waitForAll(['Count: 0']);
|
||||||
expect(root).toMatchRenderedOutput('0');
|
expect(root).toMatchRenderedOutput('0');
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
|
@ -308,11 +282,11 @@ describe('ReactHooks', () => {
|
||||||
'declare it in the component body with useEffect().',
|
'declare it in the component body with useEffect().',
|
||||||
{withoutStack: true},
|
{withoutStack: true},
|
||||||
);
|
);
|
||||||
expect(Scheduler).toHaveYielded(['Count: 1']);
|
assertLog(['Count: 1']);
|
||||||
expect(root).toMatchRenderedOutput('1');
|
expect(root).toMatchRenderedOutput('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('never bails out if context has changed', () => {
|
it('never bails out if context has changed', async () => {
|
||||||
const {useState, useLayoutEffect, useContext} = React;
|
const {useState, useLayoutEffect, useContext} = React;
|
||||||
|
|
||||||
const ThemeContext = React.createContext('light');
|
const ThemeContext = React.createContext('light');
|
||||||
|
@ -355,7 +329,7 @@ describe('ReactHooks', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
'Theme: light',
|
'Theme: light',
|
||||||
'Parent: 0 (light)',
|
'Parent: 0 (light)',
|
||||||
'Child: 0 (light)',
|
'Child: 0 (light)',
|
||||||
|
@ -366,21 +340,17 @@ describe('ReactHooks', () => {
|
||||||
// Updating the theme to the same value doesn't cause the consumers
|
// Updating the theme to the same value doesn't cause the consumers
|
||||||
// to re-render.
|
// to re-render.
|
||||||
setTheme('light');
|
setTheme('light');
|
||||||
expect(Scheduler).toFlushAndYield([]);
|
await waitForAll([]);
|
||||||
expect(root).toMatchRenderedOutput('0 (light)');
|
expect(root).toMatchRenderedOutput('0 (light)');
|
||||||
|
|
||||||
// Normal update
|
// Normal update
|
||||||
act(() => setCounter(1));
|
act(() => setCounter(1));
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog(['Parent: 1 (light)', 'Child: 1 (light)', 'Effect: 1 (light)']);
|
||||||
'Parent: 1 (light)',
|
|
||||||
'Child: 1 (light)',
|
|
||||||
'Effect: 1 (light)',
|
|
||||||
]);
|
|
||||||
expect(root).toMatchRenderedOutput('1 (light)');
|
expect(root).toMatchRenderedOutput('1 (light)');
|
||||||
|
|
||||||
// Update that doesn't change state, so it bails out
|
// Update that doesn't change state, so it bails out
|
||||||
act(() => setCounter(1));
|
act(() => setCounter(1));
|
||||||
expect(Scheduler).toHaveYielded(['Parent: 1 (light)']);
|
assertLog(['Parent: 1 (light)']);
|
||||||
expect(root).toMatchRenderedOutput('1 (light)');
|
expect(root).toMatchRenderedOutput('1 (light)');
|
||||||
|
|
||||||
// Update that doesn't change state, but the context changes, too, so it
|
// Update that doesn't change state, but the context changes, too, so it
|
||||||
|
@ -390,7 +360,7 @@ describe('ReactHooks', () => {
|
||||||
setTheme('dark');
|
setTheme('dark');
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
'Theme: dark',
|
'Theme: dark',
|
||||||
'Parent: 1 (dark)',
|
'Parent: 1 (dark)',
|
||||||
'Child: 1 (dark)',
|
'Child: 1 (dark)',
|
||||||
|
@ -399,7 +369,7 @@ describe('ReactHooks', () => {
|
||||||
expect(root).toMatchRenderedOutput('1 (dark)');
|
expect(root).toMatchRenderedOutput('1 (dark)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can bail out without calling render phase (as an optimization) if queue is known to be empty', () => {
|
it('can bail out without calling render phase (as an optimization) if queue is known to be empty', async () => {
|
||||||
const {useState, useLayoutEffect} = React;
|
const {useState, useLayoutEffect} = React;
|
||||||
|
|
||||||
function Child({text}) {
|
function Child({text}) {
|
||||||
|
@ -420,12 +390,12 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
||||||
root.update(<Parent />);
|
root.update(<Parent />);
|
||||||
expect(Scheduler).toFlushAndYield(['Parent: 0', 'Child: 0', 'Effect: 0']);
|
await waitForAll(['Parent: 0', 'Child: 0', 'Effect: 0']);
|
||||||
expect(root).toMatchRenderedOutput('0');
|
expect(root).toMatchRenderedOutput('0');
|
||||||
|
|
||||||
// Normal update
|
// Normal update
|
||||||
act(() => setCounter(1));
|
act(() => setCounter(1));
|
||||||
expect(Scheduler).toHaveYielded(['Parent: 1', 'Child: 1', 'Effect: 1']);
|
assertLog(['Parent: 1', 'Child: 1', 'Effect: 1']);
|
||||||
expect(root).toMatchRenderedOutput('1');
|
expect(root).toMatchRenderedOutput('1');
|
||||||
|
|
||||||
// Update to the same state. React doesn't know if the queue is empty
|
// Update to the same state. React doesn't know if the queue is empty
|
||||||
|
@ -433,25 +403,25 @@ describe('ReactHooks', () => {
|
||||||
// enter the render phase before we can bail out. But we bail out before
|
// enter the render phase before we can bail out. But we bail out before
|
||||||
// rendering the child, and we don't fire any effects.
|
// rendering the child, and we don't fire any effects.
|
||||||
act(() => setCounter(1));
|
act(() => setCounter(1));
|
||||||
expect(Scheduler).toHaveYielded(['Parent: 1']);
|
assertLog(['Parent: 1']);
|
||||||
expect(root).toMatchRenderedOutput('1');
|
expect(root).toMatchRenderedOutput('1');
|
||||||
|
|
||||||
// Update to the same state again. This times, neither fiber has pending
|
// Update to the same state again. This times, neither fiber has pending
|
||||||
// update priority, so we can bail out before even entering the render phase.
|
// update priority, so we can bail out before even entering the render phase.
|
||||||
act(() => setCounter(1));
|
act(() => setCounter(1));
|
||||||
expect(Scheduler).toFlushAndYield([]);
|
await waitForAll([]);
|
||||||
expect(root).toMatchRenderedOutput('1');
|
expect(root).toMatchRenderedOutput('1');
|
||||||
|
|
||||||
// This changes the state to something different so it renders normally.
|
// This changes the state to something different so it renders normally.
|
||||||
act(() => setCounter(2));
|
act(() => setCounter(2));
|
||||||
expect(Scheduler).toHaveYielded(['Parent: 2', 'Child: 2', 'Effect: 2']);
|
assertLog(['Parent: 2', 'Child: 2', 'Effect: 2']);
|
||||||
expect(root).toMatchRenderedOutput('2');
|
expect(root).toMatchRenderedOutput('2');
|
||||||
|
|
||||||
// prepare to check SameValue
|
// prepare to check SameValue
|
||||||
act(() => {
|
act(() => {
|
||||||
setCounter(0);
|
setCounter(0);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toHaveYielded(['Parent: 0', 'Child: 0', 'Effect: 0']);
|
assertLog(['Parent: 0', 'Child: 0', 'Effect: 0']);
|
||||||
expect(root).toMatchRenderedOutput('0');
|
expect(root).toMatchRenderedOutput('0');
|
||||||
|
|
||||||
// Update to the same state for the first time to flush the queue
|
// Update to the same state for the first time to flush the queue
|
||||||
|
@ -459,25 +429,25 @@ describe('ReactHooks', () => {
|
||||||
setCounter(0);
|
setCounter(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded(['Parent: 0']);
|
assertLog(['Parent: 0']);
|
||||||
expect(root).toMatchRenderedOutput('0');
|
expect(root).toMatchRenderedOutput('0');
|
||||||
|
|
||||||
// Update again to the same state. Should bail out.
|
// Update again to the same state. Should bail out.
|
||||||
act(() => {
|
act(() => {
|
||||||
setCounter(0);
|
setCounter(0);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYield([]);
|
await waitForAll([]);
|
||||||
expect(root).toMatchRenderedOutput('0');
|
expect(root).toMatchRenderedOutput('0');
|
||||||
|
|
||||||
// Update to a different state (positive 0 to negative 0)
|
// Update to a different state (positive 0 to negative 0)
|
||||||
act(() => {
|
act(() => {
|
||||||
setCounter(0 / -1);
|
setCounter(0 / -1);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toHaveYielded(['Parent: 0', 'Child: 0', 'Effect: 0']);
|
assertLog(['Parent: 0', 'Child: 0', 'Effect: 0']);
|
||||||
expect(root).toMatchRenderedOutput('0');
|
expect(root).toMatchRenderedOutput('0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bails out multiple times in a row without entering render phase', () => {
|
it('bails out multiple times in a row without entering render phase', async () => {
|
||||||
const {useState} = React;
|
const {useState} = React;
|
||||||
|
|
||||||
function Child({text}) {
|
function Child({text}) {
|
||||||
|
@ -495,7 +465,7 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
||||||
root.update(<Parent />);
|
root.update(<Parent />);
|
||||||
expect(Scheduler).toFlushAndYield(['Parent: 0', 'Child: 0']);
|
await waitForAll(['Parent: 0', 'Child: 0']);
|
||||||
expect(root).toMatchRenderedOutput('0');
|
expect(root).toMatchRenderedOutput('0');
|
||||||
|
|
||||||
const update = value => {
|
const update = value => {
|
||||||
|
@ -515,7 +485,7 @@ describe('ReactHooks', () => {
|
||||||
update(3);
|
update(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
// The first four updates were eagerly computed, because the queue is
|
// The first four updates were eagerly computed, because the queue is
|
||||||
// empty before each one.
|
// empty before each one.
|
||||||
'Compute state (0 -> 0)',
|
'Compute state (0 -> 0)',
|
||||||
|
@ -527,7 +497,7 @@ describe('ReactHooks', () => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Now let's enter the render phase
|
// Now let's enter the render phase
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
// We don't need to re-compute the first four updates. Only the final two.
|
// We don't need to re-compute the first four updates. Only the final two.
|
||||||
'Compute state (1 -> 2)',
|
'Compute state (1 -> 2)',
|
||||||
'Compute state (2 -> 3)',
|
'Compute state (2 -> 3)',
|
||||||
|
@ -537,7 +507,7 @@ describe('ReactHooks', () => {
|
||||||
expect(root).toMatchRenderedOutput('3');
|
expect(root).toMatchRenderedOutput('3');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can rebase on top of a previously skipped update', () => {
|
it('can rebase on top of a previously skipped update', async () => {
|
||||||
const {useState} = React;
|
const {useState} = React;
|
||||||
|
|
||||||
function Child({text}) {
|
function Child({text}) {
|
||||||
|
@ -555,7 +525,7 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
||||||
root.update(<Parent />);
|
root.update(<Parent />);
|
||||||
expect(Scheduler).toFlushAndYield(['Parent: 1', 'Child: 1']);
|
await waitForAll(['Parent: 1', 'Child: 1']);
|
||||||
expect(root).toMatchRenderedOutput('1');
|
expect(root).toMatchRenderedOutput('1');
|
||||||
|
|
||||||
const update = compute => {
|
const update = compute => {
|
||||||
|
@ -576,13 +546,13 @@ describe('ReactHooks', () => {
|
||||||
ReactTestRenderer.unstable_batchedUpdates(() => update(n => n * 100));
|
ReactTestRenderer.unstable_batchedUpdates(() => update(n => n * 100));
|
||||||
}
|
}
|
||||||
// The new state is eagerly computed.
|
// The new state is eagerly computed.
|
||||||
expect(Scheduler).toHaveYielded(['Compute state (1 -> 100)']);
|
assertLog(['Compute state (1 -> 100)']);
|
||||||
|
|
||||||
// but before it's flushed, a higher priority update interrupts it.
|
// but before it's flushed, a higher priority update interrupts it.
|
||||||
root.unstable_flushSync(() => {
|
root.unstable_flushSync(() => {
|
||||||
update(n => n + 5);
|
update(n => n + 5);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
// The eagerly computed state was completely skipped
|
// The eagerly computed state was completely skipped
|
||||||
'Compute state (1 -> 6)',
|
'Compute state (1 -> 6)',
|
||||||
'Parent: 6',
|
'Parent: 6',
|
||||||
|
@ -593,7 +563,7 @@ describe('ReactHooks', () => {
|
||||||
// Now when we finish the first update, the second update is rebased on top.
|
// Now when we finish the first update, the second update is rebased on top.
|
||||||
// Notice we didn't have to recompute the first update even though it was
|
// Notice we didn't have to recompute the first update even though it was
|
||||||
// skipped in the previous render.
|
// skipped in the previous render.
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
'Compute state (100 -> 105)',
|
'Compute state (100 -> 105)',
|
||||||
'Parent: 105',
|
'Parent: 105',
|
||||||
'Child: 105',
|
'Child: 105',
|
||||||
|
@ -612,7 +582,7 @@ describe('ReactHooks', () => {
|
||||||
return props.dependencies;
|
return props.dependencies;
|
||||||
}
|
}
|
||||||
const root = ReactTestRenderer.create(<App dependencies={['A']} />);
|
const root = ReactTestRenderer.create(<App dependencies={['A']} />);
|
||||||
expect(Scheduler).toHaveYielded(['Did commit: A']);
|
assertLog(['Did commit: A']);
|
||||||
expect(() => {
|
expect(() => {
|
||||||
root.update(<App dependencies={['A', 'B']} />);
|
root.update(<App dependencies={['A', 'B']} />);
|
||||||
}).toErrorDev([
|
}).toErrorDev([
|
||||||
|
@ -639,7 +609,7 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
const root = ReactTestRenderer.create(null);
|
const root = ReactTestRenderer.create(null);
|
||||||
root.update(<App text="Hello" hasDeps={true} />);
|
root.update(<App text="Hello" hasDeps={true} />);
|
||||||
expect(Scheduler).toHaveYielded(['Compute']);
|
assertLog(['Compute']);
|
||||||
expect(root).toMatchRenderedOutput('HELLO');
|
expect(root).toMatchRenderedOutput('HELLO');
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
|
@ -1841,7 +1811,7 @@ describe('ReactHooks', () => {
|
||||||
);
|
);
|
||||||
expect(root).toMatchRenderedOutput('loading');
|
expect(root).toMatchRenderedOutput('loading');
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
Scheduler.unstable_flushAll();
|
await waitForAll([]);
|
||||||
expect(root).toMatchRenderedOutput('hello');
|
expect(root).toMatchRenderedOutput('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1873,7 +1843,7 @@ describe('ReactHooks', () => {
|
||||||
);
|
);
|
||||||
expect(root).toMatchRenderedOutput('loading');
|
expect(root).toMatchRenderedOutput('loading');
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
Scheduler.unstable_flushAll();
|
await waitForAll([]);
|
||||||
expect(root).toMatchRenderedOutput('hello');
|
expect(root).toMatchRenderedOutput('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1905,7 +1875,7 @@ describe('ReactHooks', () => {
|
||||||
);
|
);
|
||||||
expect(root).toMatchRenderedOutput('loading');
|
expect(root).toMatchRenderedOutput('loading');
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
Scheduler.unstable_flushAll();
|
await waitForAll([]);
|
||||||
expect(root).toMatchRenderedOutput('hello');
|
expect(root).toMatchRenderedOutput('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -16,6 +16,10 @@ let React;
|
||||||
let ReactNoop;
|
let ReactNoop;
|
||||||
let Scheduler;
|
let Scheduler;
|
||||||
let act;
|
let act;
|
||||||
|
let assertLog;
|
||||||
|
let waitForAll;
|
||||||
|
let waitFor;
|
||||||
|
let waitForThrow;
|
||||||
|
|
||||||
describe('ReactIncrementalErrorHandling', () => {
|
describe('ReactIncrementalErrorHandling', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -27,6 +31,12 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
ReactNoop = require('react-noop-renderer');
|
ReactNoop = require('react-noop-renderer');
|
||||||
Scheduler = require('scheduler');
|
Scheduler = require('scheduler');
|
||||||
act = require('jest-react').act;
|
act = require('jest-react').act;
|
||||||
|
|
||||||
|
const InternalTestUtils = require('internal-test-utils');
|
||||||
|
assertLog = InternalTestUtils.assertLog;
|
||||||
|
waitForAll = InternalTestUtils.waitForAll;
|
||||||
|
waitFor = InternalTestUtils.waitFor;
|
||||||
|
waitForThrow = InternalTestUtils.waitForThrow;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -55,7 +65,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('recovers from errors asynchronously', () => {
|
it('recovers from errors asynchronously', async () => {
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component {
|
||||||
state = {error: null};
|
state = {error: null};
|
||||||
static getDerivedStateFromError(error) {
|
static getDerivedStateFromError(error) {
|
||||||
|
@ -104,7 +114,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start rendering asynchronously
|
// Start rendering asynchronously
|
||||||
expect(Scheduler).toFlushAndYieldThrough([
|
await waitFor([
|
||||||
'ErrorBoundary (try)',
|
'ErrorBoundary (try)',
|
||||||
'Indirection',
|
'Indirection',
|
||||||
'Indirection',
|
'Indirection',
|
||||||
|
@ -114,9 +124,9 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Still rendering async...
|
// Still rendering async...
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Indirection']);
|
await waitFor(['Indirection']);
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYieldThrough([
|
await waitFor([
|
||||||
'Indirection',
|
'Indirection',
|
||||||
|
|
||||||
// Call getDerivedStateFromError and re-render the error boundary, this
|
// Call getDerivedStateFromError and re-render the error boundary, this
|
||||||
|
@ -153,7 +163,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('recovers from errors asynchronously (legacy, no getDerivedStateFromError)', () => {
|
it('recovers from errors asynchronously (legacy, no getDerivedStateFromError)', async () => {
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component {
|
||||||
state = {error: null};
|
state = {error: null};
|
||||||
componentDidCatch(error) {
|
componentDidCatch(error) {
|
||||||
|
@ -202,7 +212,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start rendering asynchronously
|
// Start rendering asynchronously
|
||||||
expect(Scheduler).toFlushAndYieldThrough([
|
await waitFor([
|
||||||
'ErrorBoundary (try)',
|
'ErrorBoundary (try)',
|
||||||
'Indirection',
|
'Indirection',
|
||||||
'Indirection',
|
'Indirection',
|
||||||
|
@ -212,9 +222,9 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Still rendering async...
|
// Still rendering async...
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Indirection']);
|
await waitFor(['Indirection']);
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYieldThrough([
|
await waitFor([
|
||||||
'Indirection',
|
'Indirection',
|
||||||
// Now that the tree is complete, and there's no remaining work, React
|
// Now that the tree is complete, and there's no remaining work, React
|
||||||
// reverts to legacy mode to retry one more time before handling the error.
|
// reverts to legacy mode to retry one more time before handling the error.
|
||||||
|
@ -254,7 +264,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
React.startTransition(() => {
|
React.startTransition(() => {
|
||||||
ReactNoop.render(<App isBroken={true} />, onCommit);
|
ReactNoop.render(<App isBroken={true} />, onCommit);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['error']);
|
await waitFor(['error']);
|
||||||
|
|
||||||
React.startTransition(() => {
|
React.startTransition(() => {
|
||||||
// This update is in a separate batch
|
// This update is in a separate batch
|
||||||
|
@ -268,7 +278,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
// to recover from the error is synchronous, this should be enough to
|
// to recover from the error is synchronous, this should be enough to
|
||||||
// finish the rest of the work.
|
// finish the rest of the work.
|
||||||
Scheduler.unstable_flushNumberOfYields(1);
|
Scheduler.unstable_flushNumberOfYields(1);
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
'success',
|
'success',
|
||||||
// Nothing commits until the second update completes.
|
// Nothing commits until the second update completes.
|
||||||
'commit',
|
'commit',
|
||||||
|
@ -280,7 +290,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// @gate www
|
// @gate www
|
||||||
it('does not include offscreen work when retrying after an error', () => {
|
it('does not include offscreen work when retrying after an error', async () => {
|
||||||
function App(props) {
|
function App(props) {
|
||||||
if (props.isBroken) {
|
if (props.isBroken) {
|
||||||
Scheduler.unstable_yieldValue('error');
|
Scheduler.unstable_yieldValue('error');
|
||||||
|
@ -304,7 +314,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
React.startTransition(() => {
|
React.startTransition(() => {
|
||||||
ReactNoop.render(<App isBroken={true} />, onCommit);
|
ReactNoop.render(<App isBroken={true} />, onCommit);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['error']);
|
await waitFor(['error']);
|
||||||
|
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
|
|
||||||
|
@ -320,7 +330,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
// to recover from the error is synchronous, this should be enough to
|
// to recover from the error is synchronous, this should be enough to
|
||||||
// finish the rest of the work.
|
// finish the rest of the work.
|
||||||
Scheduler.unstable_flushNumberOfYields(1);
|
Scheduler.unstable_flushNumberOfYields(1);
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
'success',
|
'success',
|
||||||
// Nothing commits until the second update completes.
|
// Nothing commits until the second update completes.
|
||||||
'commit',
|
'commit',
|
||||||
|
@ -335,7 +345,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// The offscreen content finishes in a subsequent render
|
// The offscreen content finishes in a subsequent render
|
||||||
expect(Scheduler).toFlushAndYield([]);
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<>
|
<>
|
||||||
Everything is fine
|
Everything is fine
|
||||||
|
@ -346,7 +356,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('retries one more time before handling error', () => {
|
it('retries one more time before handling error', async () => {
|
||||||
function BadRender({unused}) {
|
function BadRender({unused}) {
|
||||||
Scheduler.unstable_yieldValue('BadRender');
|
Scheduler.unstable_yieldValue('BadRender');
|
||||||
throw new Error('oops');
|
throw new Error('oops');
|
||||||
|
@ -374,19 +384,14 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render the bad component asynchronously
|
// Render the bad component asynchronously
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Parent', 'BadRender']);
|
await waitFor(['Parent', 'BadRender']);
|
||||||
|
|
||||||
// Finish the rest of the async work
|
// Finish the rest of the async work
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Sibling']);
|
await waitFor(['Sibling']);
|
||||||
|
|
||||||
// Old scheduler renders, commits, and throws synchronously
|
// Old scheduler renders, commits, and throws synchronously
|
||||||
expect(() => Scheduler.unstable_flushNumberOfYields(1)).toThrow('oops');
|
expect(() => Scheduler.unstable_flushNumberOfYields(1)).toThrow('oops');
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog(['Parent', 'BadRender', 'Sibling', 'commit']);
|
||||||
'Parent',
|
|
||||||
'BadRender',
|
|
||||||
'Sibling',
|
|
||||||
'commit',
|
|
||||||
]);
|
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -418,7 +423,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render part of the tree
|
// Render part of the tree
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['A', 'B']);
|
await waitFor(['A', 'B']);
|
||||||
|
|
||||||
// Expire the render midway through
|
// Expire the render midway through
|
||||||
Scheduler.unstable_advanceTime(10000);
|
Scheduler.unstable_advanceTime(10000);
|
||||||
|
@ -428,7 +433,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
ReactNoop.flushSync();
|
ReactNoop.flushSync();
|
||||||
}).toThrow('Oops');
|
}).toThrow('Oops');
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
// The render expired, but we shouldn't throw out the partial work.
|
// The render expired, but we shouldn't throw out the partial work.
|
||||||
// Finish the current level.
|
// Finish the current level.
|
||||||
'Oops',
|
'Oops',
|
||||||
|
@ -446,7 +451,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls componentDidCatch multiple times for multiple errors', () => {
|
it('calls componentDidCatch multiple times for multiple errors', async () => {
|
||||||
let id = 0;
|
let id = 0;
|
||||||
class BadMount extends React.Component {
|
class BadMount extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -481,7 +486,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
</ErrorBoundary>,
|
</ErrorBoundary>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
'ErrorBoundary',
|
'ErrorBoundary',
|
||||||
'BadMount',
|
'BadMount',
|
||||||
'BadMount',
|
'BadMount',
|
||||||
|
@ -495,7 +500,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('catches render error in a boundary during full deferred mounting', () => {
|
it('catches render error in a boundary during full deferred mounting', async () => {
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component {
|
||||||
state = {error: null};
|
state = {error: null};
|
||||||
componentDidCatch(error) {
|
componentDidCatch(error) {
|
||||||
|
@ -520,13 +525,13 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
<BrokenRender />
|
<BrokenRender />
|
||||||
</ErrorBoundary>,
|
</ErrorBoundary>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<span prop="Caught an error: Hello." />,
|
<span prop="Caught an error: Hello." />,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('catches render error in a boundary during partial deferred mounting', () => {
|
it('catches render error in a boundary during partial deferred mounting', async () => {
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component {
|
||||||
state = {error: null};
|
state = {error: null};
|
||||||
componentDidCatch(error) {
|
componentDidCatch(error) {
|
||||||
|
@ -558,10 +563,10 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['ErrorBoundary render success']);
|
await waitFor(['ErrorBoundary render success']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
'BrokenRender',
|
'BrokenRender',
|
||||||
// React retries one more time
|
// React retries one more time
|
||||||
'ErrorBoundary render success',
|
'ErrorBoundary render success',
|
||||||
|
@ -608,7 +613,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
'ErrorBoundary render success',
|
'ErrorBoundary render success',
|
||||||
'BrokenRender',
|
'BrokenRender',
|
||||||
|
|
||||||
|
@ -658,7 +663,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
'ErrorBoundary render success',
|
'ErrorBoundary render success',
|
||||||
'BrokenRender',
|
'BrokenRender',
|
||||||
|
|
||||||
|
@ -675,7 +680,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('propagates an error from a noop error boundary during full deferred mounting', () => {
|
it('propagates an error from a noop error boundary during full deferred mounting', async () => {
|
||||||
class RethrowErrorBoundary extends React.Component {
|
class RethrowErrorBoundary extends React.Component {
|
||||||
componentDidCatch(error) {
|
componentDidCatch(error) {
|
||||||
Scheduler.unstable_yieldValue('RethrowErrorBoundary componentDidCatch');
|
Scheduler.unstable_yieldValue('RethrowErrorBoundary componentDidCatch');
|
||||||
|
@ -698,23 +703,22 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
</RethrowErrorBoundary>,
|
</RethrowErrorBoundary>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(() => {
|
await waitForThrow('Hello');
|
||||||
expect(Scheduler).toFlushAndYield([
|
assertLog([
|
||||||
'RethrowErrorBoundary render',
|
'RethrowErrorBoundary render',
|
||||||
'BrokenRender',
|
'BrokenRender',
|
||||||
|
|
||||||
// React retries one more time
|
// React retries one more time
|
||||||
'RethrowErrorBoundary render',
|
'RethrowErrorBoundary render',
|
||||||
'BrokenRender',
|
'BrokenRender',
|
||||||
|
|
||||||
// Errored again on retry. Now handle it.
|
// Errored again on retry. Now handle it.
|
||||||
'RethrowErrorBoundary componentDidCatch',
|
'RethrowErrorBoundary componentDidCatch',
|
||||||
]);
|
]);
|
||||||
}).toThrow('Hello');
|
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('propagates an error from a noop error boundary during partial deferred mounting', () => {
|
it('propagates an error from a noop error boundary during partial deferred mounting', async () => {
|
||||||
class RethrowErrorBoundary extends React.Component {
|
class RethrowErrorBoundary extends React.Component {
|
||||||
componentDidCatch(error) {
|
componentDidCatch(error) {
|
||||||
Scheduler.unstable_yieldValue('RethrowErrorBoundary componentDidCatch');
|
Scheduler.unstable_yieldValue('RethrowErrorBoundary componentDidCatch');
|
||||||
|
@ -739,12 +743,10 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['RethrowErrorBoundary render']);
|
await waitFor(['RethrowErrorBoundary render']);
|
||||||
|
|
||||||
expect(() => {
|
await waitForThrow('Hello');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
assertLog([
|
||||||
}).toThrow('Hello');
|
|
||||||
expect(Scheduler).toHaveYielded([
|
|
||||||
'BrokenRender',
|
'BrokenRender',
|
||||||
|
|
||||||
// React retries one more time
|
// React retries one more time
|
||||||
|
@ -783,7 +785,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}).toThrow('Hello');
|
}).toThrow('Hello');
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
'RethrowErrorBoundary render',
|
'RethrowErrorBoundary render',
|
||||||
'BrokenRender',
|
'BrokenRender',
|
||||||
|
|
||||||
|
@ -826,7 +828,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}).toThrow('Hello');
|
}).toThrow('Hello');
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
'RethrowErrorBoundary render',
|
'RethrowErrorBoundary render',
|
||||||
'BrokenRender',
|
'BrokenRender',
|
||||||
|
|
||||||
|
@ -840,7 +842,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('applies batched updates regardless despite errors in scheduling', () => {
|
it('applies batched updates regardless despite errors in scheduling', async () => {
|
||||||
ReactNoop.render(<span prop="a:1" />);
|
ReactNoop.render(<span prop="a:1" />);
|
||||||
expect(() => {
|
expect(() => {
|
||||||
ReactNoop.batchedUpdates(() => {
|
ReactNoop.batchedUpdates(() => {
|
||||||
|
@ -849,11 +851,11 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
throw new Error('Hello');
|
throw new Error('Hello');
|
||||||
});
|
});
|
||||||
}).toThrow('Hello');
|
}).toThrow('Hello');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="a:3" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="a:3" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('applies nested batched updates despite errors in scheduling', () => {
|
it('applies nested batched updates despite errors in scheduling', async () => {
|
||||||
ReactNoop.render(<span prop="a:1" />);
|
ReactNoop.render(<span prop="a:1" />);
|
||||||
expect(() => {
|
expect(() => {
|
||||||
ReactNoop.batchedUpdates(() => {
|
ReactNoop.batchedUpdates(() => {
|
||||||
|
@ -866,12 +868,12 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).toThrow('Hello');
|
}).toThrow('Hello');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="a:5" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="a:5" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Is this a breaking change?
|
// TODO: Is this a breaking change?
|
||||||
it('defers additional sync work to a separate event after an error', () => {
|
it('defers additional sync work to a separate event after an error', async () => {
|
||||||
ReactNoop.render(<span prop="a:1" />);
|
ReactNoop.render(<span prop="a:1" />);
|
||||||
expect(() => {
|
expect(() => {
|
||||||
ReactNoop.flushSync(() => {
|
ReactNoop.flushSync(() => {
|
||||||
|
@ -882,11 +884,11 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).toThrow('Hello');
|
}).toThrow('Hello');
|
||||||
Scheduler.unstable_flushAll();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="a:3" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="a:3" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can schedule updates after uncaught error in render on mount', () => {
|
it('can schedule updates after uncaught error in render on mount', async () => {
|
||||||
function BrokenRender({unused}) {
|
function BrokenRender({unused}) {
|
||||||
Scheduler.unstable_yieldValue('BrokenRender');
|
Scheduler.unstable_yieldValue('BrokenRender');
|
||||||
throw new Error('Hello');
|
throw new Error('Hello');
|
||||||
|
@ -898,20 +900,18 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<BrokenRender />);
|
ReactNoop.render(<BrokenRender />);
|
||||||
expect(() => {
|
await waitForThrow('Hello');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
|
||||||
}).toThrow('Hello');
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
'BrokenRender',
|
'BrokenRender',
|
||||||
// React retries one more time
|
// React retries one more time
|
||||||
'BrokenRender',
|
'BrokenRender',
|
||||||
// Errored again on retry
|
// Errored again on retry
|
||||||
]);
|
]);
|
||||||
expect(Scheduler).toFlushAndYield(['Foo']);
|
await waitForAll(['Foo']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can schedule updates after uncaught error in render on update', () => {
|
it('can schedule updates after uncaught error in render on update', async () => {
|
||||||
function BrokenRender({shouldThrow}) {
|
function BrokenRender({shouldThrow}) {
|
||||||
Scheduler.unstable_yieldValue('BrokenRender');
|
Scheduler.unstable_yieldValue('BrokenRender');
|
||||||
if (shouldThrow) {
|
if (shouldThrow) {
|
||||||
|
@ -926,13 +926,11 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<BrokenRender shouldThrow={false} />);
|
ReactNoop.render(<BrokenRender shouldThrow={false} />);
|
||||||
expect(Scheduler).toFlushAndYield(['BrokenRender']);
|
await waitForAll(['BrokenRender']);
|
||||||
|
|
||||||
expect(() => {
|
ReactNoop.render(<BrokenRender shouldThrow={true} />);
|
||||||
ReactNoop.render(<BrokenRender shouldThrow={true} />);
|
await waitForThrow('Hello');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
assertLog([
|
||||||
}).toThrow('Hello');
|
|
||||||
expect(Scheduler).toHaveYielded([
|
|
||||||
'BrokenRender',
|
'BrokenRender',
|
||||||
// React retries one more time
|
// React retries one more time
|
||||||
'BrokenRender',
|
'BrokenRender',
|
||||||
|
@ -940,10 +938,10 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushAndYield(['Foo']);
|
await waitForAll(['Foo']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can schedule updates after uncaught error during unmounting', () => {
|
it('can schedule updates after uncaught error during unmounting', async () => {
|
||||||
class BrokenComponentWillUnmount extends React.Component {
|
class BrokenComponentWillUnmount extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return <div />;
|
return <div />;
|
||||||
|
@ -959,19 +957,17 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<BrokenComponentWillUnmount />);
|
ReactNoop.render(<BrokenComponentWillUnmount />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
|
|
||||||
expect(() => {
|
ReactNoop.render(<div />);
|
||||||
ReactNoop.render(<div />);
|
await waitForThrow('Hello');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
|
||||||
}).toThrow('Hello');
|
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushAndYield(['Foo']);
|
await waitForAll(['Foo']);
|
||||||
});
|
});
|
||||||
|
|
||||||
// @gate skipUnmountedBoundaries
|
// @gate skipUnmountedBoundaries
|
||||||
it('should not attempt to recover an unmounting error boundary', () => {
|
it('should not attempt to recover an unmounting error boundary', async () => {
|
||||||
class Parent extends React.Component {
|
class Parent extends React.Component {
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
Scheduler.unstable_yieldValue('Parent componentWillUnmount');
|
Scheduler.unstable_yieldValue('Parent componentWillUnmount');
|
||||||
|
@ -1001,22 +997,21 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Parent />);
|
ReactNoop.render(<Parent />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
|
|
||||||
// Because the error boundary is also unmounting,
|
// Because the error boundary is also unmounting,
|
||||||
// an error in ThrowsOnUnmount should be rethrown.
|
// an error in ThrowsOnUnmount should be rethrown.
|
||||||
expect(() => {
|
ReactNoop.render(null);
|
||||||
ReactNoop.render(null);
|
await waitForThrow('unmount error');
|
||||||
expect(Scheduler).toFlushAndYield([
|
await assertLog([
|
||||||
'Parent componentWillUnmount',
|
'Parent componentWillUnmount',
|
||||||
'ThrowsOnUnmount componentWillUnmount',
|
'ThrowsOnUnmount componentWillUnmount',
|
||||||
]);
|
]);
|
||||||
}).toThrow('unmount error');
|
|
||||||
|
|
||||||
ReactNoop.render(<Parent />);
|
ReactNoop.render(<Parent />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can unmount an error boundary before it is handled', () => {
|
it('can unmount an error boundary before it is handled', async () => {
|
||||||
let parent;
|
let parent;
|
||||||
|
|
||||||
class Parent extends React.Component {
|
class Parent extends React.Component {
|
||||||
|
@ -1045,14 +1040,14 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Parent />);
|
ReactNoop.render(<Parent />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
|
|
||||||
ReactNoop.flushSync(() => {
|
ReactNoop.flushSync(() => {
|
||||||
ReactNoop.render(<Parent />);
|
ReactNoop.render(<Parent />);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('continues work on other roots despite caught errors', () => {
|
it('continues work on other roots despite caught errors', async () => {
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component {
|
||||||
state = {error: null};
|
state = {error: null};
|
||||||
componentDidCatch(error) {
|
componentDidCatch(error) {
|
||||||
|
@ -1079,50 +1074,42 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
'a',
|
'a',
|
||||||
);
|
);
|
||||||
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
|
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(
|
||||||
<span prop="Caught an error: Hello." />,
|
<span prop="Caught an error: Hello." />,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:1" />);
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:1" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('continues work on other roots despite uncaught errors', () => {
|
it('continues work on other roots despite uncaught errors', async () => {
|
||||||
function BrokenRender(props) {
|
function BrokenRender(props) {
|
||||||
throw new Error(props.label);
|
throw new Error(props.label);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.renderToRootWithID(<BrokenRender label="a" />, 'a');
|
ReactNoop.renderToRootWithID(<BrokenRender label="a" />, 'a');
|
||||||
expect(() => {
|
await waitForThrow('a');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
|
||||||
}).toThrow('a');
|
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
|
||||||
|
|
||||||
ReactNoop.renderToRootWithID(<BrokenRender label="a" />, 'a');
|
ReactNoop.renderToRootWithID(<BrokenRender label="a" />, 'a');
|
||||||
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
|
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
|
||||||
expect(() => {
|
await waitForThrow('a');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
|
||||||
}).toThrow('a');
|
|
||||||
|
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:2" />);
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:2" />);
|
||||||
|
|
||||||
ReactNoop.renderToRootWithID(<span prop="a:3" />, 'a');
|
ReactNoop.renderToRootWithID(<span prop="a:3" />, 'a');
|
||||||
ReactNoop.renderToRootWithID(<BrokenRender label="b" />, 'b');
|
ReactNoop.renderToRootWithID(<BrokenRender label="b" />, 'b');
|
||||||
expect(() => {
|
await waitForThrow('b');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
|
||||||
}).toThrow('b');
|
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:3" />);
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:3" />);
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null);
|
||||||
|
|
||||||
ReactNoop.renderToRootWithID(<span prop="a:4" />, 'a');
|
ReactNoop.renderToRootWithID(<span prop="a:4" />, 'a');
|
||||||
ReactNoop.renderToRootWithID(<BrokenRender label="b" />, 'b');
|
ReactNoop.renderToRootWithID(<BrokenRender label="b" />, 'b');
|
||||||
ReactNoop.renderToRootWithID(<span prop="c:4" />, 'c');
|
ReactNoop.renderToRootWithID(<span prop="c:4" />, 'c');
|
||||||
expect(() => {
|
await waitForThrow('b');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
}).toThrow('b');
|
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:4" />);
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:4" />);
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null);
|
||||||
expect(ReactNoop.getChildrenAsJSX('c')).toEqual(<span prop="c:4" />);
|
expect(ReactNoop.getChildrenAsJSX('c')).toEqual(<span prop="c:4" />);
|
||||||
|
@ -1132,10 +1119,8 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
ReactNoop.renderToRootWithID(<span prop="c:5" />, 'c');
|
ReactNoop.renderToRootWithID(<span prop="c:5" />, 'c');
|
||||||
ReactNoop.renderToRootWithID(<span prop="d:5" />, 'd');
|
ReactNoop.renderToRootWithID(<span prop="d:5" />, 'd');
|
||||||
ReactNoop.renderToRootWithID(<BrokenRender label="e" />, 'e');
|
ReactNoop.renderToRootWithID(<BrokenRender label="e" />, 'e');
|
||||||
expect(() => {
|
await waitForThrow('e');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
}).toThrow('e');
|
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:5" />);
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:5" />);
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:5" />);
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:5" />);
|
||||||
expect(ReactNoop.getChildrenAsJSX('c')).toEqual(<span prop="c:5" />);
|
expect(ReactNoop.getChildrenAsJSX('c')).toEqual(<span prop="c:5" />);
|
||||||
|
@ -1149,17 +1134,11 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
ReactNoop.renderToRootWithID(<BrokenRender label="e" />, 'e');
|
ReactNoop.renderToRootWithID(<BrokenRender label="e" />, 'e');
|
||||||
ReactNoop.renderToRootWithID(<span prop="f:6" />, 'f');
|
ReactNoop.renderToRootWithID(<span prop="f:6" />, 'f');
|
||||||
|
|
||||||
expect(() => {
|
await waitForThrow('a');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForThrow('c');
|
||||||
}).toThrow('a');
|
await waitForThrow('e');
|
||||||
expect(() => {
|
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
|
||||||
}).toThrow('c');
|
|
||||||
expect(() => {
|
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
|
||||||
}).toThrow('e');
|
|
||||||
|
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:6" />);
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:6" />);
|
||||||
expect(ReactNoop.getChildrenAsJSX('c')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('c')).toEqual(null);
|
||||||
|
@ -1173,7 +1152,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
ReactNoop.unmountRootWithID('d');
|
ReactNoop.unmountRootWithID('d');
|
||||||
ReactNoop.unmountRootWithID('e');
|
ReactNoop.unmountRootWithID('e');
|
||||||
ReactNoop.unmountRootWithID('f');
|
ReactNoop.unmountRootWithID('f');
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(null);
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(null);
|
||||||
expect(ReactNoop.getChildrenAsJSX('c')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('c')).toEqual(null);
|
||||||
|
@ -1182,7 +1161,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
expect(ReactNoop.getChildrenAsJSX('f')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('f')).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('unwinds the context stack correctly on error', () => {
|
it('unwinds the context stack correctly on error', async () => {
|
||||||
class Provider extends React.Component {
|
class Provider extends React.Component {
|
||||||
static childContextTypes = {message: PropTypes.string};
|
static childContextTypes = {message: PropTypes.string};
|
||||||
static contextTypes = {message: PropTypes.string};
|
static contextTypes = {message: PropTypes.string};
|
||||||
|
@ -1234,7 +1213,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
<Connector />
|
<Connector />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
|
|
||||||
// If the context stack does not unwind, span will get 'abcde'
|
// If the context stack does not unwind, span will get 'abcde'
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="a" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="a" />);
|
||||||
|
@ -1283,7 +1262,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('catches reconciler errors in a boundary during update', () => {
|
it('catches reconciler errors in a boundary during update', async () => {
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component {
|
||||||
state = {error: null};
|
state = {error: null};
|
||||||
componentDidCatch(error) {
|
componentDidCatch(error) {
|
||||||
|
@ -1307,7 +1286,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
<BrokenRender fail={false} />
|
<BrokenRender fail={false} />
|
||||||
</ErrorBoundary>,
|
</ErrorBoundary>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
|
|
||||||
ReactNoop.render(
|
ReactNoop.render(
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
@ -1334,7 +1313,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('recovers from uncaught reconciler errors', () => {
|
it('recovers from uncaught reconciler errors', async () => {
|
||||||
const InvalidType = undefined;
|
const InvalidType = undefined;
|
||||||
expect(() => ReactNoop.render(<InvalidType />)).toErrorDev(
|
expect(() => ReactNoop.render(<InvalidType />)).toErrorDev(
|
||||||
'Warning: React.createElement: type is invalid -- expected a string',
|
'Warning: React.createElement: type is invalid -- expected a string',
|
||||||
|
@ -1350,11 +1329,11 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactNoop.render(<span prop="hi" />);
|
ReactNoop.render(<span prop="hi" />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="hi" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="hi" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('unmounts components with uncaught errors', () => {
|
it('unmounts components with uncaught errors', async () => {
|
||||||
let inst;
|
let inst;
|
||||||
|
|
||||||
class BrokenRenderAndUnmount extends React.Component {
|
class BrokenRenderAndUnmount extends React.Component {
|
||||||
|
@ -1390,14 +1369,21 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
</Parent>
|
</Parent>
|
||||||
</Parent>,
|
</Parent>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
|
|
||||||
inst.setState({fail: true});
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
expect(() => {
|
||||||
}).toThrowError('Hello.');
|
ReactNoop.flushSync(() => {
|
||||||
|
inst.setState({fail: true});
|
||||||
|
});
|
||||||
|
}).toThrow('Hello.');
|
||||||
|
|
||||||
expect(Scheduler).toHaveYielded([
|
// The unmount is queued in a microtask. In order to capture the error
|
||||||
|
// that occurs during unmount, we can flush it early with `flushSync`.
|
||||||
|
ReactNoop.flushSync();
|
||||||
|
}).toThrow('One does not simply unmount me.');
|
||||||
|
|
||||||
|
assertLog([
|
||||||
// Attempt to clean up.
|
// Attempt to clean up.
|
||||||
// Errors in parents shouldn't stop children from unmounting.
|
// Errors in parents shouldn't stop children from unmounting.
|
||||||
'Parent componentWillUnmount [!]',
|
'Parent componentWillUnmount [!]',
|
||||||
|
@ -1405,13 +1391,9 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
'BrokenRenderAndUnmount componentWillUnmount',
|
'BrokenRenderAndUnmount componentWillUnmount',
|
||||||
]);
|
]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
ReactNoop.flushSync();
|
|
||||||
}).toThrow('One does not simply unmount me.');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not interrupt unmounting if detaching a ref throws', () => {
|
it('does not interrupt unmounting if detaching a ref throws', async () => {
|
||||||
class Bar extends React.Component {
|
class Bar extends React.Component {
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
Scheduler.unstable_yieldValue('Bar unmount');
|
Scheduler.unstable_yieldValue('Bar unmount');
|
||||||
|
@ -1434,7 +1416,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushAndYield(['barRef attach']);
|
await waitForAll(['barRef attach']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<span prop="Bar" />
|
<span prop="Bar" />
|
||||||
|
@ -1444,7 +1426,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
// Unmount
|
// Unmount
|
||||||
ReactNoop.render(<Foo hide={true} />);
|
ReactNoop.render(<Foo hide={true} />);
|
||||||
expect(Scheduler).toFlushAndThrow('Detach error');
|
expect(Scheduler).toFlushAndThrow('Detach error');
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
'barRef detach',
|
'barRef detach',
|
||||||
// Bar should unmount even though its ref threw an error while detaching
|
// Bar should unmount even though its ref threw an error while detaching
|
||||||
'Bar unmount',
|
'Bar unmount',
|
||||||
|
@ -1465,7 +1447,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
expect(Scheduler).toFlushAndThrow('Error!');
|
expect(Scheduler).toFlushAndThrow('Error!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('error boundaries capture non-errors', () => {
|
it('error boundaries capture non-errors', async () => {
|
||||||
spyOnProd(console, 'error').mockImplementation(() => {});
|
spyOnProd(console, 'error').mockImplementation(() => {});
|
||||||
spyOnDev(console, 'error').mockImplementation(() => {});
|
spyOnDev(console, 'error').mockImplementation(() => {});
|
||||||
|
|
||||||
|
@ -1509,7 +1491,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
</ErrorBoundary>,
|
</ErrorBoundary>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
'ErrorBoundary (try)',
|
'ErrorBoundary (try)',
|
||||||
'Indirection',
|
'Indirection',
|
||||||
'BadRender',
|
'BadRender',
|
||||||
|
@ -1540,7 +1522,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
|
|
||||||
// TODO: Error boundary does not catch promises
|
// TODO: Error boundary does not catch promises
|
||||||
|
|
||||||
it('continues working on siblings of a component that throws', () => {
|
it('continues working on siblings of a component that throws', async () => {
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component {
|
||||||
state = {error: null};
|
state = {error: null};
|
||||||
componentDidCatch(error) {
|
componentDidCatch(error) {
|
||||||
|
@ -1580,7 +1562,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
</ErrorBoundary>,
|
</ErrorBoundary>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
'ErrorBoundary (try)',
|
'ErrorBoundary (try)',
|
||||||
'throw',
|
'throw',
|
||||||
// Continue rendering siblings after BadRender throws
|
// Continue rendering siblings after BadRender throws
|
||||||
|
@ -1603,7 +1585,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls the correct lifecycles on the error boundary after catching an error (mixed)', () => {
|
it('calls the correct lifecycles on the error boundary after catching an error (mixed)', async () => {
|
||||||
// This test seems a bit contrived, but it's based on an actual regression
|
// This test seems a bit contrived, but it's based on an actual regression
|
||||||
// where we checked for the existence of didUpdate instead of didMount, and
|
// where we checked for the existence of didUpdate instead of didMount, and
|
||||||
// didMount was not defined.
|
// didMount was not defined.
|
||||||
|
@ -1632,7 +1614,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Parent step={1} />);
|
ReactNoop.render(<Parent step={1} />);
|
||||||
expect(Scheduler).toFlushAndYieldThrough([
|
await waitFor([
|
||||||
'render',
|
'render',
|
||||||
'throw',
|
'throw',
|
||||||
'render',
|
'render',
|
||||||
|
@ -1646,7 +1628,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('provides component stack to the error boundary with componentDidCatch', () => {
|
it('provides component stack to the error boundary with componentDidCatch', async () => {
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component {
|
||||||
state = {error: null, errorInfo: null};
|
state = {error: null, errorInfo: null};
|
||||||
componentDidCatch(error, errorInfo) {
|
componentDidCatch(error, errorInfo) {
|
||||||
|
@ -1676,7 +1658,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
<BrokenRender />
|
<BrokenRender />
|
||||||
</ErrorBoundary>,
|
</ErrorBoundary>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushAndYield(['render error message']);
|
await waitForAll(['render error message']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<span
|
<span
|
||||||
prop={
|
prop={
|
||||||
|
@ -1688,7 +1670,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not provide component stack to the error boundary with getDerivedStateFromError', () => {
|
it('does not provide component stack to the error boundary with getDerivedStateFromError', async () => {
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component {
|
||||||
state = {error: null};
|
state = {error: null};
|
||||||
static getDerivedStateFromError(error, errorInfo) {
|
static getDerivedStateFromError(error, errorInfo) {
|
||||||
|
@ -1712,13 +1694,13 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
<BrokenRender />
|
<BrokenRender />
|
||||||
</ErrorBoundary>,
|
</ErrorBoundary>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<span prop="Caught an error: Hello" />,
|
<span prop="Caught an error: Hello" />,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('provides component stack even if overriding prepareStackTrace', () => {
|
it('provides component stack even if overriding prepareStackTrace', async () => {
|
||||||
Error.prepareStackTrace = function (error, callsites) {
|
Error.prepareStackTrace = function (error, callsites) {
|
||||||
const stack = ['An error occurred:', error.message];
|
const stack = ['An error occurred:', error.message];
|
||||||
for (let i = 0; i < callsites.length; i++) {
|
for (let i = 0; i < callsites.length; i++) {
|
||||||
|
@ -1762,7 +1744,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
<BrokenRender />
|
<BrokenRender />
|
||||||
</ErrorBoundary>,
|
</ErrorBoundary>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushAndYield(['render error message']);
|
await waitForAll(['render error message']);
|
||||||
Error.prepareStackTrace = undefined;
|
Error.prepareStackTrace = undefined;
|
||||||
|
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
|
@ -1822,7 +1804,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render past the component that throws, then yield.
|
// Render past the component that throws, then yield.
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Oops']);
|
await waitFor(['Oops']);
|
||||||
expect(root).toMatchRenderedOutput(null);
|
expect(root).toMatchRenderedOutput(null);
|
||||||
// Interleaved update. When the root completes, instead of throwing the
|
// Interleaved update. When the root completes, instead of throwing the
|
||||||
// error, it should try rendering again. This update will cause it to
|
// error, it should try rendering again. This update will cause it to
|
||||||
|
@ -1868,7 +1850,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
|
|
||||||
// Render through just the default pri update. The low pri update remains on
|
// Render through just the default pri update. The low pri update remains on
|
||||||
// the queue.
|
// the queue.
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Everything is fine.']);
|
await waitFor(['Everything is fine.']);
|
||||||
|
|
||||||
// Schedule a discrete update on a child that triggers an error.
|
// Schedule a discrete update on a child that triggers an error.
|
||||||
// The root should capture this error. But since there's still a pending
|
// The root should capture this error. But since there's still a pending
|
||||||
|
@ -1878,7 +1860,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// Should render the final state without throwing the error.
|
// Should render the final state without throwing the error.
|
||||||
expect(Scheduler).toHaveYielded(['Everything is fine.']);
|
assertLog(['Everything is fine.']);
|
||||||
expect(root).toMatchRenderedOutput('Everything is fine.');
|
expect(root).toMatchRenderedOutput('Everything is fine.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
let React;
|
let React;
|
||||||
let ReactNoop;
|
let ReactNoop;
|
||||||
let Scheduler;
|
let Scheduler;
|
||||||
|
let waitForAll;
|
||||||
|
|
||||||
describe('ReactIncrementalErrorLogging', () => {
|
describe('ReactIncrementalErrorLogging', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -20,6 +21,9 @@ describe('ReactIncrementalErrorLogging', () => {
|
||||||
React = require('react');
|
React = require('react');
|
||||||
ReactNoop = require('react-noop-renderer');
|
ReactNoop = require('react-noop-renderer');
|
||||||
Scheduler = require('scheduler');
|
Scheduler = require('scheduler');
|
||||||
|
|
||||||
|
const InternalTestUtils = require('internal-test-utils');
|
||||||
|
waitForAll = InternalTestUtils.waitForAll;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Note: in this test file we won't be using toErrorDev() matchers
|
// Note: in this test file we won't be using toErrorDev() matchers
|
||||||
|
@ -151,7 +155,7 @@ describe('ReactIncrementalErrorLogging', () => {
|
||||||
}).toThrow('logCapturedError error');
|
}).toThrow('logCapturedError error');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resets instance variables before unmounting failed node', () => {
|
it('resets instance variables before unmounting failed node', async () => {
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends React.Component {
|
||||||
state = {error: null};
|
state = {error: null};
|
||||||
componentDidCatch(error) {
|
componentDidCatch(error) {
|
||||||
|
@ -185,7 +189,7 @@ describe('ReactIncrementalErrorLogging', () => {
|
||||||
<Foo />
|
<Foo />
|
||||||
</ErrorBoundary>,
|
</ErrorBoundary>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushAndYield(
|
await waitForAll(
|
||||||
[
|
[
|
||||||
'render: 0',
|
'render: 0',
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
let React;
|
let React;
|
||||||
let ReactNoop;
|
let ReactNoop;
|
||||||
let Scheduler;
|
let Scheduler;
|
||||||
|
let waitForAll;
|
||||||
|
|
||||||
describe('ReactIncrementalErrorReplay', () => {
|
describe('ReactIncrementalErrorReplay', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -20,6 +21,9 @@ describe('ReactIncrementalErrorReplay', () => {
|
||||||
React = require('react');
|
React = require('react');
|
||||||
ReactNoop = require('react-noop-renderer');
|
ReactNoop = require('react-noop-renderer');
|
||||||
Scheduler = require('scheduler');
|
Scheduler = require('scheduler');
|
||||||
|
|
||||||
|
const InternalTestUtils = require('internal-test-utils');
|
||||||
|
waitForAll = InternalTestUtils.waitForAll;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail gracefully on error in the host environment', () => {
|
it('should fail gracefully on error in the host environment', () => {
|
||||||
|
@ -27,7 +31,7 @@ describe('ReactIncrementalErrorReplay', () => {
|
||||||
expect(Scheduler).toFlushAndThrow('Error in host config.');
|
expect(Scheduler).toFlushAndThrow('Error in host config.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should ignore error if it doesn't throw on retry", () => {
|
it("should ignore error if it doesn't throw on retry", async () => {
|
||||||
let didInit = false;
|
let didInit = false;
|
||||||
|
|
||||||
function badLazyInit() {
|
function badLazyInit() {
|
||||||
|
@ -45,6 +49,6 @@ describe('ReactIncrementalErrorReplay', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReactNoop.render(<App />);
|
ReactNoop.render(<App />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
let React;
|
let React;
|
||||||
let ReactNoop;
|
let ReactNoop;
|
||||||
let Scheduler;
|
let Scheduler;
|
||||||
|
let waitFor;
|
||||||
|
let waitForAll;
|
||||||
|
|
||||||
describe('ReactIncrementalReflection', () => {
|
describe('ReactIncrementalReflection', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -21,6 +23,10 @@ describe('ReactIncrementalReflection', () => {
|
||||||
React = require('react');
|
React = require('react');
|
||||||
ReactNoop = require('react-noop-renderer');
|
ReactNoop = require('react-noop-renderer');
|
||||||
Scheduler = require('scheduler');
|
Scheduler = require('scheduler');
|
||||||
|
|
||||||
|
const InternalTestUtils = require('internal-test-utils');
|
||||||
|
waitFor = InternalTestUtils.waitFor;
|
||||||
|
waitForAll = InternalTestUtils.waitForAll;
|
||||||
});
|
});
|
||||||
|
|
||||||
function div(...children) {
|
function div(...children) {
|
||||||
|
@ -34,7 +40,7 @@ describe('ReactIncrementalReflection', () => {
|
||||||
return {type: 'span', children: [], prop, hidden: false};
|
return {type: 'span', children: [], prop, hidden: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
it('handles isMounted even when the initial render is deferred', () => {
|
it('handles isMounted even when the initial render is deferred', async () => {
|
||||||
const instances = [];
|
const instances = [];
|
||||||
|
|
||||||
class Component extends React.Component {
|
class Component extends React.Component {
|
||||||
|
@ -68,17 +74,17 @@ describe('ReactIncrementalReflection', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render part way through but don't yet commit the updates.
|
// Render part way through but don't yet commit the updates.
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['componentWillMount: false']);
|
await waitFor(['componentWillMount: false']);
|
||||||
|
|
||||||
expect(instances[0]._isMounted()).toBe(false);
|
expect(instances[0]._isMounted()).toBe(false);
|
||||||
|
|
||||||
// Render the rest and commit the updates.
|
// Render the rest and commit the updates.
|
||||||
expect(Scheduler).toFlushAndYield(['componentDidMount: true']);
|
await waitForAll(['componentDidMount: true']);
|
||||||
|
|
||||||
expect(instances[0]._isMounted()).toBe(true);
|
expect(instances[0]._isMounted()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles isMounted when an unmount is deferred', () => {
|
it('handles isMounted when an unmount is deferred', async () => {
|
||||||
const instances = [];
|
const instances = [];
|
||||||
|
|
||||||
class Component extends React.Component {
|
class Component extends React.Component {
|
||||||
|
@ -109,7 +115,7 @@ describe('ReactIncrementalReflection', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo mount={true} />);
|
ReactNoop.render(<Foo mount={true} />);
|
||||||
expect(Scheduler).toFlushAndYield(['Component']);
|
await waitForAll(['Component']);
|
||||||
|
|
||||||
expect(instances[0]._isMounted()).toBe(true);
|
expect(instances[0]._isMounted()).toBe(true);
|
||||||
|
|
||||||
|
@ -118,17 +124,17 @@ describe('ReactIncrementalReflection', () => {
|
||||||
});
|
});
|
||||||
// Render part way through but don't yet commit the updates so it is not
|
// Render part way through but don't yet commit the updates so it is not
|
||||||
// fully unmounted yet.
|
// fully unmounted yet.
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Other']);
|
await waitFor(['Other']);
|
||||||
|
|
||||||
expect(instances[0]._isMounted()).toBe(true);
|
expect(instances[0]._isMounted()).toBe(true);
|
||||||
|
|
||||||
// Finish flushing the unmount.
|
// Finish flushing the unmount.
|
||||||
expect(Scheduler).toFlushAndYield(['componentWillUnmount: true']);
|
await waitForAll(['componentWillUnmount: true']);
|
||||||
|
|
||||||
expect(instances[0]._isMounted()).toBe(false);
|
expect(instances[0]._isMounted()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('finds no node before insertion and correct node before deletion', () => {
|
it('finds no node before insertion and correct node before deletion', async () => {
|
||||||
let classInstance = null;
|
let classInstance = null;
|
||||||
|
|
||||||
function findInstance(inst) {
|
function findInstance(inst) {
|
||||||
|
@ -202,18 +208,14 @@ describe('ReactIncrementalReflection', () => {
|
||||||
ReactNoop.render(<Foo step={0} />);
|
ReactNoop.render(<Foo step={0} />);
|
||||||
});
|
});
|
||||||
// Flush past Component but don't complete rendering everything yet.
|
// Flush past Component but don't complete rendering everything yet.
|
||||||
expect(Scheduler).toFlushAndYieldThrough([
|
await waitFor([['componentWillMount', null], 'render', 'render sibling']);
|
||||||
['componentWillMount', null],
|
|
||||||
'render',
|
|
||||||
'render sibling',
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(classInstance).toBeDefined();
|
expect(classInstance).toBeDefined();
|
||||||
// The instance has been complete but is still not committed so it should
|
// The instance has been complete but is still not committed so it should
|
||||||
// not find any host nodes in it.
|
// not find any host nodes in it.
|
||||||
expect(findInstance(classInstance)).toBe(null);
|
expect(findInstance(classInstance)).toBe(null);
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield([['componentDidMount', span()]]);
|
await waitForAll([['componentDidMount', span()]]);
|
||||||
|
|
||||||
const hostSpan = classInstance.span;
|
const hostSpan = classInstance.span;
|
||||||
expect(hostSpan).toBeDefined();
|
expect(hostSpan).toBeDefined();
|
||||||
|
@ -223,7 +225,7 @@ describe('ReactIncrementalReflection', () => {
|
||||||
// Flush next step which will cause an update but not yet render a new host
|
// Flush next step which will cause an update but not yet render a new host
|
||||||
// node.
|
// node.
|
||||||
ReactNoop.render(<Foo step={1} />);
|
ReactNoop.render(<Foo step={1} />);
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
['componentWillUpdate', hostSpan],
|
['componentWillUpdate', hostSpan],
|
||||||
'render',
|
'render',
|
||||||
'render sibling',
|
'render sibling',
|
||||||
|
@ -237,7 +239,7 @@ describe('ReactIncrementalReflection', () => {
|
||||||
React.startTransition(() => {
|
React.startTransition(() => {
|
||||||
ReactNoop.render(<Foo step={2} />);
|
ReactNoop.render(<Foo step={2} />);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYieldThrough([
|
await waitFor([
|
||||||
['componentWillUpdate', hostSpan],
|
['componentWillUpdate', hostSpan],
|
||||||
'render',
|
'render',
|
||||||
'render sibling',
|
'render sibling',
|
||||||
|
@ -247,7 +249,7 @@ describe('ReactIncrementalReflection', () => {
|
||||||
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
|
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
|
||||||
|
|
||||||
// When we finally flush the tree it will get committed.
|
// When we finally flush the tree it will get committed.
|
||||||
expect(Scheduler).toFlushAndYield([['componentDidUpdate', div()]]);
|
await waitForAll([['componentDidUpdate', div()]]);
|
||||||
|
|
||||||
const hostDiv = classInstance.div;
|
const hostDiv = classInstance.div;
|
||||||
expect(hostDiv).toBeDefined();
|
expect(hostDiv).toBeDefined();
|
||||||
|
@ -260,7 +262,7 @@ describe('ReactIncrementalReflection', () => {
|
||||||
React.startTransition(() => {
|
React.startTransition(() => {
|
||||||
ReactNoop.render(<Foo step={3} />);
|
ReactNoop.render(<Foo step={3} />);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYieldThrough([
|
await waitFor([
|
||||||
['componentWillUpdate', hostDiv],
|
['componentWillUpdate', hostDiv],
|
||||||
'render',
|
'render',
|
||||||
'render sibling',
|
'render sibling',
|
||||||
|
@ -269,14 +271,14 @@ describe('ReactIncrementalReflection', () => {
|
||||||
// This should still be the host div since the deletion is not committed.
|
// This should still be the host div since the deletion is not committed.
|
||||||
expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
|
expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield([['componentDidUpdate', null]]);
|
await waitForAll([['componentDidUpdate', null]]);
|
||||||
|
|
||||||
// This should still be the host div since the deletion is not committed.
|
// This should still be the host div since the deletion is not committed.
|
||||||
expect(ReactNoop.findInstance(classInstance)).toBe(null);
|
expect(ReactNoop.findInstance(classInstance)).toBe(null);
|
||||||
|
|
||||||
// Render a div again
|
// Render a div again
|
||||||
ReactNoop.render(<Foo step={4} />);
|
ReactNoop.render(<Foo step={4} />);
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
['componentWillUpdate', null],
|
['componentWillUpdate', null],
|
||||||
'render',
|
'render',
|
||||||
'render sibling',
|
'render sibling',
|
||||||
|
@ -285,6 +287,6 @@ describe('ReactIncrementalReflection', () => {
|
||||||
|
|
||||||
// Unmount the component.
|
// Unmount the component.
|
||||||
ReactNoop.render([]);
|
ReactNoop.render([]);
|
||||||
expect(Scheduler).toFlushAndYield([['componentWillUnmount', hostDiv]]);
|
await waitForAll([['componentWillUnmount', hostDiv]]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,10 @@ let React;
|
||||||
let ReactNoop;
|
let ReactNoop;
|
||||||
let Scheduler;
|
let Scheduler;
|
||||||
let act;
|
let act;
|
||||||
|
let waitForAll;
|
||||||
|
let waitFor;
|
||||||
|
let assertLog;
|
||||||
|
let waitForPaint;
|
||||||
|
|
||||||
describe('ReactIncrementalScheduling', () => {
|
describe('ReactIncrementalScheduling', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -23,32 +27,38 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
ReactNoop = require('react-noop-renderer');
|
ReactNoop = require('react-noop-renderer');
|
||||||
Scheduler = require('scheduler');
|
Scheduler = require('scheduler');
|
||||||
act = require('jest-react').act;
|
act = require('jest-react').act;
|
||||||
|
|
||||||
|
const InternalTestUtils = require('internal-test-utils');
|
||||||
|
waitForAll = InternalTestUtils.waitForAll;
|
||||||
|
waitFor = InternalTestUtils.waitFor;
|
||||||
|
assertLog = InternalTestUtils.assertLog;
|
||||||
|
waitForPaint = InternalTestUtils.waitForPaint;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('schedules and flushes deferred work', () => {
|
it('schedules and flushes deferred work', async () => {
|
||||||
ReactNoop.render(<span prop="1" />);
|
ReactNoop.render(<span prop="1" />);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
|
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="1" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="1" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('searches for work on other roots once the current root completes', () => {
|
it('searches for work on other roots once the current root completes', async () => {
|
||||||
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
|
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
|
||||||
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
|
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
|
||||||
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
|
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
|
||||||
|
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
|
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:1" />);
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual(<span prop="a:1" />);
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:1" />);
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual(<span prop="b:1" />);
|
||||||
expect(ReactNoop.getChildrenAsJSX('c')).toEqual(<span prop="c:1" />);
|
expect(ReactNoop.getChildrenAsJSX('c')).toEqual(<span prop="c:1" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('schedules top-level updates in order of priority', () => {
|
it('schedules top-level updates in order of priority', async () => {
|
||||||
// Initial render.
|
// Initial render.
|
||||||
ReactNoop.render(<span prop={1} />);
|
ReactNoop.render(<span prop={1} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
|
||||||
|
|
||||||
ReactNoop.batchedUpdates(() => {
|
ReactNoop.batchedUpdates(() => {
|
||||||
|
@ -64,14 +74,14 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
|
|
||||||
// The terminal value should be the last update that was scheduled,
|
// The terminal value should be the last update that was scheduled,
|
||||||
// regardless of priority. In this case, that's the last sync update.
|
// regardless of priority. In this case, that's the last sync update.
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={4} />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop={4} />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('schedules top-level updates with same priority in order of insertion', () => {
|
it('schedules top-level updates with same priority in order of insertion', async () => {
|
||||||
// Initial render.
|
// Initial render.
|
||||||
ReactNoop.render(<span prop={1} />);
|
ReactNoop.render(<span prop={1} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
|
||||||
|
|
||||||
ReactNoop.render(<span prop={2} />);
|
ReactNoop.render(<span prop={2} />);
|
||||||
|
@ -79,7 +89,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
ReactNoop.render(<span prop={4} />);
|
ReactNoop.render(<span prop={4} />);
|
||||||
ReactNoop.render(<span prop={5} />);
|
ReactNoop.render(<span prop={5} />);
|
||||||
|
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={5} />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop={5} />);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -97,20 +107,20 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
ReactNoop.renderToRootWithID(<Text text="b:1" />, 'b');
|
ReactNoop.renderToRootWithID(<Text text="b:1" />, 'b');
|
||||||
ReactNoop.renderToRootWithID(<Text text="c:1" />, 'c');
|
ReactNoop.renderToRootWithID(<Text text="c:1" />, 'c');
|
||||||
});
|
});
|
||||||
expect(Scheduler).toHaveYielded(['a:1', 'b:1', 'c:1']);
|
assertLog(['a:1', 'b:1', 'c:1']);
|
||||||
|
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1');
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1');
|
||||||
expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:1');
|
expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:1');
|
||||||
|
|
||||||
// Schedule deferred work in the reverse order
|
// Schedule deferred work in the reverse order
|
||||||
act(() => {
|
act(async () => {
|
||||||
React.startTransition(() => {
|
React.startTransition(() => {
|
||||||
ReactNoop.renderToRootWithID(<Text text="c:2" />, 'c');
|
ReactNoop.renderToRootWithID(<Text text="c:2" />, 'c');
|
||||||
ReactNoop.renderToRootWithID(<Text text="b:2" />, 'b');
|
ReactNoop.renderToRootWithID(<Text text="b:2" />, 'b');
|
||||||
});
|
});
|
||||||
// Ensure it starts in the order it was scheduled
|
// Ensure it starts in the order it was scheduled
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['c:2']);
|
await waitFor(['c:2']);
|
||||||
|
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1');
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1');
|
||||||
|
@ -122,19 +132,19 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keep performing work in the order it was scheduled
|
// Keep performing work in the order it was scheduled
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['b:2']);
|
await waitFor(['b:2']);
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2');
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2');
|
||||||
expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
|
expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['a:2']);
|
await waitFor(['a:2']);
|
||||||
expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:2');
|
expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:2');
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2');
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2');
|
||||||
expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
|
expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('schedules sync updates when inside componentDidMount/Update', () => {
|
it('schedules sync updates when inside componentDidMount/Update', async () => {
|
||||||
let instance;
|
let instance;
|
||||||
|
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
|
@ -176,7 +186,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
});
|
});
|
||||||
// Render without committing
|
// Render without committing
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['render: 0']);
|
await waitFor(['render: 0']);
|
||||||
|
|
||||||
// Do one more unit of work to commit
|
// Do one more unit of work to commit
|
||||||
expect(ReactNoop.flushNextYield()).toEqual([
|
expect(ReactNoop.flushNextYield()).toEqual([
|
||||||
|
@ -191,7 +201,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
React.startTransition(() => {
|
React.startTransition(() => {
|
||||||
instance.setState({tick: 2});
|
instance.setState({tick: 2});
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['render: 2']);
|
await waitFor(['render: 2']);
|
||||||
expect(ReactNoop.flushNextYield()).toEqual([
|
expect(ReactNoop.flushNextYield()).toEqual([
|
||||||
'componentDidUpdate: 2',
|
'componentDidUpdate: 2',
|
||||||
'componentDidUpdate (before setState): 2',
|
'componentDidUpdate (before setState): 2',
|
||||||
|
@ -203,7 +213,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can opt-in to async scheduling inside componentDidMount/Update', () => {
|
it('can opt-in to async scheduling inside componentDidMount/Update', async () => {
|
||||||
let instance;
|
let instance;
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
state = {tick: 0};
|
state = {tick: 0};
|
||||||
|
@ -248,7 +258,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
});
|
});
|
||||||
// The cDM update should not have flushed yet because it has async priority.
|
// The cDM update should not have flushed yet because it has async priority.
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
'render: 0',
|
'render: 0',
|
||||||
'componentDidMount (before setState): 0',
|
'componentDidMount (before setState): 0',
|
||||||
'componentDidMount (after setState): 0',
|
'componentDidMount (after setState): 0',
|
||||||
|
@ -256,14 +266,14 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
|
||||||
|
|
||||||
// Now flush the cDM update.
|
// Now flush the cDM update.
|
||||||
expect(Scheduler).toFlushAndYield(['render: 1', 'componentDidUpdate: 1']);
|
await waitForAll(['render: 1', 'componentDidUpdate: 1']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
|
||||||
|
|
||||||
React.startTransition(() => {
|
React.startTransition(() => {
|
||||||
instance.setState({tick: 2});
|
instance.setState({tick: 2});
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushUntilNextPaint([
|
await waitForPaint([
|
||||||
'render: 2',
|
'render: 2',
|
||||||
'componentDidUpdate: 2',
|
'componentDidUpdate: 2',
|
||||||
'componentDidUpdate (before setState): 2',
|
'componentDidUpdate (before setState): 2',
|
||||||
|
@ -272,11 +282,11 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={2} />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop={2} />);
|
||||||
|
|
||||||
// Now flush the cDU update.
|
// Now flush the cDU update.
|
||||||
expect(Scheduler).toFlushAndYield(['render: 3', 'componentDidUpdate: 3']);
|
await waitForAll(['render: 3', 'componentDidUpdate: 3']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={3} />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop={3} />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('performs Task work even after time runs out', () => {
|
it('performs Task work even after time runs out', async () => {
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
state = {step: 1};
|
state = {step: 1};
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -299,7 +309,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
|
|
||||||
// This should be just enough to complete all the work, but not enough to
|
// This should be just enough to complete all the work, but not enough to
|
||||||
// commit it.
|
// commit it.
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Foo']);
|
await waitFor(['Foo']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
|
|
||||||
// Do one more unit of work.
|
// Do one more unit of work.
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
let React;
|
let React;
|
||||||
let ReactNoop;
|
let ReactNoop;
|
||||||
let Scheduler;
|
let Scheduler;
|
||||||
|
let waitForAll;
|
||||||
|
let waitFor;
|
||||||
|
|
||||||
describe('ReactIncrementalSideEffects', () => {
|
describe('ReactIncrementalSideEffects', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -21,6 +23,10 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
React = require('react');
|
React = require('react');
|
||||||
ReactNoop = require('react-noop-renderer');
|
ReactNoop = require('react-noop-renderer');
|
||||||
Scheduler = require('scheduler');
|
Scheduler = require('scheduler');
|
||||||
|
|
||||||
|
const InternalTestUtils = require('internal-test-utils');
|
||||||
|
waitForAll = InternalTestUtils.waitForAll;
|
||||||
|
waitFor = InternalTestUtils.waitFor;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Note: This is based on a similar component we use in www. We can delete
|
// Note: This is based on a similar component we use in www. We can delete
|
||||||
|
@ -36,7 +42,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can update child nodes of a host instance', () => {
|
it('can update child nodes of a host instance', async () => {
|
||||||
function Bar(props) {
|
function Bar(props) {
|
||||||
return <span>{props.text}</span>;
|
return <span>{props.text}</span>;
|
||||||
}
|
}
|
||||||
|
@ -51,7 +57,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo text="Hello" />);
|
ReactNoop.render(<Foo text="Hello" />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<span>Hello</span>
|
<span>Hello</span>
|
||||||
|
@ -59,7 +65,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactNoop.render(<Foo text="World" />);
|
ReactNoop.render(<Foo text="World" />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<span>World</span>
|
<span>World</span>
|
||||||
|
@ -68,7 +74,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can update child nodes of a fragment', function () {
|
it('can update child nodes of a fragment', async function () {
|
||||||
function Bar(props) {
|
function Bar(props) {
|
||||||
return <span>{props.text}</span>;
|
return <span>{props.text}</span>;
|
||||||
}
|
}
|
||||||
|
@ -88,7 +94,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo text="Hello" />);
|
ReactNoop.render(<Foo text="Hello" />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<span>Hello</span>
|
<span>Hello</span>
|
||||||
|
@ -97,7 +103,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactNoop.render(<Foo text="World" />);
|
ReactNoop.render(<Foo text="World" />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<span>World</span>
|
<span>World</span>
|
||||||
|
@ -108,7 +114,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactNoop.render(<Foo text="Hi" />);
|
ReactNoop.render(<Foo text="Hi" />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<span>Hi</span>
|
<span>Hi</span>
|
||||||
|
@ -119,7 +125,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can update child nodes rendering into text nodes', function () {
|
it('can update child nodes rendering into text nodes', async function () {
|
||||||
function Bar(props) {
|
function Bar(props) {
|
||||||
return props.text;
|
return props.text;
|
||||||
}
|
}
|
||||||
|
@ -136,15 +142,15 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo text="Hello" />);
|
ReactNoop.render(<Foo text="Hello" />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
|
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
|
||||||
|
|
||||||
ReactNoop.render(<Foo text="World" />);
|
ReactNoop.render(<Foo text="World" />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<div>WorldWorld!</div>);
|
expect(ReactNoop).toMatchRenderedOutput(<div>WorldWorld!</div>);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can deletes children either components, host or text', function () {
|
it('can deletes children either components, host or text', async function () {
|
||||||
function Bar(props) {
|
function Bar(props) {
|
||||||
return <span prop={props.children} />;
|
return <span prop={props.children} />;
|
||||||
}
|
}
|
||||||
|
@ -160,7 +166,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo show={true} />);
|
ReactNoop.render(<Foo show={true} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<div />
|
<div />
|
||||||
|
@ -170,11 +176,11 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactNoop.render(<Foo show={false} />);
|
ReactNoop.render(<Foo show={false} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<div />);
|
expect(ReactNoop).toMatchRenderedOutput(<div />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can delete a child that changes type - implicit keys', function () {
|
it('can delete a child that changes type - implicit keys', async function () {
|
||||||
let unmounted = false;
|
let unmounted = false;
|
||||||
|
|
||||||
class ClassComponent extends React.Component {
|
class ClassComponent extends React.Component {
|
||||||
|
@ -206,7 +212,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo useClass={true} />);
|
ReactNoop.render(<Foo useClass={true} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<span prop="Class" />
|
<span prop="Class" />
|
||||||
|
@ -217,7 +223,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
expect(unmounted).toBe(false);
|
expect(unmounted).toBe(false);
|
||||||
|
|
||||||
ReactNoop.render(<Foo useFunction={true} />);
|
ReactNoop.render(<Foo useFunction={true} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<span prop="Function" />
|
<span prop="Function" />
|
||||||
|
@ -228,15 +234,15 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
expect(unmounted).toBe(true);
|
expect(unmounted).toBe(true);
|
||||||
|
|
||||||
ReactNoop.render(<Foo useText={true} />);
|
ReactNoop.render(<Foo useText={true} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<div>TextTrail</div>);
|
expect(ReactNoop).toMatchRenderedOutput(<div>TextTrail</div>);
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<div>Trail</div>);
|
expect(ReactNoop).toMatchRenderedOutput(<div>Trail</div>);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can delete a child that changes type - explicit keys', function () {
|
it('can delete a child that changes type - explicit keys', async function () {
|
||||||
let unmounted = false;
|
let unmounted = false;
|
||||||
|
|
||||||
class ClassComponent extends React.Component {
|
class ClassComponent extends React.Component {
|
||||||
|
@ -266,7 +272,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo useClass={true} />);
|
ReactNoop.render(<Foo useClass={true} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<span prop="Class" />
|
<span prop="Class" />
|
||||||
|
@ -277,7 +283,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
expect(unmounted).toBe(false);
|
expect(unmounted).toBe(false);
|
||||||
|
|
||||||
ReactNoop.render(<Foo useFunction={true} />);
|
ReactNoop.render(<Foo useFunction={true} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<span prop="Function" />
|
<span prop="Function" />
|
||||||
|
@ -288,11 +294,11 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
expect(unmounted).toBe(true);
|
expect(unmounted).toBe(true);
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<div>Trail</div>);
|
expect(ReactNoop).toMatchRenderedOutput(<div>Trail</div>);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can delete a child when it unmounts inside a portal', () => {
|
it('can delete a child when it unmounts inside a portal', async () => {
|
||||||
function Bar(props) {
|
function Bar(props) {
|
||||||
return <span prop={props.children} />;
|
return <span prop={props.children} />;
|
||||||
}
|
}
|
||||||
|
@ -312,7 +318,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
<Foo show={true} />
|
<Foo show={true} />
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<div />);
|
expect(ReactNoop).toMatchRenderedOutput(<div />);
|
||||||
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
|
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
|
||||||
<>
|
<>
|
||||||
|
@ -327,7 +333,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
<Foo show={false} />
|
<Foo show={false} />
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<div />);
|
expect(ReactNoop).toMatchRenderedOutput(<div />);
|
||||||
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
|
||||||
|
|
||||||
|
@ -336,7 +342,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
<Foo show={true} />
|
<Foo show={true} />
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<div />);
|
expect(ReactNoop).toMatchRenderedOutput(<div />);
|
||||||
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
|
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
|
||||||
<>
|
<>
|
||||||
|
@ -347,17 +353,17 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactNoop.render(null);
|
ReactNoop.render(null);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
|
||||||
|
|
||||||
ReactNoop.render(<Foo show={false} />);
|
ReactNoop.render(<Foo show={false} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
|
||||||
|
|
||||||
ReactNoop.render(<Foo show={true} />);
|
ReactNoop.render(<Foo show={true} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
|
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
|
||||||
<>
|
<>
|
||||||
|
@ -368,12 +374,12 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactNoop.render(null);
|
ReactNoop.render(null);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can delete a child when it unmounts with a portal', () => {
|
it('can delete a child when it unmounts with a portal', async () => {
|
||||||
function Bar(props) {
|
function Bar(props) {
|
||||||
return <span prop={props.children} />;
|
return <span prop={props.children} />;
|
||||||
}
|
}
|
||||||
|
@ -393,7 +399,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
<Foo />
|
<Foo />
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<div />);
|
expect(ReactNoop).toMatchRenderedOutput(<div />);
|
||||||
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
|
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
|
||||||
<>
|
<>
|
||||||
|
@ -404,12 +410,12 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactNoop.render(null);
|
ReactNoop.render(null);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
|
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(
|
||||||
<>
|
<>
|
||||||
|
@ -420,12 +426,12 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactNoop.render(null);
|
ReactNoop.render(null);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||||
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX('portalContainer')).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not update child nodes if a flush is aborted', () => {
|
it('does not update child nodes if a flush is aborted', async () => {
|
||||||
function Bar(props) {
|
function Bar(props) {
|
||||||
Scheduler.unstable_yieldValue('Bar');
|
Scheduler.unstable_yieldValue('Bar');
|
||||||
return <span prop={props.text} />;
|
return <span prop={props.text} />;
|
||||||
|
@ -445,7 +451,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo text="Hello" />);
|
ReactNoop.render(<Foo text="Hello" />);
|
||||||
expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Bar', 'Bar']);
|
await waitForAll(['Foo', 'Bar', 'Bar', 'Bar']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -461,7 +467,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Flush some of the work without committing
|
// Flush some of the work without committing
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Bar']);
|
await waitFor(['Foo', 'Bar']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -474,7 +480,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// @gate www
|
// @gate www
|
||||||
it('preserves a previously rendered node when deprioritized', () => {
|
it('preserves a previously rendered node when deprioritized', async () => {
|
||||||
function Middle(props) {
|
function Middle(props) {
|
||||||
Scheduler.unstable_yieldValue('Middle');
|
Scheduler.unstable_yieldValue('Middle');
|
||||||
return <span prop={props.children} />;
|
return <span prop={props.children} />;
|
||||||
|
@ -492,7 +498,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo text="foo" />);
|
ReactNoop.render(<Foo text="foo" />);
|
||||||
expect(Scheduler).toFlushAndYield(['Foo', 'Middle']);
|
await waitForAll(['Foo', 'Middle']);
|
||||||
|
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||||
<div>
|
<div>
|
||||||
|
@ -505,7 +511,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
ReactNoop.render(<Foo text="bar" />, () =>
|
ReactNoop.render(<Foo text="bar" />, () =>
|
||||||
Scheduler.unstable_yieldValue('commit'),
|
Scheduler.unstable_yieldValue('commit'),
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Foo', 'commit']);
|
await waitFor(['Foo', 'commit']);
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||||
<div>
|
<div>
|
||||||
<div hidden={true}>
|
<div hidden={true}>
|
||||||
|
@ -514,7 +520,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield(['Middle']);
|
await waitForAll(['Middle']);
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||||
<div>
|
<div>
|
||||||
<div hidden={true}>
|
<div hidden={true}>
|
||||||
|
@ -525,7 +531,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// @gate www
|
// @gate www
|
||||||
it('can reuse side-effects after being preempted', () => {
|
it('can reuse side-effects after being preempted', async () => {
|
||||||
function Bar(props) {
|
function Bar(props) {
|
||||||
Scheduler.unstable_yieldValue('Bar');
|
Scheduler.unstable_yieldValue('Bar');
|
||||||
return <span prop={props.children} />;
|
return <span prop={props.children} />;
|
||||||
|
@ -556,7 +562,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
|
|
||||||
// Init
|
// Init
|
||||||
ReactNoop.render(<Foo text="foo" step={0} />);
|
ReactNoop.render(<Foo text="foo" step={0} />);
|
||||||
expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Bar']);
|
await waitForAll(['Foo', 'Bar', 'Bar']);
|
||||||
|
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||||
<div hidden={true}>
|
<div hidden={true}>
|
||||||
|
@ -572,7 +578,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
ReactNoop.render(<Foo text="bar" step={1} />, () =>
|
ReactNoop.render(<Foo text="bar" step={1} />, () =>
|
||||||
Scheduler.unstable_yieldValue('commit'),
|
Scheduler.unstable_yieldValue('commit'),
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Foo', 'commit', 'Bar']);
|
await waitFor(['Foo', 'commit', 'Bar']);
|
||||||
|
|
||||||
// The tree remains unchanged.
|
// The tree remains unchanged.
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||||
|
@ -588,7 +594,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
// render some higher priority work. The middle content will bailout so
|
// render some higher priority work. The middle content will bailout so
|
||||||
// it remains untouched which means that it should reuse it next time.
|
// it remains untouched which means that it should reuse it next time.
|
||||||
ReactNoop.render(<Foo text="foo" step={1} />);
|
ReactNoop.render(<Foo text="foo" step={1} />);
|
||||||
expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Bar']);
|
await waitForAll(['Foo', 'Bar', 'Bar']);
|
||||||
|
|
||||||
// Since we did nothing to the middle subtree during the interruption,
|
// Since we did nothing to the middle subtree during the interruption,
|
||||||
// we should be able to reuse the reconciliation work that we already did
|
// we should be able to reuse the reconciliation work that we already did
|
||||||
|
@ -605,7 +611,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// @gate www
|
// @gate www
|
||||||
it('can reuse side-effects after being preempted, if shouldComponentUpdate is false', () => {
|
it('can reuse side-effects after being preempted, if shouldComponentUpdate is false', async () => {
|
||||||
class Bar extends React.Component {
|
class Bar extends React.Component {
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
return this.props.children !== nextProps.children;
|
return this.props.children !== nextProps.children;
|
||||||
|
@ -642,7 +648,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
|
|
||||||
// Init
|
// Init
|
||||||
ReactNoop.render(<Foo text="foo" step={0} />);
|
ReactNoop.render(<Foo text="foo" step={0} />);
|
||||||
expect(Scheduler).toFlushAndYield(['Foo', 'Content', 'Bar', 'Bar']);
|
await waitForAll(['Foo', 'Content', 'Bar', 'Bar']);
|
||||||
|
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||||
<div hidden={true}>
|
<div hidden={true}>
|
||||||
|
@ -656,7 +662,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
// Make a quick update which will schedule low priority work to
|
// Make a quick update which will schedule low priority work to
|
||||||
// update the middle content.
|
// update the middle content.
|
||||||
ReactNoop.render(<Foo text="bar" step={1} />);
|
ReactNoop.render(<Foo text="bar" step={1} />);
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Content', 'Bar']);
|
await waitFor(['Foo', 'Content', 'Bar']);
|
||||||
|
|
||||||
// The tree remains unchanged.
|
// The tree remains unchanged.
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||||
|
@ -672,7 +678,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
// render some higher priority work. The middle content will bailout so
|
// render some higher priority work. The middle content will bailout so
|
||||||
// it remains untouched which means that it should reuse it next time.
|
// it remains untouched which means that it should reuse it next time.
|
||||||
ReactNoop.render(<Foo text="foo" step={1} />);
|
ReactNoop.render(<Foo text="foo" step={1} />);
|
||||||
expect(Scheduler).toFlushAndYield(['Foo', 'Content', 'Bar', 'Bar']);
|
await waitForAll(['Foo', 'Content', 'Bar', 'Bar']);
|
||||||
|
|
||||||
// Since we did nothing to the middle subtree during the interruption,
|
// Since we did nothing to the middle subtree during the interruption,
|
||||||
// we should be able to reuse the reconciliation work that we already did
|
// we should be able to reuse the reconciliation work that we already did
|
||||||
|
@ -688,7 +694,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can update a completed tree before it has a chance to commit', () => {
|
it('can update a completed tree before it has a chance to commit', async () => {
|
||||||
function Foo(props) {
|
function Foo(props) {
|
||||||
Scheduler.unstable_yieldValue('Foo');
|
Scheduler.unstable_yieldValue('Foo');
|
||||||
return <span prop={props.step} />;
|
return <span prop={props.step} />;
|
||||||
|
@ -697,7 +703,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
ReactNoop.render(<Foo step={1} />);
|
ReactNoop.render(<Foo step={1} />);
|
||||||
});
|
});
|
||||||
// This should be just enough to complete the tree without committing it
|
// This should be just enough to complete the tree without committing it
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Foo']);
|
await waitFor(['Foo']);
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(null);
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(null);
|
||||||
// To confirm, perform one more unit of work. The tree should now
|
// To confirm, perform one more unit of work. The tree should now
|
||||||
// be flushed.
|
// be flushed.
|
||||||
|
@ -708,7 +714,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
ReactNoop.render(<Foo step={2} />);
|
ReactNoop.render(<Foo step={2} />);
|
||||||
});
|
});
|
||||||
// This should be just enough to complete the tree without committing it
|
// This should be just enough to complete the tree without committing it
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Foo']);
|
await waitFor(['Foo']);
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
|
||||||
// This time, before we commit the tree, we update the root component with
|
// This time, before we commit the tree, we update the root component with
|
||||||
// new props
|
// new props
|
||||||
|
@ -723,12 +729,12 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={2} />);
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={2} />);
|
||||||
// If we flush the rest of the work, we should get another commit that
|
// 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.
|
// renders 3. If it renders 2 again, that means an update was dropped.
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={3} />);
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={3} />);
|
||||||
});
|
});
|
||||||
|
|
||||||
// @gate www
|
// @gate www
|
||||||
it('updates a child even though the old props is empty', () => {
|
it('updates a child even though the old props is empty', async () => {
|
||||||
function Foo(props) {
|
function Foo(props) {
|
||||||
return (
|
return (
|
||||||
<LegacyHiddenDiv mode="hidden">
|
<LegacyHiddenDiv mode="hidden">
|
||||||
|
@ -738,7 +744,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||||
<div hidden={true}>
|
<div hidden={true}>
|
||||||
<span prop={1} />
|
<span prop={1} />
|
||||||
|
@ -746,7 +752,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
xit('can defer side-effects and resume them later on', () => {
|
xit('can defer side-effects and resume them later on', async () => {
|
||||||
class Bar extends React.Component {
|
class Bar extends React.Component {
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
return this.props.idx !== nextProps.idx;
|
return this.props.idx !== nextProps.idx;
|
||||||
|
@ -809,7 +815,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
ReactNoop.render(<Foo tick={3} idx={1} />);
|
ReactNoop.render(<Foo tick={3} idx={1} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(
|
expect(ReactNoop).toMatchRenderedOutput(
|
||||||
<div>
|
<div>
|
||||||
<span prop={3} />
|
<span prop={3} />
|
||||||
|
@ -829,7 +835,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
expect(innerSpanA).toBe(innerSpanB);
|
expect(innerSpanA).toBe(innerSpanB);
|
||||||
});
|
});
|
||||||
|
|
||||||
xit('can defer side-effects and reuse them later - complex', function () {
|
xit('can defer side-effects and reuse them later - complex', async function () {
|
||||||
let ops = [];
|
let ops = [];
|
||||||
|
|
||||||
class Bar extends React.Component {
|
class Bar extends React.Component {
|
||||||
|
@ -892,7 +898,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
expect(ops).toEqual(['Foo']);
|
expect(ops).toEqual(['Foo']);
|
||||||
ops = [];
|
ops = [];
|
||||||
|
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput([
|
expect(ReactNoop).toMatchRenderedOutput([
|
||||||
<div>
|
<div>
|
||||||
<span prop={1} />,
|
<span prop={1} />,
|
||||||
|
@ -959,7 +965,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
|
|
||||||
// We should now be able to reuse some of the work we've already done
|
// We should now be able to reuse some of the work we've already done
|
||||||
// and replay those side-effects.
|
// and replay those side-effects.
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput([
|
expect(ReactNoop).toMatchRenderedOutput([
|
||||||
<div>
|
<div>
|
||||||
<span prop={3} />,
|
<span prop={3} />,
|
||||||
|
@ -979,7 +985,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// @gate www
|
// @gate www
|
||||||
it('deprioritizes setStates that happens within a deprioritized tree', () => {
|
it('deprioritizes setStates that happens within a deprioritized tree', async () => {
|
||||||
const barInstances = [];
|
const barInstances = [];
|
||||||
|
|
||||||
class Bar extends React.Component {
|
class Bar extends React.Component {
|
||||||
|
@ -1010,7 +1016,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ReactNoop.render(<Foo tick={0} idx={0} />);
|
ReactNoop.render(<Foo tick={0} idx={0} />);
|
||||||
expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Bar', 'Bar']);
|
await waitForAll(['Foo', 'Bar', 'Bar', 'Bar']);
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||||
<div>
|
<div>
|
||||||
<span prop={0} />
|
<span prop={0} />
|
||||||
|
@ -1023,7 +1029,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactNoop.render(<Foo tick={1} idx={1} />);
|
ReactNoop.render(<Foo tick={1} idx={1} />);
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Bar', 'Bar']);
|
await waitFor(['Foo', 'Bar', 'Bar']);
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||||
<div>
|
<div>
|
||||||
{/* Updated */}
|
{/* Updated */}
|
||||||
|
@ -1041,7 +1047,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
// This should not be enough time to render the content of all the hidden
|
// This should not be enough time to render the content of all the hidden
|
||||||
// items. Including the set state since that is deprioritized.
|
// items. Including the set state since that is deprioritized.
|
||||||
// ReactNoop.flushDeferredPri(35);
|
// ReactNoop.flushDeferredPri(35);
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['Bar']);
|
await waitFor(['Bar']);
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||||
<div>
|
<div>
|
||||||
{/* Updated */}
|
{/* Updated */}
|
||||||
|
@ -1057,7 +1063,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
|
|
||||||
// However, once we render fully, we will have enough time to finish it all
|
// However, once we render fully, we will have enough time to finish it all
|
||||||
// at once.
|
// at once.
|
||||||
expect(Scheduler).toFlushAndYield(['Bar', 'Bar']);
|
await waitForAll(['Bar', 'Bar']);
|
||||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||||
<div>
|
<div>
|
||||||
<span prop={1} />
|
<span prop={1} />
|
||||||
|
@ -1074,7 +1080,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
// moves to "current" without flushing due to having lower priority. Does this
|
// moves to "current" without flushing due to having lower priority. Does this
|
||||||
// even happen? Maybe a child doesn't get processed because it is lower prio?
|
// even happen? Maybe a child doesn't get processed because it is lower prio?
|
||||||
|
|
||||||
it('calls callback after update is flushed', () => {
|
it('calls callback after update is flushed', async () => {
|
||||||
let instance;
|
let instance;
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1088,18 +1094,18 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="foo" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="foo" />);
|
||||||
let called = false;
|
let called = false;
|
||||||
instance.setState({text: 'bar'}, () => {
|
instance.setState({text: 'bar'}, () => {
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="bar" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="bar" />);
|
||||||
called = true;
|
called = true;
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(called).toBe(true);
|
expect(called).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls setState callback even if component bails out', () => {
|
it('calls setState callback even if component bails out', async () => {
|
||||||
let instance;
|
let instance;
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1116,19 +1122,19 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="foo" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="foo" />);
|
||||||
let called = false;
|
let called = false;
|
||||||
instance.setState({}, () => {
|
instance.setState({}, () => {
|
||||||
called = true;
|
called = true;
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(called).toBe(true);
|
expect(called).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Test that callbacks are not lost if an update is preempted.
|
// TODO: Test that callbacks are not lost if an update is preempted.
|
||||||
|
|
||||||
it('calls componentWillUnmount after a deletion, even if nested', () => {
|
it('calls componentWillUnmount after a deletion, even if nested', async () => {
|
||||||
const ops = [];
|
const ops = [];
|
||||||
|
|
||||||
class Bar extends React.Component {
|
class Bar extends React.Component {
|
||||||
|
@ -1170,11 +1176,11 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo show={true} />);
|
ReactNoop.render(<Foo show={true} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ops).toEqual([]);
|
expect(ops).toEqual([]);
|
||||||
|
|
||||||
ReactNoop.render(<Foo show={false} />);
|
ReactNoop.render(<Foo show={false} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ops).toEqual([
|
expect(ops).toEqual([
|
||||||
'A',
|
'A',
|
||||||
'Wrapper',
|
'Wrapper',
|
||||||
|
@ -1188,7 +1194,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls componentDidMount/Update after insertion/update', () => {
|
it('calls componentDidMount/Update after insertion/update', async () => {
|
||||||
let ops = [];
|
let ops = [];
|
||||||
|
|
||||||
class Bar extends React.Component {
|
class Bar extends React.Component {
|
||||||
|
@ -1233,7 +1239,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ops).toEqual([
|
expect(ops).toEqual([
|
||||||
'mount:A',
|
'mount:A',
|
||||||
'mount:B',
|
'mount:B',
|
||||||
|
@ -1249,7 +1255,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
ops = [];
|
ops = [];
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ops).toEqual([
|
expect(ops).toEqual([
|
||||||
'update:A',
|
'update:A',
|
||||||
'update:B',
|
'update:B',
|
||||||
|
@ -1263,7 +1269,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invokes ref callbacks after insertion/update/unmount', () => {
|
it('invokes ref callbacks after insertion/update/unmount', async () => {
|
||||||
let classInstance = null;
|
let classInstance = null;
|
||||||
|
|
||||||
let ops = [];
|
let ops = [];
|
||||||
|
@ -1310,7 +1316,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
|
|
||||||
// Refs that switch function instances get reinvoked
|
// Refs that switch function instances get reinvoked
|
||||||
ReactNoop.render(<Foo show={true} />);
|
ReactNoop.render(<Foo show={true} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ops).toEqual([
|
expect(ops).toEqual([
|
||||||
// detach all refs that switched handlers first.
|
// detach all refs that switched handlers first.
|
||||||
null,
|
null,
|
||||||
|
@ -1323,7 +1329,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
ops = [];
|
ops = [];
|
||||||
|
|
||||||
ReactNoop.render(<Foo show={false} />);
|
ReactNoop.render(<Foo show={false} />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(ops).toEqual([
|
expect(ops).toEqual([
|
||||||
// unmount
|
// unmount
|
||||||
null,
|
null,
|
||||||
|
@ -1334,7 +1340,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
// TODO: Test that mounts, updates, refs, unmounts and deletions happen in the
|
// TODO: Test that mounts, updates, refs, unmounts and deletions happen in the
|
||||||
// expected way for aborted and resumed render life-cycles.
|
// expected way for aborted and resumed render life-cycles.
|
||||||
|
|
||||||
it('supports string refs', () => {
|
it('supports string refs', async () => {
|
||||||
let fooInstance = null;
|
let fooInstance = null;
|
||||||
|
|
||||||
class Bar extends React.Component {
|
class Bar extends React.Component {
|
||||||
|
@ -1354,8 +1360,8 @@ describe('ReactIncrementalSideEffects', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(() => {
|
await expect(async () => {
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
}).toErrorDev([
|
}).toErrorDev([
|
||||||
'Warning: Component "Foo" contains the string ref "bar". ' +
|
'Warning: Component "Foo" contains the string ref "bar". ' +
|
||||||
'Support for string refs will be removed in a future major release. ' +
|
'Support for string refs will be removed in a future major release. ' +
|
||||||
|
|
|
@ -15,6 +15,9 @@ let ReactNoop;
|
||||||
let Scheduler;
|
let Scheduler;
|
||||||
let ContinuousEventPriority;
|
let ContinuousEventPriority;
|
||||||
let act;
|
let act;
|
||||||
|
let waitForAll;
|
||||||
|
let waitFor;
|
||||||
|
let assertLog;
|
||||||
|
|
||||||
describe('ReactIncrementalUpdates', () => {
|
describe('ReactIncrementalUpdates', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -26,6 +29,11 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
act = require('jest-react').act;
|
act = require('jest-react').act;
|
||||||
ContinuousEventPriority =
|
ContinuousEventPriority =
|
||||||
require('react-reconciler/constants').ContinuousEventPriority;
|
require('react-reconciler/constants').ContinuousEventPriority;
|
||||||
|
|
||||||
|
const InternalTestUtils = require('internal-test-utils');
|
||||||
|
waitForAll = InternalTestUtils.waitForAll;
|
||||||
|
waitFor = InternalTestUtils.waitFor;
|
||||||
|
assertLog = InternalTestUtils.assertLog;
|
||||||
});
|
});
|
||||||
|
|
||||||
function flushNextRenderIfExpired() {
|
function flushNextRenderIfExpired() {
|
||||||
|
@ -37,7 +45,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
ReactNoop.flushSync();
|
ReactNoop.flushSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
it('applies updates in order of priority', () => {
|
it('applies updates in order of priority', async () => {
|
||||||
let state;
|
let state;
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
state = {};
|
state = {};
|
||||||
|
@ -58,14 +66,14 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['commit']);
|
await waitFor(['commit']);
|
||||||
|
|
||||||
expect(state).toEqual({a: 'a'});
|
expect(state).toEqual({a: 'a'});
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(state).toEqual({a: 'a', b: 'b', c: 'c'});
|
expect(state).toEqual({a: 'a', b: 'b', c: 'c'});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('applies updates with equal priority in insertion order', () => {
|
it('applies updates with equal priority in insertion order', async () => {
|
||||||
let state;
|
let state;
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
state = {};
|
state = {};
|
||||||
|
@ -82,11 +90,11 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(state).toEqual({a: 'a', b: 'b', c: 'c'});
|
expect(state).toEqual({a: 'a', b: 'b', c: 'c'});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('only drops updates with equal or lesser priority when replaceState is called', () => {
|
it('only drops updates with equal or lesser priority when replaceState is called', async () => {
|
||||||
let instance;
|
let instance;
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
state = {};
|
state = {};
|
||||||
|
@ -104,7 +112,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushAndYield(['render', 'componentDidMount']);
|
await waitForAll(['render', 'componentDidMount']);
|
||||||
|
|
||||||
ReactNoop.flushSync(() => {
|
ReactNoop.flushSync(() => {
|
||||||
React.startTransition(() => {
|
React.startTransition(() => {
|
||||||
|
@ -122,14 +130,14 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
// Even though a replaceState has been already scheduled, it hasn't been
|
// Even though a replaceState has been already scheduled, it hasn't been
|
||||||
// flushed yet because it has async priority.
|
// flushed yet because it has async priority.
|
||||||
expect(instance.state).toEqual({a: 'a', b: 'b'});
|
expect(instance.state).toEqual({a: 'a', b: 'b'});
|
||||||
expect(Scheduler).toHaveYielded(['render', 'componentDidUpdate']);
|
assertLog(['render', 'componentDidUpdate']);
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield(['render', 'componentDidUpdate']);
|
await waitForAll(['render', 'componentDidUpdate']);
|
||||||
// Now the rest of the updates are flushed, including the replaceState.
|
// Now the rest of the updates are flushed, including the replaceState.
|
||||||
expect(instance.state).toEqual({c: 'c', d: 'd'});
|
expect(instance.state).toEqual({c: 'c', d: 'd'});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can abort an update, schedule additional updates, and resume', () => {
|
it('can abort an update, schedule additional updates, and resume', async () => {
|
||||||
let instance;
|
let instance;
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
state = {};
|
state = {};
|
||||||
|
@ -140,7 +148,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
|
|
||||||
function createUpdate(letter) {
|
function createUpdate(letter) {
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -159,7 +167,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Begin the updates but don't flush them yet
|
// Begin the updates but don't flush them yet
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['a', 'b', 'c']);
|
await waitFor(['a', 'b', 'c']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="" />);
|
||||||
|
|
||||||
// Schedule some more updates at different priorities
|
// Schedule some more updates at different priorities
|
||||||
|
@ -174,11 +182,11 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
|
|
||||||
// The sync updates should have flushed, but not the async ones.
|
// The sync updates should have flushed, but not the async ones.
|
||||||
if (gate(flags => flags.enableUnifiedSyncLane)) {
|
if (gate(flags => flags.enableUnifiedSyncLane)) {
|
||||||
expect(Scheduler).toHaveYielded(['d', 'e', 'f']);
|
assertLog(['d', 'e', 'f']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="def" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="def" />);
|
||||||
} else {
|
} else {
|
||||||
// Update d was dropped and replaced by e.
|
// Update d was dropped and replaced by e.
|
||||||
expect(Scheduler).toHaveYielded(['e', 'f']);
|
assertLog(['e', 'f']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="ef" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="ef" />);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +194,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
// they should be processed again, to ensure that the terminal state
|
// they should be processed again, to ensure that the terminal state
|
||||||
// is deterministic.
|
// is deterministic.
|
||||||
if (gate(flags => !flags.enableUnifiedSyncLane)) {
|
if (gate(flags => !flags.enableUnifiedSyncLane)) {
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
// Since 'g' is in a transition, we'll process 'd' separately first.
|
// Since 'g' is in a transition, we'll process 'd' separately first.
|
||||||
// That causes us to process 'd' with 'e' and 'f' rebased.
|
// That causes us to process 'd' with 'e' and 'f' rebased.
|
||||||
'd',
|
'd',
|
||||||
|
@ -202,7 +210,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
'g',
|
'g',
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
// Then we'll re-process everything for 'g'.
|
// Then we'll re-process everything for 'g'.
|
||||||
'a',
|
'a',
|
||||||
'b',
|
'b',
|
||||||
|
@ -216,7 +224,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="abcdefg" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="abcdefg" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can abort an update, schedule a replaceState, and resume', () => {
|
it('can abort an update, schedule a replaceState, and resume', async () => {
|
||||||
let instance;
|
let instance;
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
state = {};
|
state = {};
|
||||||
|
@ -227,7 +235,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
|
|
||||||
function createUpdate(letter) {
|
function createUpdate(letter) {
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -246,7 +254,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Begin the updates but don't flush them yet
|
// Begin the updates but don't flush them yet
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['a', 'b', 'c']);
|
await waitFor(['a', 'b', 'c']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="" />);
|
||||||
|
|
||||||
// Schedule some more updates at different priorities
|
// Schedule some more updates at different priorities
|
||||||
|
@ -264,10 +272,10 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
|
|
||||||
// The sync updates should have flushed, but not the async ones.
|
// The sync updates should have flushed, but not the async ones.
|
||||||
if (gate(flags => flags.enableUnifiedSyncLane)) {
|
if (gate(flags => flags.enableUnifiedSyncLane)) {
|
||||||
expect(Scheduler).toHaveYielded(['d', 'e', 'f']);
|
assertLog(['d', 'e', 'f']);
|
||||||
} else {
|
} else {
|
||||||
// Update d was dropped and replaced by e.
|
// Update d was dropped and replaced by e.
|
||||||
expect(Scheduler).toHaveYielded(['e', 'f']);
|
assertLog(['e', 'f']);
|
||||||
}
|
}
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="f" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="f" />);
|
||||||
|
|
||||||
|
@ -275,7 +283,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
// they should be processed again, to ensure that the terminal state
|
// they should be processed again, to ensure that the terminal state
|
||||||
// is deterministic.
|
// is deterministic.
|
||||||
if (gate(flags => !flags.enableUnifiedSyncLane)) {
|
if (gate(flags => !flags.enableUnifiedSyncLane)) {
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
// Since 'g' is in a transition, we'll process 'd' separately first.
|
// Since 'g' is in a transition, we'll process 'd' separately first.
|
||||||
// That causes us to process 'd' with 'e' and 'f' rebased.
|
// That causes us to process 'd' with 'e' and 'f' rebased.
|
||||||
'd',
|
'd',
|
||||||
|
@ -291,7 +299,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
'g',
|
'g',
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
// Then we'll re-process everything for 'g'.
|
// Then we'll re-process everything for 'g'.
|
||||||
'a',
|
'a',
|
||||||
'b',
|
'b',
|
||||||
|
@ -305,7 +313,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="fg" />);
|
expect(ReactNoop).toMatchRenderedOutput(<span prop="fg" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes accumulation of previous updates to replaceState updater function', () => {
|
it('passes accumulation of previous updates to replaceState updater function', async () => {
|
||||||
let instance;
|
let instance;
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
state = {};
|
state = {};
|
||||||
|
@ -315,7 +323,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
|
|
||||||
instance.setState({a: 'a'});
|
instance.setState({a: 'a'});
|
||||||
instance.setState({b: 'b'});
|
instance.setState({b: 'b'});
|
||||||
|
@ -324,11 +332,11 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
instance.updater.enqueueReplaceState(instance, previousState => ({
|
instance.updater.enqueueReplaceState(instance, previousState => ({
|
||||||
previousState,
|
previousState,
|
||||||
}));
|
}));
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
await waitForAll([]);
|
||||||
expect(instance.state).toEqual({previousState: {a: 'a', b: 'b'}});
|
expect(instance.state).toEqual({previousState: {a: 'a', b: 'b'}});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not call callbacks that are scheduled by another callback until a later commit', () => {
|
it('does not call callbacks that are scheduled by another callback until a later commit', async () => {
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
state = {};
|
state = {};
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -347,7 +355,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
'render',
|
'render',
|
||||||
'did mount',
|
'did mount',
|
||||||
'render',
|
'render',
|
||||||
|
@ -357,7 +365,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gives setState during reconciliation the same priority as whatever level is currently reconciling', () => {
|
it('gives setState during reconciliation the same priority as whatever level is currently reconciling', async () => {
|
||||||
let instance;
|
let instance;
|
||||||
|
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
|
@ -373,7 +381,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushAndYield(['render']);
|
await waitForAll(['render']);
|
||||||
|
|
||||||
ReactNoop.flushSync(() => {
|
ReactNoop.flushSync(() => {
|
||||||
instance.setState({a: 'a'});
|
instance.setState({a: 'a'});
|
||||||
|
@ -384,17 +392,13 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
expect(instance.state).toEqual({a: 'a', b: 'b'});
|
expect(instance.state).toEqual({a: 'a', b: 'b'});
|
||||||
|
|
||||||
if (gate(flags => flags.deferRenderPhaseUpdateToNextBatch)) {
|
if (gate(flags => flags.deferRenderPhaseUpdateToNextBatch)) {
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog(['componentWillReceiveProps', 'render', 'render']);
|
||||||
'componentWillReceiveProps',
|
|
||||||
'render',
|
|
||||||
'render',
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
expect(Scheduler).toHaveYielded(['componentWillReceiveProps', 'render']);
|
assertLog(['componentWillReceiveProps', 'render']);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates triggered from inside a class setState updater', () => {
|
it('updates triggered from inside a class setState updater', async () => {
|
||||||
let instance;
|
let instance;
|
||||||
class Foo extends React.Component {
|
class Foo extends React.Component {
|
||||||
state = {};
|
state = {};
|
||||||
|
@ -406,7 +410,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactNoop.render(<Foo />);
|
ReactNoop.render(<Foo />);
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll([
|
||||||
// Initial render
|
// Initial render
|
||||||
'render',
|
'render',
|
||||||
]);
|
]);
|
||||||
|
@ -451,7 +455,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
this.setState({a: 'a'});
|
this.setState({a: 'a'});
|
||||||
return {b: 'b'};
|
return {b: 'b'};
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYield(
|
await waitForAll(
|
||||||
gate(flags =>
|
gate(flags =>
|
||||||
flags.deferRenderPhaseUpdateToNextBatch
|
flags.deferRenderPhaseUpdateToNextBatch
|
||||||
? // In the new reconciler, updates inside the render phase are
|
? // In the new reconciler, updates inside the render phase are
|
||||||
|
@ -529,24 +533,20 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
act(() => {
|
act(async () => {
|
||||||
React.startTransition(() => {
|
React.startTransition(() => {
|
||||||
ReactNoop.render(<App />);
|
ReactNoop.render(<App />);
|
||||||
});
|
});
|
||||||
flushNextRenderIfExpired();
|
flushNextRenderIfExpired();
|
||||||
expect(Scheduler).toHaveYielded([]);
|
assertLog([]);
|
||||||
expect(Scheduler).toFlushAndYield([
|
await waitForAll(['Render: 0', 'Commit: 0', 'Render: 1']);
|
||||||
'Render: 0',
|
|
||||||
'Commit: 0',
|
|
||||||
'Render: 1',
|
|
||||||
]);
|
|
||||||
|
|
||||||
Scheduler.unstable_advanceTime(10000);
|
Scheduler.unstable_advanceTime(10000);
|
||||||
React.startTransition(() => {
|
React.startTransition(() => {
|
||||||
setCount(2);
|
setCount(2);
|
||||||
});
|
});
|
||||||
flushNextRenderIfExpired();
|
flushNextRenderIfExpired();
|
||||||
expect(Scheduler).toHaveYielded([]);
|
assertLog([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -559,7 +559,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
ReactNoop.flushSync(() => {
|
ReactNoop.flushSync(() => {
|
||||||
ReactNoop.render(<Text text="A" />);
|
ReactNoop.render(<Text text="A" />);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toHaveYielded(['A']);
|
assertLog(['A']);
|
||||||
|
|
||||||
Scheduler.unstable_advanceTime(10000);
|
Scheduler.unstable_advanceTime(10000);
|
||||||
|
|
||||||
|
@ -567,7 +567,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
ReactNoop.render(<Text text="B" />);
|
ReactNoop.render(<Text text="B" />);
|
||||||
});
|
});
|
||||||
flushNextRenderIfExpired();
|
flushNextRenderIfExpired();
|
||||||
expect(Scheduler).toHaveYielded([]);
|
assertLog([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('regression: does not expire soon due to previous expired work', () => {
|
it('regression: does not expire soon due to previous expired work', () => {
|
||||||
|
@ -581,7 +581,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
});
|
});
|
||||||
Scheduler.unstable_advanceTime(10000);
|
Scheduler.unstable_advanceTime(10000);
|
||||||
flushNextRenderIfExpired();
|
flushNextRenderIfExpired();
|
||||||
expect(Scheduler).toHaveYielded(['A']);
|
assertLog(['A']);
|
||||||
|
|
||||||
Scheduler.unstable_advanceTime(10000);
|
Scheduler.unstable_advanceTime(10000);
|
||||||
|
|
||||||
|
@ -589,7 +589,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
ReactNoop.render(<Text text="B" />);
|
ReactNoop.render(<Text text="B" />);
|
||||||
});
|
});
|
||||||
flushNextRenderIfExpired();
|
flushNextRenderIfExpired();
|
||||||
expect(Scheduler).toHaveYielded([]);
|
assertLog([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when rebasing, does not exclude updates that were already committed, regardless of priority', async () => {
|
it('when rebasing, does not exclude updates that were already committed, regardless of priority', async () => {
|
||||||
|
@ -620,7 +620,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
root.render(<App />);
|
root.render(<App />);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toHaveYielded(['Committed: ']);
|
assertLog(['Committed: ']);
|
||||||
expect(root).toMatchRenderedOutput(null);
|
expect(root).toMatchRenderedOutput(null);
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
@ -633,13 +633,9 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (gate(flags => flags.enableUnifiedSyncLane)) {
|
if (gate(flags => flags.enableUnifiedSyncLane)) {
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog(['Committed: B', 'Committed: BCD', 'Committed: ABCD']);
|
||||||
'Committed: B',
|
|
||||||
'Committed: BCD',
|
|
||||||
'Committed: ABCD',
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
// A and B are pending. B is higher priority, so we'll render that first.
|
// A and B are pending. B is higher priority, so we'll render that first.
|
||||||
'Committed: B',
|
'Committed: B',
|
||||||
// Because A comes first in the queue, we're now in rebase mode. B must
|
// Because A comes first in the queue, we're now in rebase mode. B must
|
||||||
|
@ -685,7 +681,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
root.render(<App />);
|
root.render(<App />);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toHaveYielded([]);
|
assertLog([]);
|
||||||
expect(root).toMatchRenderedOutput(null);
|
expect(root).toMatchRenderedOutput(null);
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
@ -697,13 +693,9 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (gate(flags => flags.enableUnifiedSyncLane)) {
|
if (gate(flags => flags.enableUnifiedSyncLane)) {
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog(['Committed: B', 'Committed: BCD', 'Committed: ABCD']);
|
||||||
'Committed: B',
|
|
||||||
'Committed: BCD',
|
|
||||||
'Committed: ABCD',
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
expect(Scheduler).toHaveYielded([
|
assertLog([
|
||||||
// A and B are pending. B is higher priority, so we'll render that first.
|
// A and B are pending. B is higher priority, so we'll render that first.
|
||||||
'Committed: B',
|
'Committed: B',
|
||||||
// Because A comes first in the queue, we're now in rebase mode. B must
|
// Because A comes first in the queue, we're now in rebase mode. B must
|
||||||
|
|
|
@ -5,6 +5,9 @@ let startTransition;
|
||||||
let useState;
|
let useState;
|
||||||
let useEffect;
|
let useEffect;
|
||||||
let act;
|
let act;
|
||||||
|
let assertLog;
|
||||||
|
let waitFor;
|
||||||
|
let waitForPaint;
|
||||||
|
|
||||||
describe('ReactInterleavedUpdates', () => {
|
describe('ReactInterleavedUpdates', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -17,6 +20,11 @@ describe('ReactInterleavedUpdates', () => {
|
||||||
startTransition = React.startTransition;
|
startTransition = React.startTransition;
|
||||||
useState = React.useState;
|
useState = React.useState;
|
||||||
useEffect = React.useEffect;
|
useEffect = React.useEffect;
|
||||||
|
|
||||||
|
const InternalTestUtils = require('internal-test-utils');
|
||||||
|
assertLog = InternalTestUtils.assertLog;
|
||||||
|
waitFor = InternalTestUtils.waitFor;
|
||||||
|
waitForPaint = InternalTestUtils.waitForPaint;
|
||||||
});
|
});
|
||||||
|
|
||||||
function Text({text}) {
|
function Text({text}) {
|
||||||
|
@ -53,7 +61,7 @@ describe('ReactInterleavedUpdates', () => {
|
||||||
</>,
|
</>,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toHaveYielded([0, 0, 0]);
|
assertLog([0, 0, 0]);
|
||||||
expect(root).toMatchRenderedOutput('000');
|
expect(root).toMatchRenderedOutput('000');
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
@ -61,7 +69,7 @@ describe('ReactInterleavedUpdates', () => {
|
||||||
updateChildren(1);
|
updateChildren(1);
|
||||||
});
|
});
|
||||||
// Partially render the children. Only the first one.
|
// Partially render the children. Only the first one.
|
||||||
expect(Scheduler).toFlushAndYieldThrough([1]);
|
await waitFor([1]);
|
||||||
|
|
||||||
// In an interleaved event, schedule an update on each of the children.
|
// In an interleaved event, schedule an update on each of the children.
|
||||||
// Including the two that haven't rendered yet.
|
// Including the two that haven't rendered yet.
|
||||||
|
@ -70,11 +78,11 @@ describe('ReactInterleavedUpdates', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// We should continue rendering without including the interleaved updates.
|
// We should continue rendering without including the interleaved updates.
|
||||||
expect(Scheduler).toFlushUntilNextPaint([1, 1]);
|
await waitForPaint([1, 1]);
|
||||||
expect(root).toMatchRenderedOutput('111');
|
expect(root).toMatchRenderedOutput('111');
|
||||||
});
|
});
|
||||||
// The interleaved updates flush in a separate render.
|
// The interleaved updates flush in a separate render.
|
||||||
expect(Scheduler).toHaveYielded([2, 2, 2]);
|
assertLog([2, 2, 2]);
|
||||||
expect(root).toMatchRenderedOutput('222');
|
expect(root).toMatchRenderedOutput('222');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -96,7 +104,7 @@ describe('ReactInterleavedUpdates', () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
root.render(<App />);
|
root.render(<App />);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toHaveYielded(['A0', 'B0', 'C0']);
|
assertLog(['A0', 'B0', 'C0']);
|
||||||
expect(root).toMatchRenderedOutput('A0B0C0');
|
expect(root).toMatchRenderedOutput('A0B0C0');
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
@ -104,7 +112,7 @@ describe('ReactInterleavedUpdates', () => {
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
setStep(1);
|
setStep(1);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYieldThrough(['A1', 'B1']);
|
await waitFor(['A1', 'B1']);
|
||||||
|
|
||||||
// Schedule an interleaved update. This gets placed on a special queue.
|
// Schedule an interleaved update. This gets placed on a special queue.
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
|
@ -112,7 +120,7 @@ describe('ReactInterleavedUpdates', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Finish rendering the first update.
|
// Finish rendering the first update.
|
||||||
expect(Scheduler).toFlushUntilNextPaint(['C1']);
|
await waitForPaint(['C1']);
|
||||||
|
|
||||||
// Schedule another update. (In the regression case, this was treated
|
// Schedule another update. (In the regression case, this was treated
|
||||||
// as a normal, non-interleaved update and it was inserted into the queue
|
// as a normal, non-interleaved update and it was inserted into the queue
|
||||||
|
@ -122,7 +130,7 @@ describe('ReactInterleavedUpdates', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// The last update should win.
|
// The last update should win.
|
||||||
expect(Scheduler).toHaveYielded(['A3', 'B3', 'C3']);
|
assertLog(['A3', 'B3', 'C3']);
|
||||||
expect(root).toMatchRenderedOutput('A3B3C3');
|
expect(root).toMatchRenderedOutput('A3B3C3');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1874,7 +1874,7 @@ describe(`onPostCommit`, () => {
|
||||||
loadModules();
|
loadModules();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should report time spent in passive effects', () => {
|
it('should report time spent in passive effects', async () => {
|
||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
|
|
||||||
const ComponentWithEffects = () => {
|
const ComponentWithEffects = () => {
|
||||||
|
@ -1910,7 +1910,7 @@ describe(`onPostCommit`, () => {
|
||||||
</React.Profiler>,
|
</React.Profiler>,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
Scheduler.unstable_flushAll();
|
await waitForAll([]);
|
||||||
|
|
||||||
expect(callback).toHaveBeenCalledTimes(1);
|
expect(callback).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
@ -1931,7 +1931,7 @@ describe(`onPostCommit`, () => {
|
||||||
</React.Profiler>,
|
</React.Profiler>,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
Scheduler.unstable_flushAll();
|
await waitForAll([]);
|
||||||
|
|
||||||
expect(callback).toHaveBeenCalledTimes(2);
|
expect(callback).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
@ -1950,7 +1950,7 @@ describe(`onPostCommit`, () => {
|
||||||
<React.Profiler id="unmount-test" onPostCommit={callback} />,
|
<React.Profiler id="unmount-test" onPostCommit={callback} />,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
Scheduler.unstable_flushAll();
|
await waitForAll([]);
|
||||||
|
|
||||||
expect(callback).toHaveBeenCalledTimes(3);
|
expect(callback).toHaveBeenCalledTimes(3);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue