From 3fa927b6746a8666309ba1dc945f022b55b94b27 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Mon, 15 Sep 2025 15:31:58 +0200 Subject: [PATCH 1/3] Fix some DevTools regression test actions and assertions (#34459) --- .../__tests__/__e2e__/components.test.js | 4 +- .../__serializers__/storeSerializer.js | 4 +- .../src/__tests__/profilingCache-test.js | 93 ++++--- .../profilingCommitTreeBuilder-test.js | 68 +++++- .../src/__tests__/store-test.js | 69 +++--- .../__tests__/storeComponentFilters-test.js | 2 +- .../src/__tests__/storeStressSync-test.js | 228 +++++++++++++----- scripts/jest/preprocessor.js | 19 +- 8 files changed, 342 insertions(+), 145 deletions(-) diff --git a/packages/react-devtools-inline/__tests__/__e2e__/components.test.js b/packages/react-devtools-inline/__tests__/__e2e__/components.test.js index ae451d29587b0..3d187c3ddabae 100644 --- a/packages/react-devtools-inline/__tests__/__e2e__/components.test.js +++ b/packages/react-devtools-inline/__tests__/__e2e__/components.test.js @@ -93,7 +93,9 @@ test.describe('Components', () => { const name = isEditable.name ? existingNameElements[0].value - : existingNameElements[0].innerText; + : existingNameElements[0].innerText + // remove trailing colon + .slice(0, -1); const value = isEditable.value ? existingValueElements[0].value : existingValueElements[0].innerText; diff --git a/packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js b/packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js index ea5a402bdc235..9c13799214dd3 100644 --- a/packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js +++ b/packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js @@ -12,8 +12,8 @@ export function test(maybeStore) { } // print() is part of Jest's serializer API -export function print(store, serialize, indent) { - return printStore(store); +export function print(store, serialize, indent, includeSuspense = true) { + return printStore(store, false, null, includeSuspense); } // Used for Jest snapshot testing. diff --git a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js index d16062c69f488..0d26d7107cb6d 100644 --- a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js @@ -724,34 +724,69 @@ describe('ProfilingCache', () => { const rootID = store.roots[0]; 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 { - 5 => 3, - 3 => 3, - } - `); - expect(commitData[1].fiberSelfDurations).toMatchInlineSnapshot(` - Map { - 5 => 3, - 3 => 0, - } - `); + + const isLegacySuspense = React.version.startsWith('17'); + if (isLegacySuspense) { + expect(commitData[0].fiberActualDurations).toMatchInlineSnapshot(` + Map { + 1 => 15, + 2 => 15, + 3 => 5, + 4 => 3, + 5 => 2, + } + `); + expect(commitData[0].fiberSelfDurations).toMatchInlineSnapshot(` + Map { + 1 => 0, + 2 => 10, + 3 => 3, + 4 => 3, + 5 => 2, + } + `); + expect(commitData[1].fiberActualDurations).toMatchInlineSnapshot(` + Map { + 6 => 3, + 3 => 3, + } + `); + expect(commitData[1].fiberSelfDurations).toMatchInlineSnapshot(` + Map { + 6 => 3, + 3 => 0, + } + `); + } else { + 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 { + 5 => 3, + 3 => 3, + } + `); + expect(commitData[1].fiberSelfDurations).toMatchInlineSnapshot(` + Map { + 5 => 3, + 3 => 0, + } + `); + } }); // @reactVersion >= 16.9 @@ -866,6 +901,7 @@ describe('ProfilingCache', () => { "hocDisplayNames": null, "id": 1, "key": null, + "stack": null, "type": 11, }, ], @@ -908,6 +944,7 @@ describe('ProfilingCache', () => { "hocDisplayNames": null, "id": 1, "key": null, + "stack": null, "type": 11, }, ], diff --git a/packages/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder-test.js b/packages/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder-test.js index 73df17071bac2..176056cf10e63 100644 --- a/packages/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder-test.js @@ -15,10 +15,12 @@ import { } from './utils'; describe('commit tree', () => { - let React; + let React = require('react'); let Scheduler; let store: Store; let utils; + const isLegacySuspense = + React.version.startsWith('16') || React.version.startsWith('17'); beforeEach(() => { utils = require('./utils'); @@ -184,17 +186,32 @@ describe('commit tree', () => { utils.act(() => store.profilerStore.startProfiling()); utils.act(() => legacyRender()); await Promise.resolve(); - expect(store).toMatchInlineSnapshot(` - [root] - ▾ - - `); + if (isLegacySuspense) { + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + ▾ + + [suspense-root] rects={null} + + `); + } else { + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + + [suspense-root] rects={null} + + `); + } utils.act(() => legacyRender()); expect(store).toMatchInlineSnapshot(` [root] ▾ + [suspense-root] rects={null} + `); utils.act(() => legacyRender()); expect(store).toMatchInlineSnapshot(` @@ -214,7 +231,13 @@ describe('commit tree', () => { ); } - expect(commitTrees[0].nodes.size).toBe(3); // + + + expect(commitTrees[0].nodes.size).toBe( + isLegacySuspense + ? // + + + + 4 + : // + + + 3, + ); expect(commitTrees[1].nodes.size).toBe(4); // + + + expect(commitTrees[2].nodes.size).toBe(2); // + }); @@ -268,11 +291,24 @@ describe('commit tree', () => { it('should support Lazy components that are unmounted before resolving (legacy render)', async () => { utils.act(() => store.profilerStore.startProfiling()); utils.act(() => legacyRender()); - expect(store).toMatchInlineSnapshot(` - [root] - ▾ - - `); + if (isLegacySuspense) { + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + ▾ + + [suspense-root] rects={null} + + `); + } else { + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + + [suspense-root] rects={null} + + `); + } utils.act(() => legacyRender()); expect(store).toMatchInlineSnapshot(` [root] @@ -291,7 +327,13 @@ describe('commit tree', () => { ); } - expect(commitTrees[0].nodes.size).toBe(3); // + + + expect(commitTrees[0].nodes.size).toBe( + isLegacySuspense + ? // + + + + 4 + : // + + + 3, + ); expect(commitTrees[1].nodes.size).toBe(2); // + }); diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js index 88144b4aa1c13..a2024be3d2e5e 100644 --- a/packages/react-devtools-shared/src/__tests__/store-test.js +++ b/packages/react-devtools-shared/src/__tests__/store-test.js @@ -24,6 +24,35 @@ describe('Store', () => { let store; let withErrorsOrWarningsIgnored; + function readValue(promise) { + if (typeof React.use === 'function') { + return React.use(promise); + } + + // Support for React < 19.0 + switch (promise.status) { + case 'fulfilled': + return promise.value; + case 'rejected': + throw promise.reason; + case 'pending': + throw promise; + default: + promise.status = 'pending'; + promise.then( + value => { + promise.status = 'fulfilled'; + promise.value = value; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } + } + beforeAll(() => { // JSDDOM doesn't implement getClientRects so we're just faking one for testing purposes Element.prototype.getClientRects = function (this: Element) { @@ -107,11 +136,7 @@ describe('Store', () => { let Dynamic = null; const Owner = () => { Dynamic = ; - if (React.use) { - React.use(promise); - } else { - throw promise; - } + readValue(promise); }; const Parent = () => { return Dynamic; @@ -462,12 +487,9 @@ describe('Store', () => { // @reactVersion >= 18.0 it('should display Suspense nodes properly in various states', async () => { const Loading = () =>
Loading...
; + const never = new Promise(() => {}); const SuspendingComponent = () => { - if (React.use) { - React.use(new Promise(() => {})); - } else { - throw new Promise(() => {}); - } + readValue(never); }; const Component = () => { return
Hello
; @@ -514,12 +536,9 @@ describe('Store', () => { it('should support nested Suspense nodes', async () => { const Component = () => null; const Loading = () =>
Loading...
; + const never = new Promise(() => {}); const Never = () => { - if (React.use) { - React.use(new Promise(() => {})); - } else { - throw new Promise(() => {}); - } + readValue(never); }; const Wrapper = ({ @@ -1019,12 +1038,9 @@ describe('Store', () => { it('should display a partially rendered SuspenseList', async () => { const Loading = () =>
Loading...
; + const never = new Promise(() => {}); const SuspendingComponent = () => { - if (React.use) { - React.use(new Promise(() => {})); - } else { - throw new Promise(() => {}); - } + readValue(never); }; const Component = () => { return
Hello
; @@ -1379,12 +1395,9 @@ describe('Store', () => { // @reactVersion >= 18.0 it('should display Suspense nodes properly in various states', async () => { const Loading = () =>
Loading...
; + const never = new Promise(() => {}); const SuspendingComponent = () => { - if (React.use) { - React.use(new Promise(() => {})); - } else { - throw new Promise(() => {}); - } + readValue(never); }; const Component = () => { return
Hello
; @@ -2081,6 +2094,8 @@ describe('Store', () => { [root] ▾ + [suspense-root] rects={null} + `); // Render again to unmount it before it finishes loading @@ -2826,7 +2841,7 @@ describe('Store', () => { function Component({children, promise}) { if (promise) { - React.use(promise); + readValue(promise); } return
{children}
; } @@ -2901,7 +2916,7 @@ describe('Store', () => { function Component({children, promise}) { if (promise) { - React.use(promise); + readValue(promise); } return
{children}
; } diff --git a/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js b/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js index 89076ea63daa3..b5cf97a4730a3 100644 --- a/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js +++ b/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js @@ -134,7 +134,7 @@ describe('Store component filters', () => { `); }); - // @reactVersion >= 16.0 + // @reactVersion >= 16.6 it('should filter Suspense', async () => { const Suspense = React.Suspense; await actAsync(async () => diff --git a/packages/react-devtools-shared/src/__tests__/storeStressSync-test.js b/packages/react-devtools-shared/src/__tests__/storeStressSync-test.js index 759ce79590371..db93c280cdf42 100644 --- a/packages/react-devtools-shared/src/__tests__/storeStressSync-test.js +++ b/packages/react-devtools-shared/src/__tests__/storeStressSync-test.js @@ -16,6 +16,35 @@ describe('StoreStress (Legacy Mode)', () => { let store; let print; + function readValue(promise) { + if (typeof React.use === 'function') { + return React.use(promise); + } + + // Support for React < 19.0 + switch (promise.status) { + case 'fulfilled': + return promise.value; + case 'rejected': + throw promise.reason; + case 'pending': + throw promise; + default: + promise.status = 'pending'; + promise.then( + value => { + promise.status = 'fulfilled'; + promise.value = value; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + throw promise; + } + } + beforeEach(() => { bridge = global.bridge; store = global.store; @@ -415,118 +444,116 @@ describe('StoreStress (Legacy Mode)', () => { a, ]; + // Excluding Suspense tree here due to different measurement semantics for fallbacks const stepsSnapshot = [ ` - [root] + "[root] ▾ - + " `, ` - [root] + "[root] ▾ - + " `, ` - [root] + "[root] ▾ - + " `, ` - [root] + "[root] ▾ - + " `, ` - [root] + "[root] ▾ - + " `, ` - [root] + "[root] ▾ - + " `, ` - [root] + "[root] ▾ - + " `, ` - [root] + "[root] ▾ - + " `, ` - [root] + "[root] ▾ - + " `, ` - [root] + "[root] ▾ - + " `, ` - [root] + "[root] ▾ - + " `, ` - [root] + "[root] ▾ - + " `, ]; + const never = new Promise(() => {}); const Never = () => { - if (React.use) { - React.use(new Promise(() => {})); - } else { - throw new Promise(() => {}); - } + readValue(never); }; const Root = ({children}) => { @@ -549,8 +576,10 @@ describe('StoreStress (Legacy Mode)', () => { ), ); // We snapshot each step once so it doesn't regress. - expect(store).toMatchInlineSnapshot(stepsSnapshot[i]); - snapshots.push(print(store)); + expect(print(store, undefined, undefined, false)).toMatchInlineSnapshot( + stepsSnapshot[i], + ); + snapshots.push(print(store, undefined, undefined, false)); act(() => unmount()); expect(print(store)).toBe(''); } @@ -572,7 +601,7 @@ describe('StoreStress (Legacy Mode)', () => { , ), ); - expect(print(store)).toEqual(snapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[i]); act(() => unmount()); expect(print(store)).toBe(''); } @@ -592,7 +621,7 @@ describe('StoreStress (Legacy Mode)', () => {
, ), ); - expect(print(store)).toEqual(snapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[i]); // Re-render with steps[j]. act(() => render( @@ -604,7 +633,7 @@ describe('StoreStress (Legacy Mode)', () => { ), ); // Verify the successful transition to steps[j]. - expect(print(store)).toEqual(snapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[j]); // Check that we can transition back again. act(() => render( @@ -615,7 +644,7 @@ describe('StoreStress (Legacy Mode)', () => {
, ), ); - expect(print(store)).toEqual(snapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[i]); // Clean up after every iteration. act(() => unmount()); expect(print(store)).toBe(''); @@ -641,7 +670,7 @@ describe('StoreStress (Legacy Mode)', () => {
, ), ); - expect(print(store)).toEqual(snapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[i]); // Re-render with steps[j]. act(() => render( @@ -657,7 +686,7 @@ describe('StoreStress (Legacy Mode)', () => { ), ); // Verify the successful transition to steps[j]. - expect(print(store)).toEqual(snapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[j]); // Check that we can transition back again. act(() => render( @@ -672,7 +701,7 @@ describe('StoreStress (Legacy Mode)', () => {
, ), ); - expect(print(store)).toEqual(snapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[i]); // Clean up after every iteration. act(() => unmount()); expect(print(store)).toBe(''); @@ -694,7 +723,7 @@ describe('StoreStress (Legacy Mode)', () => {
, ), ); - expect(print(store)).toEqual(snapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[i]); // Re-render with steps[j]. act(() => render( @@ -710,7 +739,7 @@ describe('StoreStress (Legacy Mode)', () => { ), ); // Verify the successful transition to steps[j]. - expect(print(store)).toEqual(snapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[j]); // Check that we can transition back again. act(() => render( @@ -721,7 +750,7 @@ describe('StoreStress (Legacy Mode)', () => {
, ), ); - expect(print(store)).toEqual(snapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[i]); // Clean up after every iteration. act(() => unmount()); expect(print(store)).toBe(''); @@ -747,7 +776,7 @@ describe('StoreStress (Legacy Mode)', () => {
, ), ); - expect(print(store)).toEqual(snapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[i]); // Re-render with steps[j]. act(() => render( @@ -759,7 +788,7 @@ describe('StoreStress (Legacy Mode)', () => { ), ); // Verify the successful transition to steps[j]. - expect(print(store)).toEqual(snapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[j]); // Check that we can transition back again. act(() => render( @@ -774,7 +803,7 @@ describe('StoreStress (Legacy Mode)', () => {
, ), ); - expect(print(store)).toEqual(snapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[i]); // Clean up after every iteration. act(() => unmount()); expect(print(store)).toBe(''); @@ -803,7 +832,7 @@ describe('StoreStress (Legacy Mode)', () => { const suspenseID = store.getElementIDAtIndex(2); // Force fallback. - expect(print(store)).toEqual(snapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[i]); act(() => { bridge.send('overrideSuspense', { id: suspenseID, @@ -811,7 +840,7 @@ describe('StoreStress (Legacy Mode)', () => { forceFallback: true, }); }); - expect(print(store)).toEqual(snapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[j]); // Stop forcing fallback. act(() => { @@ -821,7 +850,7 @@ describe('StoreStress (Legacy Mode)', () => { forceFallback: false, }); }); - expect(print(store)).toEqual(snapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[i]); // Trigger actual fallback. act(() => @@ -837,7 +866,7 @@ describe('StoreStress (Legacy Mode)', () => {
, ), ); - expect(print(store)).toEqual(snapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[j]); // Force fallback while we're in fallback mode. act(() => { @@ -848,7 +877,7 @@ describe('StoreStress (Legacy Mode)', () => { }); }); // Keep seeing fallback content. - expect(print(store)).toEqual(snapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[j]); // Switch to primary mode. act(() => @@ -861,7 +890,7 @@ describe('StoreStress (Legacy Mode)', () => { ), ); // Fallback is still forced though. - expect(print(store)).toEqual(snapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[j]); // Stop forcing fallback. This reverts to primary content. act(() => { @@ -872,7 +901,7 @@ describe('StoreStress (Legacy Mode)', () => { }); }); // Now we see primary content. - expect(print(store)).toEqual(snapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual(snapshots[i]); // Clean up after every iteration. act(() => unmount()); @@ -921,6 +950,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -931,6 +962,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -943,6 +976,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -955,6 +990,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -966,6 +1003,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -977,6 +1016,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -988,6 +1029,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -999,6 +1042,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -1009,6 +1054,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -1018,6 +1065,8 @@ describe('StoreStress (Legacy Mode)', () => { ▾ + [suspense-root] rects={[]} + `, ` [root] @@ -1028,6 +1077,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -1038,6 +1089,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ]; @@ -1049,6 +1102,8 @@ describe('StoreStress (Legacy Mode)', () => { ▾ + [suspense-root] rects={[]} + `, ` [root] @@ -1057,6 +1112,8 @@ describe('StoreStress (Legacy Mode)', () => { ▾ + [suspense-root] rects={[]} + `, ` [root] @@ -1067,6 +1124,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -1077,6 +1136,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -1086,6 +1147,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -1095,6 +1158,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -1104,6 +1169,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -1113,6 +1180,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -1121,6 +1190,8 @@ describe('StoreStress (Legacy Mode)', () => { ▾ + [suspense-root] rects={[]} + `, ` [root] @@ -1128,6 +1199,8 @@ describe('StoreStress (Legacy Mode)', () => { + [suspense-root] rects={[]} + `, ` [root] @@ -1136,6 +1209,8 @@ describe('StoreStress (Legacy Mode)', () => { ▾ + [suspense-root] rects={[]} + `, ` [root] @@ -1144,15 +1219,14 @@ describe('StoreStress (Legacy Mode)', () => { ▾ + [suspense-root] rects={[]} + `, ]; + const never = new Promise(() => {}); const Never = () => { - if (React.use) { - React.use(new Promise(() => {})); - } else { - throw new Promise(() => {}); - } + readValue(never); }; const MaybeSuspend = ({children, suspend}) => { @@ -1224,7 +1298,7 @@ describe('StoreStress (Legacy Mode)', () => { ); // We snapshot each step once so it doesn't regress. expect(store).toMatchInlineSnapshot(stepsSnapshotTwo[i]); - fallbackSnapshots.push(print(store)); + fallbackSnapshots.push(print(store, undefined, undefined, false)); act(() => unmount()); expect(print(store)).toBe(''); } @@ -1302,7 +1376,9 @@ describe('StoreStress (Legacy Mode)', () => { , ), ); - expect(print(store)).toEqual(fallbackSnapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual( + fallbackSnapshots[i], + ); // Re-render with steps[j]. act(() => render( @@ -1321,7 +1397,9 @@ describe('StoreStress (Legacy Mode)', () => { ), ); // Verify the successful transition to steps[j]. - expect(print(store)).toEqual(fallbackSnapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual( + fallbackSnapshots[j], + ); // Check that we can transition back again. act(() => render( @@ -1339,7 +1417,9 @@ describe('StoreStress (Legacy Mode)', () => { , ), ); - expect(print(store)).toEqual(fallbackSnapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual( + fallbackSnapshots[i], + ); // Clean up after every iteration. act(() => unmount()); expect(print(store)).toBe(''); @@ -1377,7 +1457,9 @@ describe('StoreStress (Legacy Mode)', () => { ), ); // Verify the successful transition to steps[j]. - expect(print(store)).toEqual(fallbackSnapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual( + fallbackSnapshots[j], + ); // Check that we can transition back again. act(() => render( @@ -1414,7 +1496,9 @@ describe('StoreStress (Legacy Mode)', () => { , ), ); - expect(print(store)).toEqual(fallbackSnapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual( + fallbackSnapshots[i], + ); // Re-render with steps[j]. act(() => render( @@ -1441,7 +1525,9 @@ describe('StoreStress (Legacy Mode)', () => { , ), ); - expect(print(store)).toEqual(fallbackSnapshots[i]); + expect(print(store, undefined, undefined, false)).toEqual( + fallbackSnapshots[i], + ); // Clean up after every iteration. act(() => unmount()); expect(print(store)).toBe(''); @@ -1480,7 +1566,9 @@ describe('StoreStress (Legacy Mode)', () => { forceFallback: true, }); }); - expect(print(store)).toEqual(fallbackSnapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual( + fallbackSnapshots[j], + ); // Stop forcing fallback. act(() => { @@ -1504,7 +1592,9 @@ describe('StoreStress (Legacy Mode)', () => { , ), ); - expect(print(store)).toEqual(fallbackSnapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual( + fallbackSnapshots[j], + ); // Force fallback while we're in fallback mode. act(() => { @@ -1515,7 +1605,9 @@ describe('StoreStress (Legacy Mode)', () => { }); }); // Keep seeing fallback content. - expect(print(store)).toEqual(fallbackSnapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual( + fallbackSnapshots[j], + ); // Switch to primary mode. act(() => @@ -1530,7 +1622,9 @@ describe('StoreStress (Legacy Mode)', () => { ), ); // Fallback is still forced though. - expect(print(store)).toEqual(fallbackSnapshots[j]); + expect(print(store, undefined, undefined, false)).toEqual( + fallbackSnapshots[j], + ); // Stop forcing fallback. This reverts to primary content. act(() => { diff --git a/scripts/jest/preprocessor.js b/scripts/jest/preprocessor.js index 5561bc5f5383c..3d7e98eab97da 100644 --- a/scripts/jest/preprocessor.js +++ b/scripts/jest/preprocessor.js @@ -80,7 +80,19 @@ module.exports = { // This is only for React DevTools tests with React 16.x // `react/jsx-dev-runtime` and `react/jsx-runtime` are included in the package starting from v17 - if (semver.gte(ReactVersionTestingAgainst, '17.0.0')) { + // Technically 16.14 and 15.7 have the new runtime but we're not testing those versions. + if ( + semver.gte(ReactVersionTestingAgainst, '15.0.0') && + semver.lt(ReactVersionTestingAgainst, '17.0.0') + ) { + plugins.push( + [ + require.resolve('@babel/plugin-transform-react-jsx'), + {runtime: 'classic'}, + ], + require.resolve('@babel/plugin-transform-react-jsx-source') + ); + } else { plugins.push([ process.env.NODE_ENV === 'development' ? require.resolve('@babel/plugin-transform-react-jsx-development') @@ -89,11 +101,6 @@ module.exports = { // would be React.createElement. {runtime: 'automatic'}, ]); - } else { - plugins.push( - require.resolve('@babel/plugin-transform-react-jsx'), - require.resolve('@babel/plugin-transform-react-jsx-source') - ); } plugins.push(pathToTransformLazyJSXImport); From 67a44bcd1b09ab809cf503b39c2568212e13e1a5 Mon Sep 17 00:00:00 2001 From: Eugene Choi <4eugenechoi@gmail.com> Date: Mon, 15 Sep 2025 12:13:28 -0400 Subject: [PATCH 2/3] Playground applied configs (#34474) ## Summary Added an "Applied Configs" section under the Config Overrides panel. Users will now be able to see the full list of configs applied to the compiler in the playground. Adds greater discoverability for config options to override as well. Updated the default config as well to be a commented config option, so users will start with empty overrides. ## How did you test this change? https://github.com/user-attachments/assets/1a57b2d5-0405-4fc8-9990-1747c30181c0 --- .../components/Editor/ConfigEditor.tsx | 128 +++++++++++------- .../components/Editor/EditorImpl.tsx | 11 +- .../playground/components/TabbedWindow.tsx | 2 +- compiler/apps/playground/lib/defaultStore.ts | 4 +- 4 files changed, 89 insertions(+), 56 deletions(-) diff --git a/compiler/apps/playground/components/Editor/ConfigEditor.tsx b/compiler/apps/playground/components/Editor/ConfigEditor.tsx index 5f904960bacbe..162d82591cb00 100644 --- a/compiler/apps/playground/components/Editor/ConfigEditor.tsx +++ b/compiler/apps/playground/components/Editor/ConfigEditor.tsx @@ -6,6 +6,7 @@ */ import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react'; +import {PluginOptions} from 'babel-plugin-react-compiler'; import type {editor} from 'monaco-editor'; import * as monaco from 'monaco-editor'; import React, {useState} from 'react'; @@ -13,30 +14,33 @@ import {Resizable} from 're-resizable'; import {useStore, useStoreDispatch} from '../StoreContext'; import {monacoOptions} from './monacoOptions'; import {IconChevron} from '../Icons/IconChevron'; +import prettyFormat from 'pretty-format'; // @ts-expect-error - webpack asset/source loader handles .d.ts files as strings import compilerTypeDefs from 'babel-plugin-react-compiler/dist/index.d.ts'; loader.config({monaco}); -export default function ConfigEditor(): React.ReactElement { +export default function ConfigEditor({ + appliedOptions, +}: { + appliedOptions: PluginOptions | null; +}): React.ReactElement { const [isExpanded, setIsExpanded] = useState(false); - return ( -
- {isExpanded ? ( - - ) : ( - - )} -
+ return isExpanded ? ( + + ) : ( + ); } function ExpandedEditor({ onToggle, + appliedOptions, }: { onToggle: (expanded: boolean) => void; + appliedOptions: PluginOptions | null; }): React.ReactElement { const store = useStore(); const dispatchStore = useStoreDispatch(); @@ -81,22 +85,22 @@ function ExpandedEditor({ } }; + const formattedAppliedOptions = appliedOptions + ? prettyFormat(appliedOptions, { + printFunctionName: false, + printBasicPrototype: false, + }) + : 'Invalid configs'; + return ( -
-
-

- Config Overrides -

-
+ defaultSize={{width: 350}} + enable={{right: true, bottom: false}}> +
onToggle(false)} style={{ @@ -106,30 +110,60 @@ function ExpandedEditor({ borderTopLeftRadius: 0, borderBottomLeftRadius: 0, }}> - + +
+ +
+
+

+ Config Overrides +

+
+
+ +
-
- + +
+
+

+ Applied Configs +

+
+
+ +
@@ -143,10 +177,10 @@ function CollapsedEditor({ }): React.ReactElement { return (
+ className="w-4 !h-[calc(100vh_-_3.5rem)]" + style={{position: 'relative'}}>
onToggle(true)} style={{ @@ -156,7 +190,7 @@ function CollapsedEditor({ borderTopLeftRadius: 0, borderBottomLeftRadius: 0, }}> - +
); diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index a90447c96b50a..8b75ce6ac5eb5 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -201,7 +201,7 @@ function compile( source: string, mode: 'compiler' | 'linter', configOverrides: string, -): [CompilerOutput, 'flow' | 'typescript'] { +): [CompilerOutput, 'flow' | 'typescript', PluginOptions | null] { const results = new Map>(); const error = new CompilerError(); const otherErrors: Array = []; @@ -279,7 +279,7 @@ function compile( ...baseOpts, logger: { debugLogIRs: logIR, - logEvent: (_filename: string | null, event: LoggerEvent) => { + logEvent: (_filename: string | null, event: LoggerEvent): void => { if (event.kind === 'CompileError') { otherErrors.push(event.detail); } @@ -315,11 +315,12 @@ function compile( otherErrors.forEach(e => error.details.push(e)); } if (error.hasErrors()) { - return [{kind: 'err', results, error}, language]; + return [{kind: 'err', results, error}, language, baseOpts]; } return [ {kind: 'ok', results, transformOutput, errors: error.details}, language, + baseOpts, ]; } @@ -328,7 +329,7 @@ export default function Editor(): JSX.Element { const deferredStore = useDeferredValue(store); const dispatchStore = useStoreDispatch(); const {enqueueSnackbar} = useSnackbar(); - const [compilerOutput, language] = useMemo( + const [compilerOutput, language, appliedOptions] = useMemo( () => compile(deferredStore.source, 'compiler', deferredStore.config), [deferredStore.source, deferredStore.config], ); @@ -379,7 +380,7 @@ export default function Editor(): JSX.Element { <>
- +
diff --git a/compiler/apps/playground/components/TabbedWindow.tsx b/compiler/apps/playground/components/TabbedWindow.tsx index 49ff76543bb5c..1751bd87e26c2 100644 --- a/compiler/apps/playground/components/TabbedWindow.tsx +++ b/compiler/apps/playground/components/TabbedWindow.tsx @@ -33,7 +33,7 @@ export default function TabbedWindow({ key={tab} onClick={() => onTabChange(tab)} className={clsx( - 'active:scale-95 transition-transform text-center outline-none py-1.5 px-1.5 xs:px-3 sm:px-4 rounded-full capitalize whitespace-nowrap text-sm', + 'active:scale-95 transition-transform py-1.5 px-1.5 xs:px-3 sm:px-4 rounded-full text-sm', !isActive && 'hover:bg-primary/5', isActive && 'bg-highlight text-link', )}> diff --git a/compiler/apps/playground/lib/defaultStore.ts b/compiler/apps/playground/lib/defaultStore.ts index 17a80094a6628..bb33c1b76c539 100644 --- a/compiler/apps/playground/lib/defaultStore.ts +++ b/compiler/apps/playground/lib/defaultStore.ts @@ -17,9 +17,7 @@ export const defaultConfig = `\ import type { PluginOptions } from 'babel-plugin-react-compiler/dist'; ({ - environment: { - enableResetCacheOnSourceFileChanges: false - } + //compilationMode: "all" } satisfies Partial);`; export const defaultStore: Store = { From 92d7ad5dd976f4b7bc22abc507569b30609b1579 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:14:09 +0100 Subject: [PATCH 3/3] [DevTools] fix: validate url in file fetcher bridging calls (#34498) This was prone to races and sometimes messed up symbolication when multiple source maps were fetched simultaneously. --- .../react-devtools-extensions/src/main/fetchFileWithCaching.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-devtools-extensions/src/main/fetchFileWithCaching.js b/packages/react-devtools-extensions/src/main/fetchFileWithCaching.js index 97bf1fe35bcbe..15028e99094e1 100644 --- a/packages/react-devtools-extensions/src/main/fetchFileWithCaching.js +++ b/packages/react-devtools-extensions/src/main/fetchFileWithCaching.js @@ -82,7 +82,7 @@ const fetchFromPage = async (url, resolve, reject) => { debugLog('[main] fetchFromPage()', url); function onPortMessage({payload, source}) { - if (source === 'react-devtools-background') { + if (source === 'react-devtools-background' && payload?.url === url) { switch (payload?.type) { case 'fetch-file-with-cache-complete': chrome.runtime.onMessage.removeListener(onPortMessage);