0% found this document useful (0 votes)
15 views42 pages

Preprocessdata Test

The document describes tests for preprocessing timeline data captured by the React profiler. It defines functions for creating simulated profiling data with different types of marks and entries. It also tests that preprocessing throws errors for invalid input data lacking required metadata.

Uploaded by

mahoraga
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views42 pages

Preprocessdata Test

The document describes tests for preprocessing timeline data captured by the React profiler. It defines functions for creating simulated profiling data with different types of marks and entries. It also tests that preprocessing throws errors for invalid input data lacking required metadata.

Uploaded by

mahoraga
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 42

/**

* 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.
*
* @flow
*/

'use strict';

import {normalizeCodeLocInfo} from './utils';

describe('Timeline profiler', () => {


let React;
let ReactDOM;
let ReactDOMClient;
let Scheduler;
let utils;
let assertLog;
let waitFor;

describe('User Timing API', () => {


let clearedMarks;
let featureDetectionMarkName = null;
let marks;
let setPerformanceMock;

function createUserTimingPolyfill() {
featureDetectionMarkName = null;

clearedMarks = [];
marks = [];

// Remove file-system specific bits or version-specific bits of information


from the module range marks.
function filterMarkData(markName) {
if (markName.startsWith('--react-internal-module-start')) {
return '--react-internal-module-start- at filtered (<anonymous>:0:0)';
} else if (markName.startsWith('--react-internal-module-stop')) {
return '--react-internal-module-stop- at filtered (<anonymous>:1:1)';
} else if (markName.startsWith('--react-version')) {
return '--react-version-<filtered-version>';
} else {
return markName;
}
}

// This is not a true polyfill, but it gives us enough to capture marks.


// Reference:
https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
return {
clearMarks(markName) {
markName = filterMarkData(markName);

clearedMarks.push(markName);
marks = marks.filter(mark => mark !== markName);
},
mark(markName, markOptions) {
markName = filterMarkData(markName);

if (featureDetectionMarkName === null) {


featureDetectionMarkName = markName;
}

marks.push(markName);

if (markOptions != null) {
// This is triggers the feature detection.
markOptions.startTime++;
}
},
};
}

function clearPendingMarks() {
clearedMarks.splice(0);
}

beforeEach(() => {
utils = require('./utils');
utils.beforeEachProfiling();

React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');

const InternalTestUtils = require('internal-test-utils');


assertLog = InternalTestUtils.assertLog;
waitFor = InternalTestUtils.waitFor;

setPerformanceMock =

require('react-devtools-shared/src/backend/profilingHooks').setPerformanceMock_ONLY
_FOR_TESTING;
setPerformanceMock(createUserTimingPolyfill());

const store = global.store;

// Start profiling so that data will actually be recorded.


utils.act(() => store.profilerStore.startProfiling());

global.IS_REACT_ACT_ENVIRONMENT = true;
});

afterEach(() => {
// Verify all logged marks also get cleared.
expect(marks).toHaveLength(0);

setPerformanceMock(null);
});

describe('getLanesFromTransportDecimalBitmask', () => {
let getLanesFromTransportDecimalBitmask;

beforeEach(() => {
getLanesFromTransportDecimalBitmask =
require('react-devtools-timeline/src/import-worker/preprocessData').getLanesFromTra
nsportDecimalBitmask;
});

// @reactVersion >= 18.0


it('should return array of lane numbers from bitmask string', () => {
expect(getLanesFromTransportDecimalBitmask('1')).toEqual([0]);
expect(getLanesFromTransportDecimalBitmask('512')).toEqual([9]);
expect(getLanesFromTransportDecimalBitmask('3')).toEqual([0, 1]);
expect(getLanesFromTransportDecimalBitmask('1234')).toEqual([
1, 4, 6, 7, 10,
]); // 2 + 16 + 64 + 128 + 1024
expect(
getLanesFromTransportDecimalBitmask('1073741824'), //
0b1000000000000000000000000000000
).toEqual([30]);
expect(
getLanesFromTransportDecimalBitmask('2147483647'), //
0b1111111111111111111111111111111
).toEqual(Array.from(Array(31).keys()));
});

// @reactVersion >= 18.0


it('should return empty array if laneBitmaskString is not a bitmask', () => {
expect(getLanesFromTransportDecimalBitmask('')).toEqual([]);
expect(getLanesFromTransportDecimalBitmask('hello')).toEqual([]);
expect(getLanesFromTransportDecimalBitmask('-1')).toEqual([]);
expect(getLanesFromTransportDecimalBitmask('-0')).toEqual([]);
});

// @reactVersion >= 18.0


it('should ignore lanes outside REACT_TOTAL_NUM_LANES', () => {
const REACT_TOTAL_NUM_LANES =
require('react-devtools-timeline/src/constants').REACT_TOTAL_NUM_LANES;

// Sanity check; this test may need to be updated when the no. of fiber
lanes are changed.
expect(REACT_TOTAL_NUM_LANES).toBe(31);

expect(
getLanesFromTransportDecimalBitmask(
'4294967297', // 2^32 + 1
),
).toEqual([0]);
});
});

describe('preprocessData', () => {
let preprocessData;

beforeEach(() => {
preprocessData =

require('react-devtools-timeline/src/import-worker/preprocessData').default;
});

// These should be dynamic to mimic a real profile,


// but reprooducible between test runs.
let pid = 0;
let tid = 0;
let startTime = 0;

function createUserTimingEntry(data) {
return {
pid: ++pid,
tid: ++tid,
ts: ++startTime,
...data,
};
}

function createProfilerVersionEntry() {
const SCHEDULING_PROFILER_VERSION =

require('react-devtools-timeline/src/constants').SCHEDULING_PROFILER_VERSION;
return createUserTimingEntry({
cat: 'blink.user_timing',
name: '--profiler-version-' + SCHEDULING_PROFILER_VERSION,
});
}

function createReactVersionEntry() {
return createUserTimingEntry({
cat: 'blink.user_timing',
name: '--react-version-<filtered-version>',
});
}

function createLaneLabelsEntry() {
return createUserTimingEntry({
cat: 'blink.user_timing',
name: '--react-lane-labels-
Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHy
dration,Transition,Transition,Transition,Transition,Transition,Transition,Transitio
n,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Tran
sition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Id
le,Offscreen',
});
}

function createNativeEventEntry(type, duration) {


return createUserTimingEntry({
cat: 'devtools.timeline',
name: 'EventDispatch',
args: {data: {type}},
dur: duration,
tdur: duration,
});
}

function creactCpuProfilerSample() {
return createUserTimingEntry({
args: {data: {startTime: ++startTime}},
cat: 'disabled-by-default-v8.cpu_profiler',
id: '0x1',
name: 'Profile',
ph: 'P',
});
}

function createBoilerplateEntries() {
return [
createProfilerVersionEntry(),
createReactVersionEntry(),
createLaneLabelsEntry(),
];
}

function createUserTimingData(sampleMarks) {
const cpuProfilerSample = creactCpuProfilerSample();

const randomSample = createUserTimingEntry({


dur: 100,
tdur: 200,
ph: 'X',
cat: 'disabled-by-default-devtools.timeline',
name: 'RunTask',
args: {},
});

const userTimingData = [cpuProfilerSample, randomSample];

sampleMarks.forEach(markName => {
userTimingData.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},
cat: 'blink.user_timing',
name: markName,
ph: 'R',
});
});

return userTimingData;
}

beforeEach(() => {
tid = 0;
pid = 0;
startTime = 0;
});

// @reactVersion >= 18.0


it('should throw given an empty timeline', async () => {
await expect(async () => preprocessData([])).rejects.toThrow();
});

// @reactVersion >= 18.0


it('should throw given a timeline with no Profile event', async () => {
const randomSample = createUserTimingEntry({
dur: 100,
tdur: 200,
ph: 'X',
cat: 'disabled-by-default-devtools.timeline',
name: 'RunTask',
args: {},
});

await expect(async () =>


preprocessData([randomSample]),
).rejects.toThrow();
});

// @reactVersion >= 18.0


it('should throw given a timeline without an explicit profiler version mark
nor any other React marks', async () => {
const cpuProfilerSample = creactCpuProfilerSample();

await expect(
async () => await preprocessData([cpuProfilerSample]),
).rejects.toThrow(
'Please provide profiling data from an React application',
);
});

// @reactVersion >= 18.0


it('should throw given a timeline with React scheduling marks, but without an
explicit profiler version mark', async () => {
const cpuProfilerSample = creactCpuProfilerSample();
const scheduleRenderSample = createUserTimingEntry({
cat: 'blink.user_timing',
name: '--schedule-render-512-',
});
const samples = [cpuProfilerSample, scheduleRenderSample];

await expect(async () => await preprocessData(samples)).rejects.toThrow(


'This version of profiling data is not supported',
);
});

// @reactVersion >= 18.0


it('should return empty data given a timeline with no React scheduling
profiling marks', async () => {
const cpuProfilerSample = creactCpuProfilerSample();
const randomSample = createUserTimingEntry({
dur: 100,
tdur: 200,
ph: 'X',
cat: 'disabled-by-default-devtools.timeline',
name: 'RunTask',
args: {},
});

const data = await preprocessData([


...createBoilerplateEntries(),
cpuProfilerSample,
randomSample,
]);
expect(data).toMatchInlineSnapshot(`
{
"batchUIDToMeasuresMap": Map {},
"componentMeasures": [],
"duration": 0.005,
"flamechart": [],
"internalModuleSourceToRanges": Map {},
"laneToLabelMap": Map {
0 => "Sync",
1 => "InputContinuousHydration",
2 => "InputContinuous",
3 => "DefaultHydration",
4 => "Default",
5 => "TransitionHydration",
6 => "Transition",
7 => "Transition",
8 => "Transition",
9 => "Transition",
10 => "Transition",
11 => "Transition",
12 => "Transition",
13 => "Transition",
14 => "Transition",
15 => "Transition",
16 => "Transition",
17 => "Transition",
18 => "Transition",
19 => "Transition",
20 => "Transition",
21 => "Transition",
22 => "Retry",
23 => "Retry",
24 => "Retry",
25 => "Retry",
26 => "Retry",
27 => "SelectiveHydration",
28 => "IdleHydration",
29 => "Idle",
30 => "Offscreen",
},
"laneToReactMeasureMap": Map {
0 => [],
1 => [],
2 => [],
3 => [],
4 => [],
5 => [],
6 => [],
7 => [],
8 => [],
9 => [],
10 => [],
11 => [],
12 => [],
13 => [],
14 => [],
15 => [],
16 => [],
17 => [],
18 => [],
19 => [],
20 => [],
21 => [],
22 => [],
23 => [],
24 => [],
25 => [],
26 => [],
27 => [],
28 => [],
29 => [],
30 => [],
},
"nativeEvents": [],
"networkMeasures": [],
"otherUserTimingMarks": [],
"reactVersion": "<filtered-version>",
"schedulingEvents": [],
"snapshotHeight": 0,
"snapshots": [],
"startTime": 1,
"suspenseEvents": [],
"thrownErrors": [],
}
`);
});

