Stop using Scheduler.log to test double invocations (#29008)

This commit is contained in:
Sebastian Silbermann
2024-05-21 00:30:45 +02:00
committed by GitHub
parent 7fff0270fb
commit 4d232a05fd
3 changed files with 322 additions and 213 deletions
@@ -161,7 +161,11 @@ describe('ReactInternalTestUtils', () => {
const root = ReactNoop.createRoot();
await act(() => {
root.render(<App />);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
});
assertLog(['A', 'B', 'C']);
});
@@ -11,33 +11,29 @@
let React;
let ReactNoop;
let Scheduler;
let act;
let assertLog;
describe('StrictEffectsMode', () => {
beforeEach(() => {
jest.resetModules();
act = require('internal-test-utils').act;
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
React = require('react');
Scheduler = require('scheduler');
ReactNoop = require('react-noop-renderer');
});
// @gate !disableLegacyMode
it('should not double invoke effects in legacy mode', async () => {
const log = [];
function App({text}) {
React.useEffect(() => {
Scheduler.log('useEffect mount');
return () => Scheduler.log('useEffect unmount');
log.push('useEffect mount');
return () => log.push('useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect mount');
return () => Scheduler.log('useLayoutEffect unmount');
log.push('useLayoutEffect mount');
return () => log.push('useLayoutEffect unmount');
});
return text;
@@ -52,19 +48,20 @@ describe('StrictEffectsMode', () => {
);
});
assertLog(['useLayoutEffect mount', 'useEffect mount']);
expect(log).toEqual(['useLayoutEffect mount', 'useEffect mount']);
});
it('double invoking for effects works properly', async () => {
const log = [];
function App({text}) {
React.useEffect(() => {
Scheduler.log('useEffect mount');
return () => Scheduler.log('useEffect unmount');
log.push('useEffect mount');
return () => log.push('useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect mount');
return () => Scheduler.log('useLayoutEffect unmount');
log.push('useLayoutEffect mount');
return () => log.push('useLayoutEffect unmount');
});
return text;
@@ -80,7 +77,7 @@ describe('StrictEffectsMode', () => {
});
if (__DEV__) {
assertLog([
expect(log).toEqual([
'useLayoutEffect mount',
'useEffect mount',
'useLayoutEffect unmount',
@@ -89,9 +86,10 @@ describe('StrictEffectsMode', () => {
'useEffect mount',
]);
} else {
assertLog(['useLayoutEffect mount', 'useEffect mount']);
expect(log).toEqual(['useLayoutEffect mount', 'useEffect mount']);
}
log.length = 0;
await act(() => {
ReactNoop.renderToRootWithID(
<React.StrictMode>
@@ -101,30 +99,32 @@ describe('StrictEffectsMode', () => {
);
});
assertLog([
expect(log).toEqual([
'useLayoutEffect unmount',
'useLayoutEffect mount',
'useEffect unmount',
'useEffect mount',
]);
log.length = 0;
await act(() => {
ReactNoop.unmountRootWithID('root');
});
assertLog(['useLayoutEffect unmount', 'useEffect unmount']);
expect(log).toEqual(['useLayoutEffect unmount', 'useEffect unmount']);
});
it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', async () => {
const log = [];
function App({text}) {
React.useEffect(() => {
Scheduler.log('useEffect One mount');
return () => Scheduler.log('useEffect One unmount');
log.push('useEffect One mount');
return () => log.push('useEffect One unmount');
});
React.useEffect(() => {
Scheduler.log('useEffect Two mount');
return () => Scheduler.log('useEffect Two unmount');
log.push('useEffect Two mount');
return () => log.push('useEffect Two unmount');
});
return text;
@@ -140,7 +140,7 @@ describe('StrictEffectsMode', () => {
});
if (__DEV__) {
assertLog([
expect(log).toEqual([
'useEffect One mount',
'useEffect Two mount',
'useEffect One unmount',
@@ -149,9 +149,10 @@ describe('StrictEffectsMode', () => {
'useEffect Two mount',
]);
} else {
assertLog(['useEffect One mount', 'useEffect Two mount']);
expect(log).toEqual(['useEffect One mount', 'useEffect Two mount']);
}
log.length = 0;
await act(() => {
ReactNoop.renderToRootWithID(
<React.StrictMode>
@@ -161,30 +162,32 @@ describe('StrictEffectsMode', () => {
);
});
assertLog([
expect(log).toEqual([
'useEffect One unmount',
'useEffect Two unmount',
'useEffect One mount',
'useEffect Two mount',
]);
log.length = 0;
await act(() => {
ReactNoop.unmountRootWithID('root');
});
assertLog(['useEffect One unmount', 'useEffect Two unmount']);
expect(log).toEqual(['useEffect One unmount', 'useEffect Two unmount']);
});
it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', async () => {
const log = [];
function App({text}) {
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect One mount');
return () => Scheduler.log('useLayoutEffect One unmount');
log.push('useLayoutEffect One mount');
return () => log.push('useLayoutEffect One unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect Two mount');
return () => Scheduler.log('useLayoutEffect Two unmount');
log.push('useLayoutEffect Two mount');
return () => log.push('useLayoutEffect Two unmount');
});
return text;
@@ -200,7 +203,7 @@ describe('StrictEffectsMode', () => {
});
if (__DEV__) {
assertLog([
expect(log).toEqual([
'useLayoutEffect One mount',
'useLayoutEffect Two mount',
'useLayoutEffect One unmount',
@@ -209,9 +212,13 @@ describe('StrictEffectsMode', () => {
'useLayoutEffect Two mount',
]);
} else {
assertLog(['useLayoutEffect One mount', 'useLayoutEffect Two mount']);
expect(log).toEqual([
'useLayoutEffect One mount',
'useLayoutEffect Two mount',
]);
}
log.length = 0;
await act(() => {
ReactNoop.renderToRootWithID(
<React.StrictMode>
@@ -221,28 +228,33 @@ describe('StrictEffectsMode', () => {
);
});
assertLog([
expect(log).toEqual([
'useLayoutEffect One unmount',
'useLayoutEffect Two unmount',
'useLayoutEffect One mount',
'useLayoutEffect Two mount',
]);
log.length = 0;
await act(() => {
ReactNoop.unmountRootWithID('root');
});
assertLog(['useLayoutEffect One unmount', 'useLayoutEffect Two unmount']);
expect(log).toEqual([
'useLayoutEffect One unmount',
'useLayoutEffect Two unmount',
]);
});
it('useEffect and useLayoutEffect is called twice when there is no unmount', async () => {
const log = [];
function App({text}) {
React.useEffect(() => {
Scheduler.log('useEffect mount');
log.push('useEffect mount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect mount');
log.push('useLayoutEffect mount');
});
return text;
@@ -257,16 +269,17 @@ describe('StrictEffectsMode', () => {
});
if (__DEV__) {
assertLog([
expect(log).toEqual([
'useLayoutEffect mount',
'useEffect mount',
'useLayoutEffect mount',
'useEffect mount',
]);
} else {
assertLog(['useLayoutEffect mount', 'useEffect mount']);
expect(log).toEqual(['useLayoutEffect mount', 'useEffect mount']);
}
log.length = 0;
await act(() => {
ReactNoop.renderToRootWithID(
<React.StrictMode>
@@ -275,32 +288,34 @@ describe('StrictEffectsMode', () => {
);
});
assertLog(['useLayoutEffect mount', 'useEffect mount']);
expect(log).toEqual(['useLayoutEffect mount', 'useEffect mount']);
log.length = 0;
await act(() => {
ReactNoop.unmountRootWithID('root');
});
assertLog([]);
expect(log).toEqual([]);
});
it('passes the right context to class component lifecycles', async () => {
const log = [];
class App extends React.PureComponent {
test() {}
componentDidMount() {
this.test();
Scheduler.log('componentDidMount');
log.push('componentDidMount');
}
componentDidUpdate() {
this.test();
Scheduler.log('componentDidUpdate');
log.push('componentDidUpdate');
}
componentWillUnmount() {
this.test();
Scheduler.log('componentWillUnmount');
log.push('componentWillUnmount');
}
render() {
@@ -317,28 +332,29 @@ describe('StrictEffectsMode', () => {
});
if (__DEV__) {
assertLog([
expect(log).toEqual([
'componentDidMount',
'componentWillUnmount',
'componentDidMount',
]);
} else {
assertLog(['componentDidMount']);
expect(log).toEqual(['componentDidMount']);
}
});
it('double invoking works for class components', async () => {
const log = [];
class App extends React.PureComponent {
componentDidMount() {
Scheduler.log('componentDidMount');
log.push('componentDidMount');
}
componentDidUpdate() {
Scheduler.log('componentDidUpdate');
log.push('componentDidUpdate');
}
componentWillUnmount() {
Scheduler.log('componentWillUnmount');
log.push('componentWillUnmount');
}
render() {
@@ -356,15 +372,16 @@ describe('StrictEffectsMode', () => {
});
if (__DEV__) {
assertLog([
expect(log).toEqual([
'componentDidMount',
'componentWillUnmount',
'componentDidMount',
]);
} else {
assertLog(['componentDidMount']);
expect(log).toEqual(['componentDidMount']);
}
log.length = 0;
await act(() => {
ReactNoop.renderToRootWithID(
<React.StrictMode>
@@ -374,23 +391,25 @@ describe('StrictEffectsMode', () => {
);
});
assertLog(['componentDidUpdate']);
expect(log).toEqual(['componentDidUpdate']);
log.length = 0;
await act(() => {
ReactNoop.unmountRootWithID('root');
});
assertLog(['componentWillUnmount']);
expect(log).toEqual(['componentWillUnmount']);
});
it('invokes componentWillUnmount for class components without componentDidMount', async () => {
const log = [];
class App extends React.PureComponent {
componentDidUpdate() {
Scheduler.log('componentDidUpdate');
log.push('componentDidUpdate');
}
componentWillUnmount() {
Scheduler.log('componentWillUnmount');
log.push('componentWillUnmount');
}
render() {
@@ -408,11 +427,12 @@ describe('StrictEffectsMode', () => {
});
if (__DEV__) {
assertLog(['componentWillUnmount']);
expect(log).toEqual(['componentWillUnmount']);
} else {
assertLog([]);
expect(log).toEqual([]);
}
log.length = 0;
await act(() => {
ReactNoop.renderToRootWithID(
<React.StrictMode>
@@ -422,28 +442,30 @@ describe('StrictEffectsMode', () => {
);
});
assertLog(['componentDidUpdate']);
expect(log).toEqual(['componentDidUpdate']);
log.length = 0;
await act(() => {
ReactNoop.unmountRootWithID('root');
});
assertLog(['componentWillUnmount']);
expect(log).toEqual(['componentWillUnmount']);
});
// @gate !disableLegacyMode
it('should not double invoke class lifecycles in legacy mode', async () => {
const log = [];
class App extends React.PureComponent {
componentDidMount() {
Scheduler.log('componentDidMount');
log.push('componentDidMount');
}
componentDidUpdate() {
Scheduler.log('componentDidUpdate');
log.push('componentDidUpdate');
}
componentWillUnmount() {
Scheduler.log('componentWillUnmount');
log.push('componentWillUnmount');
}
render() {
@@ -460,26 +482,27 @@ describe('StrictEffectsMode', () => {
);
});
assertLog(['componentDidMount']);
expect(log).toEqual(['componentDidMount']);
});
it('double flushing passive effects only results in one double invoke', async () => {
const log = [];
function App({text}) {
const [state, setState] = React.useState(0);
React.useEffect(() => {
if (state !== 1) {
setState(1);
}
Scheduler.log('useEffect mount');
return () => Scheduler.log('useEffect unmount');
log.push('useEffect mount');
return () => log.push('useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect mount');
return () => Scheduler.log('useLayoutEffect unmount');
log.push('useLayoutEffect mount');
return () => log.push('useLayoutEffect unmount');
});
Scheduler.log(text);
log.push(text);
return text;
}
@@ -492,7 +515,8 @@ describe('StrictEffectsMode', () => {
});
if (__DEV__) {
assertLog([
expect(log).toEqual([
'mount',
'mount',
'useLayoutEffect mount',
'useEffect mount',
@@ -501,13 +525,14 @@ describe('StrictEffectsMode', () => {
'useLayoutEffect mount',
'useEffect mount',
'mount',
'mount',
'useLayoutEffect unmount',
'useLayoutEffect mount',
'useEffect unmount',
'useEffect mount',
]);
} else {
assertLog([
expect(log).toEqual([
'mount',
'useLayoutEffect mount',
'useEffect mount',
@@ -521,15 +546,16 @@ describe('StrictEffectsMode', () => {
});
it('newly mounted components after initial mount get double invoked', async () => {
const log = [];
let _setShowChild;
function Child() {
React.useEffect(() => {
Scheduler.log('Child useEffect mount');
return () => Scheduler.log('Child useEffect unmount');
log.push('Child useEffect mount');
return () => log.push('Child useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('Child useLayoutEffect mount');
return () => Scheduler.log('Child useLayoutEffect unmount');
log.push('Child useLayoutEffect mount');
return () => log.push('Child useLayoutEffect unmount');
});
return null;
@@ -539,12 +565,12 @@ describe('StrictEffectsMode', () => {
const [showChild, setShowChild] = React.useState(false);
_setShowChild = setShowChild;
React.useEffect(() => {
Scheduler.log('App useEffect mount');
return () => Scheduler.log('App useEffect unmount');
log.push('App useEffect mount');
return () => log.push('App useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('App useLayoutEffect mount');
return () => Scheduler.log('App useLayoutEffect unmount');
log.push('App useLayoutEffect mount');
return () => log.push('App useLayoutEffect unmount');
});
return showChild && <Child />;
@@ -560,7 +586,7 @@ describe('StrictEffectsMode', () => {
});
if (__DEV__) {
assertLog([
expect(log).toEqual([
'App useLayoutEffect mount',
'App useEffect mount',
'App useLayoutEffect unmount',
@@ -569,15 +595,16 @@ describe('StrictEffectsMode', () => {
'App useEffect mount',
]);
} else {
assertLog(['App useLayoutEffect mount', 'App useEffect mount']);
expect(log).toEqual(['App useLayoutEffect mount', 'App useEffect mount']);
}
log.length = 0;
await act(() => {
_setShowChild(true);
});
if (__DEV__) {
assertLog([
expect(log).toEqual([
'App useLayoutEffect unmount',
'Child useLayoutEffect mount',
'App useLayoutEffect mount',
@@ -590,7 +617,7 @@ describe('StrictEffectsMode', () => {
'Child useEffect mount',
]);
} else {
assertLog([
expect(log).toEqual([
'App useLayoutEffect unmount',
'Child useLayoutEffect mount',
'App useLayoutEffect mount',
@@ -602,13 +629,14 @@ describe('StrictEffectsMode', () => {
});
it('classes and functions are double invoked together correctly', async () => {
const log = [];
class ClassChild extends React.PureComponent {
componentDidMount() {
Scheduler.log('componentDidMount');
log.push('componentDidMount');
}
componentWillUnmount() {
Scheduler.log('componentWillUnmount');
log.push('componentWillUnmount');
}
render() {
@@ -618,12 +646,12 @@ describe('StrictEffectsMode', () => {
function FunctionChild({text}) {
React.useEffect(() => {
Scheduler.log('useEffect mount');
return () => Scheduler.log('useEffect unmount');
log.push('useEffect mount');
return () => log.push('useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect mount');
return () => Scheduler.log('useLayoutEffect unmount');
log.push('useLayoutEffect mount');
return () => log.push('useLayoutEffect unmount');
});
return text;
}
@@ -647,7 +675,7 @@ describe('StrictEffectsMode', () => {
});
if (__DEV__) {
assertLog([
expect(log).toEqual([
'componentDidMount',
'useLayoutEffect mount',
'useEffect mount',
@@ -659,13 +687,14 @@ describe('StrictEffectsMode', () => {
'useEffect mount',
]);
} else {
assertLog([
expect(log).toEqual([
'componentDidMount',
'useLayoutEffect mount',
'useEffect mount',
]);
}
log.length = 0;
await act(() => {
ReactNoop.renderToRootWithID(
<React.StrictMode>
@@ -675,18 +704,19 @@ describe('StrictEffectsMode', () => {
);
});
assertLog([
expect(log).toEqual([
'useLayoutEffect unmount',
'useLayoutEffect mount',
'useEffect unmount',
'useEffect mount',
]);
log.length = 0;
await act(() => {
ReactNoop.unmountRootWithID('root');
});
assertLog([
expect(log).toEqual([
'componentWillUnmount',
'useLayoutEffect unmount',
'useEffect unmount',
@@ -694,9 +724,10 @@ describe('StrictEffectsMode', () => {
});
it('classes without componentDidMount and functions are double invoked together correctly', async () => {
const log = [];
class ClassChild extends React.PureComponent {
componentWillUnmount() {
Scheduler.log('componentWillUnmount');
log.push('componentWillUnmount');
}
render() {
@@ -706,12 +737,12 @@ describe('StrictEffectsMode', () => {
function FunctionChild({text}) {
React.useEffect(() => {
Scheduler.log('useEffect mount');
return () => Scheduler.log('useEffect unmount');
log.push('useEffect mount');
return () => log.push('useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect mount');
return () => Scheduler.log('useLayoutEffect unmount');
log.push('useLayoutEffect mount');
return () => log.push('useLayoutEffect unmount');
});
return text;
}
@@ -735,7 +766,7 @@ describe('StrictEffectsMode', () => {
});
if (__DEV__) {
assertLog([
expect(log).toEqual([
'useLayoutEffect mount',
'useEffect mount',
'componentWillUnmount',
@@ -745,9 +776,10 @@ describe('StrictEffectsMode', () => {
'useEffect mount',
]);
} else {
assertLog(['useLayoutEffect mount', 'useEffect mount']);
expect(log).toEqual(['useLayoutEffect mount', 'useEffect mount']);
}
log.length = 0;
await act(() => {
ReactNoop.renderToRootWithID(
<React.StrictMode>
@@ -757,18 +789,19 @@ describe('StrictEffectsMode', () => {
);
});
assertLog([
expect(log).toEqual([
'useLayoutEffect unmount',
'useLayoutEffect mount',
'useEffect unmount',
'useEffect mount',
]);
log.length = 0;
await act(() => {
ReactNoop.unmountRootWithID('root');
});
assertLog([
expect(log).toEqual([
'componentWillUnmount',
'useLayoutEffect unmount',
'useEffect unmount',
@@ -777,7 +810,7 @@ describe('StrictEffectsMode', () => {
// @gate __DEV__
it('should double invoke effects after a re-suspend', async () => {
// Not using Scheduler.log because it silences double render logs.
// Not using log.push because it silences double render logs.
let log = [];
let shouldSuspend = true;
let resolve;
@@ -867,8 +900,17 @@ describe('StrictEffectsMode', () => {
);
});
expect(log).toEqual([
'Parent rendered',
'Parent rendered',
'Child rendered',
'Child suspended',
'Fallback',
'Fallback',
]);
log = [];
// while suspended, update
log.push('-----------------------after update');
await act(() => {
ReactNoop.render(
<React.StrictMode>
@@ -877,9 +919,19 @@ describe('StrictEffectsMode', () => {
);
});
// Now resolve and commit
log.push('-----------------------after suspense');
expect(log).toEqual([
'Parent rendered',
'Parent rendered',
'Child rendered',
'Child suspended',
'Fallback',
'Fallback',
'Parent dep destroy',
'Parent dep create',
]);
log = [];
// Now resolve and commit
await act(() => {
resolve();
shouldSuspend = false;
@@ -887,22 +939,6 @@ describe('StrictEffectsMode', () => {
if (gate(flags => flags.useModernStrictMode)) {
expect(log).toEqual([
'Parent rendered',
'Parent rendered',
'Child rendered',
'Child suspended',
'Fallback',
'Fallback',
'-----------------------after update',
'Parent rendered',
'Parent rendered',
'Child rendered',
'Child suspended',
'Fallback',
'Fallback',
'Parent dep destroy',
'Parent dep create',
'-----------------------after suspense',
'Child rendered',
'Child rendered',
// !!! Committed, destroy and create effect.
@@ -924,22 +960,6 @@ describe('StrictEffectsMode', () => {
]);
} else {
expect(log).toEqual([
'Parent rendered',
'Parent rendered',
'Child rendered',
'Child suspended',
'Fallback',
'Fallback',
'-----------------------after update',
'Parent rendered',
'Parent rendered',
'Child rendered',
'Child suspended',
'Fallback',
'Fallback',
'Parent dep destroy',
'Parent dep create',
'-----------------------after suspense',
'Child rendered',
'Child rendered',
'Child dep destroy',
@@ -13,7 +13,6 @@ let React;
let ReactNoop;
let Scheduler;
let act;
let assertLog;
let waitFor;
let waitForAll;
let waitForPaint;
@@ -31,20 +30,20 @@ describe('StrictEffectsMode defaults', () => {
waitFor = InternalTestUtils.waitFor;
waitForAll = InternalTestUtils.waitForAll;
waitForPaint = InternalTestUtils.waitForPaint;
assertLog = InternalTestUtils.assertLog;
});
// @gate !disableLegacyMode
it('should not double invoke effects in legacy mode', async () => {
const log = [];
function App({text}) {
React.useEffect(() => {
Scheduler.log('useEffect mount');
return () => Scheduler.log('useEffect unmount');
log.push('useEffect mount');
return () => log.push('useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect mount');
return () => Scheduler.log('useLayoutEffect unmount');
log.push('useLayoutEffect mount');
return () => log.push('useLayoutEffect unmount');
});
return text;
@@ -58,22 +57,23 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog(['useLayoutEffect mount', 'useEffect mount']);
expect(log).toEqual(['useLayoutEffect mount', 'useEffect mount']);
});
// @gate !disableLegacyMode
it('should not double invoke class lifecycles in legacy mode', async () => {
const log = [];
class App extends React.PureComponent {
componentDidMount() {
Scheduler.log('componentDidMount');
log.push('componentDidMount');
}
componentDidUpdate() {
Scheduler.log('componentDidUpdate');
log.push('componentDidUpdate');
}
componentWillUnmount() {
Scheduler.log('componentWillUnmount');
log.push('componentWillUnmount');
}
render() {
@@ -89,15 +89,20 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog(['componentDidMount']);
expect(log).toEqual(['componentDidMount']);
});
if (__DEV__) {
it('should flush double-invoked effects within the same frame as layout effects if there are no passive effects', async () => {
const log = [];
function ComponentWithEffects({label}) {
React.useLayoutEffect(() => {
Scheduler.log(`useLayoutEffect mount "${label}"`);
return () => Scheduler.log(`useLayoutEffect unmount "${label}"`);
log.push(`useLayoutEffect mount "${label}"`);
return () => {
Scheduler.log(`useLayoutEffect unmount "${label}"`);
log.push(`useLayoutEffect unmount "${label}"`);
};
});
return label;
@@ -115,8 +120,14 @@ describe('StrictEffectsMode defaults', () => {
'useLayoutEffect unmount "one"',
'useLayoutEffect mount "one"',
]);
expect(log).toEqual([
'useLayoutEffect mount "one"',
'useLayoutEffect unmount "one"',
'useLayoutEffect mount "one"',
]);
});
log.length = 0;
await act(async () => {
ReactNoop.render(
<React.StrictMode>
@@ -125,13 +136,23 @@ describe('StrictEffectsMode defaults', () => {
</React.StrictMode>,
);
assertLog([]);
expect(log).toEqual([]);
await waitForPaint([
// Cleanup and re-run "one" (and "two") since there is no dependencies array.
'useLayoutEffect unmount "one"',
'useLayoutEffect mount "one"',
'useLayoutEffect mount "two"',
// Since "two" is new, it should be double-invoked.
'useLayoutEffect unmount "two"',
'useLayoutEffect mount "two"',
]);
expect(log).toEqual([
// Cleanup and re-run "one" (and "two") since there is no dependencies array.
'useLayoutEffect unmount "one"',
'useLayoutEffect mount "one"',
'useLayoutEffect mount "two"',
// Since "two" is new, it should be double-invoked.
'useLayoutEffect unmount "two"',
'useLayoutEffect mount "two"',
@@ -142,15 +163,24 @@ describe('StrictEffectsMode defaults', () => {
// This test also verifies that double-invoked effects flush synchronously
// within the same frame as passive effects.
it('should double invoke effects only for newly mounted components', async () => {
const log = [];
function ComponentWithEffects({label}) {
React.useEffect(() => {
log.push(`useEffect mount "${label}"`);
Scheduler.log(`useEffect mount "${label}"`);
return () => Scheduler.log(`useEffect unmount "${label}"`);
return () => {
log.push(`useEffect unmount "${label}"`);
Scheduler.log(`useEffect unmount "${label}"`);
};
});
React.useLayoutEffect(() => {
log.push(`useLayoutEffect mount "${label}"`);
Scheduler.log(`useLayoutEffect mount "${label}"`);
return () => Scheduler.log(`useLayoutEffect unmount "${label}"`);
return () => {
log.push(`useLayoutEffect unmount "${label}"`);
Scheduler.log(`useLayoutEffect unmount "${label}"`);
};
});
return label;
@@ -171,8 +201,17 @@ describe('StrictEffectsMode defaults', () => {
'useLayoutEffect mount "one"',
'useEffect mount "one"',
]);
expect(log).toEqual([
'useLayoutEffect mount "one"',
'useEffect mount "one"',
'useLayoutEffect unmount "one"',
'useEffect unmount "one"',
'useLayoutEffect mount "one"',
'useEffect mount "one"',
]);
});
log.length = 0;
await act(async () => {
ReactNoop.render(
<React.StrictMode>
@@ -187,11 +226,29 @@ describe('StrictEffectsMode defaults', () => {
'useLayoutEffect mount "one"',
'useLayoutEffect mount "two"',
]);
expect(log).toEqual([
// Cleanup and re-run "one" (and "two") since there is no dependencies array.
'useLayoutEffect unmount "one"',
'useLayoutEffect mount "one"',
'useLayoutEffect mount "two"',
]);
log.length = 0;
await waitForAll([
'useEffect unmount "one"',
'useEffect mount "one"',
'useEffect mount "two"',
// Since "two" is new, it should be double-invoked.
'useLayoutEffect unmount "two"',
'useEffect unmount "two"',
'useLayoutEffect mount "two"',
'useEffect mount "two"',
]);
expect(log).toEqual([
'useEffect unmount "one"',
'useEffect mount "one"',
'useEffect mount "two"',
// Since "two" is new, it should be double-invoked.
'useLayoutEffect unmount "two"',
'useEffect unmount "two"',
@@ -202,15 +259,16 @@ describe('StrictEffectsMode defaults', () => {
});
it('double invoking for effects for modern roots', async () => {
const log = [];
function App({text}) {
React.useEffect(() => {
Scheduler.log('useEffect mount');
return () => Scheduler.log('useEffect unmount');
log.push('useEffect mount');
return () => log.push('useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect mount');
return () => Scheduler.log('useLayoutEffect unmount');
log.push('useLayoutEffect mount');
return () => log.push('useLayoutEffect unmount');
});
return text;
@@ -223,7 +281,7 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'useLayoutEffect mount',
'useEffect mount',
'useLayoutEffect unmount',
@@ -232,6 +290,7 @@ describe('StrictEffectsMode defaults', () => {
'useEffect mount',
]);
log.length = 0;
await act(() => {
ReactNoop.render(
<React.StrictMode>
@@ -240,30 +299,32 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'useLayoutEffect unmount',
'useLayoutEffect mount',
'useEffect unmount',
'useEffect mount',
]);
log.length = 0;
await act(() => {
ReactNoop.render(null);
});
assertLog(['useLayoutEffect unmount', 'useEffect unmount']);
expect(log).toEqual(['useLayoutEffect unmount', 'useEffect unmount']);
});
it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', async () => {
const log = [];
function App({text}) {
React.useEffect(() => {
Scheduler.log('useEffect One mount');
return () => Scheduler.log('useEffect One unmount');
log.push('useEffect One mount');
return () => log.push('useEffect One unmount');
});
React.useEffect(() => {
Scheduler.log('useEffect Two mount');
return () => Scheduler.log('useEffect Two unmount');
log.push('useEffect Two mount');
return () => log.push('useEffect Two unmount');
});
return text;
@@ -277,7 +338,7 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'useEffect One mount',
'useEffect Two mount',
'useEffect One unmount',
@@ -286,6 +347,7 @@ describe('StrictEffectsMode defaults', () => {
'useEffect Two mount',
]);
log.length = 0;
await act(() => {
ReactNoop.render(
<React.StrictMode>
@@ -294,30 +356,32 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'useEffect One unmount',
'useEffect Two unmount',
'useEffect One mount',
'useEffect Two mount',
]);
log.length = 0;
await act(() => {
ReactNoop.render(null);
});
assertLog(['useEffect One unmount', 'useEffect Two unmount']);
expect(log).toEqual(['useEffect One unmount', 'useEffect Two unmount']);
});
it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', async () => {
const log = [];
function App({text}) {
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect One mount');
return () => Scheduler.log('useLayoutEffect One unmount');
log.push('useLayoutEffect One mount');
return () => log.push('useLayoutEffect One unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect Two mount');
return () => Scheduler.log('useLayoutEffect Two unmount');
log.push('useLayoutEffect Two mount');
return () => log.push('useLayoutEffect Two unmount');
});
return text;
@@ -331,7 +395,7 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'useLayoutEffect One mount',
'useLayoutEffect Two mount',
'useLayoutEffect One unmount',
@@ -340,6 +404,7 @@ describe('StrictEffectsMode defaults', () => {
'useLayoutEffect Two mount',
]);
log.length = 0;
await act(() => {
ReactNoop.render(
<React.StrictMode>
@@ -348,28 +413,33 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'useLayoutEffect One unmount',
'useLayoutEffect Two unmount',
'useLayoutEffect One mount',
'useLayoutEffect Two mount',
]);
log.length = 0;
await act(() => {
ReactNoop.render(null);
});
assertLog(['useLayoutEffect One unmount', 'useLayoutEffect Two unmount']);
expect(log).toEqual([
'useLayoutEffect One unmount',
'useLayoutEffect Two unmount',
]);
});
it('useEffect and useLayoutEffect is called twice when there is no unmount', async () => {
const log = [];
function App({text}) {
React.useEffect(() => {
Scheduler.log('useEffect mount');
log.push('useEffect mount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect mount');
log.push('useLayoutEffect mount');
});
return text;
@@ -383,13 +453,14 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'useLayoutEffect mount',
'useEffect mount',
'useLayoutEffect mount',
'useEffect mount',
]);
log.length = 0;
await act(() => {
ReactNoop.render(
<React.StrictMode>
@@ -398,13 +469,14 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog(['useLayoutEffect mount', 'useEffect mount']);
expect(log).toEqual(['useLayoutEffect mount', 'useEffect mount']);
log.length = 0;
await act(() => {
ReactNoop.render(null);
});
assertLog([]);
expect(log).toEqual([]);
});
//@gate useModernStrictMode
@@ -436,22 +508,23 @@ describe('StrictEffectsMode defaults', () => {
});
it('passes the right context to class component lifecycles', async () => {
const log = [];
class App extends React.PureComponent {
test() {}
componentDidMount() {
this.test();
Scheduler.log('componentDidMount');
log.push('componentDidMount');
}
componentDidUpdate() {
this.test();
Scheduler.log('componentDidUpdate');
log.push('componentDidUpdate');
}
componentWillUnmount() {
this.test();
Scheduler.log('componentWillUnmount');
log.push('componentWillUnmount');
}
render() {
@@ -467,7 +540,7 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'componentDidMount',
'componentWillUnmount',
'componentDidMount',
@@ -475,17 +548,18 @@ describe('StrictEffectsMode defaults', () => {
});
it('double invoking works for class components', async () => {
const log = [];
class App extends React.PureComponent {
componentDidMount() {
Scheduler.log('componentDidMount');
log.push('componentDidMount');
}
componentDidUpdate() {
Scheduler.log('componentDidUpdate');
log.push('componentDidUpdate');
}
componentWillUnmount() {
Scheduler.log('componentWillUnmount');
log.push('componentWillUnmount');
}
render() {
@@ -501,12 +575,13 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'componentDidMount',
'componentWillUnmount',
'componentDidMount',
]);
log.length = 0;
await act(() => {
ReactNoop.render(
<React.StrictMode>
@@ -515,35 +590,38 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog(['componentDidUpdate']);
expect(log).toEqual(['componentDidUpdate']);
log.length = 0;
await act(() => {
ReactNoop.render(null);
});
assertLog(['componentWillUnmount']);
expect(log).toEqual(['componentWillUnmount']);
});
it('double flushing passive effects only results in one double invoke', async () => {
const log = [];
function App({text}) {
const [state, setState] = React.useState(0);
React.useEffect(() => {
if (state !== 1) {
setState(1);
}
Scheduler.log('useEffect mount');
return () => Scheduler.log('useEffect unmount');
log.push('useEffect mount');
return () => log.push('useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect mount');
return () => Scheduler.log('useLayoutEffect unmount');
log.push('useLayoutEffect mount');
return () => log.push('useLayoutEffect unmount');
});
Scheduler.log(text);
log.push(text);
return text;
}
log.length = 0;
await act(() => {
ReactNoop.render(
<React.StrictMode>
@@ -552,7 +630,8 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'mount',
'mount',
'useLayoutEffect mount',
'useEffect mount',
@@ -561,6 +640,7 @@ describe('StrictEffectsMode defaults', () => {
'useLayoutEffect mount',
'useEffect mount',
'mount',
'mount',
'useLayoutEffect unmount',
'useLayoutEffect mount',
'useEffect unmount',
@@ -570,14 +650,15 @@ describe('StrictEffectsMode defaults', () => {
it('newly mounted components after initial mount get double invoked', async () => {
let _setShowChild;
const log = [];
function Child() {
React.useEffect(() => {
Scheduler.log('Child useEffect mount');
return () => Scheduler.log('Child useEffect unmount');
log.push('Child useEffect mount');
return () => log.push('Child useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('Child useLayoutEffect mount');
return () => Scheduler.log('Child useLayoutEffect unmount');
log.push('Child useLayoutEffect mount');
return () => log.push('Child useLayoutEffect unmount');
});
return null;
@@ -587,12 +668,12 @@ describe('StrictEffectsMode defaults', () => {
const [showChild, setShowChild] = React.useState(false);
_setShowChild = setShowChild;
React.useEffect(() => {
Scheduler.log('App useEffect mount');
return () => Scheduler.log('App useEffect unmount');
log.push('App useEffect mount');
return () => log.push('App useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('App useLayoutEffect mount');
return () => Scheduler.log('App useLayoutEffect unmount');
log.push('App useLayoutEffect mount');
return () => log.push('App useLayoutEffect unmount');
});
return showChild && <Child />;
@@ -606,7 +687,7 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'App useLayoutEffect mount',
'App useEffect mount',
'App useLayoutEffect unmount',
@@ -615,11 +696,12 @@ describe('StrictEffectsMode defaults', () => {
'App useEffect mount',
]);
log.length = 0;
await act(() => {
_setShowChild(true);
});
assertLog([
expect(log).toEqual([
'App useLayoutEffect unmount',
'Child useLayoutEffect mount',
'App useLayoutEffect mount',
@@ -634,13 +716,14 @@ describe('StrictEffectsMode defaults', () => {
});
it('classes and functions are double invoked together correctly', async () => {
const log = [];
class ClassChild extends React.PureComponent {
componentDidMount() {
Scheduler.log('componentDidMount');
log.push('componentDidMount');
}
componentWillUnmount() {
Scheduler.log('componentWillUnmount');
log.push('componentWillUnmount');
}
render() {
@@ -650,12 +733,12 @@ describe('StrictEffectsMode defaults', () => {
function FunctionChild({text}) {
React.useEffect(() => {
Scheduler.log('useEffect mount');
return () => Scheduler.log('useEffect unmount');
log.push('useEffect mount');
return () => log.push('useEffect unmount');
});
React.useLayoutEffect(() => {
Scheduler.log('useLayoutEffect mount');
return () => Scheduler.log('useLayoutEffect unmount');
log.push('useLayoutEffect mount');
return () => log.push('useLayoutEffect unmount');
});
return text;
}
@@ -677,7 +760,7 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'componentDidMount',
'useLayoutEffect mount',
'useEffect mount',
@@ -689,6 +772,7 @@ describe('StrictEffectsMode defaults', () => {
'useEffect mount',
]);
log.length = 0;
await act(() => {
ReactNoop.render(
<React.StrictMode>
@@ -697,18 +781,19 @@ describe('StrictEffectsMode defaults', () => {
);
});
assertLog([
expect(log).toEqual([
'useLayoutEffect unmount',
'useLayoutEffect mount',
'useEffect unmount',
'useEffect mount',
]);
log.length = 0;
await act(() => {
ReactNoop.render(null);
});
assertLog([
expect(log).toEqual([
'componentWillUnmount',
'useLayoutEffect unmount',
'useEffect unmount',