From 98005c3e2f8021ddbf754823ea668039ed1c83a5 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Tue, 13 Jan 2026 12:30:42 +0100 Subject: [PATCH 1/2] [test] Fix DevTools regression tests --- .../src/__tests__/store-test.js | 151 +++++++++++++++++- 1 file changed, 149 insertions(+), 2 deletions(-) diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js index c4fe7d337dd..9564e45425b 100644 --- a/packages/react-devtools-shared/src/__tests__/store-test.js +++ b/packages/react-devtools-shared/src/__tests__/store-test.js @@ -3143,8 +3143,9 @@ describe('Store', () => { expect(store).toMatchInlineSnapshot(``); }); - // @reactVersion >= 17.0 - it('should track suspended by in filtered fallback', async () => { + // Can't suspend the root in React 17. + // @reactVersion >= 18.0 + it('should track suspended-by in filtered fallback suspending the root', async () => { function IgnoreMe({promise}) { return readValue(promise); } @@ -3196,6 +3197,152 @@ describe('Store', () => { `); }); + // Only difference between React 17 and 18 are snapshots. + // @reactVersion >= 17.0 + // @reactVersion < 18.0 + it('should track suspended-by in filtered fallback in React 17', async () => { + function IgnoreMe({promise}) { + return readValue(promise); + } + + function Component({promise}) { + if (promise) { + return readValue(promise); + } + return null; + } + + await actAsync( + async () => + (store.componentFilters = [createDisplayNameFilter('^IgnoreMe', true)]), + ); + + let resolveFallback; + const fallbackPromise = new Promise(resolve => { + resolveFallback = resolve; + }); + let resolveContent; + const contentPromise = new Promise(resolve => { + resolveContent = resolve; + }); + + await actAsync(() => + render( + } + name="root"> + }> + + + , + ), + ); + // React 18 would not show the "main" Suspense boundary yet since it hasn't + // finished completely. React 17 commits partial trees as hidden so "main" + // shows up in the Suspense tree. + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + + [suspense-root] rects={null} + + + `); + + await actAsync(() => resolveFallback('loading')); + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + + [suspense-root] rects={null} + + + `); + + await actAsync(() => resolveContent('content')); + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + ▾ + + [suspense-root] rects={null} + + + `); + }); + + // @reactVersion >= 18.0 + it('should track suspended-by in filtered fallback in React 18', async () => { + function IgnoreMe({promise}) { + return readValue(promise); + } + + function Component({promise}) { + if (promise) { + return readValue(promise); + } + return null; + } + + await actAsync( + async () => + (store.componentFilters = [createDisplayNameFilter('^IgnoreMe', true)]), + ); + + let resolveFallback; + const fallbackPromise = new Promise(resolve => { + resolveFallback = resolve; + }); + let resolveContent; + const contentPromise = new Promise(resolve => { + resolveContent = resolve; + }); + + await actAsync(() => + render( + } + name="root"> + }> + + + , + ), + ); + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + + [suspense-root] rects={null} + + `); + + await actAsync(() => resolveFallback('loading')); + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + + [suspense-root] rects={null} + + + `); + + await actAsync(() => resolveContent('content')); + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + ▾ + + [suspense-root] rects={null} + + + `); + }); + // @reactVersion >= 19 it('should keep suspended boundaries in the Suspense tree but not hidden Activity', async () => { const Activity = React.Activity || React.unstable_Activity; From 1d57d5034ef860ab3ebd427d3b4d22b1c8ca9ef5 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Tue, 13 Jan 2026 15:18:58 +0100 Subject: [PATCH 2/2] Fork assertion instead --- .../src/__tests__/store-test.js | 108 +++++------------- 1 file changed, 26 insertions(+), 82 deletions(-) diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js index 9564e45425b..8c9beb18544 100644 --- a/packages/react-devtools-shared/src/__tests__/store-test.js +++ b/packages/react-devtools-shared/src/__tests__/store-test.js @@ -7,7 +7,12 @@ * @flow */ +import semver from 'semver'; + import {getVersionedRenderImplementation} from './utils'; +import {ReactVersion} from '../../../../ReactVersions'; + +const ReactVersionTestingAgainst = process.env.REACT_VERSION || ReactVersion; describe('Store', () => { let React; @@ -3197,10 +3202,8 @@ describe('Store', () => { `); }); - // Only difference between React 17 and 18 are snapshots. // @reactVersion >= 17.0 - // @reactVersion < 18.0 - it('should track suspended-by in filtered fallback in React 17', async () => { + it('should track suspended-by in filtered fallback', async () => { function IgnoreMe({promise}) { return readValue(promise); } @@ -3239,88 +3242,29 @@ describe('Store', () => { , ), ); - // React 18 would not show the "main" Suspense boundary yet since it hasn't - // finished completely. React 17 commits partial trees as hidden so "main" - // shows up in the Suspense tree. - expect(store).toMatchInlineSnapshot(` - [root] - ▾ - - [suspense-root] rects={null} - - - `); - - await actAsync(() => resolveFallback('loading')); - expect(store).toMatchInlineSnapshot(` - [root] - ▾ - - [suspense-root] rects={null} - - - `); - - await actAsync(() => resolveContent('content')); - expect(store).toMatchInlineSnapshot(` - [root] - ▾ - ▾ - - [suspense-root] rects={null} - - - `); - }); - // @reactVersion >= 18.0 - it('should track suspended-by in filtered fallback in React 18', async () => { - function IgnoreMe({promise}) { - return readValue(promise); - } - - function Component({promise}) { - if (promise) { - return readValue(promise); - } - return null; + if (semver.lt(ReactVersionTestingAgainst, '18.0.0')) { + // React 17 commits partial trees hidden which causes the "main" + // Suspense boundary to be included. + // React 18 and upwards excluded partial tree entirely. + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + + [suspense-root] rects={null} + + + `); + } else { + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + + [suspense-root] rects={null} + + `); } - await actAsync( - async () => - (store.componentFilters = [createDisplayNameFilter('^IgnoreMe', true)]), - ); - - let resolveFallback; - const fallbackPromise = new Promise(resolve => { - resolveFallback = resolve; - }); - let resolveContent; - const contentPromise = new Promise(resolve => { - resolveContent = resolve; - }); - - await actAsync(() => - render( - } - name="root"> - }> - - - , - ), - ); - expect(store).toMatchInlineSnapshot(` - [root] - ▾ - - [suspense-root] rects={null} - - `); - await actAsync(() => resolveFallback('loading')); expect(store).toMatchInlineSnapshot(` [root]