/*** Copyright (c) Meta Platforms, Inc. and 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*/// sanity tests to make sure act() works without a mocked schedulerlet React;
let ReactDOM;
let act;
let container;
let yields;
function clearLog() {
try {
return yields;
} finally {
yields = [];
}}function render(el, dom) {
ReactDOM.render(el, dom);
}function unmount(dom) {
ReactDOM.unmountComponentAtNode(dom);
}beforeEach(() => {
jest.resetModules();
jest.unmock('scheduler');
yields = [];
React = require('react');
ReactDOM = require('react-dom');
act = require('react-dom/test-utils').act;
container = document.createElement('div');
document.body.appendChild(container);
});afterEach(() => {
unmount(container);
document.body.removeChild(container);
});// @gate __DEV__it('can use act to flush effects', () => {
function App() {
React.useEffect(() => {
yields.push(100);
});return null;
}act(() => {
render(<App />, container);
});expect(clearLog()).toEqual([100]);
});// @gate __DEV__it('flushes effects on every call', () => {
function App() {
const [ctr, setCtr] = React.useState(0);
React.useEffect(() => {
yields.push(ctr);
});return (
<button id="button" onClick={() => setCtr(x => x + 1)}>
{ctr}
</button>
);}act(() => {render(<App />, container);});expect(clearLog()).toEqual([0]);
const button = container.querySelector('#button');
function click() {button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
}act(() => {click();click();click();});// it consolidates the 3 updates, then fires the effect
expect(clearLog()).toEqual([3]);
act(click);
expect(clearLog()).toEqual([4]);
act(click);
expect(clearLog()).toEqual([5]);
expect(button.innerHTML).toEqual('5');
});// @gate __DEV__it("should keep flushing effects until they're done", () => {
function App() {
const [ctr, setCtr] = React.useState(0);
React.useEffect(() => {
if (ctr < 5) {
setCtr(x => x + 1);
}});return ctr;
}act(() => {
render(<App />, container);
});expect(container.innerHTML).toEqual('5');
});// @gate __DEV__it('should flush effects only on exiting the outermost act', () => {
function App() {
React.useEffect(() => {
yields.push(0);
});return null;
}// let's nest a couple of act() calls
act(() => {
act(() => {
render(<App />, container);
});// the effect wouldn't have yielded yet because
// we're still inside an act() scope
expect(clearLog()).toEqual([]);
});// but after exiting the last one, effects get flushed
expect(clearLog()).toEqual([0]);
});// @gate __DEV__it('can handle cascading promises', async () => {
// this component triggers an effect, that waits a tick,
// then sets state. repeats this 5 times.
function App() {
const [state, setState] = React.useState(0);
async function ticker() {
await null;
setState(x => x + 1);
}React.useEffect(() => {
yields.push(state);
ticker();
}, [Math.min(state, 4)]);
return state;
}await act(async () => {
render(<App />, container);
});// all 5 ticks present and accounted for
expect(clearLog()).toEqual([0, 1, 2, 3, 4]);
expect(container.innerHTML).toBe('5');
});