@samitouri / QOS-React-2 / commits / 80208e7696

[Transition Tracing] Add onTransitionProgress Callback (#24833)

This PR adds support for `onTransitionProgress` (`onTransitionProgress(transitionName: string, startTime: number, currentTime: number, pending: Array<{name: null | string}>)`) We call this callback when: * When **a child suspense boundary of the transition commits in a fallback state**. Only the suspense boundaries that are triggered and commit in a fallback state when the transition first occurs (and all subsequent suspense boundaries in the initial suspense boundary's subtree) are considered a part of the transition * **A child suspense boundary of the transition resolves** When we call `onTransitionProgress`, we call the function with a `pending` array. This array contains the names of the transition's suspense boundaries that are still in a fallback state

Luna Ruan committed Jul 8, 2022 at 12:13 UTC 80208e7696b8e391fe301ab312c73cd5f500bd4c
8 files changed +498 -51
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+41 -20
@@ -143,6 +143,7 @@ import {
143 enqueuePendingPassiveProfilerEffect,
144 restorePendingUpdaters,
145 addTransitionStartCallbackToPendingTransition,
146 + addTransitionProgressCallbackToPendingTransition,
147 addTransitionCompleteCallbackToPendingTransition,
148 addMarkerCompleteCallbackToPendingTransition,
149 setIsRunningInsertionEffect,
@@ -1119,10 +1120,24 @@ function commitTransitionProgress(offscreenFiber: Fiber) {
1120 // The suspense boundaries was just hidden. Add the boundary
1121 // to the pending boundary set if it's there
1122 if (pendingMarkers !== null) {
1122 - pendingMarkers.forEach(pendingBoundaries => {
1123 - pendingBoundaries.set(offscreenInstance, {
1124 - name,
1125 - });
1123 + pendingMarkers.forEach(markerInstance => {
1124 + const pendingBoundaries = markerInstance.pendingSuspenseBoundaries;
1125 + if (
1126 + pendingBoundaries !== null &&
1127 + !pendingBoundaries.has(offscreenInstance)
1128 + ) {
1129 + pendingBoundaries.set(offscreenInstance, {
1130 + name,
1131 + });
1132 + if (markerInstance.transitions !== null) {
1133 + markerInstance.transitions.forEach(transition => {
1134 + addTransitionProgressCallbackToPendingTransition(
1135 + transition,
1136 + pendingBoundaries,
1137 + );
1138 + });
1139 + }
1140 + }
1141 });
1142 }
1143 } else if (wasHidden && !isHidden) {
@@ -1130,9 +1145,21 @@ function commitTransitionProgress(offscreenFiber: Fiber) {
1145 // the boundary from the pending suspense boundaries set
1146 // if it's there
1147 if (pendingMarkers !== null) {
1133 - pendingMarkers.forEach(pendingBoundaries => {
1134 - if (pendingBoundaries.has(offscreenInstance)) {
1148 + pendingMarkers.forEach(markerInstance => {
1149 + const pendingBoundaries = markerInstance.pendingSuspenseBoundaries;
1150 + if (
1151 + pendingBoundaries !== null &&
1152 + pendingBoundaries.has(offscreenInstance)
1153 + ) {
1154 pendingBoundaries.delete(offscreenInstance);
1155 + if (markerInstance.transitions !== null) {
1156 + markerInstance.transitions.forEach(transition => {
1157 + addTransitionProgressCallbackToPendingTransition(
1158 + transition,
1159 + pendingBoundaries,
1160 + );
1161 + });
1162 + }
1163 }
1164 });
1165 }
@@ -2888,17 +2915,13 @@ function commitPassiveMountOnFiber(
2915 clearTransitionsForLanes(finishedRoot, committedLanes);
2916 }
2917
2891 - incompleteTransitions.forEach(
2892 - ({pendingSuspenseBoundaries}, transition) => {
2893 - if (
2894 - pendingSuspenseBoundaries === null ||
2895 - pendingSuspenseBoundaries.size === 0
2896 - ) {
2897 - addTransitionCompleteCallbackToPendingTransition(transition);
2898 - incompleteTransitions.delete(transition);
2899 - }
2900 - },
2901 - );
2918 + incompleteTransitions.forEach((markerInstance, transition) => {
2919 + const pendingBoundaries = markerInstance.pendingSuspenseBoundaries;
2920 + if (pendingBoundaries === null || pendingBoundaries.size === 0) {
2921 + addTransitionCompleteCallbackToPendingTransition(transition);
2922 + incompleteTransitions.delete(transition);
2923 + }
2924 + });
2925
2926 clearTransitionsForLanes(finishedRoot, committedLanes);
2927 }
@@ -2975,9 +2998,7 @@ function commitPassiveMountOnFiber(
2998 instance.pendingMarkers = new Set();
2999 }
3000
2978 - instance.pendingMarkers.add(
2979 - markerInstance.pendingSuspenseBoundaries,
2980 - );
3001 + instance.pendingMarkers.add(markerInstance);
3002 }
3003 });
3004 }
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+41 -20
@@ -143,6 +143,7 @@ import {
143 enqueuePendingPassiveProfilerEffect,
144 restorePendingUpdaters,
145 addTransitionStartCallbackToPendingTransition,
146 + addTransitionProgressCallbackToPendingTransition,
147 addTransitionCompleteCallbackToPendingTransition,
148 addMarkerCompleteCallbackToPendingTransition,
149 setIsRunningInsertionEffect,
@@ -1119,10 +1120,24 @@ function commitTransitionProgress(offscreenFiber: Fiber) {
1120 // The suspense boundaries was just hidden. Add the boundary
1121 // to the pending boundary set if it's there
1122 if (pendingMarkers !== null) {
1122 - pendingMarkers.forEach(pendingBoundaries => {
1123 - pendingBoundaries.set(offscreenInstance, {
1124 - name,
1125 - });
1123 + pendingMarkers.forEach(markerInstance => {
1124 + const pendingBoundaries = markerInstance.pendingSuspenseBoundaries;
1125 + if (
1126 + pendingBoundaries !== null &&
1127 + !pendingBoundaries.has(offscreenInstance)
1128 + ) {
1129 + pendingBoundaries.set(offscreenInstance, {
1130 + name,
1131 + });
1132 + if (markerInstance.transitions !== null) {
1133 + markerInstance.transitions.forEach(transition => {
1134 + addTransitionProgressCallbackToPendingTransition(
1135 + transition,
1136 + pendingBoundaries,
1137 + );
1138 + });
1139 + }
1140 + }
1141 });
1142 }
1143 } else if (wasHidden && !isHidden) {
@@ -1130,9 +1145,21 @@ function commitTransitionProgress(offscreenFiber: Fiber) {
1145 // the boundary from the pending suspense boundaries set
1146 // if it's there
1147 if (pendingMarkers !== null) {
1133 - pendingMarkers.forEach(pendingBoundaries => {
1134 - if (pendingBoundaries.has(offscreenInstance)) {
1148 + pendingMarkers.forEach(markerInstance => {
1149 + const pendingBoundaries = markerInstance.pendingSuspenseBoundaries;
1150 + if (
1151 + pendingBoundaries !== null &&
1152 + pendingBoundaries.has(offscreenInstance)
1153 + ) {
1154 pendingBoundaries.delete(offscreenInstance);
1155 + if (markerInstance.transitions !== null) {
1156 + markerInstance.transitions.forEach(transition => {
1157 + addTransitionProgressCallbackToPendingTransition(
1158 + transition,
1159 + pendingBoundaries,
1160 + );
1161 + });
1162 + }
1163 }
1164 });
1165 }
@@ -2888,17 +2915,13 @@ function commitPassiveMountOnFiber(
2915 clearTransitionsForLanes(finishedRoot, committedLanes);
2916 }
2917
2891 - incompleteTransitions.forEach(
2892 - ({pendingSuspenseBoundaries}, transition) => {
2893 - if (
2894 - pendingSuspenseBoundaries === null ||
2895 - pendingSuspenseBoundaries.size === 0
2896 - ) {
2897 - addTransitionCompleteCallbackToPendingTransition(transition);
2898 - incompleteTransitions.delete(transition);
2899 - }
2900 - },
2901 - );
2918 + incompleteTransitions.forEach((markerInstance, transition) => {
2919 + const pendingBoundaries = markerInstance.pendingSuspenseBoundaries;
2920 + if (pendingBoundaries === null || pendingBoundaries.size === 0) {
2921 + addTransitionCompleteCallbackToPendingTransition(transition);
2922 + incompleteTransitions.delete(transition);
2923 + }
2924 + });
2925
2926 clearTransitionsForLanes(finishedRoot, committedLanes);
2927 }
@@ -2975,9 +2998,7 @@ function commitPassiveMountOnFiber(
2998 instance.pendingMarkers = new Set();
2999 }
3000
2978 - instance.pendingMarkers.add(
2979 - markerInstance.pendingSuspenseBoundaries,
2980 - );
3001 + instance.pendingMarkers.add(markerInstance);
3002 }
3003 });
3004 }
packages/react-reconciler/src/ReactFiberOffscreenComponent.js
+1 -2
@@ -12,7 +12,6 @@ import type {Lanes} from './ReactFiberLane.old';
12 import type {SpawnedCachePool} from './ReactFiberCacheComponent.new';
13 import type {
14 Transition,
15 - PendingSuspenseBoundaries,
15 TracingMarkerInstance,
16 } from './ReactFiberTracingMarkerComponent.new';
17
@@ -45,7 +44,7 @@ export type OffscreenQueue = {|
44
45 export type OffscreenInstance = {|
46 isHidden: boolean,
48 - pendingMarkers: Set<PendingSuspenseBoundaries> | null,
47 + pendingMarkers: Set<TracingMarkerInstance> | null,
48 transitions: Set<Transition> | null,
49 retryCache: WeakSet<Wakeable> | Set<Wakeable> | null,
50 |};
packages/react-reconciler/src/ReactFiberTracingMarkerComponent.new.js
+17 -2
@@ -24,6 +24,7 @@ export type MarkerTransition = {
24
25 export type PendingTransitionCallbacks = {
26 transitionStart: Array<Transition> | null,
27 + transitionProgress: Map<Transition, PendingSuspenseBoundaries> | null,
28 transitionComplete: Array<Transition> | null,
29 markerComplete: Array<MarkerTransition> | null,
30 };
@@ -76,6 +77,19 @@ export function processTransitionCallbacks(
77 });
78 }
79
80 + const transitionProgress = pendingTransitions.transitionProgress;
81 + const onTransitionProgress = callbacks.onTransitionProgress;
82 + if (onTransitionProgress != null && transitionProgress !== null) {
83 + transitionProgress.forEach((pending, transition) => {
84 + onTransitionProgress(
85 + transition.name,
86 + transition.startTime,
87 + endTime,
88 + Array.from(pending.values()),
89 + );
90 + });
91 + }
92 +
93 const transitionComplete = pendingTransitions.transitionComplete;
94 if (transitionComplete !== null) {
95 transitionComplete.forEach(transition => {
@@ -117,10 +131,11 @@ export function pushRootMarkerInstance(workInProgress: Fiber): void {
131 if (transitions !== null) {
132 transitions.forEach(transition => {
133 if (!root.incompleteTransitions.has(transition)) {
120 - root.incompleteTransitions.set(transition, {
134 + const markerInstance: TracingMarkerInstance = {
135 transitions: new Set([transition]),
136 pendingSuspenseBoundaries: null,
123 - });
137 + };
138 + root.incompleteTransitions.set(transition, markerInstance);
139 }
140 });
141 }
packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js
+17 -2
@@ -24,6 +24,7 @@ export type MarkerTransition = {
24
25 export type PendingTransitionCallbacks = {
26 transitionStart: Array<Transition> | null,
27 + transitionProgress: Map<Transition, PendingSuspenseBoundaries> | null,
28 transitionComplete: Array<Transition> | null,
29 markerComplete: Array<MarkerTransition> | null,
30 };
@@ -76,6 +77,19 @@ export function processTransitionCallbacks(
77 });
78 }
79
80 + const transitionProgress = pendingTransitions.transitionProgress;
81 + const onTransitionProgress = callbacks.onTransitionProgress;
82 + if (onTransitionProgress != null && transitionProgress !== null) {
83 + transitionProgress.forEach((pending, transition) => {
84 + onTransitionProgress(
85 + transition.name,
86 + transition.startTime,
87 + endTime,
88 + Array.from(pending.values()),
89 + );
90 + });
91 + }
92 +
93 const transitionComplete = pendingTransitions.transitionComplete;
94 if (transitionComplete !== null) {
95 transitionComplete.forEach(transition => {
@@ -117,10 +131,11 @@ export function pushRootMarkerInstance(workInProgress: Fiber): void {
131 if (transitions !== null) {
132 transitions.forEach(transition => {
133 if (!root.incompleteTransitions.has(transition)) {
120 - root.incompleteTransitions.set(transition, {
134 + const markerInstance: TracingMarkerInstance = {
135 transitions: new Set([transition]),
136 pendingSuspenseBoundaries: null,
123 - });
137 + };
138 + root.incompleteTransitions.set(transition, markerInstance);
139 }
140 });
141 }
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+29
@@ -17,6 +17,7 @@ import type {EventPriority} from './ReactEventPriorities.new';
17 import type {
18 PendingTransitionCallbacks,
19 MarkerTransition,
20 + PendingSuspenseBoundaries,
21 Transition,
22 } from './ReactFiberTracingMarkerComponent.new';
23 import type {OffscreenInstance} from './ReactFiberOffscreenComponent';
@@ -339,6 +340,7 @@ export function addTransitionStartCallbackToPendingTransition(
340 if (currentPendingTransitionCallbacks === null) {
341 currentPendingTransitionCallbacks = {
342 transitionStart: [],
343 + transitionProgress: null,
344 transitionComplete: null,
345 markerComplete: null,
346 };
@@ -359,6 +361,7 @@ export function addMarkerCompleteCallbackToPendingTransition(
361 if (currentPendingTransitionCallbacks === null) {
362 currentPendingTransitionCallbacks = {
363 transitionStart: null,
364 + transitionProgress: null,
365 transitionComplete: null,
366 markerComplete: [],
367 };
@@ -372,6 +375,31 @@ export function addMarkerCompleteCallbackToPendingTransition(
375 }
376 }
377
378 +export function addTransitionProgressCallbackToPendingTransition(
379 + transition: Transition,
380 + boundaries: PendingSuspenseBoundaries,
381 +) {
382 + if (enableTransitionTracing) {
383 + if (currentPendingTransitionCallbacks === null) {
384 + currentPendingTransitionCallbacks = {
385 + transitionStart: null,
386 + transitionProgress: new Map(),
387 + transitionComplete: null,
388 + markerComplete: null,
389 + };
390 + }
391 +
392 + if (currentPendingTransitionCallbacks.transitionProgress === null) {
393 + currentPendingTransitionCallbacks.transitionProgress = new Map();
394 + }
395 +
396 + currentPendingTransitionCallbacks.transitionProgress.set(
397 + transition,
398 + boundaries,
399 + );
400 + }
401 +}
402 +
403 export function addTransitionCompleteCallbackToPendingTransition(
404 transition: Transition,
405 ) {
@@ -379,6 +407,7 @@ export function addTransitionCompleteCallbackToPendingTransition(
407 if (currentPendingTransitionCallbacks === null) {
408 currentPendingTransitionCallbacks = {
409 transitionStart: null,
410 + transitionProgress: null,
411 transitionComplete: [],
412 markerComplete: null,
413 };
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+29
@@ -17,6 +17,7 @@ import type {EventPriority} from './ReactEventPriorities.old';
17 import type {
18 PendingTransitionCallbacks,
19 MarkerTransition,
20 + PendingSuspenseBoundaries,
21 Transition,
22 } from './ReactFiberTracingMarkerComponent.old';
23 import type {OffscreenInstance} from './ReactFiberOffscreenComponent';
@@ -339,6 +340,7 @@ export function addTransitionStartCallbackToPendingTransition(
340 if (currentPendingTransitionCallbacks === null) {
341 currentPendingTransitionCallbacks = {
342 transitionStart: [],
343 + transitionProgress: null,
344 transitionComplete: null,
345 markerComplete: null,
346 };
@@ -359,6 +361,7 @@ export function addMarkerCompleteCallbackToPendingTransition(
361 if (currentPendingTransitionCallbacks === null) {
362 currentPendingTransitionCallbacks = {
363 transitionStart: null,
364 + transitionProgress: null,
365 transitionComplete: null,
366 markerComplete: [],
367 };
@@ -372,6 +375,31 @@ export function addMarkerCompleteCallbackToPendingTransition(
375 }
376 }
377
378 +export function addTransitionProgressCallbackToPendingTransition(
379 + transition: Transition,
380 + boundaries: PendingSuspenseBoundaries,
381 +) {
382 + if (enableTransitionTracing) {
383 + if (currentPendingTransitionCallbacks === null) {
384 + currentPendingTransitionCallbacks = {
385 + transitionStart: null,
386 + transitionProgress: new Map(),
387 + transitionComplete: null,
388 + markerComplete: null,
389 + };
390 + }
391 +
392 + if (currentPendingTransitionCallbacks.transitionProgress === null) {
393 + currentPendingTransitionCallbacks.transitionProgress = new Map();
394 + }
395 +
396 + currentPendingTransitionCallbacks.transitionProgress.set(
397 + transition,
398 + boundaries,
399 + );
400 + }
401 +}
402 +
403 export function addTransitionCompleteCallbackToPendingTransition(
404 transition: Transition,
405 ) {
@@ -379,6 +407,7 @@ export function addTransitionCompleteCallbackToPendingTransition(
407 if (currentPendingTransitionCallbacks === null) {
408 currentPendingTransitionCallbacks = {
409 transitionStart: null,
410 + transitionProgress: null,
411 transitionComplete: [],
412 markerComplete: null,
413 };
packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js
+323 -5
@@ -168,6 +168,12 @@ describe('ReactInteractionTracing', () => {
168 `onTransitionStart(${name}, ${startTime})`,
169 );
170 },
171 + onTransitionProgress: (name, startTime, endTime, pending) => {
172 + const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
173 + Scheduler.unstable_yieldValue(
174 + `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
175 + );
176 + },
177 onTransitionComplete: (name, startTime, endTime) => {
178 Scheduler.unstable_yieldValue(
179 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
@@ -283,6 +289,12 @@ describe('ReactInteractionTracing', () => {
289 `onTransitionStart(${name}, ${startTime})`,
290 );
291 },
292 + onTransitionProgress: (name, startTime, endTime, pending) => {
293 + const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
294 + Scheduler.unstable_yieldValue(
295 + `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
296 + );
297 + },
298 onTransitionComplete: (name, startTime, endTime) => {
299 Scheduler.unstable_yieldValue(
300 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
@@ -301,7 +313,7 @@ describe('ReactInteractionTracing', () => {
313 {navigate ? (
314 <Suspense
315 fallback={<Text text="Loading..." />}
304 - name="suspense page">
316 + unstable_name="suspense page">
317 <AsyncText text="Page Two" />
318 </Suspense>
319 ) : (
@@ -330,6 +342,7 @@ describe('ReactInteractionTracing', () => {
342 'Suspend [Page Two]',
343 'Loading...',
344 'onTransitionStart(page transition, 1000)',
345 + 'onTransitionProgress(page transition, 1000, 2000, [suspense page])',
346 ]);
347
348 ReactNoop.expire(1000);
@@ -338,6 +351,7 @@ describe('ReactInteractionTracing', () => {
351
352 expect(Scheduler).toFlushAndYield([
353 'Page Two',
354 + 'onTransitionProgress(page transition, 1000, 3000, [])',
355 'onTransitionComplete(page transition, 1000, 3000)',
356 ]);
357 });
@@ -351,6 +365,12 @@ describe('ReactInteractionTracing', () => {
365 `onTransitionStart(${name}, ${startTime})`,
366 );
367 },
368 + onTransitionProgress: (name, startTime, endTime, pending) => {
369 + const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
370 + Scheduler.unstable_yieldValue(
371 + `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
372 + );
373 + },
374 onTransitionComplete: (name, startTime, endTime) => {
375 Scheduler.unstable_yieldValue(
376 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
@@ -377,13 +397,15 @@ describe('ReactInteractionTracing', () => {
397 {navigate ? (
398 <>
399 {showText ? (
380 - <Suspense fallback={<Text text="Show Text Loading..." />}>
400 + <Suspense
401 + unstable_name="show text"
402 + fallback={<Text text="Show Text Loading..." />}>
403 <AsyncText text="Show Text" />
404 </Suspense>
405 ) : null}
406 <Suspense
407 fallback={<Text text="Loading..." />}
386 - name="suspense page">
408 + unstable_name="suspense page">
409 <AsyncText text="Page Two" />
410 </Suspense>
411 </>
@@ -410,6 +432,7 @@ describe('ReactInteractionTracing', () => {
432 'Suspend [Page Two]',
433 'Loading...',
434 'onTransitionStart(page transition, 1000)',
435 + 'onTransitionProgress(page transition, 1000, 1000, [suspense page])',
436 ]);
437
438 await resolveText('Page Two');
@@ -417,6 +440,7 @@ describe('ReactInteractionTracing', () => {
440 await advanceTimers(1000);
441 expect(Scheduler).toFlushAndYield([
442 'Page Two',
443 + 'onTransitionProgress(page transition, 1000, 2000, [])',
444 'onTransitionComplete(page transition, 1000, 2000)',
445 ]);
446
@@ -426,6 +450,7 @@ describe('ReactInteractionTracing', () => {
450 'Show Text Loading...',
451 'Page Two',
452 'onTransitionStart(text transition, 2000)',
453 + 'onTransitionProgress(text transition, 2000, 2000, [show text])',
454 ]);
455
456 await resolveText('Show Text');
@@ -433,6 +458,7 @@ describe('ReactInteractionTracing', () => {
458 await advanceTimers(1000);
459 expect(Scheduler).toFlushAndYield([
460 'Show Text',
461 + 'onTransitionProgress(text transition, 2000, 3000, [])',
462 'onTransitionComplete(text transition, 2000, 3000)',
463 ]);
464 });
@@ -446,6 +472,12 @@ describe('ReactInteractionTracing', () => {
472 `onTransitionStart(${name}, ${startTime})`,
473 );
474 },
475 + onTransitionProgress: (name, startTime, endTime, pending) => {
476 + const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
477 + Scheduler.unstable_yieldValue(
478 + `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
479 + );
480 + },
481 onTransitionComplete: (name, startTime, endTime) => {
482 Scheduler.unstable_yieldValue(
483 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
@@ -470,13 +502,15 @@ describe('ReactInteractionTracing', () => {
502 {navigate ? (
503 <>
504 {showText ? (
473 - <Suspense fallback={<Text text="Show Text Loading..." />}>
505 + <Suspense
506 + unstable_name="show text"
507 + fallback={<Text text="Show Text Loading..." />}>
508 <AsyncText text="Show Text" />
509 </Suspense>
510 ) : null}
511 <Suspense
512 fallback={<Text text="Loading..." />}
479 - name="suspense page">
513 + unstable_name="suspense page">
514 <AsyncText text="Page Two" />
515 </Suspense>
516 </>
@@ -505,6 +539,7 @@ describe('ReactInteractionTracing', () => {
539 'Suspend [Page Two]',
540 'Loading...',
541 'onTransitionStart(page transition, 1000)',
542 + 'onTransitionProgress(page transition, 1000, 2000, [suspense page])',
543 ]);
544 });
545
@@ -517,6 +552,7 @@ describe('ReactInteractionTracing', () => {
552 'Suspend [Page Two]',
553 'Loading...',
554 'onTransitionStart(show text, 2000)',
555 + 'onTransitionProgress(show text, 2000, 2000, [show text])',
556 ]);
557 });
558
@@ -527,6 +563,7 @@ describe('ReactInteractionTracing', () => {
563
564 expect(Scheduler).toFlushAndYield([
565 'Page Two',
566 + 'onTransitionProgress(page transition, 1000, 3000, [])',
567 'onTransitionComplete(page transition, 1000, 3000)',
568 ]);
569
@@ -536,11 +573,292 @@ describe('ReactInteractionTracing', () => {
573
574 expect(Scheduler).toFlushAndYield([
575 'Show Text',
576 + 'onTransitionProgress(show text, 2000, 4000, [])',
577 'onTransitionComplete(show text, 2000, 4000)',
578 ]);
579 });
580 });
581
582 + // @gate enableTransitionTracing
583 + it('trace interaction with nested and sibling suspense boundaries', async () => {
584 + const transitionCallbacks = {
585 + onTransitionStart: (name, startTime) => {
586 + Scheduler.unstable_yieldValue(
587 + `onTransitionStart(${name}, ${startTime})`,
588 + );
589 + },
590 + onTransitionProgress: (name, startTime, endTime, pending) => {
591 + const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
592 + Scheduler.unstable_yieldValue(
593 + `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
594 + );
595 + },
596 + onTransitionComplete: (name, startTime, endTime) => {
597 + Scheduler.unstable_yieldValue(
598 + `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
599 + );
600 + },
601 + };
602 +
603 + let navigateToPageTwo;
604 + function App() {
605 + const [navigate, setNavigate] = useState(false);
606 + navigateToPageTwo = () => {
607 + setNavigate(true);
608 + };
609 +
610 + return (
611 + <div>
612 + {navigate ? (
613 + <>
614 + <Suspense
615 + fallback={<Text text="Loading..." />}
616 + unstable_name="suspense page">
617 + <AsyncText text="Page Two" />
618 + <Suspense
619 + unstable_name="show text one"
620 + fallback={<Text text="Show Text One Loading..." />}>
621 + <AsyncText text="Show Text One" />
622 + </Suspense>
623 + <div>
624 + <Suspense
625 + unstable_name="show text two"
626 + fallback={<Text text="Show Text Two Loading..." />}>
627 + <AsyncText text="Show Text Two" />
628 + </Suspense>
629 + </div>
630 + </Suspense>
631 + </>
632 + ) : (
633 + <Text text="Page One" />
634 + )}
635 + </div>
636 + );
637 + }
638 +
639 + const root = ReactNoop.createRoot({transitionCallbacks});
640 + await act(async () => {
641 + root.render(<App />);
642 + ReactNoop.expire(1000);
643 + await advanceTimers(1000);
644 +
645 + expect(Scheduler).toFlushAndYield(['Page One']);
646 + });
647 +
648 + await act(async () => {
649 + startTransition(() => navigateToPageTwo(), {name: 'page transition'});
650 + ReactNoop.expire(1000);
651 + await advanceTimers(1000);
652 +
653 + expect(Scheduler).toFlushAndYield([
654 + 'Suspend [Page Two]',
655 + 'Suspend [Show Text One]',
656 + 'Show Text One Loading...',
657 + 'Suspend [Show Text Two]',
658 + 'Show Text Two Loading...',
659 + 'Loading...',
660 + 'onTransitionStart(page transition, 1000)',
661 + 'onTransitionProgress(page transition, 1000, 2000, [suspense page])',
662 + ]);
663 +
664 + resolveText('Page Two');
665 + ReactNoop.expire(1000);
666 + await advanceTimers(1000);
667 +
668 + expect(Scheduler).toFlushAndYield([
669 + 'Page Two',
670 + 'Suspend [Show Text One]',
671 + 'Show Text One Loading...',
672 + 'Suspend [Show Text Two]',
673 + 'Show Text Two Loading...',
674 + 'onTransitionProgress(page transition, 1000, 3000, [show text one, show text two])',
675 + ]);
676 +
677 + resolveText('Show Text One');
678 + ReactNoop.expire(1000);
679 + await advanceTimers(1000);
680 +
681 + expect(Scheduler).toFlushAndYield([
682 + 'Show Text One',
683 + 'onTransitionProgress(page transition, 1000, 4000, [show text two])',
684 + ]);
685 +
686 + resolveText('Show Text Two');
687 + ReactNoop.expire(1000);
688 + await advanceTimers(1000);
689 +
690 + expect(Scheduler).toFlushAndYield([
691 + 'Show Text Two',
692 + 'onTransitionProgress(page transition, 1000, 5000, [])',
693 + 'onTransitionComplete(page transition, 1000, 5000)',
694 + ]);
695 + });
696 + });
697 +
698 + // @gate enableTransitionTracing
699 + it('trace interactions with the same child suspense boundaries', async () => {
700 + const transitionCallbacks = {
701 + onTransitionStart: (name, startTime) => {
702 + Scheduler.unstable_yieldValue(
703 + `onTransitionStart(${name}, ${startTime})`,
704 + );
705 + },
706 + onTransitionProgress: (name, startTime, endTime, pending) => {
707 + const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
708 + Scheduler.unstable_yieldValue(
709 + `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
710 + );
711 + },
712 + onTransitionComplete: (name, startTime, endTime) => {
713 + Scheduler.unstable_yieldValue(
714 + `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
715 + );
716 + },
717 + };
718 +
719 + let setNavigate;
720 + let setShowTextOne;
721 + let setShowTextTwo;
722 + function App() {
723 + const [navigate, _setNavigate] = useState(false);
724 + const [showTextOne, _setShowTextOne] = useState(false);
725 + const [showTextTwo, _setShowTextTwo] = useState(false);
726 +
727 + setNavigate = () => _setNavigate(true);
728 + setShowTextOne = () => _setShowTextOne(true);
729 + setShowTextTwo = () => _setShowTextTwo(true);
730 +
731 + return (
732 + <div>
733 + {navigate ? (
734 + <>
735 + <Suspense
736 + fallback={<Text text="Loading..." />}
737 + unstable_name="suspense page">
738 + <AsyncText text="Page Two" />
739 + {/* showTextOne is entangled with navigate */}
740 + {showTextOne ? (
741 + <Suspense
742 + unstable_name="show text one"
743 + fallback={<Text text="Show Text One Loading..." />}>
744 + <AsyncText text="Show Text One" />
745 + </Suspense>
746 + ) : null}
747 + <Suspense fallback={<Text text="Show Text Loading..." />}>
748 + <AsyncText text="Show Text" />
749 + </Suspense>
750 + {/* showTextTwo's suspense boundaries shouldn't stop navigate's suspense boundaries
751 + from completing */}
752 + {showTextTwo ? (
753 + <Suspense
754 + unstable_name="show text two"
755 + fallback={<Text text="Show Text Two Loading..." />}>
756 + <AsyncText text="Show Text Two" />
757 + </Suspense>
758 + ) : null}
759 + </Suspense>
760 + </>
761 + ) : (
762 + <Text text="Page One" />
763 + )}
764 + </div>
765 + );
766 + }
767 +
768 + const root = ReactNoop.createRoot({transitionCallbacks});
769 + await act(async () => {
770 + root.render(<App />);
771 + ReactNoop.expire(1000);
772 + await advanceTimers(1000);
773 +
774 + expect(Scheduler).toFlushAndYield(['Page One']);
775 + });
776 +
777 + await act(async () => {
778 + startTransition(() => setNavigate(), {name: 'navigate'});
779 + startTransition(() => setShowTextOne(), {name: 'show text one'});
780 + ReactNoop.expire(1000);
781 + await advanceTimers(1000);
782 +
783 + expect(Scheduler).toFlushAndYield([
784 + 'Suspend [Page Two]',
785 + 'Suspend [Show Text One]',
786 + 'Show Text One Loading...',
787 + 'Suspend [Show Text]',
788 + 'Show Text Loading...',
789 + 'Loading...',
790 + 'onTransitionStart(navigate, 1000)',
791 + 'onTransitionStart(show text one, 1000)',
792 + 'onTransitionProgress(navigate, 1000, 2000, [suspense page])',
793 + 'onTransitionProgress(show text one, 1000, 2000, [suspense page])',
794 + ]);
795 +
796 + resolveText('Page Two');
797 + ReactNoop.expire(1000);
798 + await advanceTimers(1000);
799 + expect(Scheduler).toFlushAndYield([
800 + 'Page Two',
801 + 'Suspend [Show Text One]',
802 + 'Show Text One Loading...',
803 + 'Suspend [Show Text]',
804 + 'Show Text Loading...',
805 + 'onTransitionProgress(navigate, 1000, 3000, [show text one, <null>])',
806 + 'onTransitionProgress(show text one, 1000, 3000, [show text one, <null>])',
807 + ]);
808 +
809 + startTransition(() => setShowTextTwo(), {name: 'show text two'});
810 + ReactNoop.expire(1000);
811 + await advanceTimers(1000);
812 +
813 + expect(Scheduler).toFlushAndYield([
814 + 'Page Two',
815 + 'Suspend [Show Text One]',
816 + 'Show Text One Loading...',
817 + 'Suspend [Show Text]',
818 + 'Show Text Loading...',
819 + 'Suspend [Show Text Two]',
820 + 'Show Text Two Loading...',
821 + 'onTransitionStart(show text two, 3000)',
822 + 'onTransitionProgress(show text two, 3000, 4000, [show text two])',
823 + ]);
824 +
825 + // This should not cause navigate to finish because it's entangled with
826 + // show text one
827 + resolveText('Show Text');
828 + ReactNoop.expire(1000);
829 + await advanceTimers(1000);
830 +
831 + expect(Scheduler).toFlushAndYield([
832 + 'Show Text',
833 + 'onTransitionProgress(navigate, 1000, 5000, [show text one])',
834 + 'onTransitionProgress(show text one, 1000, 5000, [show text one])',
835 + ]);
836 +
837 + // This should not cause show text two to finish but nothing else
838 + resolveText('Show Text Two');
839 + ReactNoop.expire(1000);
840 + await advanceTimers(1000);
841 + expect(Scheduler).toFlushAndYield([
842 + 'Show Text Two',
843 + 'onTransitionProgress(show text two, 3000, 6000, [])',
844 + 'onTransitionComplete(show text two, 3000, 6000)',
845 + ]);
846 +
847 + // This should cause everything to finish
848 + resolveText('Show Text One');
849 + ReactNoop.expire(1000);
850 + await advanceTimers(1000);
851 +
852 + expect(Scheduler).toFlushAndYield([
853 + 'Show Text One',
854 + 'onTransitionProgress(navigate, 1000, 7000, [])',
855 + 'onTransitionProgress(show text one, 1000, 7000, [])',
856 + 'onTransitionComplete(navigate, 1000, 7000)',
857 + 'onTransitionComplete(show text one, 1000, 7000)',
858 + ]);
859 + });
860 + });
861 +
862 // @gate enableTransitionTracing
863 it('should correctly trace interactions for tracing markers complete', async () => {
864 const transitionCallbacks = {