// @reactVersion >= 18.0


it('should process legacy data format (before lane labels were added)', async
() => {
const cpuProfilerSample = creactCpuProfilerSample();

// Data below is hard-coded based on an older profile sample.


// Should be fine since this is explicitly a legacy-format test.
const data = await preprocessData([
...createBoilerplateEntries(),
cpuProfilerSample,
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--schedule-render-512-',
}),
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--render-start-512',
}),
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--render-stop',
}),
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--commit-start-512',
}),
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--layout-effects-start-512',
}),
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--layout-effects-stop',
}),
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--commit-stop',
}),
]);
expect(data).toMatchInlineSnapshot(`
{
"batchUIDToMeasuresMap": Map {
0 => [
{
"batchUID": 0,
"depth": 0,
"duration": 0.005,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.006,
"type": "render-idle",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.001,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.006,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.008,
"type": "commit",
},
{
"batchUID": 0,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.009,
"type": "layout-effects",
},
],
},
"componentMeasures": [],
"duration": 0.011,
"flamechart": [],
"internalModuleSourceToRanges": Map {},
"laneToLabelMap": Map {
0 => "Sync",
1 => "InputContinuousHydration",
2 => "InputContinuous",
3 => "DefaultHydration",
4 => "Default",
5 => "TransitionHydration",
6 => "Transition",
7 => "Transition",
8 => "Transition",
9 => "Transition",
10 => "Transition",
11 => "Transition",
12 => "Transition",
13 => "Transition",
14 => "Transition",
15 => "Transition",
16 => "Transition",
17 => "Transition",
18 => "Transition",
19 => "Transition",
20 => "Transition",
21 => "Transition",
22 => "Retry",
23 => "Retry",
24 => "Retry",
25 => "Retry",
26 => "Retry",
27 => "SelectiveHydration",
28 => "IdleHydration",
29 => "Idle",
30 => "Offscreen",
},
"laneToReactMeasureMap": Map {
0 => [],
1 => [],
2 => [],
3 => [],
4 => [],
5 => [],
6 => [],
7 => [],
8 => [],
9 => [
{
"batchUID": 0,
"depth": 0,
"duration": 0.005,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.006,
"type": "render-idle",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.001,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.006,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.008,
"type": "commit",
},
{
"batchUID": 0,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.009,
"type": "layout-effects",
},
],
10 => [],
11 => [],
12 => [],
13 => [],
14 => [],
15 => [],
16 => [],
17 => [],
18 => [],
19 => [],
20 => [],
21 => [],
22 => [],
23 => [],
24 => [],
25 => [],
26 => [],
27 => [],
28 => [],
29 => [],
30 => [],
},
"nativeEvents": [],
"networkMeasures": [],
"otherUserTimingMarks": [],
"reactVersion": "<filtered-version>",
"schedulingEvents": [
{
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.005,
"type": "schedule-render",
"warning": null,
},
],
"snapshotHeight": 0,
"snapshots": [],
"startTime": 1,
"suspenseEvents": [],
"thrownErrors": [],
}
`);
});

