@@ -17,6 +17,58 @@ let ReactFeatureFlags;
1717let Suspense ;
1818let SuspenseList ;
1919let act ;
20+ let useHover ;
21+
22+ function dispatchMouseEvent ( to , from ) {
23+ if ( ! to ) {
24+ to = null ;
25+ }
26+ if ( ! from ) {
27+ from = null ;
28+ }
29+ if ( from ) {
30+ const mouseOutEvent = document . createEvent ( 'MouseEvents' ) ;
31+ mouseOutEvent . initMouseEvent (
32+ 'mouseout' ,
33+ true ,
34+ true ,
35+ window ,
36+ 0 ,
37+ 50 ,
38+ 50 ,
39+ 50 ,
40+ 50 ,
41+ false ,
42+ false ,
43+ false ,
44+ false ,
45+ 0 ,
46+ to ,
47+ ) ;
48+ from . dispatchEvent ( mouseOutEvent ) ;
49+ }
50+ if ( to ) {
51+ const mouseOverEvent = document . createEvent ( 'MouseEvents' ) ;
52+ mouseOverEvent . initMouseEvent (
53+ 'mouseover' ,
54+ true ,
55+ true ,
56+ window ,
57+ 0 ,
58+ 50 ,
59+ 50 ,
60+ 50 ,
61+ 50 ,
62+ false ,
63+ false ,
64+ false ,
65+ false ,
66+ 0 ,
67+ from ,
68+ ) ;
69+ to . dispatchEvent ( mouseOverEvent ) ;
70+ }
71+ }
2072
2173describe ( 'ReactDOMServerPartialHydration' , ( ) => {
2274 beforeEach ( ( ) => {
@@ -34,6 +86,8 @@ describe('ReactDOMServerPartialHydration', () => {
3486 Scheduler = require ( 'scheduler' ) ;
3587 Suspense = React . Suspense ;
3688 SuspenseList = React . unstable_SuspenseList ;
89+
90+ useHover = require ( 'react-ui/events/hover' ) . useHover ;
3791 } ) ;
3892
3993 it ( 'hydrates a parent even if a child Suspense boundary is blocked' , async ( ) => {
@@ -2040,4 +2094,223 @@ describe('ReactDOMServerPartialHydration', () => {
20402094
20412095 document . body . removeChild ( parentContainer ) ;
20422096 } ) ;
2097+
2098+ it ( 'blocks only on the last continuous event (legacy system)' , async ( ) => {
2099+ let suspend1 = false ;
2100+ let resolve1 ;
2101+ let promise1 = new Promise ( resolvePromise => ( resolve1 = resolvePromise ) ) ;
2102+ let suspend2 = false ;
2103+ let resolve2 ;
2104+ let promise2 = new Promise ( resolvePromise => ( resolve2 = resolvePromise ) ) ;
2105+
2106+ function First ( { text} ) {
2107+ if ( suspend1 ) {
2108+ throw promise1 ;
2109+ } else {
2110+ return 'Hello' ;
2111+ }
2112+ }
2113+
2114+ function Second ( { text} ) {
2115+ if ( suspend2 ) {
2116+ throw promise2 ;
2117+ } else {
2118+ return 'World' ;
2119+ }
2120+ }
2121+
2122+ let ops = [ ] ;
2123+
2124+ function App ( ) {
2125+ return (
2126+ < div >
2127+ < Suspense fallback = "Loading First..." >
2128+ < span
2129+ onMouseEnter = { ( ) => ops . push ( 'Mouse Enter First' ) }
2130+ onMouseLeave = { ( ) => ops . push ( 'Mouse Leave First' ) }
2131+ />
2132+ { /* We suspend after to test what happens when we eager
2133+ attach the listener. */ }
2134+ < First />
2135+ </ Suspense >
2136+ < Suspense fallback = "Loading Second..." >
2137+ < span
2138+ onMouseEnter = { ( ) => ops . push ( 'Mouse Enter Second' ) }
2139+ onMouseLeave = { ( ) => ops . push ( 'Mouse Leave Second' ) } >
2140+ < Second />
2141+ </ span >
2142+ </ Suspense >
2143+ </ div >
2144+ ) ;
2145+ }
2146+
2147+ let finalHTML = ReactDOMServer . renderToString ( < App /> ) ;
2148+ let container = document . createElement ( 'div' ) ;
2149+ container . innerHTML = finalHTML ;
2150+
2151+ // We need this to be in the document since we'll dispatch events on it.
2152+ document . body . appendChild ( container ) ;
2153+
2154+ let appDiv = container . getElementsByTagName ( 'div' ) [ 0 ] ;
2155+ let firstSpan = appDiv . getElementsByTagName ( 'span' ) [ 0 ] ;
2156+ let secondSpan = appDiv . getElementsByTagName ( 'span' ) [ 1 ] ;
2157+ expect ( firstSpan . textContent ) . toBe ( '' ) ;
2158+ expect ( secondSpan . textContent ) . toBe ( 'World' ) ;
2159+
2160+ // On the client we don't have all data yet but we want to start
2161+ // hydrating anyway.
2162+ suspend1 = true ;
2163+ suspend2 = true ;
2164+ let root = ReactDOM . unstable_createRoot ( container , { hydrate : true } ) ;
2165+ root . render ( < App /> ) ;
2166+
2167+ Scheduler . unstable_flushAll ( ) ;
2168+ jest . runAllTimers ( ) ;
2169+
2170+ dispatchMouseEvent ( appDiv , null ) ;
2171+ dispatchMouseEvent ( firstSpan , appDiv ) ;
2172+ dispatchMouseEvent ( secondSpan , firstSpan ) ;
2173+
2174+ // Neither target is yet hydrated.
2175+ expect ( ops ) . toEqual ( [ ] ) ;
2176+
2177+ // Resolving the second promise so that rendering can complete.
2178+ suspend2 = false ;
2179+ resolve2 ( ) ;
2180+ await promise2 ;
2181+
2182+ Scheduler . unstable_flushAll ( ) ;
2183+ jest . runAllTimers ( ) ;
2184+
2185+ // We've unblocked the current hover target so we should be
2186+ // able to replay it now.
2187+ expect ( ops ) . toEqual ( [ 'Mouse Enter Second' ] ) ;
2188+
2189+ // Resolving the first promise has no effect now.
2190+ suspend1 = false ;
2191+ resolve1 ( ) ;
2192+ await promise1 ;
2193+
2194+ Scheduler . unstable_flushAll ( ) ;
2195+ jest . runAllTimers ( ) ;
2196+
2197+ expect ( ops ) . toEqual ( [ 'Mouse Enter Second' ] ) ;
2198+
2199+ document . body . removeChild ( container ) ;
2200+ } ) ;
2201+
2202+ it ( 'blocks only on the last continuous event (Responder system)' , async ( ) => {
2203+ let suspend1 = false ;
2204+ let resolve1 ;
2205+ let promise1 = new Promise ( resolvePromise => ( resolve1 = resolvePromise ) ) ;
2206+ let suspend2 = false ;
2207+ let resolve2 ;
2208+ let promise2 = new Promise ( resolvePromise => ( resolve2 = resolvePromise ) ) ;
2209+
2210+ function First ( { text} ) {
2211+ if ( suspend1 ) {
2212+ throw promise1 ;
2213+ } else {
2214+ return 'Hello' ;
2215+ }
2216+ }
2217+
2218+ function Second ( { text} ) {
2219+ if ( suspend2 ) {
2220+ throw promise2 ;
2221+ } else {
2222+ return 'World' ;
2223+ }
2224+ }
2225+
2226+ let ops = [ ] ;
2227+
2228+ function App ( ) {
2229+ const listener1 = useHover ( {
2230+ onHoverStart ( ) {
2231+ ops . push ( 'Hover Start First' ) ;
2232+ } ,
2233+ onHoverEnd ( ) {
2234+ ops . push ( 'Hover End First' ) ;
2235+ } ,
2236+ } ) ;
2237+ const listener2 = useHover ( {
2238+ onHoverStart ( ) {
2239+ ops . push ( 'Hover Start Second' ) ;
2240+ } ,
2241+ onHoverEnd ( ) {
2242+ ops . push ( 'Hover End Second' ) ;
2243+ } ,
2244+ } ) ;
2245+ return (
2246+ < div >
2247+ < Suspense fallback = "Loading First..." >
2248+ < span listeners = { listener1 } />
2249+ { /* We suspend after to test what happens when we eager
2250+ attach the listener. */ }
2251+ < First />
2252+ </ Suspense >
2253+ < Suspense fallback = "Loading Second..." >
2254+ < span listeners = { listener2 } >
2255+ < Second />
2256+ </ span >
2257+ </ Suspense >
2258+ </ div >
2259+ ) ;
2260+ }
2261+
2262+ let finalHTML = ReactDOMServer . renderToString ( < App /> ) ;
2263+ let container = document . createElement ( 'div' ) ;
2264+ container . innerHTML = finalHTML ;
2265+
2266+ // We need this to be in the document since we'll dispatch events on it.
2267+ document . body . appendChild ( container ) ;
2268+
2269+ let appDiv = container . getElementsByTagName ( 'div' ) [ 0 ] ;
2270+ let firstSpan = appDiv . getElementsByTagName ( 'span' ) [ 0 ] ;
2271+ let secondSpan = appDiv . getElementsByTagName ( 'span' ) [ 1 ] ;
2272+ expect ( firstSpan . textContent ) . toBe ( '' ) ;
2273+ expect ( secondSpan . textContent ) . toBe ( 'World' ) ;
2274+
2275+ // On the client we don't have all data yet but we want to start
2276+ // hydrating anyway.
2277+ suspend1 = true ;
2278+ suspend2 = true ;
2279+ let root = ReactDOM . unstable_createRoot ( container , { hydrate : true } ) ;
2280+ root . render ( < App /> ) ;
2281+
2282+ Scheduler . unstable_flushAll ( ) ;
2283+ jest . runAllTimers ( ) ;
2284+
2285+ dispatchMouseEvent ( appDiv , null ) ;
2286+ dispatchMouseEvent ( firstSpan , appDiv ) ;
2287+ dispatchMouseEvent ( secondSpan , firstSpan ) ;
2288+
2289+ // Neither target is yet hydrated.
2290+ expect ( ops ) . toEqual ( [ ] ) ;
2291+
2292+ // Resolving the second promise so that rendering can complete.
2293+ suspend2 = false ;
2294+ resolve2 ( ) ;
2295+ await promise2 ;
2296+
2297+ Scheduler . unstable_flushAll ( ) ;
2298+ jest . runAllTimers ( ) ;
2299+
2300+ // We've unblocked the current hover target so we should be
2301+ // able to replay it now.
2302+ expect ( ops ) . toEqual ( [ 'Hover Start Second' ] ) ;
2303+
2304+ // Resolving the first promise has no effect now.
2305+ suspend1 = false ;
2306+ resolve1 ( ) ;
2307+ await promise1 ;
2308+
2309+ Scheduler . unstable_flushAll ( ) ;
2310+ jest . runAllTimers ( ) ;
2311+
2312+ expect ( ops ) . toEqual ( [ 'Hover Start Second' ] ) ;
2313+
2314+ document . body . removeChild ( container ) ;
2315+ } ) ;
20432316} ) ;
0 commit comments