@@ -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,9 @@ describe('ProfilerContext', () => {
7783 </ BridgeContext . Provider >
7884 ) ;
7985
80- it ( 'updates updates profiling support based on the attached roots' , async ( ) => {
86+ // @reactVersion <= 18.2
87+ // @reactVersion >= 18.0
88+ it ( 'updates updates profiling support based on the attached roots (legacy render)' , async ( ) => {
8189 const Component = ( ) => null ;
8290
8391 let context : Context = ( ( null : any ) : Context ) ;
@@ -110,10 +118,47 @@ describe('ProfilerContext', () => {
110118 expect ( context . supportsProfiling ) . toBe ( false ) ;
111119 } ) ;
112120
121+ // @reactVersion >= 18
122+ it ( 'updates updates profiling support based on the attached roots (createRoot)' , async ( ) => {
123+ const Component = ( ) => null ;
124+
125+ let context : Context = ( ( null : any ) : Context ) ;
126+
127+ function ContextReader ( ) {
128+ context = React . useContext ( ProfilerContext ) ;
129+ return null ;
130+ }
131+ await utils . actAsync ( ( ) => {
132+ TestRenderer . create (
133+ < Contexts >
134+ < ContextReader />
135+ </ Contexts > ,
136+ ) ;
137+ } ) ;
138+
139+ expect ( context . supportsProfiling ) . toBe ( false ) ;
140+
141+ const containerA = document . createElement ( 'div' ) ;
142+ const containerB = document . createElement ( 'div' ) ;
143+
144+ const rootA = ReactDOMClient . createRoot ( containerA ) ;
145+ const rootB = ReactDOMClient . createRoot ( containerB ) ;
146+
147+ await utils . actAsync ( ( ) => rootA . render ( < Component /> ) ) ;
148+ expect ( context . supportsProfiling ) . toBe ( true ) ;
149+
150+ await utils . actAsync ( ( ) => rootB . render ( < Component /> ) ) ;
151+ await utils . actAsync ( ( ) => rootA . unmount ( ) ) ;
152+ expect ( context . supportsProfiling ) . toBe ( true ) ;
153+
154+ await utils . actAsync ( ( ) => rootB . unmount ( ) ) ;
155+ expect ( context . supportsProfiling ) . toBe ( false ) ;
156+ } ) ;
157+
113158 it ( 'should gracefully handle an empty profiling session (with no recorded commits)' , async ( ) => {
114159 const Example = ( ) => null ;
115160
116- utils . act ( ( ) => legacyRender ( < Example /> , document . createElement ( 'div' ) ) ) ;
161+ utils . act ( ( ) => render ( < Example /> ) ) ;
117162
118163 let context : Context = ( ( null : any ) : Context ) ;
119164
@@ -145,7 +190,9 @@ describe('ProfilerContext', () => {
145190 expect ( context . profilingData ) . toBe ( null ) ;
146191 } ) ;
147192
148- it ( 'should auto-select the root ID matching the Components tab selection if it has profiling data' , async ( ) => {
193+ // @reactVersion <= 18.2
194+ // @reactVersion >= 18.0
195+ it ( 'should auto-select the root ID matching the Components tab selection if it has profiling data (legacy render)' , async ( ) => {
149196 const Parent = ( ) => < Child /> ;
150197 const Child = ( ) => null ;
151198
@@ -191,7 +238,60 @@ describe('ProfilerContext', () => {
191238 ) ;
192239 } ) ;
193240
194- it ( 'should not select the root ID matching the Components tab selection if it has no profiling data' , async ( ) => {
241+ // @reactVersion >= 18
242+ it ( 'should auto-select the root ID matching the Components tab selection if it has profiling data (createRoot)' , async ( ) => {
243+ const Parent = ( ) => < Child /> ;
244+ const Child = ( ) => null ;
245+
246+ const containerOne = document . createElement ( 'div' ) ;
247+ const containerTwo = document . createElement ( 'div' ) ;
248+
249+ const rootOne = ReactDOMClient . createRoot ( containerOne ) ;
250+ const rootTwo = ReactDOMClient . createRoot ( containerTwo ) ;
251+
252+ utils . act ( ( ) => rootOne . render ( < Parent /> ) ) ;
253+ utils . act ( ( ) => rootTwo . render ( < Parent /> ) ) ;
254+ expect ( store ) . toMatchInlineSnapshot ( `
255+ [root]
256+ ▾ <Parent>
257+ <Child>
258+ [root]
259+ ▾ <Parent>
260+ <Child>
261+ ` ) ;
262+
263+ // Profile and record updates to both roots.
264+ await utils . actAsync ( ( ) => store . profilerStore . startProfiling ( ) ) ;
265+ await utils . actAsync ( ( ) => rootOne . render ( < Parent /> ) ) ;
266+ await utils . actAsync ( ( ) => rootTwo . render ( < Parent /> ) ) ;
267+ await utils . actAsync ( ( ) => store . profilerStore . stopProfiling ( ) ) ;
268+
269+ let context : Context = ( ( null : any ) : Context ) ;
270+ function ContextReader ( ) {
271+ context = React . useContext ( ProfilerContext ) ;
272+ return null ;
273+ }
274+
275+ // Select an element within the second root.
276+ await utils . actAsync ( ( ) =>
277+ TestRenderer . create (
278+ < Contexts
279+ defaultSelectedElementID = { store . getElementIDAtIndex ( 3 ) }
280+ defaultSelectedElementIndex = { 3 } >
281+ < ContextReader />
282+ </ Contexts > ,
283+ ) ,
284+ ) ;
285+
286+ expect ( context ) . not . toBeNull ( ) ;
287+ expect ( context . rootID ) . toBe (
288+ store . getRootIDForElement ( ( ( store . getElementIDAtIndex ( 3 ) : any ) : number ) ) ,
289+ ) ;
290+ } ) ;
291+
292+ // @reactVersion <= 18.2
293+ // @reactVersion >= 18.0
294+ it ( 'should not select the root ID matching the Components tab selection if it has no profiling data (legacy render)' , async ( ) => {
195295 const Parent = ( ) => < Child /> ;
196296 const Child = ( ) => null ;
197297
@@ -237,7 +337,60 @@ describe('ProfilerContext', () => {
237337 ) ;
238338 } ) ;
239339
240- it ( 'should maintain root selection between profiling sessions so long as there is data for that root' , async ( ) => {
340+ // @reactVersion >= 18
341+ it ( 'should not select the root ID matching the Components tab selection if it has no profiling data (createRoot)' , async ( ) => {
342+ const Parent = ( ) => < Child /> ;
343+ const Child = ( ) => null ;
344+
345+ const containerOne = document . createElement ( 'div' ) ;
346+ const containerTwo = document . createElement ( 'div' ) ;
347+
348+ const rootOne = ReactDOMClient . createRoot ( containerOne ) ;
349+ const rootTwo = ReactDOMClient . createRoot ( containerTwo ) ;
350+
351+ utils . act ( ( ) => rootOne . render ( < Parent /> ) ) ;
352+ utils . act ( ( ) => rootTwo . render ( < Parent /> ) ) ;
353+ expect ( store ) . toMatchInlineSnapshot ( `
354+ [root]
355+ ▾ <Parent>
356+ <Child>
357+ [root]
358+ ▾ <Parent>
359+ <Child>
360+ ` ) ;
361+
362+ // Profile and record updates to only the first root.
363+ await utils . actAsync ( ( ) => store . profilerStore . startProfiling ( ) ) ;
364+ await utils . actAsync ( ( ) => rootOne . render ( < Parent /> ) ) ;
365+ await utils . actAsync ( ( ) => store . profilerStore . stopProfiling ( ) ) ;
366+
367+ let context : Context = ( ( null : any ) : Context ) ;
368+ function ContextReader ( ) {
369+ context = React . useContext ( ProfilerContext ) ;
370+ return null ;
371+ }
372+
373+ // Select an element within the second root.
374+ await utils . actAsync ( ( ) =>
375+ TestRenderer . create (
376+ < Contexts
377+ defaultSelectedElementID = { store . getElementIDAtIndex ( 3 ) }
378+ defaultSelectedElementIndex = { 3 } >
379+ < ContextReader />
380+ </ Contexts > ,
381+ ) ,
382+ ) ;
383+
384+ // Verify the default profiling root is the first one.
385+ expect ( context ) . not . toBeNull ( ) ;
386+ expect ( context . rootID ) . toBe (
387+ store . getRootIDForElement ( ( ( store . getElementIDAtIndex ( 0 ) : any ) : number ) ) ,
388+ ) ;
389+ } ) ;
390+
391+ // @reactVersion <= 18.2
392+ // @reactVersion >= 18.0
393+ it ( 'should maintain root selection between profiling sessions so long as there is data for that root (legacy render)' , async ( ) => {
241394 const Parent = ( ) => < Child /> ;
242395 const Child = ( ) => null ;
243396
@@ -300,17 +453,83 @@ describe('ProfilerContext', () => {
300453 expect ( context . rootID ) . toBe ( store . getRootIDForElement ( id ) ) ;
301454 } ) ;
302455
456+ // @reactVersion >= 18.0
457+ it ( 'should maintain root selection between profiling sessions so long as there is data for that root (createRoot)' , async ( ) => {
458+ const Parent = ( ) => < Child /> ;
459+ const Child = ( ) => null ;
460+
461+ const containerA = document . createElement ( 'div' ) ;
462+ const containerB = document . createElement ( 'div' ) ;
463+
464+ const rootA = ReactDOMClient . createRoot ( containerA ) ;
465+ const rootB = ReactDOMClient . createRoot ( containerB ) ;
466+
467+ utils . act ( ( ) => rootA . render ( < Parent /> ) ) ;
468+ utils . act ( ( ) => rootB . render ( < Parent /> ) ) ;
469+
470+ expect ( store ) . toMatchInlineSnapshot ( `
471+ [root]
472+ ▾ <Parent>
473+ <Child>
474+ [root]
475+ ▾ <Parent>
476+ <Child>
477+ ` ) ;
478+
479+ // Profile and record updates.
480+ await utils . actAsync ( ( ) => store . profilerStore . startProfiling ( ) ) ;
481+ await utils . actAsync ( ( ) => rootA . render ( < Parent /> ) ) ;
482+ await utils . actAsync ( ( ) => rootB . render ( < Parent /> ) ) ;
483+ await utils . actAsync ( ( ) => store . profilerStore . stopProfiling ( ) ) ;
484+
485+ let context : Context = ( ( null : any ) : Context ) ;
486+ let dispatch : DispatcherContext = ( ( null : any ) : DispatcherContext ) ;
487+ let selectedElementID = null ;
488+ function ContextReader ( ) {
489+ context = React . useContext ( ProfilerContext ) ;
490+ dispatch = React . useContext ( TreeDispatcherContext ) ;
491+ selectedElementID = React . useContext ( TreeStateContext ) . selectedElementID ;
492+ return null ;
493+ }
494+
495+ const id = ( ( store . getElementIDAtIndex ( 3 ) : any ) : number ) ;
496+
497+ // Select an element within the second root.
498+ await utils . actAsync ( ( ) =>
499+ TestRenderer . create (
500+ < Contexts defaultSelectedElementID = { id } defaultSelectedElementIndex = { 3 } >
501+ < ContextReader />
502+ </ Contexts > ,
503+ ) ,
504+ ) ;
505+
506+ expect ( selectedElementID ) . toBe ( id ) ;
507+
508+ // Profile and record more updates to both roots
509+ await utils . actAsync ( ( ) => store . profilerStore . startProfiling ( ) ) ;
510+ await utils . actAsync ( ( ) => rootA . render ( < Parent /> ) ) ;
511+ await utils . actAsync ( ( ) => rootB . render ( < Parent /> ) ) ;
512+ await utils . actAsync ( ( ) => store . profilerStore . stopProfiling ( ) ) ;
513+
514+ const otherID = ( ( store . getElementIDAtIndex ( 0 ) : any ) : number ) ;
515+
516+ // Change the selected element within a the Components tab.
517+ utils . act ( ( ) => dispatch ( { type : 'SELECT_ELEMENT_AT_INDEX' , payload : 0 } ) ) ;
518+
519+ // Verify that the initial Profiler root selection is maintained.
520+ expect ( selectedElementID ) . toBe ( otherID ) ;
521+ expect ( context ) . not . toBeNull ( ) ;
522+ expect ( context . rootID ) . toBe ( store . getRootIDForElement ( id ) ) ;
523+ } ) ;
524+
303525 it ( 'should sync selected element in the Components tab too, provided the element is a match' , async ( ) => {
304526 const GrandParent = ( { includeChild} ) => (
305527 < Parent includeChild = { includeChild } />
306528 ) ;
307529 const Parent = ( { includeChild} ) => ( includeChild ? < Child /> : null ) ;
308530 const Child = ( ) => null ;
309531
310- const container = document . createElement ( 'div' ) ;
311- utils . act ( ( ) =>
312- legacyRender ( < GrandParent includeChild = { true } /> , container ) ,
313- ) ;
532+ utils . act ( ( ) => render ( < GrandParent includeChild = { true } /> ) ) ;
314533 expect ( store ) . toMatchInlineSnapshot ( `
315534 [root]
316535 ▾ <GrandParent>
@@ -323,12 +542,8 @@ describe('ProfilerContext', () => {
323542
324543 // Profile and record updates.
325544 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- ) ;
545+ await utils . actAsync ( ( ) => render ( < GrandParent includeChild = { true } /> ) ) ;
546+ await utils . actAsync ( ( ) => render ( < GrandParent includeChild = { false } /> ) ) ;
332547 await utils . actAsync ( ( ) => store . profilerStore . stopProfiling ( ) ) ;
333548
334549 expect ( store ) . toMatchInlineSnapshot ( `
0 commit comments