it('should process a sample legacy render sequence', async () => {


utils.legacyRender(<div />, document.createElement('div'));

const data = await preprocessData([


...createBoilerplateEntries(),
...createUserTimingData(clearedMarks),
]);
expect(data).toMatchInlineSnapshot(`
{
"batchUIDToMeasuresMap": Map {
0 => [
{
"batchUID": 0,
"depth": 0,
"duration": 0.01,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.006,
"type": "render-idle",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.006,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.008,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.008,
"type": "commit",
},
{
"batchUID": 0,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.014,
"type": "layout-effects",
},
],
},
"componentMeasures": [],
"duration": 0.016,
"flamechart": [],
"internalModuleSourceToRanges": Map {
undefined => [
[
{
"columnNumber": 0,
"functionName": "filtered",
"lineNumber": 0,
"source": " at filtered (<anonymous>:0:0)",
},
{
"columnNumber": 1,
"functionName": "filtered",
"lineNumber": 1,
"source": " at filtered (<anonymous>:1:1)",
},
],
],
},
"laneToLabelMap": Map {
0 => "Sync",
1 => "InputContinuousHydration",
2 => "InputContinuous",
3 => "DefaultHydration",
4 => "Default",
5 => "TransitionHydration",
6 => "Transition",
7 => "Transition",
8 => "Transition",
9 => "Transition",
10 => "Transition",
11 => "Transition",
12 => "Transition",
13 => "Transition",
14 => "Transition",
15 => "Transition",
16 => "Transition",
17 => "Transition",
18 => "Transition",
19 => "Transition",
20 => "Transition",
21 => "Transition",
22 => "Retry",
23 => "Retry",
24 => "Retry",
25 => "Retry",
26 => "Retry",
27 => "SelectiveHydration",
28 => "IdleHydration",
29 => "Idle",
30 => "Offscreen",
},
"laneToReactMeasureMap": Map {
0 => [],
1 => [
{
"batchUID": 0,
"depth": 0,
"duration": 0.01,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.006,
"type": "render-idle",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.006,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.008,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.008,
"type": "commit",
},
{
"batchUID": 0,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.014,
"type": "layout-effects",
},
],
2 => [],
3 => [],
4 => [],
5 => [],
6 => [],
7 => [],
8 => [],
9 => [],
10 => [],
11 => [],
12 => [],
13 => [],
14 => [],
15 => [],
16 => [],
17 => [],
18 => [],
19 => [],
20 => [],
21 => [],
22 => [],
23 => [],
24 => [],
25 => [],
26 => [],
27 => [],
28 => [],
29 => [],
30 => [],
},
"nativeEvents": [],
"networkMeasures": [],
"otherUserTimingMarks": [],
"reactVersion": "<filtered-version>",
"schedulingEvents": [
{
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.005,
"type": "schedule-render",
"warning": null,
},
],
"snapshotHeight": 0,
"snapshots": [],
"startTime": 4,
"suspenseEvents": [],
"thrownErrors": [],
}
`);
});

