@@ -13,9 +13,12 @@ import type {Context} from 'react-devtools-shared/src/devtools/views/Profiler/Pr
1313import type { DispatcherContext } from 'react-devtools-shared/src/devtools/views/Components/TreeContext' ;
1414import type Store from 'react-devtools-shared/src/devtools/store' ;
1515
16+ import { getVersionedRenderImplementation } from './utils' ;
17+
1618describe ( 'ProfilerContext' , ( ) => {
1719 let React ;
1820 let ReactDOM ;
21+ let ReactDOMClient ;
1922 let TestRenderer : ReactTestRenderer ;
2023 let bridge : FrontendBridge ;
2124 let legacyRender ;
@@ -43,6 +46,7 @@ describe('ProfilerContext', () => {
4346
4447 React = require ( 'react' ) ;
4548 ReactDOM = require ( 'react-dom' ) ;
49+ ReactDOMClient = require ( 'react-dom/client' ) ;
4650 TestRenderer = utils . requireTestRenderer ( ) ;
4751
4852 BridgeContext =
@@ -61,6 +65,8 @@ describe('ProfilerContext', () => {
6165 require ( 'react-devtools-shared/src/devtools/views/Components/TreeContext' ) . TreeStateContext ;
6266 } ) ;
6367
68+ const { render} = getVersionedRenderImplementation ( ) ;
69+
6470 const Contexts = ( {
6571 children = null ,
6672 defaultSelectedElementID = null ,
@@ -77,7 +83,8 @@ describe('ProfilerContext', () => {
7783 </ BridgeContext . Provider >
7884 ) ;
7985
80- it ( 'updates updates profiling support based on the attached roots' , async ( ) => {
86+ // @reactVersion < 19
87+ it ( 'updates updates profiling support based on the attached roots (Legacy Render)' , async ( ) => {
8188 const Component = ( ) => null ;
8289
8390 let context : Context = ( ( null : any ) : Context ) ;
@@ -110,10 +117,47 @@ describe('ProfilerContext', () => {
110117 expect ( context . supportsProfiling ) . toBe ( false ) ;
111118 } ) ;
112119
120+ // @reactVersion >= 18
121+ it ( 'updates updates profiling support based on the attached roots (Concurrent Render)' , async ( ) => {
122+ const Component = ( ) => null ;
123+
124+ let context : Context = ( ( null : any ) : Context ) ;
125+
126+ function ContextReader ( ) {
127+ context = React . useContext ( ProfilerContext ) ;
128+ return null ;
129+ }
130+ await utils . actAsync ( ( ) => {
131+ TestRenderer . create (
132+ < Contexts >
133+ < ContextReader />
134+ </ Contexts > ,
135+ ) ;
136+ } ) ;
137+
138+ expect ( context . supportsProfiling ) . toBe ( false ) ;
139+
140+ const containerA = document . createElement ( 'div' ) ;
141+ const containerB = document . createElement ( 'div' ) ;
142+
143+ const rootA = ReactDOMClient . createRoot ( containerA ) ;
144+ const rootB = ReactDOMClient . createRoot ( containerB ) ;
145+
146+ await utils . actAsync ( ( ) => rootA . render ( < Component /> ) ) ;
147+ expect ( context . supportsProfiling ) . toBe ( true ) ;
148+
149+ await utils . actAsync ( ( ) => rootB . render ( < Component /> ) ) ;
150+ await utils . actAsync ( ( ) => rootA . unmount ( ) ) ;
151+ expect ( context . supportsProfiling ) . toBe ( true ) ;
152+
153+ await utils . actAsync ( ( ) => rootB . unmount ( ) ) ;
154+ expect ( context . supportsProfiling ) . toBe ( false ) ;
155+ } ) ;
156+
113157 it ( 'should gracefully handle an empty profiling session (with no recorded commits)' , async ( ) => {
114158 const Example = ( ) => null ;
115159
116- utils . act ( ( ) => legacyRender ( < Example /> , document . createElement ( 'div' ) ) ) ;
160+ utils . act ( ( ) => render ( < Example /> ) ) ;
117161
118162 let context : Context = ( ( null : any ) : Context ) ;
119163
@@ -145,7 +189,8 @@ describe('ProfilerContext', () => {
145189 expect ( context . profilingData ) . toBe ( null ) ;
146190 } ) ;
147191
148- it ( 'should auto-select the root ID matching the Components tab selection if it has profiling data' , async ( ) => {
192+ // @reactVersion < 19
193+ it ( 'should auto-select the root ID matching the Components tab selection if it has profiling data (Legacy Render)' , async ( ) => {
149194 const Parent = ( ) => < Child /> ;
150195 const Child = ( ) => null ;
151196
@@ -191,7 +236,59 @@ describe('ProfilerContext', () => {
191236 ) ;
192237 } ) ;
193238
194- it ( 'should not select the root ID matching the Components tab selection if it has no profiling data' , async ( ) => {
239+ // @reactVersion >= 18
240+ it ( 'should auto-select the root ID matching the Components tab selection if it has profiling data (Concurrent Render)' , async ( ) => {
241+ const Parent = ( ) => < Child /> ;
242+ const Child = ( ) => null ;
243+
244+ const containerOne = document . createElement ( 'div' ) ;
245+ const containerTwo = document . createElement ( 'div' ) ;
246+
247+ const rootOne = ReactDOMClient . createRoot ( containerOne ) ;
248+ const rootTwo = ReactDOMClient . createRoot ( containerTwo ) ;
249+
250+ utils . act ( ( ) => rootOne . render ( < Parent /> ) ) ;
251+ utils . act ( ( ) => rootTwo . render ( < Parent /> ) ) ;
252+ expect ( store ) . toMatchInlineSnapshot ( `
253+ [root]
254+ ▾ <Parent>
255+ <Child>
256+ [root]
257+ ▾ <Parent>
258+ <Child>
259+ ` ) ;
260+
261+ // Profile and record updates to both roots.
262+ await utils . actAsync ( ( ) => store . profilerStore . startProfiling ( ) ) ;
263+ await utils . actAsync ( ( ) => rootOne . render ( < Parent /> ) ) ;
264+ await utils . actAsync ( ( ) => rootTwo . render ( < Parent /> ) ) ;
265+ await utils . actAsync ( ( ) => store . profilerStore . stopProfiling ( ) ) ;
266+
267+ let context : Context = ( ( null : any ) : Context ) ;
268+ function ContextReader ( ) {
269+ context = React . useContext ( ProfilerContext ) ;
270+ return null ;
271+ }
272+
273+ // Select an element within the second root.
274+ await utils . actAsync ( ( ) =>
275+ TestRenderer . create (
276+ < Contexts
277+ defaultSelectedElementID = { store . getElementIDAtIndex ( 3 ) }
278+ defaultSelectedElementIndex = { 3 } >
279+ < ContextReader />
280+ </ Contexts > ,
281+ ) ,
282+ ) ;
283+
284+ expect ( context ) . not . toBeNull ( ) ;
285+ expect ( context . rootID ) . toBe (
286+ store . getRootIDForElement ( ( ( store . getElementIDAtIndex ( 3 ) : any ) : number ) ) ,
287+ ) ;
288+ } ) ;
289+
290+ // @reactVersion < 19
291+ it ( 'should not select the root ID matching the Components tab selection if it has no profiling data (Legacy Render)' , async ( ) => {
195292 const Parent = ( ) => < Child /> ;
196293 const Child = ( ) => null ;
197294
@@ -237,7 +334,59 @@ describe('ProfilerContext', () => {
237334 ) ;
238335 } ) ;
239336
240- it ( 'should maintain root selection between profiling sessions so long as there is data for that root' , async ( ) => {
337+ // @reactVersion >= 18
338+ it ( 'should not select the root ID matching the Components tab selection if it has no profiling data (Concurrent Render)' , async ( ) => {
339+ const Parent = ( ) => < Child /> ;
340+ const Child = ( ) => null ;
341+
342+ const containerOne = document . createElement ( 'div' ) ;
343+ const containerTwo = document . createElement ( 'div' ) ;
344+
345+ const rootOne = ReactDOMClient . createRoot ( containerOne ) ;
346+ const rootTwo = ReactDOMClient . createRoot ( containerTwo ) ;
347+
348+ utils . act ( ( ) => rootOne . render ( < Parent /> ) ) ;
349+ utils . act ( ( ) => rootTwo . render ( < Parent /> ) ) ;
350+ expect ( store ) . toMatchInlineSnapshot ( `
351+ [root]
352+ ▾ <Parent>
353+ <Child>
354+ [root]
355+ ▾ <Parent>
356+ <Child>
357+ ` ) ;
358+
359+ // Profile and record updates to only the first root.
360+ await utils . actAsync ( ( ) => store . profilerStore . startProfiling ( ) ) ;
361+ await utils . actAsync ( ( ) => rootOne . render ( < Parent /> ) ) ;
362+ await utils . actAsync ( ( ) => store . profilerStore . stopProfiling ( ) ) ;
363+
364+ let context : Context = ( ( null : any ) : Context ) ;
365+ function ContextReader ( ) {
366+ context = React . useContext ( ProfilerContext ) ;
367+ return null ;
368+ }
369+
370+ // Select an element within the second root.
371+ await utils . actAsync ( ( ) =>
372+ TestRenderer . create (
373+ < Contexts
374+ defaultSelectedElementID = { store . getElementIDAtIndex ( 3 ) }
375+ defaultSelectedElementIndex = { 3 } >
376+ < ContextReader />
377+ </ Contexts > ,
378+ ) ,
379+ ) ;
380+
381+ // Verify the default profiling root is the first one.
382+ expect ( context ) . not . toBeNull ( ) ;
383+ expect ( context . rootID ) . toBe (
384+ store . getRootIDForElement ( ( ( store . getElementIDAtIndex ( 0 ) : any ) : number ) ) ,
385+ ) ;
386+ } ) ;
387+
388+ // @reactVersion < 19
389+ it ( 'should maintain root selection between profiling sessions so long as there is data for that root (Legacy Render)' , async ( ) => {
241390 const Parent = ( ) => < Child /> ;
242391 const Child = ( ) => null ;
243392
@@ -300,17 +449,83 @@ describe('ProfilerContext', () => {
300449 expect ( context . rootID ) . toBe ( store . getRootIDForElement ( id ) ) ;
301450 } ) ;
302451
452+ // @reactVersion < 19
453+ it ( 'should maintain root selection between profiling sessions so long as there is data for that root (Legacy Render)' , async ( ) => {
454+ const Parent = ( ) => < Child /> ;
455+ const Child = ( ) => null ;
456+
457+ const containerA = document . createElement ( 'div' ) ;
458+ const containerB = document . createElement ( 'div' ) ;
459+
460+ const rootA = ReactDOMClient . createRoot ( containerA ) ;
461+ const rootB = ReactDOMClient . createRoot ( containerB ) ;
462+
463+ utils . act ( ( ) => rootA . render ( < Parent /> ) ) ;
464+ utils . act ( ( ) => rootB . render ( < Parent /> ) ) ;
465+
466+ expect ( store ) . toMatchInlineSnapshot ( `
467+ [root]
468+ ▾ <Parent>
469+ <Child>
470+ [root]
471+ ▾ <Parent>
472+ <Child>
473+ ` ) ;
474+
475+ // Profile and record updates.
476+ await utils . actAsync ( ( ) => store . profilerStore . startProfiling ( ) ) ;
477+ await utils . actAsync ( ( ) => rootA . render ( < Parent /> ) ) ;
478+ await utils . actAsync ( ( ) => rootB . render ( < Parent /> ) ) ;
479+ await utils . actAsync ( ( ) => store . profilerStore . stopProfiling ( ) ) ;
480+
481+ let context : Context = ( ( null : any ) : Context ) ;
482+ let dispatch : DispatcherContext = ( ( null : any ) : DispatcherContext ) ;
483+ let selectedElementID = null ;
484+ function ContextReader ( ) {
485+ context = React . useContext ( ProfilerContext ) ;
486+ dispatch = React . useContext ( TreeDispatcherContext ) ;
487+ selectedElementID = React . useContext ( TreeStateContext ) . selectedElementID ;
488+ return null ;
489+ }
490+
491+ const id = ( ( store . getElementIDAtIndex ( 3 ) : any ) : number ) ;
492+
493+ // Select an element within the second root.
494+ await utils . actAsync ( ( ) =>
495+ TestRenderer . create (
496+ < Contexts defaultSelectedElementID = { id } defaultSelectedElementIndex = { 3 } >
497+ < ContextReader />
498+ </ Contexts > ,
499+ ) ,
500+ ) ;
501+
502+ expect ( selectedElementID ) . toBe ( id ) ;
503+
504+ // Profile and record more updates to both roots
505+ await utils . actAsync ( ( ) => store . profilerStore . startProfiling ( ) ) ;
506+ await utils . actAsync ( ( ) => rootA . render ( < Parent /> ) ) ;
507+ await utils . actAsync ( ( ) => rootB . render ( < Parent /> ) ) ;
508+ await utils . actAsync ( ( ) => store . profilerStore . stopProfiling ( ) ) ;
509+
510+ const otherID = ( ( store . getElementIDAtIndex ( 0 ) : any ) : number ) ;
511+
512+ // Change the selected element within a the Components tab.
513+ utils . act ( ( ) => dispatch ( { type : 'SELECT_ELEMENT_AT_INDEX' , payload : 0 } ) ) ;
514+
515+ // Verify that the initial Profiler root selection is maintained.
516+ expect ( selectedElementID ) . toBe ( otherID ) ;
517+ expect ( context ) . not . toBeNull ( ) ;
518+ expect ( context . rootID ) . toBe ( store . getRootIDForElement ( id ) ) ;
519+ } ) ;
520+
303521 it ( 'should sync selected element in the Components tab too, provided the element is a match' , async ( ) => {
304522 const GrandParent = ( { includeChild} ) => (
305523 < Parent includeChild = { includeChild } />
306524 ) ;
307525 const Parent = ( { includeChild} ) => ( includeChild ? < Child /> : null ) ;
308526 const Child = ( ) => null ;
309527
310- const container = document . createElement ( 'div' ) ;
311- utils . act ( ( ) =>
312- legacyRender ( < GrandParent includeChild = { true } /> , container ) ,
313- ) ;
528+ utils . act ( ( ) => render ( < GrandParent includeChild = { true } /> ) ) ;
314529 expect ( store ) . toMatchInlineSnapshot ( `
315530 [root]
316531 ▾ <GrandParent>
@@ -323,12 +538,8 @@ describe('ProfilerContext', () => {
323538
324539 // Profile and record updates.
325540 await utils . actAsync ( ( ) => store . profilerStore . startProfiling ( ) ) ;
326- await utils . actAsync ( ( ) =>
327- legacyRender ( < GrandParent includeChild = { true } /> , container ) ,
328- ) ;
329- await utils . actAsync ( ( ) =>
330- legacyRender ( < GrandParent includeChild = { false } /> , container ) ,
331- ) ;
541+ await utils . actAsync ( ( ) => render ( < GrandParent includeChild = { true } /> ) ) ;
542+ await utils . actAsync ( ( ) => render ( < GrandParent includeChild = { false } /> ) ) ;
332543 await utils . actAsync ( ( ) => store . profilerStore . stopProfiling ( ) ) ;
333544
334545 expect ( store ) . toMatchInlineSnapshot ( `
0 commit comments