@@ -17,6 +17,58 @@ let ReactFeatureFlags;
17
17
let Suspense ;
18
18
let SuspenseList ;
19
19
let 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
+ }
20
72
21
73
describe ( 'ReactDOMServerPartialHydration' , ( ) => {
22
74
beforeEach ( ( ) => {
@@ -34,6 +86,8 @@ describe('ReactDOMServerPartialHydration', () => {
34
86
Scheduler = require ( 'scheduler' ) ;
35
87
Suspense = React . Suspense ;
36
88
SuspenseList = React . unstable_SuspenseList ;
89
+
90
+ useHover = require ( 'react-ui/events/hover' ) . useHover ;
37
91
} ) ;
38
92
39
93
it ( 'hydrates a parent even if a child Suspense boundary is blocked' , async ( ) => {
@@ -2040,4 +2094,223 @@ describe('ReactDOMServerPartialHydration', () => {
2040
2094
2041
2095
document . body . removeChild ( parentContainer ) ;
2042
2096
} ) ;
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
+ } ) ;
2043
2316
} ) ;
0 commit comments