it('should process a sample createRoot render sequence', async () => {


function App() {
const [didMount, setDidMount] = React.useState(false);
React.useEffect(() => {
if (!didMount) {
setDidMount(true);
}
});
return true;
}

const root = ReactDOMClient.createRoot(document.createElement('div'));


utils.act(() => root.render(<App />));

const data = await preprocessData([


...createBoilerplateEntries(),
...createUserTimingData(clearedMarks),
]);
expect(data).toMatchInlineSnapshot(`
{
"batchUIDToMeasuresMap": Map {
0 => [
{
"batchUID": 0,
"depth": 0,
"duration": 0.012,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.006,
"type": "render-idle",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.006,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.008,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.01,
"type": "commit",
},
{
"batchUID": 0,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.016,
"type": "layout-effects",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.004,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.019,
"type": "passive-effects",
},
],
1 => [
{
"batchUID": 1,
"depth": 0,
"duration": 0.012,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.024,
"type": "render-idle",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.024,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.008,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.028,
"type": "commit",
},
{
"batchUID": 1,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.034,
"type": "layout-effects",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.037,
"type": "passive-effects",
},
],
},
"componentMeasures": [
{
"componentName": "App",
"duration": 0.001,
"timestamp": 0.007,
"type": "render",
"warning": null,
},
{
"componentName": "App",
"duration": 0.002,
"timestamp": 0.02,
"type": "passive-effect-mount",
"warning": null,
},
{
"componentName": "App",
"duration": 0.001,
"timestamp": 0.025,
"type": "render",
"warning": null,
},
{
"componentName": "App",
"duration": 0.001,
"timestamp": 0.038,
"type": "passive-effect-mount",
"warning": null,
},
],
"duration": 0.04,
"flamechart": [],
"internalModuleSourceToRanges": Map {
undefined => [
[
{
"columnNumber": 0,
"functionName": "filtered",
"lineNumber": 0,
"source": " at filtered (<anonymous>:0:0)",
},
{
"columnNumber": 1,
"functionName": "filtered",
"lineNumber": 1,
"source": " at filtered (<anonymous>:1:1)",
},
],
],
},
"laneToLabelMap": Map {
0 => "Sync",
1 => "InputContinuousHydration",
2 => "InputContinuous",
3 => "DefaultHydration",
4 => "Default",
5 => "TransitionHydration",
6 => "Transition",
7 => "Transition",
8 => "Transition",
9 => "Transition",
10 => "Transition",
11 => "Transition",
12 => "Transition",
13 => "Transition",
14 => "Transition",
15 => "Transition",
16 => "Transition",
17 => "Transition",
18 => "Transition",
19 => "Transition",
20 => "Transition",
21 => "Transition",
22 => "Retry",
23 => "Retry",
24 => "Retry",
25 => "Retry",
26 => "Retry",
27 => "SelectiveHydration",
28 => "IdleHydration",
29 => "Idle",
30 => "Offscreen",
},
"laneToReactMeasureMap": Map {
0 => [],
1 => [],
2 => [],
3 => [],
4 => [],
5 => [
{
"batchUID": 0,
"depth": 0,
"duration": 0.012,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.006,
"type": "render-idle",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.006,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.008,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.01,
"type": "commit",
},
{
"batchUID": 0,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.016,
"type": "layout-effects",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.004,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.019,
"type": "passive-effects",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.012,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.024,
"type": "render-idle",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.024,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.008,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.028,
"type": "commit",
},
{
"batchUID": 1,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.034,
"type": "layout-effects",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.037,
"type": "passive-effects",
},
],
6 => [],
7 => [],
8 => [],
9 => [],
10 => [],
11 => [],
12 => [],
13 => [],
14 => [],
15 => [],
16 => [],
17 => [],
18 => [],
19 => [],
20 => [],
21 => [],
22 => [],
23 => [],
24 => [],
25 => [],
26 => [],
27 => [],
28 => [],
29 => [],
30 => [],
},
"nativeEvents": [],
"networkMeasures": [],
"otherUserTimingMarks": [],
"reactVersion": "<filtered-version>",
"schedulingEvents": [
{
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.005,
"type": "schedule-render",
"warning": null,
},
{
"componentName": "App",
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.021,
"type": "schedule-state-update",
"warning": null,
},
],
"snapshotHeight": 0,
"snapshots": [],
"startTime": 4,
"suspenseEvents": [],
"thrownErrors": [],
}
`);
});

// @reactVersion >= 18.0


it('should error if events and measures are incomplete', async () => {
const container = document.createElement('div');
utils.legacyRender(<div />, container);

const invalidMarks = clearedMarks.filter(


mark => !mark.includes('render-stop'),
);
const invalidUserTimingData = createUserTimingData(invalidMarks);

const error = jest.spyOn(console, 'error').mockImplementation(() => {});


preprocessData([
...createBoilerplateEntries(),
...invalidUserTimingData,
]);
expect(error).toHaveBeenCalled();
});

// @reactVersion >= 18.0


it('should error if work is completed without being started', async () => {
const container = document.createElement('div');
utils.legacyRender(<div />, container);

const invalidMarks = clearedMarks.filter(


mark => !mark.includes('render-start'),
);
const invalidUserTimingData = createUserTimingData(invalidMarks);
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
preprocessData([
...createBoilerplateEntries(),
...invalidUserTimingData,
]);
expect(error).toHaveBeenCalled();
});

// @reactVersion >= 18.0


