Repository: facebook/react
Pull request: [test] Fix DevTools regression tests

Fixes 

>  IgnoreMe suspended while rendering, but no fallback UI was specified.

-- https://github.com/facebook/react/actions/runs/20886558894/job/60010693969

Regressed in https://github.com/facebook/react/pull/35456 because in React 17 we can't suspend the root. 

It's interesting to test both 17 (because Suspense has a different impl in 17) as well as testing what happens if the root suspends (if the root wouldn't have a dedicated Suspense node, we'd need to change how we handle this case). So we test both React 17 upwards with an explicit root Suspense and React 18 upwards with an implicit one.

## Test plan

```console
$ yarn build react-debug-tools react-refresh internal-test-utils
$ ./scripts/ci/download_devtools_regression_build.js 17.0 --replaceBuild
$ node ./scripts/jest/jest-cli.js --build --project devtools --release-channel=experimental --reactVersion 17.0
```

Changed files:
filename: packages/react-devtools-shared/src/__tests__/store-test.js
patch: @@ -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;
@@ -3143,8 +3148,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 +3202,91 @@ describe('Store', () => {
     `);
   });
 
+  // @reactVersion >= 17.0
+  it('should track suspended-by in filtered fallback', 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(
+        <React.Suspense
+          fallback={<Component key="root-fallback" />}
+          name="root">
+          <React.Suspense
+            name="main"
+            fallback={<IgnoreMe promise={fallbackPromise} />}>
+            <Component promise={contentPromise} />
+          </React.Suspense>
+        </React.Suspense>,
+      ),
+    );
+
+    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 name="root">
+              <Component key="root-fallback">
+        [suspense-root]  rects={null}
+          <Suspense name="root" rects={null}>
+            <Suspense name="main" rects={null}>
+      `);
+    } else {
+      expect(store).toMatchInlineSnapshot(`
+        [root]
+          ▾ <Suspense name="root">
+              <Component key="root-fallback">
+        [suspense-root]  rects={null}
+          <Suspense name="root" rects={null}>
+      `);
+    }
+
+    await actAsync(() => resolveFallback('loading'));
+    expect(store).toMatchInlineSnapshot(`
+      [root]
+        ▾ <Suspense name="root">
+            <Suspense name="main">
+      [suspense-root]  rects={null}
+        <Suspense name="root" rects={null}>
+          <Suspense name="main" rects={null}>
+    `);
+
+    await actAsync(() => resolveContent('content'));
+    expect(store).toMatchInlineSnapshot(`
+      [root]
+        ▾ <Suspense name="root">
+          ▾ <Suspense name="main">
+              <Component>
+      [suspense-root]  rects={null}
+        <Suspense name="root" rects={null}>
+          <Suspense name="main" 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;