`act()` - s / flushPassiveEffects / Scheduler.unstable_flushWithoutYielding (#15591)
* s/flushPassiveEffects/unstable_flushWithoutYielding a first crack at flushing the scheduler manually from inside act(). uses unstable_flushWithoutYielding(). The tests that changed, mostly replaced toFlushAndYield(...) with toHaveYielded(). For some tests that tested the state of the tree before flushing effects (but still after updates), I replaced act() with bacthedUpdates(). * ugh lint * pass build, flushPassiveEffects returns nothing now * pass test-fire * flush all work (not just effects), add a compatibility mode of note, unstable_flushWithoutYielding now returns a boolean much like flushPassiveEffects * umd build for scheduler/unstable_mock, pass the fixture with it * add a comment to Shcduler.umd.js for why we're exporting unstable_flushWithoutYielding * run testsutilsact tests in both sync/concurrent modes * augh lint * use a feature flag for the missing mock scheduler warning I also tried writing a test for it, but couldn't get the scheduler to unmock. included the failing test. * Update ReactTestUtilsAct-test.js - pass the mock scheduler warning test, - rewrite some tests to use Scheduler.yieldValue - structure concurrent/legacy suites neatly * pass failing tests in batchedmode-test * fix pretty/lint/import errors * pass test-build * nit: pull .create(null) out of the act() call
This commit is contained in:
parent
aad5a264d2
commit
d278a3ff8b
|
@ -8,6 +8,8 @@ coverage
|
||||||
|
|
||||||
# production
|
# production
|
||||||
build
|
build
|
||||||
|
public/scheduler-unstable_mock.development.js
|
||||||
|
public/scheduler-unstable_mock.production.min.js
|
||||||
public/react.development.js
|
public/react.development.js
|
||||||
public/react.production.min.js
|
public/react.production.min.js
|
||||||
public/react-dom.development.js
|
public/react-dom.development.js
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"prestart": "cp ../../build/node_modules/react/umd/react.development.js ../../build/node_modules/react-dom/umd/react-dom.development.js ../../build/node_modules/react/umd/react.production.min.js ../../build/node_modules/react-dom/umd/react-dom.production.min.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.production.min.js ../../build/node_modules/react-dom/umd/react-dom-test-utils.development.js ../../build/node_modules/react-dom/umd/react-dom-test-utils.production.min.js public/",
|
"prestart": "cp ../../build/node_modules/scheduler/umd/scheduler-unstable_mock.development.js ../../build/node_modules/scheduler/umd/scheduler-unstable_mock.production.min.js ../../build/node_modules/react/umd/react.development.js ../../build/node_modules/react-dom/umd/react-dom.development.js ../../build/node_modules/react/umd/react.production.min.js ../../build/node_modules/react-dom/umd/react-dom.production.min.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.production.min.js ../../build/node_modules/react-dom/umd/react-dom-test-utils.development.js ../../build/node_modules/react-dom/umd/react-dom-test-utils.production.min.js public/",
|
||||||
"build": "react-scripts build && cp build/index.html build/200.html",
|
"build": "react-scripts build && cp build/index.html build/200.html",
|
||||||
"test": "react-scripts test --env=jsdom",
|
"test": "react-scripts test --env=jsdom",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
|
|
|
@ -7,7 +7,11 @@
|
||||||
this page tests whether act runs properly in a browser.
|
this page tests whether act runs properly in a browser.
|
||||||
<br/>
|
<br/>
|
||||||
your console should say "5"
|
your console should say "5"
|
||||||
|
<script src='scheduler-unstable_mock.development.js'></script>
|
||||||
<script src='react.development.js'></script>
|
<script src='react.development.js'></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler = window.SchedulerMock
|
||||||
|
</script>
|
||||||
<script src='react-dom.development.js'></script>
|
<script src='react-dom.development.js'></script>
|
||||||
<script src='react-dom-test-utils.development.js'></script>
|
<script src='react-dom-test-utils.development.js'></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @emails react-core
|
||||||
|
*/
|
||||||
|
|
||||||
|
let ReactFeatureFlags;
|
||||||
|
let act;
|
||||||
|
describe('mocked scheduler', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules();
|
||||||
|
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||||
|
ReactFeatureFlags.warnAboutMissingMockScheduler = true;
|
||||||
|
jest.unmock('scheduler');
|
||||||
|
act = require('react-dom/test-utils').act;
|
||||||
|
});
|
||||||
|
it("should warn when the scheduler isn't mocked", () => {
|
||||||
|
expect(() => act(() => {})).toWarnDev(
|
||||||
|
[
|
||||||
|
'Starting from React v17, the "scheduler" module will need to be mocked',
|
||||||
|
],
|
||||||
|
{withoutStack: true},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -11,6 +11,7 @@ let React;
|
||||||
let ReactDOM;
|
let ReactDOM;
|
||||||
let ReactTestUtils;
|
let ReactTestUtils;
|
||||||
let SchedulerTracing;
|
let SchedulerTracing;
|
||||||
|
let Scheduler;
|
||||||
let act;
|
let act;
|
||||||
let container;
|
let container;
|
||||||
|
|
||||||
|
@ -25,48 +26,68 @@ function sleep(period) {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ReactTestUtils.act()', () => {
|
describe('ReactTestUtils.act()', () => {
|
||||||
|
// first we run all the tests with concurrent mode
|
||||||
|
let concurrentRoot;
|
||||||
|
function renderConcurrent(el, dom) {
|
||||||
|
concurrentRoot = ReactDOM.unstable_createRoot(dom);
|
||||||
|
concurrentRoot.render(el);
|
||||||
|
}
|
||||||
|
function unmountConcurrent(_dom) {
|
||||||
|
if (concurrentRoot !== null) {
|
||||||
|
concurrentRoot.unmount();
|
||||||
|
concurrentRoot = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runActTests('concurrent mode', renderConcurrent, unmountConcurrent);
|
||||||
|
|
||||||
|
// and then in sync mode
|
||||||
|
function renderSync(el, dom) {
|
||||||
|
ReactDOM.render(el, dom);
|
||||||
|
}
|
||||||
|
function unmountSync(dom) {
|
||||||
|
ReactDOM.unmountComponentAtNode(dom);
|
||||||
|
}
|
||||||
|
runActTests('legacy sync mode', renderSync, unmountSync);
|
||||||
|
});
|
||||||
|
|
||||||
|
function runActTests(label, render, unmount) {
|
||||||
|
describe(label, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
React = require('react');
|
React = require('react');
|
||||||
ReactDOM = require('react-dom');
|
ReactDOM = require('react-dom');
|
||||||
ReactTestUtils = require('react-dom/test-utils');
|
ReactTestUtils = require('react-dom/test-utils');
|
||||||
SchedulerTracing = require('scheduler/tracing');
|
SchedulerTracing = require('scheduler/tracing');
|
||||||
|
Scheduler = require('scheduler');
|
||||||
act = ReactTestUtils.act;
|
act = ReactTestUtils.act;
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
ReactDOM.unmountComponentAtNode(container);
|
unmount(container);
|
||||||
document.body.removeChild(container);
|
document.body.removeChild(container);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sync', () => {
|
describe('sync', () => {
|
||||||
it('can use act to flush effects', () => {
|
it('can use act to flush effects', () => {
|
||||||
function App(props) {
|
function App() {
|
||||||
React.useEffect(props.callback);
|
React.useEffect(() => {
|
||||||
|
Scheduler.yieldValue(100);
|
||||||
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let calledLog = [];
|
|
||||||
act(() => {
|
act(() => {
|
||||||
ReactDOM.render(
|
render(<App />, container);
|
||||||
<App
|
|
||||||
callback={() => {
|
|
||||||
calledLog.push(calledLog.length);
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
document.createElement('div'),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(calledLog).toEqual([0]);
|
expect(Scheduler).toHaveYielded([100]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('flushes effects on every call', () => {
|
it('flushes effects on every call', () => {
|
||||||
function App(props) {
|
function App() {
|
||||||
let [ctr, setCtr] = React.useState(0);
|
let [ctr, setCtr] = React.useState(0);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
props.callback(ctr);
|
Scheduler.yieldValue(ctr);
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<button id="button" onClick={() => setCtr(x => x + 1)}>
|
<button id="button" onClick={() => setCtr(x => x + 1)}>
|
||||||
|
@ -75,18 +96,11 @@ describe('ReactTestUtils.act()', () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let calledCounter = 0;
|
|
||||||
act(() => {
|
act(() => {
|
||||||
ReactDOM.render(
|
render(<App />, container);
|
||||||
<App
|
|
||||||
callback={val => {
|
|
||||||
calledCounter = val;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
container,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
const button = document.getElementById('button');
|
expect(Scheduler).toHaveYielded([0]);
|
||||||
|
const button = container.querySelector('#button');
|
||||||
function click() {
|
function click() {
|
||||||
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
||||||
}
|
}
|
||||||
|
@ -96,15 +110,16 @@ describe('ReactTestUtils.act()', () => {
|
||||||
click();
|
click();
|
||||||
click();
|
click();
|
||||||
});
|
});
|
||||||
expect(calledCounter).toBe(3);
|
// it consolidates the 3 updates, then fires the effect
|
||||||
|
expect(Scheduler).toHaveYielded([3]);
|
||||||
act(click);
|
act(click);
|
||||||
expect(calledCounter).toBe(4);
|
expect(Scheduler).toHaveYielded([4]);
|
||||||
act(click);
|
act(click);
|
||||||
expect(calledCounter).toBe(5);
|
expect(Scheduler).toHaveYielded([5]);
|
||||||
expect(button.innerHTML).toBe('5');
|
expect(button.innerHTML).toBe('5');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should flush effects recursively', () => {
|
it("should keep flushing effects until the're done", () => {
|
||||||
function App() {
|
function App() {
|
||||||
let [ctr, setCtr] = React.useState(0);
|
let [ctr, setCtr] = React.useState(0);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -116,30 +131,24 @@ describe('ReactTestUtils.act()', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
ReactDOM.render(<App />, container);
|
render(<App />, container);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(container.innerHTML).toBe('5');
|
expect(container.innerHTML).toBe('5');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('detects setState being called outside of act(...)', () => {
|
it('warns if a setState is called outside of act(...)', () => {
|
||||||
let setValue = null;
|
let setValue = null;
|
||||||
function App() {
|
function App() {
|
||||||
let [value, _setValue] = React.useState(0);
|
let [value, _setValue] = React.useState(0);
|
||||||
setValue = _setValue;
|
setValue = _setValue;
|
||||||
return (
|
return value;
|
||||||
<button id="button" onClick={() => setValue(2)}>
|
|
||||||
{value}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
let button;
|
|
||||||
act(() => {
|
act(() => {
|
||||||
ReactDOM.render(<App />, container);
|
render(<App />, container);
|
||||||
button = container.querySelector('#button');
|
|
||||||
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
|
||||||
});
|
});
|
||||||
expect(button.innerHTML).toBe('2');
|
|
||||||
expect(() => setValue(1)).toWarnDev([
|
expect(() => setValue(1)).toWarnDev([
|
||||||
'An update to App inside a test was not wrapped in act(...).',
|
'An update to App inside a test was not wrapped in act(...).',
|
||||||
]);
|
]);
|
||||||
|
@ -164,7 +173,7 @@ describe('ReactTestUtils.act()', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
ReactDOM.render(<App />, container);
|
render(<App />, container);
|
||||||
});
|
});
|
||||||
act(() => {
|
act(() => {
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
|
@ -185,7 +194,7 @@ describe('ReactTestUtils.act()', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
ReactDOM.render(<App />, container);
|
render(<App />, container);
|
||||||
});
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
|
@ -210,13 +219,13 @@ describe('ReactTestUtils.act()', () => {
|
||||||
);
|
);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
const el = document.createElement('div');
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
ReactDOM.render(<App />, el);
|
render(<App />, container);
|
||||||
});
|
});
|
||||||
|
|
||||||
// all 5 ticks present and accounted for
|
// all 5 ticks present and accounted for
|
||||||
expect(el.innerHTML).toBe('5');
|
expect(container.innerHTML).toBe('5');
|
||||||
});
|
});
|
||||||
it('flushes immediate re-renders with act', () => {
|
it('flushes immediate re-renders with act', () => {
|
||||||
function App() {
|
function App() {
|
||||||
|
@ -232,8 +241,8 @@ describe('ReactTestUtils.act()', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
ReactDOM.render(<App />, container);
|
render(<App />, container);
|
||||||
// Since the effects won't be flushed yet, this does not advance the timer
|
// Since effects haven't been flushed yet, this does not advance the timer
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -262,7 +271,7 @@ describe('ReactTestUtils.act()', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warns if you try to await an .act call', () => {
|
it('warns if you try to await a sync .act call', () => {
|
||||||
expect(() => act(() => {}).then(() => {})).toWarnDev(
|
expect(() => act(() => {}).then(() => {})).toWarnDev(
|
||||||
[
|
[
|
||||||
'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
|
'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
|
||||||
|
@ -286,15 +295,13 @@ describe('ReactTestUtils.act()', () => {
|
||||||
}, []);
|
}, []);
|
||||||
return ctr;
|
return ctr;
|
||||||
}
|
}
|
||||||
const el = document.createElement('div');
|
|
||||||
await act(async () => {
|
|
||||||
act(() => {
|
act(() => {
|
||||||
ReactDOM.render(<App />, el);
|
render(<App />, container);
|
||||||
});
|
});
|
||||||
|
await act(async () => {
|
||||||
await sleep(100);
|
await sleep(100);
|
||||||
expect(el.innerHTML).toBe('1');
|
|
||||||
});
|
});
|
||||||
|
expect(container.innerHTML).toBe('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle async/await', async () => {
|
it('can handle async/await', async () => {
|
||||||
|
@ -312,15 +319,14 @@ describe('ReactTestUtils.act()', () => {
|
||||||
}, []);
|
}, []);
|
||||||
return ctr;
|
return ctr;
|
||||||
}
|
}
|
||||||
const el = document.createElement('div');
|
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
ReactDOM.render(<App />, el);
|
render(<App />, container);
|
||||||
});
|
});
|
||||||
// pending promises will close before this ends
|
// pending promises will close before this ends
|
||||||
});
|
});
|
||||||
expect(el.innerHTML).toEqual('1');
|
expect(container.innerHTML).toEqual('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warns if you do not await an act call', async () => {
|
it('warns if you do not await an act call', async () => {
|
||||||
|
@ -356,7 +362,7 @@ describe('ReactTestUtils.act()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('commits and effects are guaranteed to be flushed', async () => {
|
it('commits and effects are guaranteed to be flushed', async () => {
|
||||||
function App(props) {
|
function App() {
|
||||||
let [state, setState] = React.useState(0);
|
let [state, setState] = React.useState(0);
|
||||||
async function something() {
|
async function something() {
|
||||||
await null;
|
await null;
|
||||||
|
@ -366,31 +372,30 @@ describe('ReactTestUtils.act()', () => {
|
||||||
something();
|
something();
|
||||||
}, []);
|
}, []);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
props.callback();
|
Scheduler.yieldValue(state);
|
||||||
});
|
});
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
let ctr = 0;
|
|
||||||
const div = document.createElement('div');
|
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
ReactDOM.render(<App callback={() => ctr++} />, div);
|
render(<App />, container);
|
||||||
});
|
});
|
||||||
expect(div.innerHTML).toBe('0');
|
expect(container.innerHTML).toBe('0');
|
||||||
expect(ctr).toBe(1);
|
expect(Scheduler).toHaveYielded([0]);
|
||||||
});
|
});
|
||||||
// this may seem odd, but it matches user behaviour -
|
// this may seem odd, but it matches user behaviour -
|
||||||
// a flash of "0" followed by "1"
|
// a flash of "0" followed by "1"
|
||||||
|
|
||||||
expect(div.innerHTML).toBe('1');
|
expect(container.innerHTML).toBe('1');
|
||||||
expect(ctr).toBe(2);
|
expect(Scheduler).toHaveYielded([1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('propagates errors', async () => {
|
it('propagates errors', async () => {
|
||||||
let err;
|
let err;
|
||||||
try {
|
try {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
await sleep(100);
|
||||||
throw new Error('some error');
|
throw new Error('some error');
|
||||||
});
|
});
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
|
@ -411,18 +416,20 @@ describe('ReactTestUtils.act()', () => {
|
||||||
}
|
}
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
() => {
|
() => {
|
||||||
|
Scheduler.yieldValue(state);
|
||||||
ticker();
|
ticker();
|
||||||
},
|
},
|
||||||
[Math.min(state, 4)],
|
[Math.min(state, 4)],
|
||||||
);
|
);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
const el = document.createElement('div');
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
ReactDOM.render(<App />, el);
|
render(<App />, container);
|
||||||
});
|
});
|
||||||
// all 5 ticks present and accounted for
|
// all 5 ticks present and accounted for
|
||||||
expect(el.innerHTML).toBe('5');
|
expect(Scheduler).toHaveYielded([0, 1, 2, 3, 4]);
|
||||||
|
expect(container.innerHTML).toBe('5');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -450,7 +457,7 @@ describe('ReactTestUtils.act()', () => {
|
||||||
expect(interactions.size).toBe(1);
|
expect(interactions.size).toBe(1);
|
||||||
expectedInteraction = Array.from(interactions)[0];
|
expectedInteraction = Array.from(interactions)[0];
|
||||||
|
|
||||||
ReactDOM.render(<Component />, container);
|
render(<Component />, container);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -464,7 +471,7 @@ describe('ReactTestUtils.act()', () => {
|
||||||
expect(interactions.size).toBe(1);
|
expect(interactions.size).toBe(1);
|
||||||
expectedInteraction = Array.from(interactions)[0];
|
expectedInteraction = Array.from(interactions)[0];
|
||||||
|
|
||||||
ReactDOM.render(<Component />, container);
|
render(<Component />, container);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -480,7 +487,7 @@ describe('ReactTestUtils.act()', () => {
|
||||||
expect(interactions.size).toBe(1);
|
expect(interactions.size).toBe(1);
|
||||||
expectedInteraction = Array.from(interactions)[0];
|
expectedInteraction = Array.from(interactions)[0];
|
||||||
|
|
||||||
ReactDOM.render(<Component />, secondContainer);
|
render(<Component />, secondContainer);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -494,13 +501,15 @@ describe('ReactTestUtils.act()', () => {
|
||||||
expect(interactions.size).toBe(1);
|
expect(interactions.size).toBe(1);
|
||||||
expectedInteraction = Array.from(interactions)[0];
|
expectedInteraction = Array.from(interactions)[0];
|
||||||
|
|
||||||
ReactDOM.render(<Component />, secondContainer);
|
render(<Component />, secondContainer);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(Component).toHaveBeenCalledTimes(4);
|
expect(Component).toHaveBeenCalledTimes(4);
|
||||||
|
unmount(secondContainer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,9 @@ import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
|
||||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||||
|
import {warnAboutMissingMockScheduler} from 'shared/ReactFeatureFlags';
|
||||||
import enqueueTask from 'shared/enqueueTask';
|
import enqueueTask from 'shared/enqueueTask';
|
||||||
|
import * as Scheduler from 'scheduler';
|
||||||
|
|
||||||
// Keep in sync with ReactDOMUnstableNativeDependencies.js
|
// Keep in sync with ReactDOMUnstableNativeDependencies.js
|
||||||
// ReactDOM.js, and ReactTestUtils.js:
|
// ReactDOM.js, and ReactTestUtils.js:
|
||||||
|
@ -40,16 +42,33 @@ const {ReactShouldWarnActingUpdates} = ReactSharedInternals;
|
||||||
// this implementation should be exactly the same in
|
// this implementation should be exactly the same in
|
||||||
// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
|
// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
|
||||||
|
|
||||||
// we track the 'depth' of the act() calls with this counter,
|
let hasWarnedAboutMissingMockScheduler = false;
|
||||||
// so we can tell if any async act() calls try to run in parallel.
|
const flushWork =
|
||||||
let actingUpdatesScopeDepth = 0;
|
Scheduler.unstable_flushWithoutYielding ||
|
||||||
|
function() {
|
||||||
|
if (warnAboutMissingMockScheduler === true) {
|
||||||
|
if (hasWarnedAboutMissingMockScheduler === false) {
|
||||||
|
warningWithoutStack(
|
||||||
|
null,
|
||||||
|
'Starting from React v17, the "scheduler" module will need to be mocked ' +
|
||||||
|
'to guarantee consistent behaviour across tests and browsers. To fix this, add the following ' +
|
||||||
|
"to the top of your tests, or in your framework's global config file -\n\n" +
|
||||||
|
'As an example, for jest - \n' +
|
||||||
|
"jest.mock('scheduler', () => require.requireActual('scheduler/unstable_mock'));\n\n" +
|
||||||
|
'For more info, visit https://fb.me/react-mock-scheduler',
|
||||||
|
);
|
||||||
|
hasWarnedAboutMissingMockScheduler = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (flushPassiveEffects()) {}
|
||||||
|
};
|
||||||
|
|
||||||
function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
|
function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
|
||||||
try {
|
try {
|
||||||
flushPassiveEffects();
|
flushWork();
|
||||||
enqueueTask(() => {
|
enqueueTask(() => {
|
||||||
if (flushPassiveEffects()) {
|
if (flushWork()) {
|
||||||
flushEffectsAndMicroTasks(onDone);
|
flushWorkAndMicroTasks(onDone);
|
||||||
} else {
|
} else {
|
||||||
onDone();
|
onDone();
|
||||||
}
|
}
|
||||||
|
@ -59,6 +78,11 @@ function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we track the 'depth' of the act() calls with this counter,
|
||||||
|
// so we can tell if any async act() calls try to run in parallel.
|
||||||
|
|
||||||
|
let actingUpdatesScopeDepth = 0;
|
||||||
|
|
||||||
function act(callback: () => Thenable) {
|
function act(callback: () => Thenable) {
|
||||||
let previousActingUpdatesScopeDepth;
|
let previousActingUpdatesScopeDepth;
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
|
@ -119,7 +143,7 @@ function act(callback: () => Thenable) {
|
||||||
called = true;
|
called = true;
|
||||||
result.then(
|
result.then(
|
||||||
() => {
|
() => {
|
||||||
flushEffectsAndMicroTasks((err: ?Error) => {
|
flushWorkAndMicroTasks((err: ?Error) => {
|
||||||
onDone();
|
onDone();
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -147,7 +171,7 @@ function act(callback: () => Thenable) {
|
||||||
|
|
||||||
// flush effects until none remain, and cleanup
|
// flush effects until none remain, and cleanup
|
||||||
try {
|
try {
|
||||||
while (flushPassiveEffects()) {}
|
flushWork();
|
||||||
onDone();
|
onDone();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
onDone();
|
onDone();
|
||||||
|
|
|
@ -32,7 +32,10 @@ import warning from 'shared/warning';
|
||||||
import enqueueTask from 'shared/enqueueTask';
|
import enqueueTask from 'shared/enqueueTask';
|
||||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||||
import {enableEventAPI} from 'shared/ReactFeatureFlags';
|
import {
|
||||||
|
warnAboutMissingMockScheduler,
|
||||||
|
enableEventAPI,
|
||||||
|
} from 'shared/ReactFeatureFlags';
|
||||||
import {ConcurrentRoot, BatchedRoot, LegacyRoot} from 'shared/ReactRootTags';
|
import {ConcurrentRoot, BatchedRoot, LegacyRoot} from 'shared/ReactRootTags';
|
||||||
|
|
||||||
type EventTargetChildElement = {
|
type EventTargetChildElement = {
|
||||||
|
@ -652,14 +655,33 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||||
// this act() implementation should be exactly the same in
|
// this act() implementation should be exactly the same in
|
||||||
// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
|
// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
|
||||||
|
|
||||||
let actingUpdatesScopeDepth = 0;
|
let hasWarnedAboutMissingMockScheduler = false;
|
||||||
|
const flushWork =
|
||||||
|
Scheduler.unstable_flushWithoutYielding ||
|
||||||
|
function() {
|
||||||
|
if (warnAboutMissingMockScheduler === true) {
|
||||||
|
if (hasWarnedAboutMissingMockScheduler === false) {
|
||||||
|
warningWithoutStack(
|
||||||
|
null,
|
||||||
|
'Starting from React v17, the "scheduler" module will need to be mocked ' +
|
||||||
|
'to guarantee consistent behaviour across tests and browsers. To fix this, add the following ' +
|
||||||
|
"to the top of your tests, or in your framework's global config file -\n\n" +
|
||||||
|
'As an example, for jest - \n' +
|
||||||
|
"jest.mock('scheduler', () => require.requireActual('scheduler/unstable_mock'));\n\n" +
|
||||||
|
'For more info, visit https://fb.me/react-mock-scheduler',
|
||||||
|
);
|
||||||
|
hasWarnedAboutMissingMockScheduler = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (flushPassiveEffects()) {}
|
||||||
|
};
|
||||||
|
|
||||||
function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
|
function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
|
||||||
try {
|
try {
|
||||||
flushPassiveEffects();
|
flushWork();
|
||||||
enqueueTask(() => {
|
enqueueTask(() => {
|
||||||
if (flushPassiveEffects()) {
|
if (flushWork()) {
|
||||||
flushEffectsAndMicroTasks(onDone);
|
flushWorkAndMicroTasks(onDone);
|
||||||
} else {
|
} else {
|
||||||
onDone();
|
onDone();
|
||||||
}
|
}
|
||||||
|
@ -669,6 +691,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we track the 'depth' of the act() calls with this counter,
|
||||||
|
// so we can tell if any async act() calls try to run in parallel.
|
||||||
|
|
||||||
|
let actingUpdatesScopeDepth = 0;
|
||||||
|
|
||||||
function act(callback: () => Thenable) {
|
function act(callback: () => Thenable) {
|
||||||
let previousActingUpdatesScopeDepth;
|
let previousActingUpdatesScopeDepth;
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
|
@ -729,7 +756,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||||
called = true;
|
called = true;
|
||||||
result.then(
|
result.then(
|
||||||
() => {
|
() => {
|
||||||
flushEffectsAndMicroTasks((err: ?Error) => {
|
flushWorkAndMicroTasks((err: ?Error) => {
|
||||||
onDone();
|
onDone();
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -757,7 +784,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||||
|
|
||||||
// flush effects until none remain, and cleanup
|
// flush effects until none remain, and cleanup
|
||||||
try {
|
try {
|
||||||
while (flushPassiveEffects()) {}
|
flushWork();
|
||||||
onDone();
|
onDone();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
onDone();
|
onDone();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
let React;
|
let React;
|
||||||
let ReactFeatureFlags;
|
let ReactFeatureFlags;
|
||||||
let ReactNoop;
|
let ReactNoop;
|
||||||
let act;
|
|
||||||
let Scheduler;
|
let Scheduler;
|
||||||
let ReactCache;
|
let ReactCache;
|
||||||
let Suspense;
|
let Suspense;
|
||||||
|
@ -15,7 +14,6 @@ describe('ReactBatchedMode', () => {
|
||||||
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
|
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
|
||||||
React = require('react');
|
React = require('react');
|
||||||
ReactNoop = require('react-noop-renderer');
|
ReactNoop = require('react-noop-renderer');
|
||||||
act = ReactNoop.act;
|
|
||||||
Scheduler = require('scheduler');
|
Scheduler = require('scheduler');
|
||||||
ReactCache = require('react-cache');
|
ReactCache = require('react-cache');
|
||||||
Suspense = React.Suspense;
|
Suspense = React.Suspense;
|
||||||
|
@ -146,10 +144,10 @@ describe('ReactBatchedMode', () => {
|
||||||
expect(root).toMatchRenderedOutput('A0B0');
|
expect(root).toMatchRenderedOutput('A0B0');
|
||||||
|
|
||||||
// Schedule a batched update to the first sibling
|
// Schedule a batched update to the first sibling
|
||||||
act(() => foo1.current.setStep(1));
|
ReactNoop.batchedUpdates(() => foo1.current.setStep(1));
|
||||||
|
|
||||||
// Before it flushes, update the second sibling inside flushSync
|
// Before it flushes, update the second sibling inside flushSync
|
||||||
act(() =>
|
ReactNoop.batchedUpdates(() =>
|
||||||
ReactNoop.flushSync(() => {
|
ReactNoop.flushSync(() => {
|
||||||
foo2.current.setStep(1);
|
foo2.current.setStep(1);
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -95,7 +95,7 @@ describe('ReactHooks', () => {
|
||||||
setCounter2(1);
|
setCounter2(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield([
|
expect(Scheduler).toHaveYielded([
|
||||||
'Parent: 1, 1',
|
'Parent: 1, 1',
|
||||||
'Child: 1, 1',
|
'Child: 1, 1',
|
||||||
'Effect: 1, 1',
|
'Effect: 1, 1',
|
||||||
|
@ -103,7 +103,7 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
// Update that bails out.
|
// Update that bails out.
|
||||||
act(() => setCounter1(1));
|
act(() => setCounter1(1));
|
||||||
expect(Scheduler).toFlushAndYield(['Parent: 1, 1']);
|
expect(Scheduler).toHaveYielded(['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.
|
||||||
|
@ -112,7 +112,7 @@ describe('ReactHooks', () => {
|
||||||
setCounter2(2);
|
setCounter2(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield([
|
expect(Scheduler).toHaveYielded([
|
||||||
'Parent: 1, 2',
|
'Parent: 1, 2',
|
||||||
'Child: 1, 2',
|
'Child: 1, 2',
|
||||||
'Effect: 1, 2',
|
'Effect: 1, 2',
|
||||||
|
@ -130,14 +130,15 @@ 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).toFlushAndYield(['Parent: 1, 2']);
|
expect(Scheduler).toHaveYielded(['Parent: 1, 2']);
|
||||||
|
|
||||||
// prepare to check SameValue
|
// prepare to check SameValue
|
||||||
act(() => {
|
act(() => {
|
||||||
setCounter1(0 / -1);
|
setCounter1(0 / -1);
|
||||||
setCounter2(NaN);
|
setCounter2(NaN);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYield([
|
|
||||||
|
expect(Scheduler).toHaveYielded([
|
||||||
'Parent: 0, NaN',
|
'Parent: 0, NaN',
|
||||||
'Child: 0, NaN',
|
'Child: 0, NaN',
|
||||||
'Effect: 0, NaN',
|
'Effect: 0, NaN',
|
||||||
|
@ -151,13 +152,13 @@ describe('ReactHooks', () => {
|
||||||
setCounter2(NaN);
|
setCounter2(NaN);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield(['Parent: 0, NaN']);
|
expect(Scheduler).toHaveYielded(['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).toFlushAndYield([
|
expect(Scheduler).toHaveYielded([
|
||||||
'Parent: 0, NaN',
|
'Parent: 0, NaN',
|
||||||
'Child: 0, NaN',
|
'Child: 0, NaN',
|
||||||
'Effect: 0, NaN',
|
'Effect: 0, NaN',
|
||||||
|
@ -201,14 +202,14 @@ describe('ReactHooks', () => {
|
||||||
setCounter2(1);
|
setCounter2(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield([
|
expect(Scheduler).toHaveYielded([
|
||||||
'Parent: 1, 1 (light)',
|
'Parent: 1, 1 (light)',
|
||||||
'Child: 1, 1 (light)',
|
'Child: 1, 1 (light)',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Update that bails out.
|
// Update that bails out.
|
||||||
act(() => setCounter1(1));
|
act(() => setCounter1(1));
|
||||||
expect(Scheduler).toFlushAndYield(['Parent: 1, 1 (light)']);
|
expect(Scheduler).toHaveYielded(['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.
|
||||||
|
@ -217,7 +218,7 @@ describe('ReactHooks', () => {
|
||||||
setCounter2(2);
|
setCounter2(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield([
|
expect(Scheduler).toHaveYielded([
|
||||||
'Parent: 1, 2 (light)',
|
'Parent: 1, 2 (light)',
|
||||||
'Child: 1, 2 (light)',
|
'Child: 1, 2 (light)',
|
||||||
]);
|
]);
|
||||||
|
@ -227,10 +228,10 @@ describe('ReactHooks', () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
setCounter1(1);
|
setCounter1(1);
|
||||||
setCounter2(2);
|
setCounter2(2);
|
||||||
|
root.update(<Parent theme="dark" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
root.update(<Parent theme="dark" />);
|
expect(Scheduler).toHaveYielded([
|
||||||
expect(Scheduler).toFlushAndYield([
|
|
||||||
'Parent: 1, 2 (dark)',
|
'Parent: 1, 2 (dark)',
|
||||||
'Child: 1, 2 (dark)',
|
'Child: 1, 2 (dark)',
|
||||||
]);
|
]);
|
||||||
|
@ -239,10 +240,10 @@ describe('ReactHooks', () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
setCounter1(1);
|
setCounter1(1);
|
||||||
setCounter2(2);
|
setCounter2(2);
|
||||||
|
root.update(<Parent theme="dark" />);
|
||||||
});
|
});
|
||||||
|
|
||||||
root.update(<Parent theme="dark" />);
|
expect(Scheduler).toHaveYielded(['Parent: 1, 2 (dark)']);
|
||||||
expect(Scheduler).toFlushAndYield(['Parent: 1, 2 (dark)']);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warns about setState second argument', () => {
|
it('warns about setState second argument', () => {
|
||||||
|
@ -275,7 +276,7 @@ describe('ReactHooks', () => {
|
||||||
'declare it in the component body with useEffect().',
|
'declare it in the component body with useEffect().',
|
||||||
{withoutStack: true},
|
{withoutStack: true},
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 1']);
|
expect(Scheduler).toHaveYielded(['Count: 1']);
|
||||||
expect(root).toMatchRenderedOutput('1');
|
expect(root).toMatchRenderedOutput('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -309,7 +310,7 @@ describe('ReactHooks', () => {
|
||||||
'declare it in the component body with useEffect().',
|
'declare it in the component body with useEffect().',
|
||||||
{withoutStack: true},
|
{withoutStack: true},
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 1']);
|
expect(Scheduler).toHaveYielded(['Count: 1']);
|
||||||
expect(root).toMatchRenderedOutput('1');
|
expect(root).toMatchRenderedOutput('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -347,14 +348,16 @@ describe('ReactHooks', () => {
|
||||||
});
|
});
|
||||||
return <Child text={text} />;
|
return <Child text={text} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
||||||
|
act(() => {
|
||||||
root.update(
|
root.update(
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<Parent />
|
<Parent />
|
||||||
</ThemeProvider>,
|
</ThemeProvider>,
|
||||||
);
|
);
|
||||||
expect(Scheduler).toFlushAndYield([
|
});
|
||||||
|
|
||||||
|
expect(Scheduler).toHaveYielded([
|
||||||
'Theme: light',
|
'Theme: light',
|
||||||
'Parent: 0 (light)',
|
'Parent: 0 (light)',
|
||||||
'Child: 0 (light)',
|
'Child: 0 (light)',
|
||||||
|
@ -370,7 +373,7 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
// Normal update
|
// Normal update
|
||||||
act(() => setCounter(1));
|
act(() => setCounter(1));
|
||||||
expect(Scheduler).toFlushAndYield([
|
expect(Scheduler).toHaveYielded([
|
||||||
'Parent: 1 (light)',
|
'Parent: 1 (light)',
|
||||||
'Child: 1 (light)',
|
'Child: 1 (light)',
|
||||||
'Effect: 1 (light)',
|
'Effect: 1 (light)',
|
||||||
|
@ -379,7 +382,7 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
// 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).toFlushAndYield(['Parent: 1 (light)']);
|
expect(Scheduler).toHaveYielded(['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
|
||||||
|
@ -389,7 +392,7 @@ describe('ReactHooks', () => {
|
||||||
setTheme('dark');
|
setTheme('dark');
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield([
|
expect(Scheduler).toHaveYielded([
|
||||||
'Theme: dark',
|
'Theme: dark',
|
||||||
'Parent: 1 (dark)',
|
'Parent: 1 (dark)',
|
||||||
'Child: 1 (dark)',
|
'Child: 1 (dark)',
|
||||||
|
@ -424,7 +427,7 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
// Normal update
|
// Normal update
|
||||||
act(() => setCounter(1));
|
act(() => setCounter(1));
|
||||||
expect(Scheduler).toFlushAndYield(['Parent: 1', 'Child: 1', 'Effect: 1']);
|
expect(Scheduler).toHaveYielded(['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
|
||||||
|
@ -432,7 +435,7 @@ 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).toFlushAndYield(['Parent: 1']);
|
expect(Scheduler).toHaveYielded(['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
|
||||||
|
@ -443,14 +446,14 @@ describe('ReactHooks', () => {
|
||||||
|
|
||||||
// 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).toFlushAndYield(['Parent: 2', 'Child: 2', 'Effect: 2']);
|
expect(Scheduler).toHaveYielded(['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).toFlushAndYield(['Parent: 0', 'Child: 0', 'Effect: 0']);
|
expect(Scheduler).toHaveYielded(['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
|
||||||
|
@ -458,7 +461,7 @@ describe('ReactHooks', () => {
|
||||||
setCounter(0);
|
setCounter(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield(['Parent: 0']);
|
expect(Scheduler).toHaveYielded(['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.
|
||||||
|
@ -472,7 +475,7 @@ describe('ReactHooks', () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
setCounter(0 / -1);
|
setCounter(0 / -1);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYield(['Parent: 0', 'Child: 0', 'Effect: 0']);
|
expect(Scheduler).toHaveYielded(['Parent: 0', 'Child: 0', 'Effect: 0']);
|
||||||
expect(root).toMatchRenderedOutput('0');
|
expect(root).toMatchRenderedOutput('0');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -503,7 +506,7 @@ describe('ReactHooks', () => {
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
act(() => {
|
ReactTestRenderer.unstable_batchedUpdates(() => {
|
||||||
update(0);
|
update(0);
|
||||||
update(0);
|
update(0);
|
||||||
update(0);
|
update(0);
|
||||||
|
@ -564,7 +567,7 @@ describe('ReactHooks', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update at normal priority
|
// Update at normal priority
|
||||||
act(() => 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)']);
|
expect(Scheduler).toHaveYielded(['Compute state (1 -> 100)']);
|
||||||
|
|
|
@ -80,7 +80,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
||||||
|
|
||||||
// Schedule some updates
|
// Schedule some updates
|
||||||
act(() => {
|
ReactNoop.batchedUpdates(() => {
|
||||||
counter.current.updateCount(1);
|
counter.current.updateCount(1);
|
||||||
counter.current.updateCount(count => count + 10);
|
counter.current.updateCount(count => count + 10);
|
||||||
});
|
});
|
||||||
|
@ -189,11 +189,11 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
||||||
|
|
||||||
act(() => counter.current.updateCount(1));
|
act(() => counter.current.updateCount(1));
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 1']);
|
expect(Scheduler).toHaveYielded(['Count: 1']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
||||||
|
|
||||||
act(() => counter.current.updateCount(count => count + 10));
|
act(() => counter.current.updateCount(count => count + 10));
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 11']);
|
expect(Scheduler).toHaveYielded(['Count: 11']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 11')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 11')]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 42')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 42')]);
|
||||||
|
|
||||||
act(() => counter.current.updateCount(7));
|
act(() => counter.current.updateCount(7));
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 7']);
|
expect(Scheduler).toHaveYielded(['Count: 7']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 7')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 7')]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -231,10 +231,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
||||||
|
|
||||||
act(() => counter.current.updateCount(7));
|
act(() => counter.current.updateCount(7));
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 7']);
|
expect(Scheduler).toHaveYielded(['Count: 7']);
|
||||||
|
|
||||||
act(() => counter.current.updateLabel('Total'));
|
act(() => counter.current.updateLabel('Total'));
|
||||||
expect(Scheduler).toFlushAndYield(['Total: 7']);
|
expect(Scheduler).toHaveYielded(['Total: 7']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the same updater function every time', () => {
|
it('returns the same updater function every time', () => {
|
||||||
|
@ -249,11 +249,11 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
||||||
|
|
||||||
act(() => updaters[0](1));
|
act(() => updaters[0](1));
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 1']);
|
expect(Scheduler).toHaveYielded(['Count: 1']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
||||||
|
|
||||||
act(() => updaters[0](count => count + 10));
|
act(() => updaters[0](count => count + 10));
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 11']);
|
expect(Scheduler).toHaveYielded(['Count: 11']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 11')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 11')]);
|
||||||
|
|
||||||
expect(updaters).toEqual([updaters[0], updaters[0], updaters[0]]);
|
expect(updaters).toEqual([updaters[0], updaters[0], updaters[0]]);
|
||||||
|
@ -298,7 +298,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
||||||
|
|
||||||
act(() => _updateCount(1));
|
act(() => _updateCount(1));
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 1']);
|
expect(Scheduler).toHaveYielded(['Count: 1']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -484,7 +484,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
counter.current.dispatch('reset');
|
counter.current.dispatch('reset');
|
||||||
});
|
});
|
||||||
ReactNoop.render(<Counter ref={counter} />);
|
ReactNoop.render(<Counter ref={counter} />);
|
||||||
expect(Scheduler).toFlushAndYield([
|
expect(Scheduler).toHaveYielded([
|
||||||
'Render: 0',
|
'Render: 0',
|
||||||
'Render: 1',
|
'Render: 1',
|
||||||
'Render: 11',
|
'Render: 11',
|
||||||
|
@ -524,7 +524,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
||||||
|
|
||||||
act(() => counter.current.dispatch(INCREMENT));
|
act(() => counter.current.dispatch(INCREMENT));
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 1']);
|
expect(Scheduler).toHaveYielded(['Count: 1']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
||||||
act(() => {
|
act(() => {
|
||||||
counter.current.dispatch(DECREMENT);
|
counter.current.dispatch(DECREMENT);
|
||||||
|
@ -532,7 +532,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
counter.current.dispatch(DECREMENT);
|
counter.current.dispatch(DECREMENT);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield(['Count: -2']);
|
expect(Scheduler).toHaveYielded(['Count: -2']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: -2')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: -2')]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -566,7 +566,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 10')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 10')]);
|
||||||
|
|
||||||
act(() => counter.current.dispatch(INCREMENT));
|
act(() => counter.current.dispatch(INCREMENT));
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 11']);
|
expect(Scheduler).toHaveYielded(['Count: 11']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 11')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 11')]);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
|
@ -575,7 +575,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
counter.current.dispatch(DECREMENT);
|
counter.current.dispatch(DECREMENT);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 8']);
|
expect(Scheduler).toHaveYielded(['Count: 8']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 8')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 8')]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -600,7 +600,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 0']);
|
expect(Scheduler).toFlushAndYield(['Count: 0']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
||||||
|
|
||||||
act(() => {
|
ReactNoop.batchedUpdates(() => {
|
||||||
counter.current.dispatch(INCREMENT);
|
counter.current.dispatch(INCREMENT);
|
||||||
counter.current.dispatch(INCREMENT);
|
counter.current.dispatch(INCREMENT);
|
||||||
counter.current.dispatch(INCREMENT);
|
counter.current.dispatch(INCREMENT);
|
||||||
|
@ -884,8 +884,12 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
|
|
||||||
// A discrete event forces the passive effect to be flushed --
|
// A discrete event forces the passive effect to be flushed --
|
||||||
// updateCount(1) happens first, so 2 wins.
|
// updateCount(1) happens first, so 2 wins.
|
||||||
|
|
||||||
ReactNoop.interactiveUpdates(() => {
|
ReactNoop.interactiveUpdates(() => {
|
||||||
act(() => _updateCount(2));
|
// (use batchedUpdates to silence the act() warning)
|
||||||
|
ReactNoop.batchedUpdates(() => {
|
||||||
|
_updateCount(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
expect(Scheduler).toHaveYielded(['Will set count to 1']);
|
expect(Scheduler).toHaveYielded(['Will set count to 1']);
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 2']);
|
expect(Scheduler).toFlushAndYield(['Count: 2']);
|
||||||
|
@ -936,7 +940,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
// A discrete event forces the passive effect to be flushed --
|
// A discrete event forces the passive effect to be flushed --
|
||||||
// updateCount(1) happens first, so 2 wins.
|
// updateCount(1) happens first, so 2 wins.
|
||||||
ReactNoop.interactiveUpdates(() => {
|
ReactNoop.interactiveUpdates(() => {
|
||||||
act(() => _updateCount(2));
|
// use batchedUpdates to silence the act warning
|
||||||
|
ReactNoop.batchedUpdates(() => _updateCount(2));
|
||||||
});
|
});
|
||||||
expect(Scheduler).toHaveYielded(['Will set count to 1']);
|
expect(Scheduler).toHaveYielded(['Will set count to 1']);
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 2']);
|
expect(Scheduler).toFlushAndYield(['Count: 2']);
|
||||||
|
@ -1527,7 +1532,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
act(button.current.increment);
|
act(button.current.increment);
|
||||||
expect(Scheduler).toFlushAndYield([
|
expect(Scheduler).toHaveYielded([
|
||||||
// Button should not re-render, because its props haven't changed
|
// Button should not re-render, because its props haven't changed
|
||||||
// 'Increment',
|
// 'Increment',
|
||||||
'Count: 1',
|
'Count: 1',
|
||||||
|
@ -1551,7 +1556,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
|
|
||||||
// Callback should have updated
|
// Callback should have updated
|
||||||
act(button.current.increment);
|
act(button.current.increment);
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 11']);
|
expect(Scheduler).toHaveYielded(['Count: 11']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([
|
expect(ReactNoop.getChildren()).toEqual([
|
||||||
span('Increment'),
|
span('Increment'),
|
||||||
span('Count: 11'),
|
span('Count: 11'),
|
||||||
|
@ -1754,7 +1759,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
counter.current.dispatch(INCREMENT);
|
counter.current.dispatch(INCREMENT);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 1']);
|
expect(Scheduler).toHaveYielded(['Count: 1']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
||||||
// Intentionally not updated because of [] deps:
|
// Intentionally not updated because of [] deps:
|
||||||
expect(counter.current.count).toBe(0);
|
expect(counter.current.count).toBe(0);
|
||||||
|
@ -1784,7 +1789,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
counter.current.dispatch(INCREMENT);
|
counter.current.dispatch(INCREMENT);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 1']);
|
expect(Scheduler).toHaveYielded(['Count: 1']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
||||||
expect(counter.current.count).toBe(1);
|
expect(counter.current.count).toBe(1);
|
||||||
});
|
});
|
||||||
|
@ -1821,7 +1826,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
counter.current.dispatch(INCREMENT);
|
counter.current.dispatch(INCREMENT);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 1']);
|
expect(Scheduler).toHaveYielded(['Count: 1']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
||||||
expect(counter.current.count).toBe(1);
|
expect(counter.current.count).toBe(1);
|
||||||
expect(totalRefUpdates).toBe(2);
|
expect(totalRefUpdates).toBe(2);
|
||||||
|
@ -1868,7 +1873,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
updateB(3);
|
updateB(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Scheduler).toFlushAndYield(['A: 2, B: 3, C: [not loaded]']);
|
expect(Scheduler).toHaveYielded(['A: 2, B: 3, C: [not loaded]']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([
|
expect(ReactNoop.getChildren()).toEqual([
|
||||||
span('A: 2, B: 3, C: [not loaded]'),
|
span('A: 2, B: 3, C: [not loaded]'),
|
||||||
]);
|
]);
|
||||||
|
@ -1929,7 +1934,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
updateB(3);
|
updateB(3);
|
||||||
updateC(4);
|
updateC(4);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYield(['A: 2, B: 3, C: 4']);
|
expect(Scheduler).toHaveYielded(['A: 2, B: 3, C: 4']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 4')]);
|
expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 4')]);
|
||||||
ReactNoop.render(<App loadC={false} />);
|
ReactNoop.render(<App loadC={false} />);
|
||||||
expect(Scheduler).toFlushAndThrow(
|
expect(Scheduler).toFlushAndThrow(
|
||||||
|
@ -2035,7 +2040,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
setCounter(2);
|
setCounter(2);
|
||||||
});
|
});
|
||||||
expect(Scheduler).toFlushAndYield([
|
expect(Scheduler).toHaveYielded([
|
||||||
'Render: 1',
|
'Render: 1',
|
||||||
'Effect: 2',
|
'Effect: 2',
|
||||||
'Reducer: 2',
|
'Reducer: 2',
|
||||||
|
@ -2074,7 +2079,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
expect(ReactNoop).toMatchRenderedOutput('0');
|
expect(ReactNoop).toMatchRenderedOutput('0');
|
||||||
|
|
||||||
act(() => dispatch());
|
act(() => dispatch());
|
||||||
expect(Scheduler).toFlushAndYield(['Step: 5, Shadow: 5']);
|
expect(Scheduler).toHaveYielded(['Step: 5, Shadow: 5']);
|
||||||
expect(ReactNoop).toMatchRenderedOutput('5');
|
expect(ReactNoop).toMatchRenderedOutput('5');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2113,7 +2118,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||||
|
|
||||||
// Enqueuing this update forces the passive effect to be flushed --
|
// Enqueuing this update forces the passive effect to be flushed --
|
||||||
// updateCount(1) happens first, so 2 wins.
|
// updateCount(1) happens first, so 2 wins.
|
||||||
act(() => _updateCount(2));
|
// (use batchedUpdates to silence the act() warning)
|
||||||
|
ReactNoop.batchedUpdates(() => _updateCount(2));
|
||||||
expect(Scheduler).toHaveYielded(['Will set count to 1']);
|
expect(Scheduler).toHaveYielded(['Will set count to 1']);
|
||||||
expect(Scheduler).toFlushAndYield(['Count: 2']);
|
expect(Scheduler).toFlushAndYield(['Count: 2']);
|
||||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]);
|
expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]);
|
||||||
|
|
|
@ -104,14 +104,14 @@ 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).toFlushAndYield(['a:1', 'b:1', 'c:1']);
|
expect(Scheduler).toHaveYielded(['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
|
||||||
ReactNoop.act(() => {
|
ReactNoop.batchedUpdates(() => {
|
||||||
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');
|
||||||
});
|
});
|
||||||
|
@ -122,7 +122,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||||
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1');
|
expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1');
|
||||||
expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
|
expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
|
||||||
// Schedule last bit of work, it will get processed the last
|
// Schedule last bit of work, it will get processed the last
|
||||||
ReactNoop.act(() => {
|
ReactNoop.batchedUpdates(() => {
|
||||||
ReactNoop.renderToRootWithID(<Text text="a:2" />, 'a');
|
ReactNoop.renderToRootWithID(<Text text="a:2" />, 'a');
|
||||||
});
|
});
|
||||||
// Keep performing work in the order it was scheduled
|
// Keep performing work in the order it was scheduled
|
||||||
|
|
|
@ -42,6 +42,7 @@ describe('ReactNoop.act()', () => {
|
||||||
Scheduler.yieldValue('stage 1');
|
Scheduler.yieldValue('stage 1');
|
||||||
await null;
|
await null;
|
||||||
Scheduler.yieldValue('stage 2');
|
Scheduler.yieldValue('stage 2');
|
||||||
|
await null;
|
||||||
setCtr(1);
|
setCtr(1);
|
||||||
}
|
}
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -50,13 +51,9 @@ describe('ReactNoop.act()', () => {
|
||||||
return ctr;
|
return ctr;
|
||||||
}
|
}
|
||||||
await ReactNoop.act(async () => {
|
await ReactNoop.act(async () => {
|
||||||
ReactNoop.act(() => {
|
|
||||||
ReactNoop.render(<App />);
|
ReactNoop.render(<App />);
|
||||||
});
|
});
|
||||||
await null;
|
expect(Scheduler).toHaveYielded(['stage 1', 'stage 2']);
|
||||||
expect(Scheduler).toFlushAndYield(['stage 1']);
|
|
||||||
});
|
|
||||||
expect(Scheduler).toHaveYielded(['stage 2']);
|
|
||||||
expect(Scheduler).toFlushWithoutYielding();
|
expect(Scheduler).toFlushWithoutYielding();
|
||||||
expect(ReactNoop.getChildren()).toEqual([{text: '1', hidden: false}]);
|
expect(ReactNoop.getChildren()).toEqual([{text: '1', hidden: false}]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,23 +14,42 @@ import {
|
||||||
} from 'react-reconciler/inline.test';
|
} from 'react-reconciler/inline.test';
|
||||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||||
|
import {warnAboutMissingMockScheduler} from 'shared/ReactFeatureFlags';
|
||||||
import enqueueTask from 'shared/enqueueTask';
|
import enqueueTask from 'shared/enqueueTask';
|
||||||
|
import * as Scheduler from 'scheduler';
|
||||||
|
|
||||||
const {ReactShouldWarnActingUpdates} = ReactSharedInternals;
|
const {ReactShouldWarnActingUpdates} = ReactSharedInternals;
|
||||||
|
|
||||||
// this implementation should be exactly the same in
|
// this implementation should be exactly the same in
|
||||||
// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
|
// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
|
||||||
|
|
||||||
// we track the 'depth' of the act() calls with this counter,
|
let hasWarnedAboutMissingMockScheduler = false;
|
||||||
// so we can tell if any async act() calls try to run in parallel.
|
const flushWork =
|
||||||
let actingUpdatesScopeDepth = 0;
|
Scheduler.unstable_flushWithoutYielding ||
|
||||||
|
function() {
|
||||||
|
if (warnAboutMissingMockScheduler === true) {
|
||||||
|
if (hasWarnedAboutMissingMockScheduler === false) {
|
||||||
|
warningWithoutStack(
|
||||||
|
null,
|
||||||
|
'Starting from React v17, the "scheduler" module will need to be mocked ' +
|
||||||
|
'to guarantee consistent behaviour across tests and browsers. To fix this, add the following ' +
|
||||||
|
"to the top of your tests, or in your framework's global config file -\n\n" +
|
||||||
|
'As an example, for jest - \n' +
|
||||||
|
"jest.mock('scheduler', () => require.requireActual('scheduler/unstable_mock'));\n\n" +
|
||||||
|
'For more info, visit https://fb.me/react-mock-scheduler',
|
||||||
|
);
|
||||||
|
hasWarnedAboutMissingMockScheduler = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (flushPassiveEffects()) {}
|
||||||
|
};
|
||||||
|
|
||||||
function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
|
function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
|
||||||
try {
|
try {
|
||||||
flushPassiveEffects();
|
flushWork();
|
||||||
enqueueTask(() => {
|
enqueueTask(() => {
|
||||||
if (flushPassiveEffects()) {
|
if (flushWork()) {
|
||||||
flushEffectsAndMicroTasks(onDone);
|
flushWorkAndMicroTasks(onDone);
|
||||||
} else {
|
} else {
|
||||||
onDone();
|
onDone();
|
||||||
}
|
}
|
||||||
|
@ -40,6 +59,11 @@ function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we track the 'depth' of the act() calls with this counter,
|
||||||
|
// so we can tell if any async act() calls try to run in parallel.
|
||||||
|
|
||||||
|
let actingUpdatesScopeDepth = 0;
|
||||||
|
|
||||||
function act(callback: () => Thenable) {
|
function act(callback: () => Thenable) {
|
||||||
let previousActingUpdatesScopeDepth;
|
let previousActingUpdatesScopeDepth;
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
|
@ -100,7 +124,7 @@ function act(callback: () => Thenable) {
|
||||||
called = true;
|
called = true;
|
||||||
result.then(
|
result.then(
|
||||||
() => {
|
() => {
|
||||||
flushEffectsAndMicroTasks((err: ?Error) => {
|
flushWorkAndMicroTasks((err: ?Error) => {
|
||||||
onDone();
|
onDone();
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -128,7 +152,7 @@ function act(callback: () => Thenable) {
|
||||||
|
|
||||||
// flush effects until none remain, and cleanup
|
// flush effects until none remain, and cleanup
|
||||||
try {
|
try {
|
||||||
while (flushPassiveEffects()) {}
|
flushWork();
|
||||||
onDone();
|
onDone();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
onDone();
|
onDone();
|
||||||
|
|
|
@ -103,12 +103,15 @@ export function unstable_flushExpired() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unstable_flushWithoutYielding(): void {
|
export function unstable_flushWithoutYielding(): boolean {
|
||||||
if (isFlushing) {
|
if (isFlushing) {
|
||||||
throw new Error('Already flushing work.');
|
throw new Error('Already flushing work.');
|
||||||
}
|
}
|
||||||
isFlushing = true;
|
isFlushing = true;
|
||||||
try {
|
try {
|
||||||
|
if (scheduledCallback === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
while (scheduledCallback !== null) {
|
while (scheduledCallback !== null) {
|
||||||
const cb = scheduledCallback;
|
const cb = scheduledCallback;
|
||||||
scheduledCallback = null;
|
scheduledCallback = null;
|
||||||
|
@ -117,6 +120,7 @@ export function unstable_flushWithoutYielding(): void {
|
||||||
scheduledCallbackExpiration <= currentTime;
|
scheduledCallbackExpiration <= currentTime;
|
||||||
cb(didTimeout);
|
cb(didTimeout);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
} finally {
|
} finally {
|
||||||
expectedNumberOfYields = -1;
|
expectedNumberOfYields = -1;
|
||||||
didStop = false;
|
didStop = false;
|
||||||
|
|
|
@ -68,5 +68,8 @@ export const enableEventAPI = false;
|
||||||
// New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107
|
// New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107
|
||||||
export const enableJSXTransformAPI = false;
|
export const enableJSXTransformAPI = false;
|
||||||
|
|
||||||
|
// We will enforce mocking scheduler with scheduler/unstable_mock at some point. (v17?)
|
||||||
|
// Till then, we warn about the missing mock, but still fallback to a sync mode compatible version
|
||||||
|
export const warnAboutMissingMockScheduler = false;
|
||||||
// Temporary flag to revert the fix in #15650
|
// Temporary flag to revert the fix in #15650
|
||||||
export const revertPassiveEffectsChange = false;
|
export const revertPassiveEffectsChange = false;
|
||||||
|
|
|
@ -32,6 +32,7 @@ export const warnAboutDeprecatedLifecycles = true;
|
||||||
export const warnAboutDeprecatedSetNativeProps = true;
|
export const warnAboutDeprecatedSetNativeProps = true;
|
||||||
export const enableEventAPI = false;
|
export const enableEventAPI = false;
|
||||||
export const enableJSXTransformAPI = false;
|
export const enableJSXTransformAPI = false;
|
||||||
|
export const warnAboutMissingMockScheduler = true;
|
||||||
export const revertPassiveEffectsChange = false;
|
export const revertPassiveEffectsChange = false;
|
||||||
|
|
||||||
// Only used in www builds.
|
// Only used in www builds.
|
||||||
|
|
|
@ -29,6 +29,7 @@ export const enableSchedulerDebugging = false;
|
||||||
export const warnAboutDeprecatedSetNativeProps = false;
|
export const warnAboutDeprecatedSetNativeProps = false;
|
||||||
export const enableEventAPI = false;
|
export const enableEventAPI = false;
|
||||||
export const enableJSXTransformAPI = false;
|
export const enableJSXTransformAPI = false;
|
||||||
|
export const warnAboutMissingMockScheduler = false;
|
||||||
export const revertPassiveEffectsChange = false;
|
export const revertPassiveEffectsChange = false;
|
||||||
|
|
||||||
// Only used in www builds.
|
// Only used in www builds.
|
||||||
|
|
|
@ -29,6 +29,7 @@ export const enableSchedulerDebugging = false;
|
||||||
export const warnAboutDeprecatedSetNativeProps = false;
|
export const warnAboutDeprecatedSetNativeProps = false;
|
||||||
export const enableEventAPI = false;
|
export const enableEventAPI = false;
|
||||||
export const enableJSXTransformAPI = false;
|
export const enableJSXTransformAPI = false;
|
||||||
|
export const warnAboutMissingMockScheduler = true;
|
||||||
export const revertPassiveEffectsChange = false;
|
export const revertPassiveEffectsChange = false;
|
||||||
|
|
||||||
// Only used in www builds.
|
// Only used in www builds.
|
||||||
|
|
|
@ -29,6 +29,7 @@ export const enableSchedulerDebugging = false;
|
||||||
export const warnAboutDeprecatedSetNativeProps = false;
|
export const warnAboutDeprecatedSetNativeProps = false;
|
||||||
export const enableEventAPI = false;
|
export const enableEventAPI = false;
|
||||||
export const enableJSXTransformAPI = false;
|
export const enableJSXTransformAPI = false;
|
||||||
|
export const warnAboutMissingMockScheduler = false;
|
||||||
export const revertPassiveEffectsChange = false;
|
export const revertPassiveEffectsChange = false;
|
||||||
|
|
||||||
// Only used in www builds.
|
// Only used in www builds.
|
||||||
|
|
|
@ -30,6 +30,7 @@ export const disableJavaScriptURLs = false;
|
||||||
export const disableYielding = false;
|
export const disableYielding = false;
|
||||||
export const enableEventAPI = true;
|
export const enableEventAPI = true;
|
||||||
export const enableJSXTransformAPI = true;
|
export const enableJSXTransformAPI = true;
|
||||||
|
export const warnAboutMissingMockScheduler = true;
|
||||||
|
|
||||||
// Only used in www builds.
|
// Only used in www builds.
|
||||||
export function addUserTimingListener() {
|
export function addUserTimingListener() {
|
||||||
|
|
|
@ -72,6 +72,8 @@ export const enableEventAPI = true;
|
||||||
|
|
||||||
export const enableJSXTransformAPI = true;
|
export const enableJSXTransformAPI = true;
|
||||||
|
|
||||||
|
export const warnAboutMissingMockScheduler = true;
|
||||||
|
|
||||||
// Flow magic to verify the exports of this file match the original version.
|
// Flow magic to verify the exports of this file match the original version.
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
type Check<_X, Y: _X, X: Y = _X> = null;
|
type Check<_X, Y: _X, X: Y = _X> = null;
|
||||||
|
|
|
@ -28,6 +28,10 @@ const {
|
||||||
unstable_LowPriority,
|
unstable_LowPriority,
|
||||||
unstable_IdlePriority,
|
unstable_IdlePriority,
|
||||||
unstable_forceFrameRate,
|
unstable_forceFrameRate,
|
||||||
|
|
||||||
|
// this doesn't actually exist on the scheduler, but it *does*
|
||||||
|
// on scheduler/unstable_mock, which we'll need inside act().
|
||||||
|
unstable_flushWithoutYielding,
|
||||||
} = ReactInternals.Scheduler;
|
} = ReactInternals.Scheduler;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -47,4 +51,5 @@ export {
|
||||||
unstable_LowPriority,
|
unstable_LowPriority,
|
||||||
unstable_IdlePriority,
|
unstable_IdlePriority,
|
||||||
unstable_forceFrameRate,
|
unstable_forceFrameRate,
|
||||||
|
unstable_flushWithoutYielding,
|
||||||
};
|
};
|
||||||
|
|
|
@ -418,7 +418,14 @@ const bundles = [
|
||||||
|
|
||||||
/******* React Scheduler Mock (experimental) *******/
|
/******* React Scheduler Mock (experimental) *******/
|
||||||
{
|
{
|
||||||
bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD],
|
bundleTypes: [
|
||||||
|
UMD_DEV,
|
||||||
|
UMD_PROD,
|
||||||
|
NODE_DEV,
|
||||||
|
NODE_PROD,
|
||||||
|
FB_WWW_DEV,
|
||||||
|
FB_WWW_PROD,
|
||||||
|
],
|
||||||
moduleType: ISOMORPHIC,
|
moduleType: ISOMORPHIC,
|
||||||
entry: 'scheduler/unstable_mock',
|
entry: 'scheduler/unstable_mock',
|
||||||
global: 'SchedulerMock',
|
global: 'SchedulerMock',
|
||||||
|
|
Loading…
Reference in New Issue