it('should populate other user timing marks', async () => {
const userTimingData = createUserTimingData([]);
userTimingData.push(
createUserTimingEntry({
args: {},
cat: 'blink.user_timing',
id: '0xcdf75f7c',
name: 'VCWithoutImage: root',
ph: 'n',
scope: 'blink.user_timing',
}),
);
userTimingData.push(
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--a-mark-that-looks-like-one-of-ours',
ph: 'R',
}),
);
userTimingData.push(
createUserTimingEntry({
cat: 'blink.user_timing',
name: 'Some other mark',
ph: 'R',
}),
);

const data = await preprocessData([


...createBoilerplateEntries(),
...userTimingData,
]);
expect(data.otherUserTimingMarks).toMatchInlineSnapshot(`
[
{
"name": "VCWithoutImage: root",
"timestamp": 0.003,
},
{
"name": "--a-mark-that-looks-like-one-of-ours",
"timestamp": 0.004,
},
{
"name": "Some other mark",
"timestamp": 0.005,
},
]
`);
});
// @reactVersion >= 18.0
it('should include a suspended resource "displayName" if one is set', async
() => {
let promise = null;
let resolvedValue = null;
function readValue(value) {
if (resolvedValue !== null) {
return resolvedValue;
} else if (promise === null) {
promise = Promise.resolve(true).then(() => {
resolvedValue = value;
});
promise.displayName = 'Testing displayName';
}
throw promise;
}

function Component() {
const value = readValue(123);
return value;
}

const testMarks = [creactCpuProfilerSample()];

const root = ReactDOMClient.createRoot(document.createElement('div'));


utils.act(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component />
</React.Suspense>,
),
);

testMarks.push(...createUserTimingData(clearedMarks));

let data;
await utils.actAsync(async () => {
data = await preprocessData(testMarks);
});
expect(data.suspenseEvents).toHaveLength(1);
expect(data.suspenseEvents[0].promiseName).toBe('Testing displayName');
});

describe('warnings', () => {
describe('long event handlers', () => {
// @reactVersion >= 18.0
it('should not warn when React scedules a (sync) update inside of a short
event handler', async () => {
function App() {
return null;
}

const testMarks = [
creactCpuProfilerSample(),
...createBoilerplateEntries(),
createNativeEventEntry('click', 5),
];

clearPendingMarks();
utils.legacyRender(<App />, document.createElement('div'));

testMarks.push(...createUserTimingData(clearedMarks));

const data = await preprocessData(testMarks);


const event = data.nativeEvents.find(({type}) => type === 'click');
expect(event.warning).toBe(null);
});

// @reactVersion >= 18.0


it('should not warn about long events if the cause was non-React
JavaScript', async () => {
function App() {
return null;
}

const testMarks = [
creactCpuProfilerSample(),
...createBoilerplateEntries(),
createNativeEventEntry('click', 25000),
];

startTime += 2000;

clearPendingMarks();

utils.legacyRender(<App />, document.createElement('div'));

testMarks.push(...createUserTimingData(clearedMarks));

const data = await preprocessData(testMarks);


const event = data.nativeEvents.find(({type}) => type === 'click');
expect(event.warning).toBe(null);
});

// @reactVersion >= 18.0


it('should warn when React scedules a long (sync) update inside of an
event', async () => {
function App() {
return null;
}

const testMarks = [
creactCpuProfilerSample(),
...createBoilerplateEntries(),
createNativeEventEntry('click', 25000),
];

clearPendingMarks();

utils.legacyRender(<App />, document.createElement('div'));

clearedMarks.forEach(markName => {
if (markName === '--render-stop') {
// Fake a long running render
startTime += 20000;
}
testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},
cat: 'blink.user_timing',
name: markName,
ph: 'R',
});
});

const data = await preprocessData(testMarks);


const event = data.nativeEvents.find(({type}) => type === 'click');
expect(event.warning).toMatchInlineSnapshot(
`"An event handler scheduled a big update with React. Consider using
the Transition API to defer some of this work."`,
);
});

// @reactVersion >= 18.2


it('should not warn when React finishes a previously long (async) update
with a short (sync) update inside of an event', async () => {
function Yield({id, value}) {
Scheduler.log(`${id}:${value}`);
return null;
}

const testMarks = [
creactCpuProfilerSample(),
...createBoilerplateEntries(),
];

// Advance the clock by some arbitrary amount.


startTime += 50000;

const root = ReactDOMClient.createRoot(


document.createElement('div'),
);

// Temporarily turn off the act environment, since we're intentionally


using Scheduler instead.
global.IS_REACT_ACT_ENVIRONMENT = false;
React.startTransition(() => {
// Start rendering an async update (but don't finish).
root.render(
<>
<Yield id="A" value={1} />
<Yield id="B" value={1} />
</>,
);
});

await waitFor(['A:1']);

testMarks.push(...createUserTimingData(clearedMarks));
clearPendingMarks();

// Advance the clock some more to make the pending React update seem
long.
startTime += 20000;

// Fake a long "click" event in the middle


// and schedule a sync update that will also flush the previous work.
testMarks.push(createNativeEventEntry('click', 25000));
ReactDOM.flushSync(() => {
root.render(
<>
<Yield id="A" value={2} />
<Yield id="B" value={2} />
</>,
);
});

assertLog(['A:2', 'B:2']);

testMarks.push(...createUserTimingData(clearedMarks));

const data = await preprocessData(testMarks);


const event = data.nativeEvents.find(({type}) => type === 'click');
expect(event.warning).toBe(null);
});
});

