@@ -1240,6 +1240,7 @@ describe('Profiler', () => {
12401240
12411241 loadModules ( {
12421242 enableSchedulerTracking : true ,
1243+ enableSuspense : true ,
12431244 } ) ;
12441245
12451246 throwInOnInteractionScheduledWorkCompleted = false ;
@@ -2301,5 +2302,159 @@ describe('Profiler', () => {
23012302 onInteractionScheduledWorkCompleted ,
23022303 ) . toHaveBeenLastNotifiedOfInteraction ( interaction ) ;
23032304 } ) ;
2305+
2306+ it ( 'does not prematurely complete for suspended sync renders' , async ( ) => {
2307+ const SimpleCacheProvider = require ( 'simple-cache-provider' ) ;
2308+ let cache ;
2309+ function invalidateCache ( ) {
2310+ cache = SimpleCacheProvider . createCache ( invalidateCache ) ;
2311+ }
2312+ invalidateCache ( ) ;
2313+
2314+ let resourcePromise ;
2315+ const TextResource = SimpleCacheProvider . createResource (
2316+ ( [ text , ms = 0 ] ) => {
2317+ resourcePromise = new Promise ( ( resolve , reject ) =>
2318+ setTimeout ( ( ) => resolve ( text ) , ms ) ,
2319+ ) ;
2320+ return resourcePromise ;
2321+ } ,
2322+ ( [ text , ms ] ) => text ,
2323+ ) ;
2324+
2325+ function AsyncText ( { ms, text} ) {
2326+ TextResource . read ( cache , [ text , ms ] ) ;
2327+ return < span prop = { text } /> ;
2328+ }
2329+
2330+ function Text ( { text} ) {
2331+ return text ;
2332+ }
2333+
2334+ const interaction = {
2335+ id : 0 ,
2336+ name : 'initial render' ,
2337+ timestamp : mockNow ( ) ,
2338+ } ;
2339+
2340+ const onRender = jest . fn ( ) ;
2341+ let renderer ;
2342+ SchedulerTracking . unstable_track (
2343+ interaction . name ,
2344+ interaction . timestamp ,
2345+ ( ) => {
2346+ renderer = ReactTestRenderer . create (
2347+ < React . unstable_Profiler id = "app" onRender = { onRender } >
2348+ < React . Placeholder
2349+ delayMs = { 1000 }
2350+ fallback = { < Text text = "loading" /> } >
2351+ < AsyncText text = "loaded" ms = { 2000 } />
2352+ </ React . Placeholder >
2353+ </ React . unstable_Profiler > ,
2354+ ) ;
2355+ } ,
2356+ ) ;
2357+
2358+ expect ( onInteractionScheduledWorkCompleted ) . not . toHaveBeenCalled ( ) ;
2359+
2360+ jest . runAllTimers ( ) ;
2361+ await resourcePromise ;
2362+
2363+ expect ( onInteractionScheduledWorkCompleted ) . toHaveBeenCalledTimes ( 1 ) ;
2364+ expect (
2365+ onInteractionScheduledWorkCompleted ,
2366+ ) . toHaveBeenLastNotifiedOfInteraction ( interaction ) ;
2367+ } ) ;
2368+
2369+ it ( 'does not prematurely complete for suspended renders that have exceeded their deadline' , async ( ) => {
2370+ function awaitableAdvanceTimers ( ms ) {
2371+ jest . advanceTimersByTime ( ms ) ;
2372+ // Wait until the end of the current tick
2373+ return new Promise ( resolve => {
2374+ setImmediate ( resolve ) ;
2375+ } ) ;
2376+ }
2377+
2378+ const SimpleCacheProvider = require ( 'simple-cache-provider' ) ;
2379+ let cache ;
2380+ function invalidateCache ( ) {
2381+ cache = SimpleCacheProvider . createCache ( invalidateCache ) ;
2382+ }
2383+ invalidateCache ( ) ;
2384+
2385+ const TextResource = SimpleCacheProvider . createResource (
2386+ ( [ text , ms = 0 ] ) => {
2387+ return new Promise ( ( resolve , reject ) => {
2388+ setTimeout ( ( ) => {
2389+ ReactTestRenderer . unstable_yield ( `Promise resolved [${ text } ]` ) ;
2390+ resolve ( text ) ;
2391+ } , ms ) ;
2392+ } ) ;
2393+ } ,
2394+ ( [ text , ms ] ) => text ,
2395+ ) ;
2396+
2397+ function AsyncText ( { ms, text} ) {
2398+ try {
2399+ TextResource . read ( cache , [ text , ms ] ) ;
2400+ ReactTestRenderer . unstable_yield ( `AsyncText [${ text } ]` ) ;
2401+ return < span prop = { text } /> ;
2402+ } catch ( promise ) {
2403+ if ( typeof promise . then === 'function' ) {
2404+ ReactTestRenderer . unstable_yield ( `Suspend! [${ text } ]` ) ;
2405+ } else {
2406+ ReactTestRenderer . unstable_yield ( `Error! [${ text } ]` ) ;
2407+ }
2408+ throw promise ;
2409+ }
2410+ }
2411+
2412+ function Text ( { text} ) {
2413+ ReactTestRenderer . unstable_yield ( `Text [${ text } ]` ) ;
2414+ return text ;
2415+ }
2416+
2417+ const interaction = {
2418+ id : 0 ,
2419+ name : 'initial render' ,
2420+ timestamp : mockNow ( ) ,
2421+ } ;
2422+
2423+ const onRender = jest . fn ( ) ;
2424+ let renderer ;
2425+ SchedulerTracking . unstable_track (
2426+ interaction . name ,
2427+ interaction . timestamp ,
2428+ ( ) => {
2429+ renderer = ReactTestRenderer . create (
2430+ < React . unstable_Profiler id = "app" onRender = { onRender } >
2431+ < React . Placeholder
2432+ delayMs = { 1000 }
2433+ fallback = { < Text text = "loading" /> } >
2434+ < AsyncText text = "loaded" ms = { 2000 } />
2435+ </ React . Placeholder >
2436+ </ React . unstable_Profiler > ,
2437+ {
2438+ unstable_isAsync : true ,
2439+ } ,
2440+ ) ;
2441+ } ,
2442+ ) ;
2443+
2444+ advanceTimeBy ( 1500 ) ;
2445+ await awaitableAdvanceTimers ( 1500 ) ;
2446+
2447+ expect ( renderer ) . toFlushAll ( [ 'Suspend! [loaded]' , 'Text [loading]' ] ) ;
2448+ expect ( onInteractionScheduledWorkCompleted ) . not . toHaveBeenCalled ( ) ;
2449+
2450+ advanceTimeBy ( 2500 ) ;
2451+ await awaitableAdvanceTimers ( 2500 ) ;
2452+
2453+ expect ( renderer ) . toFlushAll ( [ 'AsyncText [loaded]' ] ) ;
2454+ expect ( onInteractionScheduledWorkCompleted ) . toHaveBeenCalledTimes ( 1 ) ;
2455+ expect (
2456+ onInteractionScheduledWorkCompleted ,
2457+ ) . toHaveBeenLastNotifiedOfInteraction ( interaction ) ;
2458+ } ) ;
23042459 } ) ;
23052460} ) ;
0 commit comments