@@ -107,6 +107,38 @@ describe('ReactFlightDOM', () => {
107107 return maybePromise ;
108108 }
109109
110+ async function readInto (
111+ container : Document | HTMLElement ,
112+ stream : ReadableStream ,
113+ ) {
114+ const reader = stream . getReader ( ) ;
115+ const decoder = new TextDecoder ( ) ;
116+ let content = '' ;
117+ while ( true ) {
118+ const { done, value} = await reader . read ( ) ;
119+ if ( done ) {
120+ content += decoder . decode ( ) ;
121+ break ;
122+ }
123+ content += decoder . decode ( value , { stream : true } ) ;
124+ }
125+ if ( container . nodeType === 9 /* DOCUMENT */ ) {
126+ const doc = new JSDOM ( content ) . window . document ;
127+ container . documentElement . innerHTML = doc . documentElement . innerHTML ;
128+ while ( container . documentElement . attributes . length > 0 ) {
129+ container . documentElement . removeAttribute (
130+ container . documentElement . attributes [ 0 ] . name ,
131+ ) ;
132+ }
133+ const attrs = doc . documentElement . attributes ;
134+ for ( let i = 0 ; i < attrs . length ; i ++ ) {
135+ container . documentElement . setAttribute ( attrs [ i ] . name , attrs [ i ] . value ) ;
136+ }
137+ } else {
138+ container . innerHTML = content ;
139+ }
140+ }
141+
110142 function getTestStream ( ) {
111143 const writable = new Stream . PassThrough ( ) ;
112144 const readable = new ReadableStream ( {
@@ -1633,20 +1665,8 @@ describe('ReactFlightDOM', () => {
16331665 ReactDOMFizzServer . renderToPipeableStream ( < App /> ) . pipe ( fizzWritable ) ;
16341666 } ) ;
16351667
1636- const decoder = new TextDecoder ( ) ;
1637- const reader = fizzReadable . getReader ( ) ;
1638- let content = '' ;
1639- while ( true ) {
1640- const { done, value} = await reader . read ( ) ;
1641- if ( done ) {
1642- content += decoder . decode ( ) ;
1643- break ;
1644- }
1645- content += decoder . decode ( value , { stream : true } ) ;
1646- }
1647-
1648- const doc = new JSDOM ( content ) . window . document ;
1649- expect ( getMeaningfulChildren ( doc ) ) . toEqual (
1668+ await readInto ( document , fizzReadable ) ;
1669+ expect ( getMeaningfulChildren ( document ) ) . toEqual (
16501670 < html >
16511671 < head >
16521672 < link rel = "dns-prefetch" href = "d before" />
@@ -1912,4 +1932,279 @@ describe('ReactFlightDOM', () => {
19121932 } ) ;
19131933 expect ( container . innerHTML ) . toBe ( 'Hello World' ) ;
19141934 } ) ;
1935+
1936+ it ( 'can abort synchronously during render' , async ( ) => {
1937+ let siblingDidRender = false ;
1938+ function Sibling ( ) {
1939+ siblingDidRender = true ;
1940+ return < p > sibling</ p > ;
1941+ }
1942+
1943+ function App ( ) {
1944+ return (
1945+ < Suspense fallback = { < p > loading...</ p > } >
1946+ < ComponentThatAborts />
1947+ < Sibling />
1948+ < div >
1949+ < Sibling />
1950+ </ div >
1951+ </ Suspense >
1952+ ) ;
1953+ }
1954+
1955+ const abortRef = { current : null } ;
1956+ function ComponentThatAborts ( ) {
1957+ abortRef . current ( ) ;
1958+ return < p > hello world</ p > ;
1959+ }
1960+
1961+ const { writable : flightWritable , readable : flightReadable } =
1962+ getTestStream ( ) ;
1963+
1964+ await serverAct ( ( ) => {
1965+ const { pipe, abort} = ReactServerDOMServer . renderToPipeableStream (
1966+ < App /> ,
1967+ webpackMap ,
1968+ ) ;
1969+ abortRef . current = abort ;
1970+ pipe ( flightWritable ) ;
1971+ } ) ;
1972+
1973+ expect ( siblingDidRender ) . toBe ( false ) ;
1974+
1975+ const response =
1976+ ReactServerDOMClient . createFromReadableStream ( flightReadable ) ;
1977+
1978+ const { writable : fizzWritable , readable : fizzReadable } = getTestStream ( ) ;
1979+
1980+ function ClientApp ( ) {
1981+ return use ( response ) ;
1982+ }
1983+
1984+ const shellErrors = [ ] ;
1985+ await serverAct ( async ( ) => {
1986+ ReactDOMFizzServer . renderToPipeableStream (
1987+ React . createElement ( ClientApp ) ,
1988+ {
1989+ onShellError ( error ) {
1990+ shellErrors . push ( error . message ) ;
1991+ } ,
1992+ } ,
1993+ ) . pipe ( fizzWritable ) ;
1994+ } ) ;
1995+
1996+ expect ( shellErrors ) . toEqual ( [ ] ) ;
1997+
1998+ const container = document . createElement ( 'div' ) ;
1999+ await readInto ( container , fizzReadable ) ;
2000+ expect ( getMeaningfulChildren ( container ) ) . toEqual ( < p > loading...</ p > ) ;
2001+ } ) ;
2002+
2003+ it ( 'can abort during render in an async tick' , async ( ) => {
2004+ let siblingDidRender = false ;
2005+ function DidRender ( { children} ) {
2006+ siblingDidRender = true ;
2007+ }
2008+
2009+ async function Sibling ( ) {
2010+ return (
2011+ < DidRender >
2012+ < p > sibling</ p >
2013+ </ DidRender >
2014+ ) ;
2015+ }
2016+
2017+ function App ( ) {
2018+ return (
2019+ < Suspense fallback = { < p > loading...</ p > } >
2020+ < ComponentThatAborts />
2021+ < Sibling />
2022+ </ Suspense >
2023+ ) ;
2024+ }
2025+
2026+ const abortRef = { current : null } ;
2027+ async function ComponentThatAborts ( ) {
2028+ await 1 ;
2029+ abortRef . current ( ) ;
2030+ return < p > hello world</ p > ;
2031+ }
2032+
2033+ const { writable : flightWritable , readable : flightReadable } =
2034+ getTestStream ( ) ;
2035+
2036+ await serverAct ( ( ) => {
2037+ const { pipe, abort} = ReactServerDOMServer . renderToPipeableStream (
2038+ < App /> ,
2039+ webpackMap ,
2040+ ) ;
2041+ abortRef . current = abort ;
2042+ pipe ( flightWritable ) ;
2043+ } ) ;
2044+
2045+ expect ( siblingDidRender ) . toBe ( false ) ;
2046+
2047+ const response =
2048+ ReactServerDOMClient . createFromReadableStream ( flightReadable ) ;
2049+
2050+ const { writable : fizzWritable , readable : fizzReadable } = getTestStream ( ) ;
2051+
2052+ function ClientApp ( ) {
2053+ return use ( response ) ;
2054+ }
2055+
2056+ const shellErrors = [ ] ;
2057+ await serverAct ( async ( ) => {
2058+ ReactDOMFizzServer . renderToPipeableStream (
2059+ React . createElement ( ClientApp ) ,
2060+ {
2061+ onShellError ( error ) {
2062+ shellErrors . push ( error . message ) ;
2063+ } ,
2064+ } ,
2065+ ) . pipe ( fizzWritable ) ;
2066+ } ) ;
2067+
2068+ expect ( shellErrors ) . toEqual ( [ ] ) ;
2069+
2070+ const container = document . createElement ( 'div' ) ;
2071+ await readInto ( container , fizzReadable ) ;
2072+ expect ( getMeaningfulChildren ( container ) ) . toEqual ( < p > loading...</ p > ) ;
2073+ } ) ;
2074+
2075+ it ( 'can abort during render in a lazy initializer for a component' , async ( ) => {
2076+ let siblingDidRender = false ;
2077+
2078+ function Sibling ( ) {
2079+ siblingDidRender = true ;
2080+ return < p > sibling</ p > ;
2081+ }
2082+
2083+ function App ( ) {
2084+ return (
2085+ < Suspense fallback = { < p > loading...</ p > } >
2086+ < LazyAbort />
2087+ < Sibling />
2088+ </ Suspense >
2089+ ) ;
2090+ }
2091+
2092+ const abortRef = { current : null } ;
2093+ const LazyAbort = React . lazy ( ( ) => {
2094+ abortRef . current ( ) ;
2095+ return Promise . resolve ( {
2096+ default : function LazyComponent ( ) {
2097+ return < p > hello world</ p > ;
2098+ } ,
2099+ } ) ;
2100+ } ) ;
2101+
2102+ const { writable : flightWritable , readable : flightReadable } =
2103+ getTestStream ( ) ;
2104+
2105+ await serverAct ( ( ) => {
2106+ const { pipe, abort} = ReactServerDOMServer . renderToPipeableStream (
2107+ < App /> ,
2108+ webpackMap ,
2109+ ) ;
2110+ abortRef . current = abort ;
2111+ pipe ( flightWritable ) ;
2112+ } ) ;
2113+
2114+ expect ( siblingDidRender ) . toBe ( false ) ;
2115+
2116+ const response =
2117+ ReactServerDOMClient . createFromReadableStream ( flightReadable ) ;
2118+
2119+ const { writable : fizzWritable , readable : fizzReadable } = getTestStream ( ) ;
2120+
2121+ function ClientApp ( ) {
2122+ return use ( response ) ;
2123+ }
2124+
2125+ const shellErrors = [ ] ;
2126+ await serverAct ( async ( ) => {
2127+ ReactDOMFizzServer . renderToPipeableStream (
2128+ React . createElement ( ClientApp ) ,
2129+ {
2130+ onShellError ( error ) {
2131+ shellErrors . push ( error . message ) ;
2132+ } ,
2133+ } ,
2134+ ) . pipe ( fizzWritable ) ;
2135+ } ) ;
2136+
2137+ expect ( shellErrors ) . toEqual ( [ ] ) ;
2138+
2139+ const container = document . createElement ( 'div' ) ;
2140+ await readInto ( container , fizzReadable ) ;
2141+ expect ( getMeaningfulChildren ( container ) ) . toEqual ( < p > loading...</ p > ) ;
2142+ } ) ;
2143+
2144+ it ( 'can abort during render in a lazy initializer for an element' , async ( ) => {
2145+ let siblingDidRender = false ;
2146+
2147+ function Sibling ( ) {
2148+ siblingDidRender = true ;
2149+ return < p > sibling</ p > ;
2150+ }
2151+
2152+ function App ( ) {
2153+ return (
2154+ < Suspense fallback = { < p > loading...</ p > } >
2155+ { lazyAbort }
2156+ < Sibling />
2157+ </ Suspense >
2158+ ) ;
2159+ }
2160+
2161+ const abortRef = { current : null } ;
2162+ const lazyAbort = React . lazy ( ( ) => {
2163+ abortRef . current ( ) ;
2164+ return Promise . resolve ( {
2165+ default : < p > hello world</ p > ,
2166+ } ) ;
2167+ } ) ;
2168+
2169+ const { writable : flightWritable , readable : flightReadable } =
2170+ getTestStream ( ) ;
2171+
2172+ await serverAct ( ( ) => {
2173+ const { pipe, abort} = ReactServerDOMServer . renderToPipeableStream (
2174+ < App /> ,
2175+ webpackMap ,
2176+ ) ;
2177+ abortRef . current = abort ;
2178+ pipe ( flightWritable ) ;
2179+ } ) ;
2180+
2181+ expect ( siblingDidRender ) . toBe ( false ) ;
2182+
2183+ const response =
2184+ ReactServerDOMClient . createFromReadableStream ( flightReadable ) ;
2185+
2186+ const { writable : fizzWritable , readable : fizzReadable } = getTestStream ( ) ;
2187+
2188+ function ClientApp ( ) {
2189+ return use ( response ) ;
2190+ }
2191+
2192+ const shellErrors = [ ] ;
2193+ await serverAct ( async ( ) => {
2194+ ReactDOMFizzServer . renderToPipeableStream (
2195+ React . createElement ( ClientApp ) ,
2196+ {
2197+ onShellError ( error ) {
2198+ shellErrors . push ( error . message ) ;
2199+ } ,
2200+ } ,
2201+ ) . pipe ( fizzWritable ) ;
2202+ } ) ;
2203+
2204+ expect ( shellErrors ) . toEqual ( [ ] ) ;
2205+
2206+ const container = document . createElement ( 'div' ) ;
2207+ await readInto ( container , fizzReadable ) ;
2208+ expect ( getMeaningfulChildren ( container ) ) . toEqual ( < p > loading...</ p > ) ;
2209+ } ) ;
19152210} ) ;
0 commit comments