describe('nested updates', () => {


// @reactVersion >= 18.2
it('should not warn about short nested (state) updates during layout
effects', async () => {
function Component() {
const [didMount, setDidMount] = React.useState(false);
Scheduler.log(`Component ${didMount ? 'update' : 'mount'}`);
React.useLayoutEffect(() => {
setDidMount(true);
}, []);
return didMount;
}

const root = ReactDOMClient.createRoot(


document.createElement('div'),
);
utils.act(() => {
root.render(<Component />);
});

assertLog(['Component mount', 'Component update']);

const data = await preprocessData([


...createBoilerplateEntries(),
...createUserTimingData(clearedMarks),
]);

const event = data.schedulingEvents.find(


({type}) => type === 'schedule-state-update',
);
expect(event.warning).toBe(null);
});

// @reactVersion >= 18.2


it('should not warn about short (forced) updates during layout effects',
async () => {
class Component extends React.Component {
_didMount: boolean = false;
componentDidMount() {
this._didMount = true;
this.forceUpdate();
}
render() {
Scheduler.log(
`Component ${this._didMount ? 'update' : 'mount'}`,
);
return null;
}
}

const root = ReactDOMClient.createRoot(


document.createElement('div'),
);
utils.act(() => {
root.render(<Component />);
});

assertLog(['Component mount', 'Component update']);

const data = await preprocessData([


...createBoilerplateEntries(),
...createUserTimingData(clearedMarks),
]);

const event = data.schedulingEvents.find(


({type}) => type === 'schedule-force-update',
);
expect(event.warning).toBe(null);
});

// This is temporarily disabled because the warning doesn't work


// with useDeferredValue
it.skip('should warn about long nested (state) updates during layout
effects', async () => {
function Component() {
const [didMount, setDidMount] = React.useState(false);
Scheduler.log(`Component ${didMount ? 'update' : 'mount'}`);
// Fake a long render
startTime += 20000;
React.useLayoutEffect(() => {
setDidMount(true);
}, []);
return didMount;
}

const cpuProfilerSample = creactCpuProfilerSample();

const root = ReactDOMClient.createRoot(


document.createElement('div'),
);
utils.act(() => {
root.render(<Component />);
});
assertLog(['Component mount', 'Component update']);

const testMarks = [];


clearedMarks.forEach(markName => {
if (markName === '--component-render-start-Component') {
// Fake a long running render
startTime += 20000;
}

testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},
cat: 'blink.user_timing',
name: markName,
ph: 'R',
});
});

const data = await preprocessData([


cpuProfilerSample,
...createBoilerplateEntries(),
...testMarks,
]);

const event = data.schedulingEvents.find(


({type}) => type === 'schedule-state-update',
);
expect(event.warning).toMatchInlineSnapshot(
`"A big nested update was scheduled during layout. Nested updates
require React to re-render synchronously before the browser can paint. Consider
delaying this update by moving it to a passive effect (useEffect)."`,
);
});

// This is temporarily disabled because the warning doesn't work


// with useDeferredValue
it.skip('should warn about long nested (forced) updates during layout
effects', async () => {
class Component extends React.Component {
_didMount: boolean = false;
componentDidMount() {
this._didMount = true;
this.forceUpdate();
}
render() {
Scheduler.log(
`Component ${this._didMount ? 'update' : 'mount'}`,
);
return null;
}
}

const cpuProfilerSample = creactCpuProfilerSample();

const root = ReactDOMClient.createRoot(


document.createElement('div'),
);
utils.act(() => {
root.render(<Component />);
});

assertLog(['Component mount', 'Component update']);

const testMarks = [];


clearedMarks.forEach(markName => {
if (markName === '--component-render-start-Component') {
// Fake a long running render
startTime += 20000;
}

testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},
cat: 'blink.user_timing',
name: markName,
ph: 'R',
});
});

const data = await preprocessData([


cpuProfilerSample,
...createBoilerplateEntries(),
...testMarks,
]);

const event = data.schedulingEvents.find(


({type}) => type === 'schedule-force-update',
);
expect(event.warning).toMatchInlineSnapshot(
`"A big nested update was scheduled during layout. Nested updates
require React to re-render synchronously before the browser can paint. Consider
delaying this update by moving it to a passive effect (useEffect)."`,
);
});

// @reactVersion >= 18.2


it('should not warn about transition updates scheduled during commit
phase', async () => {
function Component() {
const [value, setValue] = React.useState(0);
// eslint-disable-next-line no-unused-vars
const [isPending, startTransition] = React.useTransition();

Scheduler.log(`Component rendered with value ${value}`);

// Fake a long render


if (value !== 0) {
Scheduler.log('Long render');
startTime += 20000;
}

React.useLayoutEffect(() => {
startTransition(() => {
setValue(1);
});
}, []);

return value;
}

const cpuProfilerSample = creactCpuProfilerSample();

const root = ReactDOMClient.createRoot(


document.createElement('div'),
);
utils.act(() => {
root.render(<Component />);
});

assertLog([
'Component rendered with value 0',
'Component rendered with value 0',
'Component rendered with value 1',
'Long render',
]);

const testMarks = [];


clearedMarks.forEach(markName => {
if (markName === '--component-render-start-Component') {
// Fake a long running render
startTime += 20000;
}

testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},
cat: 'blink.user_timing',
name: markName,
ph: 'R',
});
});

const data = await preprocessData([


cpuProfilerSample,
...createBoilerplateEntries(),
...testMarks,
]);

data.schedulingEvents.forEach(event => {
expect(event.warning).toBeNull();
});
});

// This is temporarily disabled because the warning doesn't work


