2019-08-28 01:54:01 +08:00
|
|
|
/**
|
2022-10-18 23:19:24 +08:00
|
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
2019-08-28 01:54:01 +08:00
|
|
|
*
|
|
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
|
|
|
|
*
|
|
|
|
|
* @flow
|
|
|
|
|
*/
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2019-08-14 08:58:03 +08:00
|
|
|
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
2019-08-14 06:59:43 +08:00
|
|
|
import type Store from 'react-devtools-shared/src/devtools/store';
|
2019-05-22 21:05:25 +08:00
|
|
|
|
|
|
|
|
describe('ProfilingCache', () => {
|
2019-06-09 00:10:54 +08:00
|
|
|
let PropTypes;
|
2019-05-22 21:05:25 +08:00
|
|
|
let React;
|
|
|
|
|
let ReactDOM;
|
2022-03-01 13:13:28 +08:00
|
|
|
let ReactDOMClient;
|
2019-05-22 21:05:25 +08:00
|
|
|
let Scheduler;
|
2019-07-21 05:08:23 +08:00
|
|
|
let bridge: FrontendBridge;
|
2021-06-25 10:42:44 +08:00
|
|
|
let legacyRender;
|
2019-05-22 21:05:25 +08:00
|
|
|
let store: Store;
|
|
|
|
|
let utils;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
utils = require('./utils');
|
|
|
|
|
utils.beforeEachProfiling();
|
|
|
|
|
|
2021-06-25 10:42:44 +08:00
|
|
|
legacyRender = utils.legacyRender;
|
|
|
|
|
|
2019-05-22 21:05:25 +08:00
|
|
|
bridge = global.bridge;
|
|
|
|
|
store = global.store;
|
|
|
|
|
store.collapseNodesByDefault = false;
|
2019-06-08 06:38:26 +08:00
|
|
|
store.recordChangeDescriptions = true;
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2019-06-09 00:10:54 +08:00
|
|
|
PropTypes = require('prop-types');
|
2019-05-22 21:05:25 +08:00
|
|
|
React = require('react');
|
|
|
|
|
ReactDOM = require('react-dom');
|
2022-03-01 13:13:28 +08:00
|
|
|
ReactDOMClient = require('react-dom/client');
|
2019-05-22 21:05:25 +08:00
|
|
|
Scheduler = require('scheduler');
|
|
|
|
|
});
|
|
|
|
|
|
2022-05-19 00:37:17 +08:00
|
|
|
// @reactVersion >= 16.9
|
2019-05-22 23:05:34 +08:00
|
|
|
it('should collect data for each root (including ones added or mounted after profiling started)', () => {
|
2019-08-14 08:58:03 +08:00
|
|
|
const Parent = ({count}) => {
|
2019-07-18 02:12:39 +08:00
|
|
|
Scheduler.unstable_advanceTime(10);
|
2019-05-22 21:05:25 +08:00
|
|
|
const children = new Array(count)
|
|
|
|
|
.fill(true)
|
|
|
|
|
.map((_, index) => <Child key={index} duration={index} />);
|
|
|
|
|
return (
|
|
|
|
|
<React.Fragment>
|
|
|
|
|
{children}
|
|
|
|
|
<MemoizedChild duration={1} />
|
|
|
|
|
</React.Fragment>
|
|
|
|
|
);
|
|
|
|
|
};
|
2019-08-14 08:58:03 +08:00
|
|
|
const Child = ({duration}) => {
|
2019-07-18 02:12:39 +08:00
|
|
|
Scheduler.unstable_advanceTime(duration);
|
2019-05-22 21:05:25 +08:00
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
const MemoizedChild = React.memo(Child);
|
|
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
const RootA = ({children}) => children;
|
|
|
|
|
const RootB = ({children}) => children;
|
|
|
|
|
const RootC = ({children}) => children;
|
|
|
|
|
|
2019-05-22 23:05:34 +08:00
|
|
|
const containerA = document.createElement('div');
|
|
|
|
|
const containerB = document.createElement('div');
|
|
|
|
|
const containerC = document.createElement('div');
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
utils.act(() =>
|
|
|
|
|
legacyRender(
|
|
|
|
|
<RootA>
|
|
|
|
|
<Parent count={2} />
|
|
|
|
|
</RootA>,
|
|
|
|
|
containerA,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
utils.act(() =>
|
|
|
|
|
legacyRender(
|
|
|
|
|
<RootB>
|
|
|
|
|
<Parent count={1} />
|
|
|
|
|
</RootB>,
|
|
|
|
|
containerB,
|
|
|
|
|
),
|
|
|
|
|
);
|
2019-05-22 22:40:41 +08:00
|
|
|
utils.act(() => store.profilerStore.startProfiling());
|
2022-03-30 23:02:51 +08:00
|
|
|
utils.act(() =>
|
|
|
|
|
legacyRender(
|
|
|
|
|
<RootA>
|
|
|
|
|
<Parent count={3} />
|
|
|
|
|
</RootA>,
|
|
|
|
|
containerA,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
utils.act(() =>
|
|
|
|
|
legacyRender(
|
|
|
|
|
<RootC>
|
|
|
|
|
<Parent count={1} />
|
|
|
|
|
</RootC>,
|
|
|
|
|
containerC,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
utils.act(() =>
|
|
|
|
|
legacyRender(
|
|
|
|
|
<RootA>
|
|
|
|
|
<Parent count={1} />
|
|
|
|
|
</RootA>,
|
|
|
|
|
containerA,
|
|
|
|
|
),
|
|
|
|
|
);
|
2019-05-22 23:05:34 +08:00
|
|
|
utils.act(() => ReactDOM.unmountComponentAtNode(containerB));
|
2022-03-30 23:02:51 +08:00
|
|
|
utils.act(() =>
|
|
|
|
|
legacyRender(
|
|
|
|
|
<RootA>
|
|
|
|
|
<Parent count={0} />
|
|
|
|
|
</RootA>,
|
|
|
|
|
containerA,
|
|
|
|
|
),
|
|
|
|
|
);
|
2019-05-22 22:40:41 +08:00
|
|
|
utils.act(() => store.profilerStore.stopProfiling());
|
2022-03-30 23:02:51 +08:00
|
|
|
utils.act(() => ReactDOM.unmountComponentAtNode(containerA));
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
const rootIDs = Array.from(
|
|
|
|
|
store.profilerStore.profilingData.dataForRoots.values(),
|
|
|
|
|
).map(({rootID}) => rootID);
|
|
|
|
|
expect(rootIDs).toHaveLength(3);
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
const originalProfilingDataForRoot = [];
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
let data = store.profilerStore.getDataForRoot(rootIDs[0]);
|
|
|
|
|
expect(data.displayName).toMatchInlineSnapshot(`"RootA"`);
|
|
|
|
|
expect(data.commitData).toHaveLength(3);
|
|
|
|
|
originalProfilingDataForRoot.push(data);
|
|
|
|
|
|
|
|
|
|
data = store.profilerStore.getDataForRoot(rootIDs[1]);
|
|
|
|
|
expect(data.displayName).toMatchInlineSnapshot(`"RootC"`);
|
|
|
|
|
expect(data.commitData).toHaveLength(1);
|
|
|
|
|
originalProfilingDataForRoot.push(data);
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
data = store.profilerStore.getDataForRoot(rootIDs[2]);
|
|
|
|
|
expect(data.displayName).toMatchInlineSnapshot(`"RootB"`);
|
|
|
|
|
expect(data.commitData).toHaveLength(1);
|
|
|
|
|
originalProfilingDataForRoot.push(data);
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2019-05-22 23:05:34 +08:00
|
|
|
utils.exportImportHelper(bridge, store);
|
|
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
rootIDs.forEach((rootID, index) => {
|
|
|
|
|
const current = store.profilerStore.getDataForRoot(rootID);
|
|
|
|
|
const prev = originalProfilingDataForRoot[index];
|
|
|
|
|
expect(current).toEqual(prev);
|
2019-05-22 23:05:34 +08:00
|
|
|
});
|
2019-05-22 21:05:25 +08:00
|
|
|
});
|
|
|
|
|
|
2022-05-19 00:37:17 +08:00
|
|
|
// @reactVersion >= 16.9
|
2019-05-22 23:05:34 +08:00
|
|
|
it('should collect data for each commit', () => {
|
2019-08-14 08:58:03 +08:00
|
|
|
const Parent = ({count}) => {
|
2019-07-18 02:12:39 +08:00
|
|
|
Scheduler.unstable_advanceTime(10);
|
2019-05-22 21:05:25 +08:00
|
|
|
const children = new Array(count)
|
|
|
|
|
.fill(true)
|
|
|
|
|
.map((_, index) => <Child key={index} duration={index} />);
|
|
|
|
|
return (
|
|
|
|
|
<React.Fragment>
|
|
|
|
|
{children}
|
|
|
|
|
<MemoizedChild duration={1} />
|
|
|
|
|
</React.Fragment>
|
|
|
|
|
);
|
|
|
|
|
};
|
2019-08-14 08:58:03 +08:00
|
|
|
const Child = ({duration}) => {
|
2019-07-18 02:12:39 +08:00
|
|
|
Scheduler.unstable_advanceTime(duration);
|
2019-05-22 21:05:25 +08:00
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
const MemoizedChild = React.memo(Child);
|
|
|
|
|
|
|
|
|
|
const container = document.createElement('div');
|
|
|
|
|
|
2019-05-22 22:40:41 +08:00
|
|
|
utils.act(() => store.profilerStore.startProfiling());
|
2021-06-25 10:42:44 +08:00
|
|
|
utils.act(() => legacyRender(<Parent count={2} />, container));
|
|
|
|
|
utils.act(() => legacyRender(<Parent count={3} />, container));
|
|
|
|
|
utils.act(() => legacyRender(<Parent count={1} />, container));
|
|
|
|
|
utils.act(() => legacyRender(<Parent count={0} />, container));
|
2019-05-22 22:40:41 +08:00
|
|
|
utils.act(() => store.profilerStore.stopProfiling());
|
2019-05-22 21:05:25 +08:00
|
|
|
|
|
|
|
|
const rootID = store.roots[0];
|
|
|
|
|
|
2023-01-31 21:25:05 +08:00
|
|
|
const prevCommitData =
|
|
|
|
|
store.profilerStore.getDataForRoot(rootID).commitData;
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(prevCommitData).toHaveLength(4);
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2019-05-22 23:05:34 +08:00
|
|
|
utils.exportImportHelper(bridge, store);
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2023-01-31 21:25:05 +08:00
|
|
|
const nextCommitData =
|
|
|
|
|
store.profilerStore.getDataForRoot(rootID).commitData;
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(nextCommitData).toHaveLength(4);
|
|
|
|
|
nextCommitData.forEach((commitData, index) => {
|
|
|
|
|
expect(commitData).toEqual(prevCommitData[index]);
|
|
|
|
|
});
|
2019-05-22 21:05:25 +08:00
|
|
|
});
|
|
|
|
|
|
2022-05-19 00:37:17 +08:00
|
|
|
// @reactVersion >= 16.9
|
2019-06-09 02:41:39 +08:00
|
|
|
it('should record changed props/state/context/hooks', () => {
|
2019-06-09 00:10:54 +08:00
|
|
|
let instance = null;
|
|
|
|
|
|
|
|
|
|
const ModernContext = React.createContext(0);
|
|
|
|
|
|
2022-09-10 04:03:48 +08:00
|
|
|
class LegacyContextProvider extends React.Component<any, {count: number}> {
|
2019-06-09 00:10:54 +08:00
|
|
|
static childContextTypes = {
|
|
|
|
|
count: PropTypes.number,
|
|
|
|
|
};
|
2019-08-14 08:58:03 +08:00
|
|
|
state = {count: 0};
|
2019-06-09 00:10:54 +08:00
|
|
|
getChildContext() {
|
|
|
|
|
return this.state;
|
|
|
|
|
}
|
|
|
|
|
render() {
|
|
|
|
|
instance = this;
|
|
|
|
|
return (
|
|
|
|
|
<ModernContext.Provider value={this.state.count}>
|
|
|
|
|
<React.Fragment>
|
|
|
|
|
<ModernContextConsumer />
|
|
|
|
|
<LegacyContextConsumer />
|
|
|
|
|
</React.Fragment>
|
|
|
|
|
</ModernContext.Provider>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-14 08:58:03 +08:00
|
|
|
const FunctionComponentWithHooks = ({count}) => {
|
2019-06-09 00:10:54 +08:00
|
|
|
React.useMemo(() => count, [count]);
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class ModernContextConsumer extends React.Component<any> {
|
|
|
|
|
static contextType = ModernContext;
|
|
|
|
|
render() {
|
|
|
|
|
return <FunctionComponentWithHooks count={this.context} />;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class LegacyContextConsumer extends React.Component<any> {
|
|
|
|
|
static contextTypes = {
|
|
|
|
|
count: PropTypes.number,
|
|
|
|
|
};
|
|
|
|
|
render() {
|
|
|
|
|
return <FunctionComponentWithHooks count={this.context.count} />;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const container = document.createElement('div');
|
|
|
|
|
|
|
|
|
|
utils.act(() => store.profilerStore.startProfiling());
|
2021-06-25 10:42:44 +08:00
|
|
|
utils.act(() => legacyRender(<LegacyContextProvider />, container));
|
2019-06-09 00:10:54 +08:00
|
|
|
expect(instance).not.toBeNull();
|
2019-08-14 08:58:03 +08:00
|
|
|
utils.act(() => (instance: any).setState({count: 1}));
|
2019-06-09 02:41:39 +08:00
|
|
|
utils.act(() =>
|
2021-06-25 10:42:44 +08:00
|
|
|
legacyRender(<LegacyContextProvider foo={123} />, container),
|
2019-06-09 02:41:39 +08:00
|
|
|
);
|
|
|
|
|
utils.act(() =>
|
2021-06-25 10:42:44 +08:00
|
|
|
legacyRender(<LegacyContextProvider bar="abc" />, container),
|
2019-06-09 02:41:39 +08:00
|
|
|
);
|
2021-06-25 10:42:44 +08:00
|
|
|
utils.act(() => legacyRender(<LegacyContextProvider />, container));
|
2019-06-09 00:10:54 +08:00
|
|
|
utils.act(() => store.profilerStore.stopProfiling());
|
|
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
const rootID = store.roots[0];
|
2019-06-09 00:10:54 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
let changeDescriptions = store.profilerStore
|
|
|
|
|
.getDataForRoot(rootID)
|
|
|
|
|
.commitData.map(commitData => commitData.changeDescriptions);
|
|
|
|
|
expect(changeDescriptions).toHaveLength(5);
|
|
|
|
|
expect(changeDescriptions[0]).toMatchInlineSnapshot(`
|
|
|
|
|
Map {
|
2023-02-10 16:58:57 +08:00
|
|
|
2 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
|
|
|
|
"isFirstMount": true,
|
|
|
|
|
"props": null,
|
|
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
4 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
|
|
|
|
"isFirstMount": true,
|
|
|
|
|
"props": null,
|
|
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
5 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
|
|
|
|
"isFirstMount": true,
|
|
|
|
|
"props": null,
|
|
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
6 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
|
|
|
|
"isFirstMount": true,
|
|
|
|
|
"props": null,
|
|
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
7 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
|
|
|
|
"isFirstMount": true,
|
|
|
|
|
"props": null,
|
|
|
|
|
"state": null,
|
|
|
|
|
},
|
2019-06-09 00:10:54 +08:00
|
|
|
}
|
2022-03-30 23:02:51 +08:00
|
|
|
`);
|
|
|
|
|
expect(changeDescriptions[1]).toMatchInlineSnapshot(`
|
|
|
|
|
Map {
|
2023-02-10 16:58:57 +08:00
|
|
|
5 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [
|
2022-03-30 23:02:51 +08:00
|
|
|
"count",
|
|
|
|
|
],
|
|
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
4 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": true,
|
|
|
|
|
"didHooksChange": false,
|
|
|
|
|
"hooks": null,
|
|
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
7 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [
|
2022-03-30 23:02:51 +08:00
|
|
|
"count",
|
|
|
|
|
],
|
|
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
6 => {
|
|
|
|
|
"context": [
|
2022-03-30 23:02:51 +08:00
|
|
|
"count",
|
|
|
|
|
],
|
|
|
|
|
"didHooksChange": false,
|
|
|
|
|
"hooks": null,
|
|
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
2 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
|
|
|
|
"state": [
|
2022-03-30 23:02:51 +08:00
|
|
|
"count",
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
`);
|
|
|
|
|
expect(changeDescriptions[2]).toMatchInlineSnapshot(`
|
|
|
|
|
Map {
|
2023-02-10 16:58:57 +08:00
|
|
|
5 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
4 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": false,
|
|
|
|
|
"didHooksChange": false,
|
|
|
|
|
"hooks": null,
|
|
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
7 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
6 => {
|
|
|
|
|
"context": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"didHooksChange": false,
|
|
|
|
|
"hooks": null,
|
|
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
2 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [
|
2022-03-30 23:02:51 +08:00
|
|
|
"foo",
|
|
|
|
|
],
|
2023-02-10 16:58:57 +08:00
|
|
|
"state": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
`);
|
|
|
|
|
expect(changeDescriptions[3]).toMatchInlineSnapshot(`
|
|
|
|
|
Map {
|
2023-02-10 16:58:57 +08:00
|
|
|
5 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
4 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": false,
|
|
|
|
|
"didHooksChange": false,
|
|
|
|
|
"hooks": null,
|
|
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
7 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
6 => {
|
|
|
|
|
"context": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"didHooksChange": false,
|
|
|
|
|
"hooks": null,
|
|
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
2 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [
|
2022-03-30 23:02:51 +08:00
|
|
|
"foo",
|
|
|
|
|
"bar",
|
|
|
|
|
],
|
2023-02-10 16:58:57 +08:00
|
|
|
"state": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
`);
|
|
|
|
|
expect(changeDescriptions[4]).toMatchInlineSnapshot(`
|
|
|
|
|
Map {
|
2023-02-10 16:58:57 +08:00
|
|
|
5 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
4 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": false,
|
|
|
|
|
"didHooksChange": false,
|
|
|
|
|
"hooks": null,
|
|
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
7 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
6 => {
|
|
|
|
|
"context": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"didHooksChange": false,
|
|
|
|
|
"hooks": null,
|
|
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
2023-02-10 16:58:57 +08:00
|
|
|
2 => {
|
2022-03-30 23:02:51 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [
|
2022-03-30 23:02:51 +08:00
|
|
|
"bar",
|
|
|
|
|
],
|
2023-02-10 16:58:57 +08:00
|
|
|
"state": [],
|
2022-03-30 23:02:51 +08:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
`);
|
2019-06-09 00:10:54 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
utils.exportImportHelper(bridge, store);
|
2019-06-09 00:10:54 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
const prevChangeDescriptions = [...changeDescriptions];
|
2019-06-09 00:10:54 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
changeDescriptions = store.profilerStore
|
|
|
|
|
.getDataForRoot(rootID)
|
|
|
|
|
.commitData.map(commitData => commitData.changeDescriptions);
|
|
|
|
|
expect(changeDescriptions).toHaveLength(5);
|
2019-06-09 00:10:54 +08:00
|
|
|
|
2019-06-09 02:41:39 +08:00
|
|
|
for (let commitIndex = 0; commitIndex < 5; commitIndex++) {
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(changeDescriptions[commitIndex]).toEqual(
|
|
|
|
|
prevChangeDescriptions[commitIndex],
|
|
|
|
|
);
|
2019-06-09 00:10:54 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2022-05-19 00:37:17 +08:00
|
|
|
// @reactVersion >= 18.0
|
2020-01-10 05:15:19 +08:00
|
|
|
it('should properly detect changed hooks', () => {
|
|
|
|
|
const Context = React.createContext(0);
|
|
|
|
|
|
|
|
|
|
function reducer(state, action) {
|
|
|
|
|
switch (action.type) {
|
|
|
|
|
case 'invert':
|
|
|
|
|
return {value: !state.value};
|
|
|
|
|
default:
|
|
|
|
|
throw new Error();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-29 23:11:31 +08:00
|
|
|
let snapshot = 0;
|
|
|
|
|
function getServerSnapshot() {
|
|
|
|
|
return snapshot;
|
|
|
|
|
}
|
|
|
|
|
function getClientSnapshot() {
|
|
|
|
|
return snapshot;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let syncExternalStoreCallback;
|
|
|
|
|
function subscribe(callback) {
|
|
|
|
|
syncExternalStoreCallback = callback;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-10 05:15:19 +08:00
|
|
|
let dispatch = null;
|
|
|
|
|
let setState = null;
|
|
|
|
|
|
|
|
|
|
const Component = ({count, string}) => {
|
|
|
|
|
// These hooks may change and initiate re-renders.
|
|
|
|
|
setState = React.useState('abc')[1];
|
|
|
|
|
dispatch = React.useReducer(reducer, {value: true})[1];
|
2022-03-29 23:11:31 +08:00
|
|
|
React.useSyncExternalStore(
|
|
|
|
|
subscribe,
|
|
|
|
|
getClientSnapshot,
|
|
|
|
|
getServerSnapshot,
|
|
|
|
|
);
|
2020-01-10 05:15:19 +08:00
|
|
|
|
|
|
|
|
// This hook's return value may change between renders,
|
|
|
|
|
// but the hook itself isn't stateful.
|
|
|
|
|
React.useContext(Context);
|
|
|
|
|
|
2022-03-29 23:11:31 +08:00
|
|
|
// These hooks never change in a way that schedules an update.
|
2020-01-10 05:15:19 +08:00
|
|
|
React.useCallback(() => () => {}, [string]);
|
|
|
|
|
React.useMemo(() => string, [string]);
|
2022-03-29 23:11:31 +08:00
|
|
|
React.useCallback(() => () => {}, [count]);
|
|
|
|
|
React.useMemo(() => count, [count]);
|
|
|
|
|
React.useCallback(() => () => {});
|
|
|
|
|
React.useMemo(() => string);
|
2020-01-10 05:15:19 +08:00
|
|
|
|
2022-03-29 23:11:31 +08:00
|
|
|
// These hooks never change in a way that schedules an update.
|
2020-01-10 05:15:19 +08:00
|
|
|
React.useEffect(() => {}, [string]);
|
|
|
|
|
React.useLayoutEffect(() => {}, [string]);
|
2022-03-29 23:11:31 +08:00
|
|
|
React.useEffect(() => {}, [count]);
|
|
|
|
|
React.useLayoutEffect(() => {}, [count]);
|
|
|
|
|
React.useEffect(() => {});
|
|
|
|
|
React.useLayoutEffect(() => {});
|
2020-01-10 05:15:19 +08:00
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const container = document.createElement('div');
|
|
|
|
|
|
|
|
|
|
utils.act(() => store.profilerStore.startProfiling());
|
|
|
|
|
utils.act(() =>
|
2021-06-25 10:42:44 +08:00
|
|
|
legacyRender(
|
2020-01-10 05:15:19 +08:00
|
|
|
<Context.Provider value={true}>
|
2022-03-29 23:11:31 +08:00
|
|
|
<Component count={1} />
|
2020-01-10 05:15:19 +08:00
|
|
|
</Context.Provider>,
|
|
|
|
|
container,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Second render has no changed hooks, only changed props.
|
|
|
|
|
utils.act(() =>
|
2021-06-25 10:42:44 +08:00
|
|
|
legacyRender(
|
2020-01-10 05:15:19 +08:00
|
|
|
<Context.Provider value={true}>
|
2022-03-29 23:11:31 +08:00
|
|
|
<Component count={2} />
|
2020-01-10 05:15:19 +08:00
|
|
|
</Context.Provider>,
|
|
|
|
|
container,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
2022-03-29 23:11:31 +08:00
|
|
|
// Third render has a changed reducer hook.
|
2020-01-10 05:15:19 +08:00
|
|
|
utils.act(() => dispatch({type: 'invert'}));
|
|
|
|
|
|
2022-03-29 23:11:31 +08:00
|
|
|
// Fourth render has a changed state hook.
|
2020-01-10 05:15:19 +08:00
|
|
|
utils.act(() => setState('def'));
|
|
|
|
|
|
2022-03-29 23:11:31 +08:00
|
|
|
// Fifth render has a changed context value, but no changed hook.
|
2020-01-10 05:15:19 +08:00
|
|
|
utils.act(() =>
|
2021-06-25 10:42:44 +08:00
|
|
|
legacyRender(
|
2020-01-10 05:15:19 +08:00
|
|
|
<Context.Provider value={false}>
|
2022-03-29 23:11:31 +08:00
|
|
|
<Component count={2} />
|
2020-01-10 05:15:19 +08:00
|
|
|
</Context.Provider>,
|
|
|
|
|
container,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
2022-03-29 23:11:31 +08:00
|
|
|
// 6th renderer is triggered by a sync external store change.
|
|
|
|
|
utils.act(() => {
|
|
|
|
|
snapshot++;
|
|
|
|
|
syncExternalStoreCallback();
|
|
|
|
|
});
|
|
|
|
|
|
2020-01-10 05:15:19 +08:00
|
|
|
utils.act(() => store.profilerStore.stopProfiling());
|
|
|
|
|
|
2022-03-29 23:11:31 +08:00
|
|
|
const rootID = store.roots[0];
|
2020-01-10 05:15:19 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
const changeDescriptions = store.profilerStore
|
|
|
|
|
.getDataForRoot(rootID)
|
|
|
|
|
.commitData.map(commitData => commitData.changeDescriptions);
|
|
|
|
|
expect(changeDescriptions).toHaveLength(6);
|
2020-01-10 05:15:19 +08:00
|
|
|
|
2022-03-29 23:11:31 +08:00
|
|
|
// 1st render: No change
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(changeDescriptions[0]).toMatchInlineSnapshot(`
|
2022-03-29 23:11:31 +08:00
|
|
|
Map {
|
2023-02-10 16:58:57 +08:00
|
|
|
3 => {
|
2022-03-29 23:11:31 +08:00
|
|
|
"context": null,
|
|
|
|
|
"didHooksChange": false,
|
|
|
|
|
"isFirstMount": true,
|
|
|
|
|
"props": null,
|
|
|
|
|
"state": null,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// 2nd render: Changed props
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(changeDescriptions[1]).toMatchInlineSnapshot(`
|
2022-03-29 23:11:31 +08:00
|
|
|
Map {
|
2023-02-10 16:58:57 +08:00
|
|
|
3 => {
|
2022-03-29 23:11:31 +08:00
|
|
|
"context": false,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-29 23:11:31 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [
|
2022-03-29 23:11:31 +08:00
|
|
|
"count",
|
|
|
|
|
],
|
|
|
|
|
"state": null,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// 3rd render: Changed useReducer
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(changeDescriptions[2]).toMatchInlineSnapshot(`
|
2022-03-29 23:11:31 +08:00
|
|
|
Map {
|
2023-02-10 16:58:57 +08:00
|
|
|
3 => {
|
2022-03-29 23:11:31 +08:00
|
|
|
"context": false,
|
|
|
|
|
"didHooksChange": true,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [
|
2022-03-29 23:11:31 +08:00
|
|
|
1,
|
|
|
|
|
],
|
|
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-29 23:11:31 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// 4th render: Changed useState
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(changeDescriptions[3]).toMatchInlineSnapshot(`
|
2022-03-29 23:11:31 +08:00
|
|
|
Map {
|
2023-02-10 16:58:57 +08:00
|
|
|
3 => {
|
2022-03-29 23:11:31 +08:00
|
|
|
"context": false,
|
|
|
|
|
"didHooksChange": true,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [
|
2022-03-29 23:11:31 +08:00
|
|
|
0,
|
|
|
|
|
],
|
|
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-29 23:11:31 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// 5th render: Changed context
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(changeDescriptions[4]).toMatchInlineSnapshot(`
|
2022-03-29 23:11:31 +08:00
|
|
|
Map {
|
2023-02-10 16:58:57 +08:00
|
|
|
3 => {
|
2022-03-29 23:11:31 +08:00
|
|
|
"context": true,
|
|
|
|
|
"didHooksChange": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [],
|
2022-03-29 23:11:31 +08:00
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-29 23:11:31 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// 6th render: Sync external store
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(changeDescriptions[5]).toMatchInlineSnapshot(`
|
2022-03-29 23:11:31 +08:00
|
|
|
Map {
|
2023-02-10 16:58:57 +08:00
|
|
|
3 => {
|
2022-03-29 23:11:31 +08:00
|
|
|
"context": false,
|
|
|
|
|
"didHooksChange": true,
|
2023-02-10 16:58:57 +08:00
|
|
|
"hooks": [
|
2022-03-29 23:11:31 +08:00
|
|
|
2,
|
|
|
|
|
],
|
|
|
|
|
"isFirstMount": false,
|
2023-02-10 16:58:57 +08:00
|
|
|
"props": [],
|
2022-03-29 23:11:31 +08:00
|
|
|
"state": null,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
`);
|
|
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(changeDescriptions).toHaveLength(6);
|
2020-01-10 05:15:19 +08:00
|
|
|
|
|
|
|
|
// Export and re-import profile data and make sure it is retained.
|
|
|
|
|
utils.exportImportHelper(bridge, store);
|
|
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
for (let commitIndex = 0; commitIndex < 6; commitIndex++) {
|
2022-03-29 23:11:31 +08:00
|
|
|
const commitData = store.profilerStore.getCommitData(rootID, commitIndex);
|
|
|
|
|
expect(commitData.changeDescriptions).toEqual(
|
2022-03-30 23:02:51 +08:00
|
|
|
changeDescriptions[commitIndex],
|
2022-03-29 23:11:31 +08:00
|
|
|
);
|
2020-01-10 05:15:19 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2022-05-24 05:18:17 +08:00
|
|
|
// @reactVersion >= 18.0
|
2022-03-30 23:02:51 +08:00
|
|
|
it('should calculate durations based on actual children (not filtered children)', () => {
|
2019-05-22 21:05:25 +08:00
|
|
|
store.componentFilters = [utils.createDisplayNameFilter('^Parent$')];
|
|
|
|
|
|
|
|
|
|
const Grandparent = () => {
|
2019-07-18 02:12:39 +08:00
|
|
|
Scheduler.unstable_advanceTime(10);
|
2019-05-22 21:05:25 +08:00
|
|
|
return (
|
|
|
|
|
<React.Fragment>
|
|
|
|
|
<Parent key="one" />
|
|
|
|
|
<Parent key="two" />
|
|
|
|
|
</React.Fragment>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
const Parent = () => {
|
2019-07-18 02:12:39 +08:00
|
|
|
Scheduler.unstable_advanceTime(2);
|
2019-05-22 21:05:25 +08:00
|
|
|
return <Child />;
|
|
|
|
|
};
|
|
|
|
|
const Child = () => {
|
2019-07-18 02:12:39 +08:00
|
|
|
Scheduler.unstable_advanceTime(1);
|
2019-05-22 21:05:25 +08:00
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
2019-05-22 22:40:41 +08:00
|
|
|
utils.act(() => store.profilerStore.startProfiling());
|
2019-05-22 21:05:25 +08:00
|
|
|
utils.act(() =>
|
2021-06-25 10:42:44 +08:00
|
|
|
legacyRender(<Grandparent />, document.createElement('div')),
|
2019-05-22 21:05:25 +08:00
|
|
|
);
|
2019-05-22 22:40:41 +08:00
|
|
|
utils.act(() => store.profilerStore.stopProfiling());
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(store).toMatchInlineSnapshot(`
|
|
|
|
|
[root]
|
|
|
|
|
▾ <Grandparent>
|
|
|
|
|
<Child>
|
|
|
|
|
<Child>
|
|
|
|
|
`);
|
2019-05-22 21:05:25 +08:00
|
|
|
|
|
|
|
|
const rootID = store.roots[0];
|
2022-03-30 23:02:51 +08:00
|
|
|
const commitData = store.profilerStore.getDataForRoot(rootID).commitData;
|
|
|
|
|
expect(commitData).toHaveLength(1);
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
// Actual duration should also include both filtered <Parent> components.
|
|
|
|
|
expect(commitData[0].fiberActualDurations).toMatchInlineSnapshot(`
|
|
|
|
|
Map {
|
|
|
|
|
1 => 16,
|
|
|
|
|
2 => 16,
|
|
|
|
|
4 => 1,
|
|
|
|
|
6 => 1,
|
|
|
|
|
}
|
|
|
|
|
`);
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(commitData[0].fiberSelfDurations).toMatchInlineSnapshot(`
|
|
|
|
|
Map {
|
|
|
|
|
1 => 0,
|
|
|
|
|
2 => 10,
|
|
|
|
|
4 => 1,
|
|
|
|
|
6 => 1,
|
|
|
|
|
}
|
|
|
|
|
`);
|
2019-05-22 21:05:25 +08:00
|
|
|
});
|
|
|
|
|
|
2022-05-19 00:37:17 +08:00
|
|
|
// @reactVersion >= 17.0
|
2022-03-30 23:02:51 +08:00
|
|
|
it('should calculate durations correctly for suspended views', async () => {
|
2019-05-22 21:05:25 +08:00
|
|
|
let data;
|
|
|
|
|
const getData = () => {
|
|
|
|
|
if (data) {
|
|
|
|
|
return data;
|
|
|
|
|
} else {
|
|
|
|
|
throw new Promise(resolve => {
|
|
|
|
|
data = 'abc';
|
|
|
|
|
resolve(data);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Parent = () => {
|
2019-07-18 02:12:39 +08:00
|
|
|
Scheduler.unstable_advanceTime(10);
|
2019-05-22 21:05:25 +08:00
|
|
|
return (
|
|
|
|
|
<React.Suspense fallback={<Fallback />}>
|
|
|
|
|
<Async />
|
|
|
|
|
</React.Suspense>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
const Fallback = () => {
|
2019-07-18 02:12:39 +08:00
|
|
|
Scheduler.unstable_advanceTime(2);
|
2019-05-22 21:05:25 +08:00
|
|
|
return 'Fallback...';
|
|
|
|
|
};
|
|
|
|
|
const Async = () => {
|
2019-07-18 02:12:39 +08:00
|
|
|
Scheduler.unstable_advanceTime(3);
|
2019-08-14 12:59:07 +08:00
|
|
|
return getData();
|
2019-05-22 21:05:25 +08:00
|
|
|
};
|
|
|
|
|
|
2019-05-22 22:40:41 +08:00
|
|
|
utils.act(() => store.profilerStore.startProfiling());
|
2019-05-22 21:05:25 +08:00
|
|
|
await utils.actAsync(() =>
|
2021-06-25 10:42:44 +08:00
|
|
|
legacyRender(<Parent />, document.createElement('div')),
|
2019-05-22 21:05:25 +08:00
|
|
|
);
|
2019-05-22 22:40:41 +08:00
|
|
|
utils.act(() => store.profilerStore.stopProfiling());
|
2019-05-22 21:05:25 +08:00
|
|
|
|
|
|
|
|
const rootID = store.roots[0];
|
2022-03-30 23:02:51 +08:00
|
|
|
const commitData = store.profilerStore.getDataForRoot(rootID).commitData;
|
|
|
|
|
expect(commitData).toHaveLength(2);
|
|
|
|
|
expect(commitData[0].fiberActualDurations).toMatchInlineSnapshot(`
|
|
|
|
|
Map {
|
|
|
|
|
1 => 15,
|
|
|
|
|
2 => 15,
|
|
|
|
|
3 => 5,
|
|
|
|
|
4 => 2,
|
|
|
|
|
}
|
|
|
|
|
`);
|
|
|
|
|
expect(commitData[0].fiberSelfDurations).toMatchInlineSnapshot(`
|
|
|
|
|
Map {
|
|
|
|
|
1 => 0,
|
|
|
|
|
2 => 10,
|
|
|
|
|
3 => 3,
|
|
|
|
|
4 => 2,
|
|
|
|
|
}
|
|
|
|
|
`);
|
|
|
|
|
expect(commitData[1].fiberActualDurations).toMatchInlineSnapshot(`
|
|
|
|
|
Map {
|
|
|
|
|
7 => 3,
|
|
|
|
|
3 => 3,
|
|
|
|
|
}
|
|
|
|
|
`);
|
|
|
|
|
expect(commitData[1].fiberSelfDurations).toMatchInlineSnapshot(`
|
|
|
|
|
Map {
|
|
|
|
|
7 => 3,
|
|
|
|
|
3 => 0,
|
|
|
|
|
}
|
|
|
|
|
`);
|
2019-05-22 21:05:25 +08:00
|
|
|
});
|
|
|
|
|
|
2022-05-19 00:37:17 +08:00
|
|
|
// @reactVersion >= 16.9
|
2019-05-22 23:05:34 +08:00
|
|
|
it('should collect data for each rendered fiber', () => {
|
2019-08-14 08:58:03 +08:00
|
|
|
const Parent = ({count}) => {
|
2019-07-18 02:12:39 +08:00
|
|
|
Scheduler.unstable_advanceTime(10);
|
2019-05-22 21:05:25 +08:00
|
|
|
const children = new Array(count)
|
|
|
|
|
.fill(true)
|
|
|
|
|
.map((_, index) => <Child key={index} duration={index} />);
|
|
|
|
|
return (
|
|
|
|
|
<React.Fragment>
|
|
|
|
|
{children}
|
|
|
|
|
<MemoizedChild duration={1} />
|
|
|
|
|
</React.Fragment>
|
|
|
|
|
);
|
|
|
|
|
};
|
2019-08-14 08:58:03 +08:00
|
|
|
const Child = ({duration}) => {
|
2019-07-18 02:12:39 +08:00
|
|
|
Scheduler.unstable_advanceTime(duration);
|
2019-05-22 21:05:25 +08:00
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
const MemoizedChild = React.memo(Child);
|
|
|
|
|
|
|
|
|
|
const container = document.createElement('div');
|
|
|
|
|
|
2019-05-22 22:40:41 +08:00
|
|
|
utils.act(() => store.profilerStore.startProfiling());
|
2021-06-25 10:42:44 +08:00
|
|
|
utils.act(() => legacyRender(<Parent count={1} />, container));
|
|
|
|
|
utils.act(() => legacyRender(<Parent count={2} />, container));
|
|
|
|
|
utils.act(() => legacyRender(<Parent count={3} />, container));
|
2019-05-22 22:40:41 +08:00
|
|
|
utils.act(() => store.profilerStore.stopProfiling());
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
const rootID = store.roots[0];
|
2019-05-22 21:05:25 +08:00
|
|
|
const allFiberCommits = [];
|
2022-03-30 23:02:51 +08:00
|
|
|
for (let index = 0; index < store.numElements; index++) {
|
|
|
|
|
const fiberID = store.getElementIDAtIndex(index);
|
2019-05-22 22:40:41 +08:00
|
|
|
const fiberCommits = store.profilerStore.profilingCache.getFiberCommits({
|
2019-05-22 21:05:25 +08:00
|
|
|
fiberID,
|
|
|
|
|
rootID,
|
|
|
|
|
});
|
|
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
allFiberCommits.push(fiberCommits);
|
2019-05-22 21:05:25 +08:00
|
|
|
}
|
|
|
|
|
|
2022-03-30 23:02:51 +08:00
|
|
|
expect(allFiberCommits).toMatchInlineSnapshot(`
|
2023-02-10 16:58:57 +08:00
|
|
|
[
|
|
|
|
|
[
|
2022-03-30 23:02:51 +08:00
|
|
|
0,
|
|
|
|
|
1,
|
|
|
|
|
2,
|
|
|
|
|
],
|
2023-02-10 16:58:57 +08:00
|
|
|
[
|
2022-03-30 23:02:51 +08:00
|
|
|
0,
|
|
|
|
|
1,
|
|
|
|
|
2,
|
|
|
|
|
],
|
2023-02-10 16:58:57 +08:00
|
|
|
[
|
2022-03-30 23:02:51 +08:00
|
|
|
1,
|
|
|
|
|
2,
|
|
|
|
|
],
|
2023-02-10 16:58:57 +08:00
|
|
|
[
|
2022-03-30 23:02:51 +08:00
|
|
|
2,
|
|
|
|
|
],
|
2023-02-10 16:58:57 +08:00
|
|
|
[
|
2022-03-30 23:02:51 +08:00
|
|
|
0,
|
|
|
|
|
],
|
|
|
|
|
]
|
|
|
|
|
`);
|
2019-05-22 21:05:25 +08:00
|
|
|
|
2019-05-22 23:05:34 +08:00
|
|
|
utils.exportImportHelper(bridge, store);
|
2019-05-22 21:05:25 +08:00
|
|
|
|
|
|
|
|
for (let index = 0; index < store.numElements; index++) {
|
2022-03-30 23:02:51 +08:00
|
|
|
const fiberID = store.getElementIDAtIndex(index);
|
|
|
|
|
const fiberCommits = store.profilerStore.profilingCache.getFiberCommits({
|
|
|
|
|
fiberID,
|
|
|
|
|
rootID,
|
2019-05-22 21:05:25 +08:00
|
|
|
});
|
2022-03-30 23:02:51 +08:00
|
|
|
|
|
|
|
|
expect(fiberCommits).toEqual(allFiberCommits[index]);
|
2019-05-22 21:05:25 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2022-05-19 00:37:17 +08:00
|
|
|
// @reactVersion >= 18.0
|
2020-07-14 04:21:56 +08:00
|
|
|
it('should handle unexpectedly shallow suspense trees', () => {
|
|
|
|
|
const container = document.createElement('div');
|
|
|
|
|
|
|
|
|
|
utils.act(() => store.profilerStore.startProfiling());
|
2021-06-25 10:42:44 +08:00
|
|
|
utils.act(() => legacyRender(<React.Suspense />, container));
|
2020-07-14 04:21:56 +08:00
|
|
|
utils.act(() => store.profilerStore.stopProfiling());
|
|
|
|
|
|
|
|
|
|
const rootID = store.roots[0];
|
2022-03-30 23:02:51 +08:00
|
|
|
const commitData = store.profilerStore.getDataForRoot(rootID).commitData;
|
|
|
|
|
expect(commitData).toMatchInlineSnapshot(`
|
2023-02-10 16:58:57 +08:00
|
|
|
[
|
|
|
|
|
{
|
2022-03-30 23:02:51 +08:00
|
|
|
"changeDescriptions": Map {},
|
|
|
|
|
"duration": 0,
|
|
|
|
|
"effectDuration": null,
|
|
|
|
|
"fiberActualDurations": Map {
|
|
|
|
|
1 => 0,
|
|
|
|
|
2 => 0,
|
|
|
|
|
},
|
|
|
|
|
"fiberSelfDurations": Map {
|
|
|
|
|
1 => 0,
|
|
|
|
|
2 => 0,
|
|
|
|
|
},
|
|
|
|
|
"passiveEffectDuration": null,
|
Move update scheduling to microtask (#26512)
When React receives new input (via `setState`, a Suspense promise
resolution, and so on), it needs to ensure there's a rendering task
associated with the update. Most of this happens
`ensureRootIsScheduled`.
If a single event contains multiple updates, we end up running the
scheduling code once per update. But this is wasteful because we really
only need to run it once, at the end of the event (or in the case of
flushSync, at the end of the scope function's execution).
So this PR moves the scheduling logic to happen in a microtask instead.
In some cases, we will force it run earlier than that, like for
`flushSync`, but since updates are batched by default, it will almost
always happen in the microtask. Even for discrete updates.
In production, this should have no observable behavior difference. In a
testing environment that uses `act`, this should also not have a
behavior difference because React will push these tasks to an internal
`act` queue.
However, tests that do not use `act` and do not simulate an actual
production environment (like an e2e test) may be affected. For example,
before this change, if a test were to call `setState` outside of `act`
and then immediately call `jest.runAllTimers()`, the update would be
synchronously applied. After this change, that will no longer work
because the rendering task (a timer, in this case) isn't scheduled until
after the microtask queue has run.
I don't expect this to be an issue in practice because most people do
not write their tests this way. They either use `act`, or they write
e2e-style tests.
The biggest exception has been... our own internal test suite. Until
recently, many of our tests were written in a way that accidentally
relied on the updates being scheduled synchronously. Over the past few
weeks, @tyao1 and I have gradually converted the test suite to use a new
set of testing helpers that are resilient to this implementation detail.
(There are also some old Relay tests that were written in the style of
React's internal test suite. Those will need to be fixed, too.)
The larger motivation behind this change, aside from a minor performance
improvement, is we intend to use this new microtask to perform
additional logic that doesn't yet exist. Like inferring the priority of
a custom event.
2023-04-01 01:04:08 +08:00
|
|
|
"priorityLevel": "Normal",
|
2022-03-30 23:02:51 +08:00
|
|
|
"timestamp": 0,
|
2023-02-10 16:58:57 +08:00
|
|
|
"updaters": [
|
|
|
|
|
{
|
2022-03-30 23:02:51 +08:00
|
|
|
"displayName": "render()",
|
|
|
|
|
"hocDisplayNames": null,
|
|
|
|
|
"id": 1,
|
|
|
|
|
"key": null,
|
|
|
|
|
"type": 11,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
`);
|
2020-07-14 04:21:56 +08:00
|
|
|
});
|
2020-07-16 00:25:27 +08:00
|
|
|
|
|
|
|
|
// See https://github.com/facebook/react/issues/18831
|
2022-05-19 00:37:17 +08:00
|
|
|
// @reactVersion >= 16.9
|
2020-07-16 00:25:27 +08:00
|
|
|
it('should not crash during route transitions with Suspense', () => {
|
|
|
|
|
const RouterContext = React.createContext();
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
return (
|
|
|
|
|
<Router>
|
|
|
|
|
<Switch>
|
|
|
|
|
<Route path="/">
|
|
|
|
|
<Home />
|
|
|
|
|
</Route>
|
|
|
|
|
<Route path="/about">
|
|
|
|
|
<About />
|
|
|
|
|
</Route>
|
|
|
|
|
</Switch>
|
|
|
|
|
</Router>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Home = () => {
|
|
|
|
|
return (
|
|
|
|
|
<React.Suspense>
|
|
|
|
|
<Link path="/about">Home</Link>
|
|
|
|
|
</React.Suspense>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const About = () => <div>About</div>;
|
|
|
|
|
|
2021-07-30 20:56:55 +08:00
|
|
|
// Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Router.js
|
2020-07-16 00:25:27 +08:00
|
|
|
function Router({children}) {
|
|
|
|
|
const [path, setPath] = React.useState('/');
|
|
|
|
|
return (
|
|
|
|
|
<RouterContext.Provider value={{path, setPath}}>
|
|
|
|
|
{children}
|
|
|
|
|
</RouterContext.Provider>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-30 20:56:55 +08:00
|
|
|
// Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Switch.js
|
2020-07-16 00:25:27 +08:00
|
|
|
function Switch({children}) {
|
|
|
|
|
return (
|
|
|
|
|
<RouterContext.Consumer>
|
|
|
|
|
{context => {
|
|
|
|
|
let element = null;
|
|
|
|
|
React.Children.forEach(children, child => {
|
|
|
|
|
if (context.path === child.props.path) {
|
|
|
|
|
element = child.props.children;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return element ? React.cloneElement(element) : null;
|
|
|
|
|
}}
|
|
|
|
|
</RouterContext.Consumer>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-30 20:56:55 +08:00
|
|
|
// Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Route.js
|
2020-07-16 00:25:27 +08:00
|
|
|
function Route({children, path}) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const linkRef = React.createRef();
|
|
|
|
|
|
2021-07-30 20:56:55 +08:00
|
|
|
// Mimics https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js
|
2020-07-16 00:25:27 +08:00
|
|
|
function Link({children, path}) {
|
|
|
|
|
return (
|
|
|
|
|
<RouterContext.Consumer>
|
|
|
|
|
{context => {
|
|
|
|
|
return (
|
|
|
|
|
<button ref={linkRef} onClick={() => context.setPath(path)}>
|
|
|
|
|
{children}
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
</RouterContext.Consumer>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const {Simulate} = require('react-dom/test-utils');
|
|
|
|
|
|
|
|
|
|
const container = document.createElement('div');
|
2021-06-25 10:42:44 +08:00
|
|
|
utils.act(() => legacyRender(<App />, container));
|
2020-07-16 00:25:27 +08:00
|
|
|
expect(container.textContent).toBe('Home');
|
|
|
|
|
utils.act(() => store.profilerStore.startProfiling());
|
|
|
|
|
utils.act(() => Simulate.click(linkRef.current));
|
|
|
|
|
utils.act(() => store.profilerStore.stopProfiling());
|
|
|
|
|
expect(container.textContent).toBe('About');
|
|
|
|
|
});
|
2022-01-21 05:29:43 +08:00
|
|
|
|
2022-05-19 00:37:17 +08:00
|
|
|
// @reactVersion >= 18.0
|
2022-01-21 05:29:43 +08:00
|
|
|
it('components that were deleted and added to updaters during the layout phase should not crash', () => {
|
|
|
|
|
let setChildUnmounted;
|
|
|
|
|
function Child() {
|
|
|
|
|
const [, setState] = React.useState(false);
|
|
|
|
|
|
|
|
|
|
React.useLayoutEffect(() => {
|
|
|
|
|
return () => setState(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
const [childUnmounted, _setChildUnmounted] = React.useState(false);
|
|
|
|
|
setChildUnmounted = _setChildUnmounted;
|
|
|
|
|
return <>{!childUnmounted && <Child />}</>;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-01 13:13:28 +08:00
|
|
|
const root = ReactDOMClient.createRoot(document.createElement('div'));
|
2022-01-21 05:29:43 +08:00
|
|
|
utils.act(() => root.render(<App />));
|
|
|
|
|
utils.act(() => store.profilerStore.startProfiling());
|
|
|
|
|
utils.act(() => setChildUnmounted(true));
|
|
|
|
|
utils.act(() => store.profilerStore.stopProfiling());
|
|
|
|
|
|
2023-01-31 21:25:05 +08:00
|
|
|
const updaters = store.profilerStore.getCommitData(
|
|
|
|
|
store.roots[0],
|
|
|
|
|
0,
|
|
|
|
|
).updaters;
|
2022-01-21 05:29:43 +08:00
|
|
|
expect(updaters.length).toEqual(1);
|
|
|
|
|
expect(updaters[0].displayName).toEqual('App');
|
|
|
|
|
});
|
|
|
|
|
|
2022-05-19 00:37:17 +08:00
|
|
|
// @reactVersion >= 18.0
|
2022-01-21 05:29:43 +08:00
|
|
|
it('components in a deleted subtree and added to updaters during the layout phase should not crash', () => {
|
|
|
|
|
let setChildUnmounted;
|
|
|
|
|
function Child() {
|
|
|
|
|
return <GrandChild />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function GrandChild() {
|
|
|
|
|
const [, setState] = React.useState(false);
|
|
|
|
|
|
|
|
|
|
React.useLayoutEffect(() => {
|
|
|
|
|
return () => setState(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
const [childUnmounted, _setChildUnmounted] = React.useState(false);
|
|
|
|
|
setChildUnmounted = _setChildUnmounted;
|
|
|
|
|
return <>{!childUnmounted && <Child />}</>;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-01 13:13:28 +08:00
|
|
|
const root = ReactDOMClient.createRoot(document.createElement('div'));
|
2022-01-21 05:29:43 +08:00
|
|
|
utils.act(() => root.render(<App />));
|
|
|
|
|
utils.act(() => store.profilerStore.startProfiling());
|
|
|
|
|
utils.act(() => setChildUnmounted(true));
|
|
|
|
|
utils.act(() => store.profilerStore.stopProfiling());
|
|
|
|
|
|
2023-01-31 21:25:05 +08:00
|
|
|
const updaters = store.profilerStore.getCommitData(
|
|
|
|
|
store.roots[0],
|
|
|
|
|
0,
|
|
|
|
|
).updaters;
|
2022-01-21 05:29:43 +08:00
|
|
|
expect(updaters.length).toEqual(1);
|
|
|
|
|
expect(updaters[0].displayName).toEqual('App');
|
|
|
|
|
});
|
|
|
|
|
|
2022-05-19 00:37:17 +08:00
|
|
|
// @reactVersion >= 18.0
|
2022-01-21 05:29:43 +08:00
|
|
|
it('components that were deleted should not be added to updaters during the passive phase', () => {
|
|
|
|
|
let setChildUnmounted;
|
|
|
|
|
function Child() {
|
|
|
|
|
const [, setState] = React.useState(false);
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
return () => setState(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
const [childUnmounted, _setChildUnmounted] = React.useState(false);
|
|
|
|
|
setChildUnmounted = _setChildUnmounted;
|
|
|
|
|
return <>{!childUnmounted && <Child />}</>;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-01 13:13:28 +08:00
|
|
|
const root = ReactDOMClient.createRoot(document.createElement('div'));
|
2022-01-21 05:29:43 +08:00
|
|
|
utils.act(() => root.render(<App />));
|
|
|
|
|
utils.act(() => store.profilerStore.startProfiling());
|
|
|
|
|
utils.act(() => setChildUnmounted(true));
|
|
|
|
|
utils.act(() => store.profilerStore.stopProfiling());
|
|
|
|
|
|
2023-01-31 21:25:05 +08:00
|
|
|
const updaters = store.profilerStore.getCommitData(
|
|
|
|
|
store.roots[0],
|
|
|
|
|
0,
|
|
|
|
|
).updaters;
|
2022-01-21 05:29:43 +08:00
|
|
|
expect(updaters.length).toEqual(1);
|
|
|
|
|
expect(updaters[0].displayName).toEqual('App');
|
|
|
|
|
});
|
2019-05-22 21:05:25 +08:00
|
|
|
});
|