let React;
let ReactDOMClient;
let act;
let container;
let yields;
let prevActGlobal;
function clearLog() {
try {
return yields;
} finally {
yields = [];
}
}
beforeEach(() => {
prevActGlobal = global.IS_REACT_ACT_ENVIRONMENT;
global.IS_REACT_ACT_ENVIRONMENT = true;
jest.resetModules();
jest.unmock('scheduler');
yields = [];
React = require('react');
ReactDOMClient = require('react-dom/client');
act = React.act;
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
global.IS_REACT_ACT_ENVIRONMENT = prevActGlobal;
document.body.removeChild(container);
});
test('can use act to flush effects', async () => {
function App() {
React.useEffect(() => {
yields.push(100);
});
return null;
}
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<App />);
});
expect(clearLog()).toEqual([100]);
});
test('flushes effects on every call', async () => {
function App() {
const [ctr, setCtr] = React.useState(0);
React.useEffect(() => {
yields.push(ctr);
});
return (
<button id="button" onClick={() => setCtr(x => x + 1)}>
{ctr}
</button>
);
}
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<App />);
});
expect(clearLog()).toEqual([0]);
const button = container.querySelector('#button');
function click() {
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
}
act(() => {
click();
click();
click();
});
expect(clearLog()).toEqual([3]);
act(click);
expect(clearLog()).toEqual([4]);
act(click);
expect(clearLog()).toEqual([5]);
expect(button.innerHTML).toEqual('5');
});
test("should keep flushing effects until they're done", async () => {
function App() {
const [ctr, setCtr] = React.useState(0);
React.useEffect(() => {
if (ctr < 5) {
setCtr(x => x + 1);
}
});
return ctr;
}
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<App />);
});
expect(container.innerHTML).toEqual('5');
});
test('should flush effects only on exiting the outermost act', async () => {
function App() {
React.useEffect(() => {
yields.push(0);
});
return null;
}
const root = ReactDOMClient.createRoot(container);
await act(async () => {
await act(() => {
root.render(<App />);
});
expect(clearLog()).toEqual([]);
});
expect(clearLog()).toEqual([0]);
});
test('can handle cascading promises', async () => {
function App() {
const [state, setState] = React.useState(0);
async function ticker() {
await null;
await act(() => {
setState(x => x + 1);
});
}
React.useEffect(() => {
yields.push(state);
ticker();
}, [Math.min(state, 4)]);
return state;
}
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<App />);
});
expect(clearLog()).toEqual([0, 1, 2, 3, 4]);
expect(container.innerHTML).toBe('5');
});