@samitouri / QOS-React / commits / 58d17912e8

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;