// with useDeferredValue
it.skip('should not warn about deferred value updates scheduled during
commit phase', async () => {
function Component() {
const [value, setValue] = React.useState(0);
const deferredValue = React.useDeferredValue(value);
Scheduler.log(
`Component rendered with value ${value} and deferredValue $
{deferredValue}`,
);

// Fake a long render


if (deferredValue !== 0) {
Scheduler.log('Long render');
startTime += 20000;
}

React.useLayoutEffect(() => {
setValue(1);
}, []);

return value + deferredValue;


}

const cpuProfilerSample = creactCpuProfilerSample();

const root = ReactDOMClient.createRoot(


document.createElement('div'),
);
utils.act(() => {
root.render(<Component />);
});

assertLog([
'Component rendered with value 0 and deferredValue 0',
'Component rendered with value 1 and deferredValue 0',
'Component rendered with value 1 and deferredValue 1',
'Long render',
]);

const testMarks = [];


clearedMarks.forEach(markName => {
if (markName === '--component-render-start-Component') {
// Fake a long running render
startTime += 20000;
}

testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},
cat: 'blink.user_timing',
name: markName,
ph: 'R',
});
});

const data = await preprocessData([


cpuProfilerSample,
...createBoilerplateEntries(),
...testMarks,
]);

data.schedulingEvents.forEach(event => {
expect(event.warning).toBeNull();
});
});
});

describe('errors thrown while rendering', () => {


// @reactVersion >= 18.0
it('shoult parse Errors thrown during render', async () => {
jest.spyOn(console, 'error');

class ErrorBoundary extends React.Component {


state = {error: null};
componentDidCatch(error) {
this.setState({error});
}
render() {
if (this.state.error) {
return null;
}
return this.props.children;
}
}

function ExampleThatThrows() {
throw Error('Expected error');
}

const testMarks = [creactCpuProfilerSample()];

// Mount and commit the app


const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
utils.act(() =>
root.render(
<ErrorBoundary>
<ExampleThatThrows />
</ErrorBoundary>,
),
);

testMarks.push(...createUserTimingData(clearedMarks));

const data = await preprocessData(testMarks);


expect(data.thrownErrors).toHaveLength(2);
expect(data.thrownErrors[0].message).toMatchInlineSnapshot(
'"Expected error"',
);
});
});

describe('suspend during an update', () => {


// This also tests an edge case where a component suspends while
profiling
// before the first commit is logged (so the lane-to-labels map will not
yet exist).
// @reactVersion >= 18.2
it('should warn about suspending during an update', async () => {
let promise = null;
let resolvedValue = null;
function readValue(value) {
if (resolvedValue !== null) {
return resolvedValue;
} else if (promise === null) {
promise = Promise.resolve(true).then(() => {
resolvedValue = value;
});
}
throw promise;
}

function Component({shouldSuspend}) {
Scheduler.log(`Component ${shouldSuspend}`);
if (shouldSuspend) {
readValue(123);
}
return null;
}

// Mount and commit the app


const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
utils.act(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component shouldSuspend={false} />
</React.Suspense>,
),
);

const testMarks = [creactCpuProfilerSample()];

// Start profiling and suspend during a render.


utils.act(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component shouldSuspend={true} />
</React.Suspense>,
),
);

testMarks.push(...createUserTimingData(clearedMarks));

let data;
await utils.actAsync(async () => {
data = await preprocessData(testMarks);
});
expect(data.suspenseEvents).toHaveLength(1);
expect(data.suspenseEvents[0].warning).toMatchInlineSnapshot(
`"A component suspended during an update which caused a fallback to
be shown. Consider using the Transition API to avoid hiding components after
they've been mounted."`,
);
});

// @reactVersion >= 18.2


it('should not warn about suspending during an transition', async () => {
let promise = null;
let resolvedValue = null;
function readValue(value) {
if (resolvedValue !== null) {
return resolvedValue;
} else if (promise === null) {
promise = Promise.resolve(true).then(() => {
resolvedValue = value;
});
}
throw promise;
}

function Component({shouldSuspend}) {
Scheduler.log(`Component ${shouldSuspend}`);
if (shouldSuspend) {
readValue(123);
}
return null;
}

// Mount and commit the app


const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
utils.act(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component shouldSuspend={false} />
</React.Suspense>,
),
);

const testMarks = [creactCpuProfilerSample()];

// Start profiling and suspend during a render.


await utils.actAsync(async () =>
React.startTransition(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component shouldSuspend={true} />
</React.Suspense>,
),
),
);

testMarks.push(...createUserTimingData(clearedMarks));

let data;
await utils.actAsync(async () => {
data = await preprocessData(testMarks);
});
expect(data.suspenseEvents).toHaveLength(1);
expect(data.suspenseEvents[0].warning).toBe(null);
});
});
});

// TODO: Add test for snapshot base64 parsing


// TODO: Add test for flamechart parsing
});
});

