Repository: facebook/react
Pull request: fix[devtools/ci]: split profiling cache test for different react versions and toEqual checker

This should fix the failing backwards-compatibility tests on CI:
- https://app.circleci.com/pipelines/github/facebook/react/51347/workflows/9d319db5-7a29-4e9a-a3a0-8d49a24ee9bd/jobs/809381
- https://app.circleci.com/pipelines/github/facebook/react/51347/workflows/9d319db5-7a29-4e9a-a3a0-8d49a24ee9bd/jobs/809386

Started failing after https://github.com/facebook/react/pull/27991.

Brief summary:
1. Revert changes to `profilingCache-test` in https://github.com/facebook/react/pull/27991.
2. Scope previous test to react versions 16.9 - 18.2
3. Add a new test for react versions > 18.2 (includes testing react from souce), which is also gated with `!disableLegacyContext`.

> [!IMPORTANT]
> `@gate` pragma expects the test to throw. Jest doesn't throw any exceptions when snapshots are mismatched, this is why I've migrated test from `toMatchInlineSnapshot` checker to `toEqual`.
> If the test doesn't throw, we will fail it manually:
> https://github.com/facebook/react/blob/5a75f9e78544fa6d052aff7fe99607e48f35b979/scripts/jest/setupTests.js#L291-L295

Changed files:
filename: packages/react-devtools-shared/src/__tests__/profilingCache-test.js
patch: @@ -310,7 +310,8 @@ describe('ProfilingCache', () => {
   });
 
   // @reactVersion >= 16.9
