diff --git a/packages/react-devtools-shared/src/__tests__/profilerContext-test.js b/packages/react-devtools-shared/src/__tests__/profilerContext-test.js
index 325e6e5075386..17f758b612216 100644
--- a/packages/react-devtools-shared/src/__tests__/profilerContext-test.js
+++ b/packages/react-devtools-shared/src/__tests__/profilerContext-test.js
@@ -13,9 +13,12 @@ import type {Context} from 'react-devtools-shared/src/devtools/views/Profiler/Pr
import type {DispatcherContext} from 'react-devtools-shared/src/devtools/views/Components/TreeContext';
import type Store from 'react-devtools-shared/src/devtools/store';
+import {getVersionedRenderImplementation} from './utils';
+
describe('ProfilerContext', () => {
let React;
let ReactDOM;
+ let ReactDOMClient;
let TestRenderer: ReactTestRenderer;
let bridge: FrontendBridge;
let legacyRender;
@@ -43,6 +46,7 @@ describe('ProfilerContext', () => {
React = require('react');
ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
TestRenderer = utils.requireTestRenderer();
BridgeContext =
@@ -61,6 +65,8 @@ describe('ProfilerContext', () => {
require('react-devtools-shared/src/devtools/views/Components/TreeContext').TreeStateContext;
});
+ const {render} = getVersionedRenderImplementation();
+
const Contexts = ({
children = null,
defaultSelectedElementID = null,
@@ -77,7 +83,9 @@ describe('ProfilerContext', () => {
);
- it('updates updates profiling support based on the attached roots', async () => {
+ // @reactVersion <= 18.2
+ // @reactVersion >= 18.0
+ it('updates updates profiling support based on the attached roots (legacy render)', async () => {
const Component = () => null;
let context: Context = ((null: any): Context);
@@ -110,10 +118,47 @@ describe('ProfilerContext', () => {
expect(context.supportsProfiling).toBe(false);
});
+ // @reactVersion >= 18
+ it('updates updates profiling support based on the attached roots (createRoot)', async () => {
+ const Component = () => null;
+
+ let context: Context = ((null: any): Context);
+
+ function ContextReader() {
+ context = React.useContext(ProfilerContext);
+ return null;
+ }
+ await utils.actAsync(() => {
+ TestRenderer.create(
+
+
+ ,
+ );
+ });
+
+ expect(context.supportsProfiling).toBe(false);
+
+ const containerA = document.createElement('div');
+ const containerB = document.createElement('div');
+
+ const rootA = ReactDOMClient.createRoot(containerA);
+ const rootB = ReactDOMClient.createRoot(containerB);
+
+ await utils.actAsync(() => rootA.render());
+ expect(context.supportsProfiling).toBe(true);
+
+ await utils.actAsync(() => rootB.render());
+ await utils.actAsync(() => rootA.unmount());
+ expect(context.supportsProfiling).toBe(true);
+
+ await utils.actAsync(() => rootB.unmount());
+ expect(context.supportsProfiling).toBe(false);
+ });
+
it('should gracefully handle an empty profiling session (with no recorded commits)', async () => {
const Example = () => null;
- utils.act(() => legacyRender(, document.createElement('div')));
+ utils.act(() => render());
let context: Context = ((null: any): Context);
@@ -145,7 +190,9 @@ describe('ProfilerContext', () => {
expect(context.profilingData).toBe(null);
});
- it('should auto-select the root ID matching the Components tab selection if it has profiling data', async () => {
+ // @reactVersion <= 18.2
+ // @reactVersion >= 18.0
+ it('should auto-select the root ID matching the Components tab selection if it has profiling data (legacy render)', async () => {
const Parent = () => ;
const Child = () => null;
@@ -191,7 +238,60 @@ describe('ProfilerContext', () => {
);
});
- it('should not select the root ID matching the Components tab selection if it has no profiling data', async () => {
+ // @reactVersion >= 18
+ it('should auto-select the root ID matching the Components tab selection if it has profiling data (createRoot)', async () => {
+ const Parent = () => ;
+ const Child = () => null;
+
+ const containerOne = document.createElement('div');
+ const containerTwo = document.createElement('div');
+
+ const rootOne = ReactDOMClient.createRoot(containerOne);
+ const rootTwo = ReactDOMClient.createRoot(containerTwo);
+
+ utils.act(() => rootOne.render());
+ utils.act(() => rootTwo.render());
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+
+ [root]
+ ▾
+
+ `);
+
+ // Profile and record updates to both roots.
+ await utils.actAsync(() => store.profilerStore.startProfiling());
+ await utils.actAsync(() => rootOne.render());
+ await utils.actAsync(() => rootTwo.render());
+ await utils.actAsync(() => store.profilerStore.stopProfiling());
+
+ let context: Context = ((null: any): Context);
+ function ContextReader() {
+ context = React.useContext(ProfilerContext);
+ return null;
+ }
+
+ // Select an element within the second root.
+ await utils.actAsync(() =>
+ TestRenderer.create(
+
+
+ ,
+ ),
+ );
+
+ expect(context).not.toBeNull();
+ expect(context.rootID).toBe(
+ store.getRootIDForElement(((store.getElementIDAtIndex(3): any): number)),
+ );
+ });
+
+ // @reactVersion <= 18.2
+ // @reactVersion >= 18.0
+ it('should not select the root ID matching the Components tab selection if it has no profiling data (legacy render)', async () => {
const Parent = () => ;
const Child = () => null;
@@ -237,7 +337,60 @@ describe('ProfilerContext', () => {
);
});
- it('should maintain root selection between profiling sessions so long as there is data for that root', async () => {
+ // @reactVersion >= 18
+ it('should not select the root ID matching the Components tab selection if it has no profiling data (createRoot)', async () => {
+ const Parent = () => ;
+ const Child = () => null;
+
+ const containerOne = document.createElement('div');
+ const containerTwo = document.createElement('div');
+
+ const rootOne = ReactDOMClient.createRoot(containerOne);
+ const rootTwo = ReactDOMClient.createRoot(containerTwo);
+
+ utils.act(() => rootOne.render());
+ utils.act(() => rootTwo.render());
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+
+ [root]
+ ▾
+
+ `);
+
+ // Profile and record updates to only the first root.
+ await utils.actAsync(() => store.profilerStore.startProfiling());
+ await utils.actAsync(() => rootOne.render());
+ await utils.actAsync(() => store.profilerStore.stopProfiling());
+
+ let context: Context = ((null: any): Context);
+ function ContextReader() {
+ context = React.useContext(ProfilerContext);
+ return null;
+ }
+
+ // Select an element within the second root.
+ await utils.actAsync(() =>
+ TestRenderer.create(
+
+
+ ,
+ ),
+ );
+
+ // Verify the default profiling root is the first one.
+ expect(context).not.toBeNull();
+ expect(context.rootID).toBe(
+ store.getRootIDForElement(((store.getElementIDAtIndex(0): any): number)),
+ );
+ });
+
+ // @reactVersion <= 18.2
+ // @reactVersion >= 18.0
+ it('should maintain root selection between profiling sessions so long as there is data for that root (legacy render)', async () => {
const Parent = () => ;
const Child = () => null;
@@ -300,6 +453,75 @@ describe('ProfilerContext', () => {
expect(context.rootID).toBe(store.getRootIDForElement(id));
});
+ // @reactVersion >= 18.0
+ it('should maintain root selection between profiling sessions so long as there is data for that root (createRoot)', async () => {
+ const Parent = () => ;
+ const Child = () => null;
+
+ const containerA = document.createElement('div');
+ const containerB = document.createElement('div');
+
+ const rootA = ReactDOMClient.createRoot(containerA);
+ const rootB = ReactDOMClient.createRoot(containerB);
+
+ utils.act(() => rootA.render());
+ utils.act(() => rootB.render());
+
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+
+ [root]
+ ▾
+
+ `);
+
+ // Profile and record updates.
+ await utils.actAsync(() => store.profilerStore.startProfiling());
+ await utils.actAsync(() => rootA.render());
+ await utils.actAsync(() => rootB.render());
+ await utils.actAsync(() => store.profilerStore.stopProfiling());
+
+ let context: Context = ((null: any): Context);
+ let dispatch: DispatcherContext = ((null: any): DispatcherContext);
+ let selectedElementID = null;
+ function ContextReader() {
+ context = React.useContext(ProfilerContext);
+ dispatch = React.useContext(TreeDispatcherContext);
+ selectedElementID = React.useContext(TreeStateContext).selectedElementID;
+ return null;
+ }
+
+ const id = ((store.getElementIDAtIndex(3): any): number);
+
+ // Select an element within the second root.
+ await utils.actAsync(() =>
+ TestRenderer.create(
+
+
+ ,
+ ),
+ );
+
+ expect(selectedElementID).toBe(id);
+
+ // Profile and record more updates to both roots
+ await utils.actAsync(() => store.profilerStore.startProfiling());
+ await utils.actAsync(() => rootA.render());
+ await utils.actAsync(() => rootB.render());
+ await utils.actAsync(() => store.profilerStore.stopProfiling());
+
+ const otherID = ((store.getElementIDAtIndex(0): any): number);
+
+ // Change the selected element within a the Components tab.
+ utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 0}));
+
+ // Verify that the initial Profiler root selection is maintained.
+ expect(selectedElementID).toBe(otherID);
+ expect(context).not.toBeNull();
+ expect(context.rootID).toBe(store.getRootIDForElement(id));
+ });
+
it('should sync selected element in the Components tab too, provided the element is a match', async () => {
const GrandParent = ({includeChild}) => (
@@ -307,10 +529,7 @@ describe('ProfilerContext', () => {
const Parent = ({includeChild}) => (includeChild ? : null);
const Child = () => null;
- const container = document.createElement('div');
- utils.act(() =>
- legacyRender(, container),
- );
+ utils.act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
▾
@@ -323,12 +542,8 @@ describe('ProfilerContext', () => {
// Profile and record updates.
await utils.actAsync(() => store.profilerStore.startProfiling());
- await utils.actAsync(() =>
- legacyRender(, container),
- );
- await utils.actAsync(() =>
- legacyRender(, container),
- );
+ await utils.actAsync(() => render());
+ await utils.actAsync(() => render());
await utils.actAsync(() => store.profilerStore.stopProfiling());
expect(store).toMatchInlineSnapshot(`