@@ -1891,4 +1891,177 @@ describe('react-devtools-facade', () => {
18911891 } ) . not . toThrow ( ) ;
18921892 } ) ;
18931893 } ) ;
1894+
1895+ describe ( 'multiple roots and renderers' , ( ) => {
1896+ it ( 'inject() registers a new renderer and initializes its internals' , ( ) => {
1897+ // react-dom registered itself as a renderer when it was required. Here we
1898+ // register a second (simulated) renderer to cover the registration
1899+ // contract; the cross-root tests below use a single react-dom renderer.
1900+ const before = facade . hook . renderers . size ;
1901+ const id = facade . hook . inject ( {
1902+ reconcilerVersion : '18.2.0' ,
1903+ version : '18.2.0' ,
1904+ } ) ;
1905+ expect ( typeof id ) . toBe ( 'number' ) ;
1906+ expect ( facade . hook . renderers . size ) . toBe ( before + 1 ) ;
1907+ expect ( facade . rendererInternals . has ( id ) ) . toBe ( true ) ;
1908+ } ) ;
1909+
1910+ it ( 'getComponentTree aggregates components from multiple roots' , ( ) => {
1911+ function AppA ( ) {
1912+ return < div > A</ div > ;
1913+ }
1914+ function AppB ( ) {
1915+ return < div > B</ div > ;
1916+ }
1917+
1918+ const containerB = document . createElement ( 'div' ) ;
1919+ document . body . appendChild ( containerB ) ;
1920+ try {
1921+ act ( ( ) => {
1922+ ReactDOMClient . createRoot ( container ) . render ( < AppA /> ) ;
1923+ ReactDOMClient . createRoot ( containerB ) . render ( < AppB /> ) ;
1924+ } ) ;
1925+
1926+ const tree = createTools ( facade ) . getComponentTree ( ) ;
1927+ // Two roots → exactly two HostRoot nodes.
1928+ expect ( tree . filter ( n => n . type === 'root' ) ) . toHaveLength ( 2 ) ;
1929+
1930+ const appA = tree . find ( n => n . name === 'AppA' ) ;
1931+ const appB = tree . find ( n => n . name === 'AppB' ) ;
1932+ // Labels come from a per-call counter that spans every root, in root
1933+ // order: root A gets @c 0–@c2, root B gets @c3–@c5, so labels are
1934+ // globally unique across roots.
1935+ expect ( appA ) . toEqual ( {
1936+ label : '@c0' ,
1937+ type : 'function' ,
1938+ name : 'AppA' ,
1939+ key : null ,
1940+ firstChild : '@c2' ,
1941+ nextSibling : null ,
1942+ } ) ;
1943+ expect ( appB ) . toEqual ( {
1944+ label : '@c3' ,
1945+ type : 'function' ,
1946+ name : 'AppB' ,
1947+ key : null ,
1948+ firstChild : '@c5' ,
1949+ nextSibling : null ,
1950+ } ) ;
1951+ } finally {
1952+ document . body . removeChild ( containerB ) ;
1953+ }
1954+ } ) ;
1955+
1956+ it ( 'findComponents finds matches across multiple roots' , ( ) => {
1957+ function Shared ( ) {
1958+ return < span > shared</ span > ;
1959+ }
1960+ function RootA ( ) {
1961+ return < Shared /> ;
1962+ }
1963+ function RootB ( ) {
1964+ return < Shared /> ;
1965+ }
1966+
1967+ const containerB = document . createElement ( 'div' ) ;
1968+ document . body . appendChild ( containerB ) ;
1969+ try {
1970+ act ( ( ) => {
1971+ ReactDOMClient . createRoot ( container ) . render ( < RootA /> ) ;
1972+ ReactDOMClient . createRoot ( containerB ) . render ( < RootB /> ) ;
1973+ } ) ;
1974+
1975+ const result = createTools ( facade ) . findComponents ( 'Shared' ) ;
1976+ expect ( result . totalCount ) . toBe ( 2 ) ;
1977+ expect ( result . results . map ( r => r . name ) ) . toEqual ( [ 'Shared' , 'Shared' ] ) ;
1978+ // Globally unique labels, assigned in result order across both roots.
1979+ expect ( result . results . map ( r => r . label ) ) . toEqual ( [ '@c0' , '@c2' ] ) ;
1980+ } finally {
1981+ document . body . removeChild ( containerB ) ;
1982+ }
1983+ } ) ;
1984+
1985+ it ( 'resolves labels from any root via getComponentByLabel' , ( ) => {
1986+ function Widget ( ) {
1987+ return < div > w</ div > ;
1988+ }
1989+ function RootA ( ) {
1990+ return < Widget /> ;
1991+ }
1992+ function RootB ( ) {
1993+ return < Widget /> ;
1994+ }
1995+
1996+ const containerB = document . createElement ( 'div' ) ;
1997+ document . body . appendChild ( containerB ) ;
1998+ try {
1999+ act ( ( ) => {
2000+ ReactDOMClient . createRoot ( container ) . render ( < RootA /> ) ;
2001+ ReactDOMClient . createRoot ( containerB ) . render ( < RootB /> ) ;
2002+ } ) ;
2003+
2004+ const tools = createTools ( facade ) ;
2005+ const widgets = tools . findComponents ( 'Widget' ) . results ;
2006+ expect ( widgets ) . toHaveLength ( 2 ) ;
2007+ widgets . forEach ( w => {
2008+ expect ( tools . getComponentByLabel ( w . label ) . name ) . toBe ( 'Widget' ) ;
2009+ } ) ;
2010+ } finally {
2011+ document . body . removeChild ( containerB ) ;
2012+ }
2013+ } ) ;
2014+
2015+ it ( 'profiling records commits from all roots' , ( ) => {
2016+ function CounterA ( { count} ) {
2017+ return < div > { 'A:' + count } </ div > ;
2018+ }
2019+ function CounterB ( { count} ) {
2020+ return < div > { 'B:' + count } </ div > ;
2021+ }
2022+
2023+ const containerB = document . createElement ( 'div' ) ;
2024+ document . body . appendChild ( containerB ) ;
2025+ try {
2026+ const rootA = ReactDOMClient . createRoot ( container ) ;
2027+ const rootB = ReactDOMClient . createRoot ( containerB ) ;
2028+ act ( ( ) => {
2029+ rootA . render ( < CounterA count = { 0 } /> ) ;
2030+ rootB . render ( < CounterB count = { 0 } /> ) ;
2031+ } ) ;
2032+
2033+ const tools = createTools ( facade ) ;
2034+ tools . startProfiling ( 'multi-root-trace' ) ;
2035+ act ( ( ) => {
2036+ rootA . render ( < CounterA count = { 1 } /> ) ;
2037+ } ) ;
2038+ act ( ( ) => {
2039+ rootB . render ( < CounterB count = { 1 } /> ) ;
2040+ } ) ;
2041+
2042+ expect ( tools . stopProfiling ( ) ) . toEqual ( {
2043+ status : 'stopped' ,
2044+ trace : 'multi-root-trace' ,
2045+ commits : 2 ,
2046+ } ) ;
2047+
2048+ const overview = tools . getTraceOverview ( 'multi-root-trace' ) ;
2049+ expect ( overview ) . toHaveLength ( 2 ) ;
2050+ // Separate act() blocks force exactly one commit each, in order, so
2051+ // commit 0 is rootA's re-render and commit 1 is rootB's.
2052+ const names0 = tools
2053+ . getCommitReport ( 'multi-root-trace' , 0 )
2054+ . components . map ( c => c . name ) ;
2055+ const names1 = tools
2056+ . getCommitReport ( 'multi-root-trace' , 1 )
2057+ . components . map ( c => c . name ) ;
2058+ expect ( names0 ) . toContain ( 'CounterA' ) ;
2059+ expect ( names0 ) . not . toContain ( 'CounterB' ) ;
2060+ expect ( names1 ) . toContain ( 'CounterB' ) ;
2061+ expect ( names1 ) . not . toContain ( 'CounterA' ) ;
2062+ } finally {
2063+ document . body . removeChild ( containerB ) ;
2064+ }
2065+ } ) ;
2066+ } ) ;
18942067} ) ;
0 commit comments