// Note the in-memory tests vary slightly (e.g. timestamp values, lane numbers)
from the above tests.
// That's okay; the important thing is the lane-to-label matches the subsequent
events/measures.
describe('DevTools hook (in memory)', () => {
let store;

beforeEach(() => {
utils = require('./utils');
utils.beforeEachProfiling();

React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');

store = global.store;

// Start profiling so that data will actually be recorded.


utils.act(() => store.profilerStore.startProfiling());

global.IS_REACT_ACT_ENVIRONMENT = true;
});

it('should process a sample legacy render sequence', async () => {


utils.legacyRender(<div />, document.createElement('div'));
utils.act(() => store.profilerStore.stopProfiling());

const data = store.profilerStore.profilingData?.timelineData;


expect(data).toHaveLength(1);
const timelineData = data[0];
expect(timelineData).toMatchInlineSnapshot(`
{
"batchUIDToMeasuresMap": Map {
1 => [
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "render-idle",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "commit",
},
{
"batchUID": 1,
"depth": 1,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "layout-effects",
},
],
},
"componentMeasures": [],
"duration": 20,
"flamechart": [],
"internalModuleSourceToRanges": Map {},
"laneToLabelMap": Map {
1 => "SyncHydrationLane",
2 => "Sync",
4 => "InputContinuousHydration",
8 => "InputContinuous",
16 => "DefaultHydration",
32 => "Default",
64 => "TransitionHydration",
128 => "Transition",
256 => "Transition",
512 => "Transition",
1024 => "Transition",
2048 => "Transition",
4096 => "Transition",
8192 => "Transition",
16384 => "Transition",
32768 => "Transition",
65536 => "Transition",
131072 => "Transition",
262144 => "Transition",
524288 => "Transition",
1048576 => "Transition",
2097152 => "Transition",
4194304 => "Retry",
8388608 => "Retry",
16777216 => "Retry",
33554432 => "Retry",
67108864 => "SelectiveHydration",
134217728 => "IdleHydration",
268435456 => "Idle",
536870912 => "Offscreen",
1073741824 => "Deferred",
},
"laneToReactMeasureMap": Map {
1 => [],
2 => [
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "render-idle",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "commit",
},
{
"batchUID": 1,
"depth": 1,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "layout-effects",
},
],
4 => [],
8 => [],
16 => [],
32 => [],
64 => [],
128 => [],
256 => [],
512 => [],
1024 => [],
2048 => [],
4096 => [],
8192 => [],
16384 => [],
32768 => [],
65536 => [],
131072 => [],
262144 => [],
524288 => [],
1048576 => [],
2097152 => [],
4194304 => [],
8388608 => [],
16777216 => [],
33554432 => [],
67108864 => [],
134217728 => [],
268435456 => [],
536870912 => [],
1073741824 => [],
},
"nativeEvents": [],
"networkMeasures": [],
"otherUserTimingMarks": [],
"reactVersion": "<filtered-version>",
"schedulingEvents": [
{
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "schedule-render",
"warning": null,
},
],
"snapshotHeight": 0,
"snapshots": [],
"startTime": -10,
"suspenseEvents": [],
"thrownErrors": [],
}
`);
});

it('should process a sample createRoot render sequence', async () => {


function App() {
const [didMount, setDidMount] = React.useState(false);
React.useEffect(() => {
if (!didMount) {
setDidMount(true);
}
});
return true;
}

const root = ReactDOMClient.createRoot(document.createElement('div'));


utils.act(() => root.render(<App />));
utils.act(() => store.profilerStore.stopProfiling());

const data = store.profilerStore.profilingData?.timelineData;


expect(data).toHaveLength(1);
const timelineData = data[0];

// normalize the location for component stack source


// for snapshot testing
timelineData.schedulingEvents.forEach(event => {
if (event.componentStack) {
event.componentStack = normalizeCodeLocInfo(event.componentStack);
}
});

expect(timelineData).toMatchInlineSnapshot(`
{
"batchUIDToMeasuresMap": Map {
1 => [
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render-idle",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "commit",
},
{
"batchUID": 1,
"depth": 1,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "layout-effects",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "passive-effects",
},
],
2 => [
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render-idle",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "commit",
},
{
"batchUID": 2,
"depth": 1,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "layout-effects",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "passive-effects",
},
],
},
"componentMeasures": [
{
"componentName": "App",
"duration": 0,
"timestamp": 10,
"type": "render",
"warning": null,
},
{
"componentName": "App",
"duration": 0,
"timestamp": 10,
"type": "passive-effect-mount",
"warning": null,
},
{
"componentName": "App",
"duration": 0,
"timestamp": 10,
"type": "render",
"warning": null,
},
{
"componentName": "App",
"duration": 0,
"timestamp": 10,
"type": "passive-effect-mount",
"warning": null,
},
],
"duration": 20,
"flamechart": [],
"internalModuleSourceToRanges": Map {},
"laneToLabelMap": Map {
1 => "SyncHydrationLane",
2 => "Sync",
4 => "InputContinuousHydration",
8 => "InputContinuous",
16 => "DefaultHydration",
32 => "Default",
64 => "TransitionHydration",
128 => "Transition",
256 => "Transition",
512 => "Transition",
1024 => "Transition",
2048 => "Transition",
4096 => "Transition",
8192 => "Transition",
16384 => "Transition",
32768 => "Transition",
65536 => "Transition",
131072 => "Transition",
262144 => "Transition",
524288 => "Transition",
1048576 => "Transition",
2097152 => "Transition",
4194304 => "Retry",
8388608 => "Retry",
16777216 => "Retry",
33554432 => "Retry",
67108864 => "SelectiveHydration",
134217728 => "IdleHydration",
268435456 => "Idle",
536870912 => "Offscreen",
1073741824 => "Deferred",
},
"laneToReactMeasureMap": Map {
1 => [],
2 => [],
4 => [],
8 => [],
16 => [],
32 => [
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render-idle",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "commit",
},
{
"batchUID": 1,
"depth": 1,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "layout-effects",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "passive-effects",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render-idle",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "commit",
},
{
"batchUID": 2,
"depth": 1,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "layout-effects",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "passive-effects",
},
],
64 => [],
128 => [],
256 => [],
512 => [],
1024 => [],
2048 => [],
4096 => [],
8192 => [],
16384 => [],
32768 => [],
65536 => [],
131072 => [],
262144 => [],
524288 => [],
1048576 => [],
2097152 => [],
4194304 => [],
8388608 => [],
16777216 => [],
33554432 => [],
67108864 => [],
134217728 => [],
268435456 => [],
536870912 => [],
1073741824 => [],
},
"nativeEvents": [],
"networkMeasures": [],
"otherUserTimingMarks": [],
"reactVersion": "<filtered-version>",
"schedulingEvents": [
{
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "schedule-render",
"warning": null,
},
{
"componentName": "App",
"componentStack": "
in App (at **)",
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "schedule-state-update",
"warning": null,
},
],
"snapshotHeight": 0,
"snapshots": [],
"startTime": -10,
"suspenseEvents": [],
"thrownErrors": [],
}
`);
});
});
});

You might also like