Fix failing React DevTools regression tests (#34585)
Sebastian "Sebbie" Silbermann committed
Sep 24, 2025 at 19:08 UTC
58d17912e89b56067eeba08619fd7f7b6c813519
2 files changed
+126
-92
packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
+101
-89
@@ -130,24 +130,28 @@ describe('Timeline profiler', () => {
130
// @reactVersion <= 18.2
131
// @reactVersion >= 18.0
132
it('should mark sync render without suspends or state updates', () => {
133
+ utils.act(() => store.profilerStore.startProfiling());
134
legacyRender(<div />);
135
+ utils.act(() => store.profilerStore.stopProfiling());
136
137
expect(registeredMarks).toMatchInlineSnapshot(`
136
- [
137
- "--schedule-render-1",
138
- "--render-start-1",
139
- "--render-stop",
140
- "--commit-start-1",
141
- "--react-version-<filtered-version>",
142
- "--profiler-version-1",
143
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
144
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
145
- "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
146
- "--layout-effects-start-1",
147
- "--layout-effects-stop",
148
- "--commit-stop",
149
- ]
150
- `);
138
+ [
139
+ "--react-internal-module-start- at filtered (<anonymous>:0:0)",
140
+ "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
141
+ "--schedule-render-1",
142
+ "--render-start-1",
143
+ "--render-stop",
144
+ "--commit-start-1",
145
+ "--react-version-<filtered-version>",
146
+ "--profiler-version-1",
147
+ "--react-internal-module-start- at filtered (<anonymous>:0:0)",
148
+ "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
149
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
150
+ "--layout-effects-start-1",
151
+ "--layout-effects-stop",
152
+ "--commit-stop",
153
+ ]
154
+ `);
155
});
156
157
// TODO(hoxyq): investigate why running this test with React 18 fails
@@ -260,46 +264,50 @@ describe('Timeline profiler', () => {
264
throw Error('Expected error');
265
}
266
267
+ utils.act(() => store.profilerStore.startProfiling());
268
legacyRender(
269
<ErrorBoundary>
270
<ExampleThatThrows />
271
</ErrorBoundary>,
272
);
273
+ utils.act(() => store.profilerStore.stopProfiling());
274
275
expect(registeredMarks).toMatchInlineSnapshot(`
270
- [
271
- "--schedule-render-1",
272
- "--render-start-1",
273
- "--component-render-start-ErrorBoundary",
274
- "--component-render-stop",
275
- "--component-render-start-ExampleThatThrows",
276
- "--component-render-start-ExampleThatThrows",
277
- "--component-render-stop",
278
- "--error-ExampleThatThrows-mount-Expected error",
279
- "--render-stop",
280
- "--commit-start-1",
281
- "--react-version-<filtered-version>",
282
- "--profiler-version-1",
283
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
284
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
285
- "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
286
- "--layout-effects-start-1",
287
- "--schedule-state-update-1-ErrorBoundary",
288
- "--layout-effects-stop",
289
- "--commit-stop",
290
- "--render-start-1",
291
- "--component-render-start-ErrorBoundary",
292
- "--component-render-stop",
293
- "--render-stop",
294
- "--commit-start-1",
295
- "--react-version-<filtered-version>",
296
- "--profiler-version-1",
297
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
298
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
299
- "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
300
- "--commit-stop",
301
- ]
302
- `);
276
+ [
277
+ "--react-internal-module-start- at filtered (<anonymous>:0:0)",
278
+ "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
279
+ "--schedule-render-1",
280
+ "--render-start-1",
281
+ "--component-render-start-ErrorBoundary",
282
+ "--component-render-stop",
283
+ "--component-render-start-ExampleThatThrows",
284
+ "--component-render-start-ExampleThatThrows",
285
+ "--component-render-stop",
286
+ "--error-ExampleThatThrows-mount-Expected error",
287
+ "--render-stop",
288
+ "--commit-start-1",
289
+ "--react-version-<filtered-version>",
290
+ "--profiler-version-1",
291
+ "--react-internal-module-start- at filtered (<anonymous>:0:0)",
292
+ "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
293
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
294
+ "--layout-effects-start-1",
295
+ "--schedule-state-update-1-ErrorBoundary",
296
+ "--layout-effects-stop",
297
+ "--commit-stop",
298
+ "--render-start-1",
299
+ "--component-render-start-ErrorBoundary",
300
+ "--component-render-stop",
301
+ "--render-stop",
302
+ "--commit-start-1",
303
+ "--react-version-<filtered-version>",
304
+ "--profiler-version-1",
305
+ "--react-internal-module-start- at filtered (<anonymous>:0:0)",
306
+ "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
307
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
308
+ "--commit-stop",
309
+ ]
310
+ `);
311
});
312
});
313
@@ -1095,24 +1103,28 @@ describe('Timeline profiler', () => {
1103
// @reactVersion <= 18.2
1104
// @reactVersion >= 18.0
1105
it('regression test SyncLane', () => {
1106
+ utils.act(() => store.profilerStore.startProfiling());
1107
legacyRender(<div />);
1108
+ utils.act(() => store.profilerStore.stopProfiling());
1109
1110
expect(registeredMarks).toMatchInlineSnapshot(`
1101
- [
1102
- "--schedule-render-1",
1103
- "--render-start-1",
1104
- "--render-stop",
1105
- "--commit-start-1",
1106
- "--react-version-<filtered-version>",
1107
- "--profiler-version-1",
1108
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1109
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1110
- "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
1111
- "--layout-effects-start-1",
1112
- "--layout-effects-stop",
1113
- "--commit-stop",
1114
- ]
1115
- `);
1111
+ [
1112
+ "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1113
+ "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1114
+ "--schedule-render-1",
1115
+ "--render-start-1",
1116
+ "--render-stop",
1117
+ "--commit-start-1",
1118
+ "--react-version-<filtered-version>",
1119
+ "--profiler-version-1",
1120
+ "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1121
+ "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1122
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
1123
+ "--layout-effects-start-1",
1124
+ "--layout-effects-stop",
1125
+ "--commit-stop",
1126
+ ]
1127
+ `);
1128
});
1129
});
1130
@@ -1432,19 +1444,19 @@ describe('Timeline profiler', () => {
1444
expect(timelineData.suspenseEvents).toHaveLength(1);
1445
const suspenseEvent = timelineData.suspenseEvents[0];
1446
expect(suspenseEvent).toMatchInlineSnapshot(`
1435
- {
1436
- "componentName": "Example",
1437
- "depth": 0,
1438
- "duration": 10,
1439
- "id": "0",
1440
- "phase": "mount",
1441
- "promiseName": "",
1442
- "resolution": "resolved",
1443
- "timestamp": 10,
1444
- "type": "suspense",
1445
- "warning": null,
1446
- }
1447
- `);
1447
+ {
1448
+ "componentName": "Example",
1449
+ "depth": 0,
1450
+ "duration": 0,
1451
+ "id": "0",
1452
+ "phase": "mount",
1453
+ "promiseName": "",
1454
+ "resolution": "unresolved",
1455
+ "timestamp": 10,
1456
+ "type": "suspense",
1457
+ "warning": null,
1458
+ }
1459
+ `);
1460
1461
// There should be two batches of renders: Suspeneded and resolved.
1462
expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
@@ -1490,19 +1502,19 @@ describe('Timeline profiler', () => {
1502
expect(timelineData.suspenseEvents).toHaveLength(1);
1503
const suspenseEvent = timelineData.suspenseEvents[0];
1504
expect(suspenseEvent).toMatchInlineSnapshot(`
1493
- {
1494
- "componentName": "Example",
1495
- "depth": 0,
1496
- "duration": 10,
1497
- "id": "0",
1498
- "phase": "mount",
1499
- "promiseName": "",
1500
- "resolution": "rejected",
1501
- "timestamp": 10,
1502
- "type": "suspense",
1503
- "warning": null,
1504
- }
1505
- `);
1505
+ {
1506
+ "componentName": "Example",
1507
+ "depth": 0,
1508
+ "duration": 0,
1509
+ "id": "0",
1510
+ "phase": "mount",
1511
+ "promiseName": "",
1512
+ "resolution": "unresolved",
1513
+ "timestamp": 10,
1514
+ "type": "suspense",
1515
+ "warning": null,
1516
+ }
1517
+ `);
1518
1519
// There should be two batches of renders: Suspeneded and resolved.
1520
expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
packages/react-devtools-shared/src/__tests__/preprocessData-test.js
+25
-3
@@ -111,9 +111,31 @@ describe('Timeline profiler', () => {
111
ReactDOMClient = require('react-dom/client');
112
Scheduler = require('scheduler');
113
114
- const InternalTestUtils = require('internal-test-utils');
115
- assertLog = InternalTestUtils.assertLog;
116
- waitFor = InternalTestUtils.waitFor;
114
+ if (typeof Scheduler.log !== 'function') {
115
+ // backwards compat for older scheduler versions
116
+ Scheduler.log = Scheduler.unstable_yieldValue;
117
+ Scheduler.unstable_clearLog = Scheduler.unstable_clearYields;
118
+ const InternalTestUtils = require('internal-test-utils');
119
+ assertLog = InternalTestUtils.assertLog;
120
+
121
+ // polyfill waitFor as Scheduler.toFlushAndYieldThrough
122
+ waitFor = expectedYields => {
123
+ let actualYields = Scheduler.unstable_clearYields();
124
+ if (actualYields.length !== 0) {
125
+ throw new Error(
126
+ 'Log of yielded values is not empty. ' +
127
+ 'Call expect(Scheduler).toHaveYielded(...) first.',
128
+ );
129
+ }
130
+ Scheduler.unstable_flushNumberOfYields(expectedYields.length);
131
+ actualYields = Scheduler.unstable_clearYields();
132
+ expect(actualYields).toEqual(expectedYields);
133
+ };
134
+ } else {
135
+ const InternalTestUtils = require('internal-test-utils');
136
+ assertLog = InternalTestUtils.assertLog;
137
+ waitFor = InternalTestUtils.waitFor;
138
+ }
139
140
setPerformanceMock =
141
require('react-devtools-shared/src/backend/profilingHooks').setPerformanceMock_ONLY_FOR_TESTING;