@@ -13,9 +13,12 @@ import type {Context} from 'react-devtools-shared/src/devtools/views/Profiler/Pr
13
13
import type { DispatcherContext } from 'react-devtools-shared/src/devtools/views/Components/TreeContext' ;
14
14
import type Store from 'react-devtools-shared/src/devtools/store' ;
15
15
16
+ import { getVersionedRenderImplementation } from './utils' ;
17
+
16
18
describe ( 'ProfilerContext' , ( ) => {
17
19
let React ;
18
20
let ReactDOM ;
21
+ let ReactDOMClient ;
19
22
let TestRenderer : ReactTestRenderer ;
20
23
let bridge : FrontendBridge ;
21
24
let legacyRender ;
@@ -43,6 +46,7 @@ describe('ProfilerContext', () => {
43
46
44
47
React = require ( 'react' ) ;
45
48
ReactDOM = require ( 'react-dom' ) ;
49
+ ReactDOMClient = require ( 'react-dom/client' ) ;
46
50
TestRenderer = utils . requireTestRenderer ( ) ;
47
51
48
52
BridgeContext =
@@ -61,6 +65,8 @@ describe('ProfilerContext', () => {
61
65
require ( 'react-devtools-shared/src/devtools/views/Components/TreeContext' ) . TreeStateContext ;
62
66
} ) ;
63
67
68
+ const { render} = getVersionedRenderImplementation ( ) ;
69
+
64
70
const Contexts = ( {
65
71
children = null ,
66
72
defaultSelectedElementID = null ,
@@ -77,7 +83,9 @@ describe('ProfilerContext', () => {
77
83
</ BridgeContext . Provider >
78
84
) ;
79
85
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 ( ) => {
81
89
const Component = ( ) => null ;
82
90
83
91
let context : Context = ( ( null : any ) : Context ) ;
@@ -110,10 +118,47 @@ describe('ProfilerContext', () => {
110
118
expect ( context . supportsProfiling ) . toBe ( false ) ;
111
119
} ) ;
112
120
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
+
113
158
it ( 'should gracefully handle an empty profiling session (with no recorded commits)' , async ( ) => {
114
159
const Example = ( ) => null ;
115
160
116
- utils . act ( ( ) => legacyRender ( < Example /> , document . createElement ( 'div' ) ) ) ;
161
+ utils . act ( ( ) => render ( < Example /> ) ) ;
117
162
118
163
let context : Context = ( ( null : any ) : Context ) ;
119
164
@@ -145,7 +190,9 @@ describe('ProfilerContext', () => {
145
190
expect ( context . profilingData ) . toBe ( null ) ;
146
191
} ) ;
147
192
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 ( ) => {
149
196
const Parent = ( ) => < Child /> ;
150
197
const Child = ( ) => null ;
151
198
@@ -191,7 +238,60 @@ describe('ProfilerContext', () => {
191
238
) ;
192
239
} ) ;
193
240
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 ( ) => {
195
295
const Parent = ( ) => < Child /> ;
196
296
const Child = ( ) => null ;
197
297
@@ -237,7 +337,60 @@ describe('ProfilerContext', () => {
237
337
) ;
238
338
} ) ;
239
339
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 ( ) => {
241
394
const Parent = ( ) => < Child /> ;
242
395
const Child = ( ) => null ;
243
396
@@ -300,17 +453,83 @@ describe('ProfilerContext', () => {
300
453
expect ( context . rootID ) . toBe ( store . getRootIDForElement ( id ) ) ;
301
454
} ) ;
302
455
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
+
303
525
it ( 'should sync selected element in the Components tab too, provided the element is a match' , async ( ) => {
304
526
const GrandParent = ( { includeChild} ) => (
305
527
< Parent includeChild = { includeChild } />
306
528
) ;
307
529
const Parent = ( { includeChild} ) => ( includeChild ? < Child /> : null ) ;
308
530
const Child = ( ) => null ;
309
531
310
- const container = document . createElement ( 'div' ) ;
311
- utils . act ( ( ) =>
312
- legacyRender ( < GrandParent includeChild = { true } /> , container ) ,
313
- ) ;
532
+ utils . act ( ( ) => render ( < GrandParent includeChild = { true } /> ) ) ;
314
533
expect ( store ) . toMatchInlineSnapshot ( `
315
534
[root]
316
535
▾ <GrandParent>
@@ -323,12 +542,8 @@ describe('ProfilerContext', () => {
323
542
324
543
// Profile and record updates.
325
544
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 } /> ) ) ;
332
547
await utils . actAsync ( ( ) => store . profilerStore . stopProfiling ( ) ) ;
333
548
334
549
expect ( store ) . toMatchInlineSnapshot ( `
0 commit comments