-  it('should record changed props/state/context/hooks', () => {
+  // @reactVersion <= 18.2
+  it('should record changed props/state/context/hooks for React version [16.9; 18.2] with legacy context', () => {
     let instance = null;
 
     const ModernContext = React.createContext(0);
@@ -411,21 +412,6 @@ describe('ProfilingCache', () => {
         },
       }
     `);
-
-    if (gate(flags => !flags.disableLegacyContext)) {
-      expect(changeDescriptions[1].get(6).context).toEqual(['count']);
-      expect(changeDescriptions[1].get(7).props).toEqual(['count']);
-      expect(changeDescriptions[2].get(6).context).toEqual([]);
-      expect(changeDescriptions[3].get(6).context).toEqual([]);
-      expect(changeDescriptions[4].get(6).context).toEqual([]);
-
-      changeDescriptions[1].get(6).context = null;
-      changeDescriptions[1].get(7).props = [];
-      changeDescriptions[2].get(6).context = null;
-      changeDescriptions[3].get(6).context = null;
-      changeDescriptions[4].get(6).context = null;
-    }
-
     expect(changeDescriptions[1]).toMatchInlineSnapshot(`
       Map {
         5 => {
@@ -451,11 +437,15 @@ describe('ProfilingCache', () => {
           "didHooksChange": false,
           "hooks": [],
           "isFirstMount": false,
-          "props": [],
+          "props": [
+            "count",
+          ],
           "state": null,
         },
         6 => {
-          "context": null,
+          "context": [
+            "count",
+          ],
           "didHooksChange": false,
           "hooks": null,
           "isFirstMount": false,
@@ -501,7 +491,7 @@ describe('ProfilingCache', () => {
           "state": null,
         },
         6 => {
-          "context": null,
+          "context": [],
           "didHooksChange": false,
           "hooks": null,
           "isFirstMount": false,
@@ -547,7 +537,7 @@ describe('ProfilingCache', () => {
           "state": null,
         },
         6 => {
-          "context": null,
+          "context": [],
           "didHooksChange": false,
           "hooks": null,
           "isFirstMount": false,
@@ -594,7 +584,7 @@ describe('ProfilingCache', () => {
           "state": null,
         },
         6 => {
-          "context": null,
+          "context": [],
           "didHooksChange": false,
           "hooks": null,
           "isFirstMount": false,
@@ -630,6 +620,381 @@ describe('ProfilingCache', () => {
     }
   });
 
+  // @reactVersion > 18.2
+  // @gate !disableLegacyContext
+  it('should record changed props/state/context/hooks for React version (18.2; ∞) with legacy context enabled', () => {
+    let instance = null;
+
+    const ModernContext = React.createContext(0);
+
+    class LegacyContextProvider extends React.Component<any, {count: number}> {
+      static childContextTypes = {
+        count: PropTypes.number,
+      };
+      state = {count: 0};
+      getChildContext() {
+        return this.state;
+      }
+      render() {
+        instance = this;
+        return (
+          <ModernContext.Provider value={this.state.count}>
+            <React.Fragment>
+              <ModernContextConsumer />
+              <LegacyContextConsumer />
+            </React.Fragment>
+          </ModernContext.Provider>
+        );
+      }
+    }
+
+    const FunctionComponentWithHooks = ({count}) => {
+      React.useMemo(() => count, [count]);
+      return null;
+    };
+
+    class ModernContextConsumer extends React.Component<any> {
+      static contextType = ModernContext;
+      render() {
+        return <FunctionComponentWithHooks count={this.context} />;
+      }
+    }
+
+    class LegacyContextConsumer extends React.Component<any> {
+      static contextTypes = {
+        count: PropTypes.number,
+      };
+      render() {
+        return <FunctionComponentWithHooks count={this.context.count} />;
+      }
+    }
+
+    utils.act(() => store.profilerStore.startProfiling());
+    utils.act(() => render(<LegacyContextProvider />));
+    expect(instance).not.toBeNull();
+    utils.act(() => (instance: any).setState({count: 1}));
+    utils.act(() => render(<LegacyContextProvider foo={123} />));
+    utils.act(() => render(<LegacyContextProvider bar="abc" />));
+    utils.act(() => render(<LegacyContextProvider />));
+    utils.act(() => store.profilerStore.stopProfiling());
+
+    const rootID = store.roots[0];
+
+    let changeDescriptions = store.profilerStore
+      .getDataForRoot(rootID)
+      .commitData.map(commitData => commitData.changeDescriptions);
+    expect(changeDescriptions).toHaveLength(5);
+    expect(changeDescriptions[0]).toEqual(
+      new Map([
+        [
+          2,
+          {
+            context: null,
+            didHooksChange: false,
+            isFirstMount: true,
+            props: null,
+            state: null,
+          },
+        ],
+        [
+          4,
+          {
+            context: null,
+            didHooksChange: false,
+            isFirstMount: true,
+            props: null,
+            state: null,
+          },
+        ],
+        [
+          5,
+          {
+            context: null,
+            didHooksChange: false,
+            isFirstMount: true,
+            props: null,
+            state: null,
+          },
+        ],
+        [
+          6,
+          {
+            context: null,
+            didHooksChange: false,
+            isFirstMount: true,
+            props: null,
+            state: null,
+          },
+        ],
+        [
+          7,
+          {
+            context: null,
+            didHooksChange: false,
+            isFirstMount: true,
+            props: null,
+            state: null,
+          },
+        ],
+      ]),
+    );
+
+    expect(changeDescriptions[1]).toEqual(
+      new Map([
+        [
+          5,
+          {
+            context: null,
+            didHooksChange: false,
+            hooks: [],
+            isFirstMount: false,
+            props: ['count'],
+            state: null,
+          },
+        ],
+        [
+          4,
+          {
+            context: true,
+            didHooksChange: false,
+            hooks: null,
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          7,
+          {
+            context: null,
+            didHooksChange: false,
+            hooks: [],
+            isFirstMount: false,
+            props: ['count'],
+            state: null,
+          },
+        ],
+        [
+          6,
+          {
+            context: ['count'],
+            didHooksChange: false,
+            hooks: null,
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          2,
+          {
+            context: null,
+            didHooksChange: false,
+            hooks: [],
+            isFirstMount: false,
+            props: [],
+            state: ['count'],
+          },
+        ],
+      ]),
+    );
+
+    expect(changeDescriptions[2]).toEqual(
+      new Map([
+        [
+          5,
+          {
+            context: null,
+            didHooksChange: false,
+            hooks: [],
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          4,
+          {
+            context: false,
+            didHooksChange: false,
+            hooks: null,
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          7,
+          {
+            context: null,
+            didHooksChange: false,
+            hooks: [],
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          6,
+          {
+            context: [],
+            didHooksChange: false,
+            hooks: null,
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          2,
+          {
+            context: null,
+            didHooksChange: false,
+            hooks: [],
+            isFirstMount: false,
+            props: ['foo'],
+            state: [],
+          },
+        ],
+      ]),
+    );
+
+    expect(changeDescriptions[3]).toEqual(
+      new Map([
+        [
+          5,
+          {
+            context: null,
+            didHooksChange: false,
+            hooks: [],
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          4,
+          {
+            context: false,
+            didHooksChange: false,
+            hooks: null,
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          7,
+          {
+            context: null,
+            didHooksChange: false,
+            hooks: [],
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          6,
+          {
+            context: [],
+            didHooksChange: false,
+            hooks: null,
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          2,
+          {
+            context: null,
+            didHooksChange: false,
+            hooks: [],
+            isFirstMount: false,
+            props: ['foo', 'bar'],
+            state: [],
+          },
+        ],
+      ]),
+    );
+
+    expect(changeDescriptions[4]).toEqual(
+      new Map([
+        [
+          5,
+          {
+            context: null,
+            didHooksChange: false,
+            hooks: [],
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          4,
+          {
+            context: false,
+            didHooksChange: false,
+            hooks: null,
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          7,
+          {
+            context: null,
+            didHooksChange: false,
+            hooks: [],
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          6,
+          {
+            context: [],
+            didHooksChange: false,
+            hooks: null,
+            isFirstMount: false,
+            props: [],
+            state: null,
+          },
+        ],
+        [
+          2,
+          {
+            context: null,
+            didHooksChange: false,
+            hooks: [],
+            isFirstMount: false,
+            props: ['bar'],
+            state: [],
+          },
+        ],
+      ]),
+    );
+
+    utils.exportImportHelper(bridge, store);
+
+    const prevChangeDescriptions = [...changeDescriptions];
+
+    changeDescriptions = store.profilerStore
+      .getDataForRoot(rootID)
+      .commitData.map(commitData => commitData.changeDescriptions);
+    expect(changeDescriptions).toHaveLength(5);
+
+    for (let commitIndex = 0; commitIndex < 5; commitIndex++) {
+      expect(changeDescriptions[commitIndex]).toEqual(
+        prevChangeDescriptions[commitIndex],
+      );
+    }
+  });
+
   // @reactVersion >= 18.0
   it('should properly detect changed hooks', () => {
     const Context = React.createContext(0);