mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Transition Tracing] Add Tracing Markers (#24686)
This PR adds support for Tracing Markers as well as onTracingMarkerComplete
This commit is contained in:
@@ -19,6 +19,7 @@ import type {
|
||||
OffscreenProps,
|
||||
OffscreenInstance,
|
||||
} from './ReactFiberOffscreenComponent';
|
||||
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
|
||||
|
||||
import {
|
||||
createRootStrictEffectsByDefault,
|
||||
@@ -757,6 +758,11 @@ export function createFiberFromTracingMarker(
|
||||
const fiber = createFiber(TracingMarkerComponent, pendingProps, key, mode);
|
||||
fiber.elementType = REACT_TRACING_MARKER_TYPE;
|
||||
fiber.lanes = lanes;
|
||||
const tracingMarkerInstance: TracingMarkerInstance = {
|
||||
transitions: null,
|
||||
pendingSuspenseBoundaries: null,
|
||||
};
|
||||
fiber.stateNode = tracingMarkerInstance;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import type {
|
||||
OffscreenProps,
|
||||
OffscreenInstance,
|
||||
} from './ReactFiberOffscreenComponent';
|
||||
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old';
|
||||
|
||||
import {
|
||||
createRootStrictEffectsByDefault,
|
||||
@@ -757,6 +758,11 @@ export function createFiberFromTracingMarker(
|
||||
const fiber = createFiber(TracingMarkerComponent, pendingProps, key, mode);
|
||||
fiber.elementType = REACT_TRACING_MARKER_TYPE;
|
||||
fiber.lanes = lanes;
|
||||
const tracingMarkerInstance: TracingMarkerInstance = {
|
||||
transitions: null,
|
||||
pendingSuspenseBoundaries: null,
|
||||
};
|
||||
fiber.stateNode = tracingMarkerInstance;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import type {
|
||||
} from './ReactFiberCacheComponent.new';
|
||||
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
|
||||
import type {RootState} from './ReactFiberRoot.new';
|
||||
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
|
||||
import {
|
||||
enableSuspenseAvoidThisFallback,
|
||||
enableCPUSuspense,
|
||||
@@ -255,9 +256,12 @@ import {
|
||||
getSuspendedCache,
|
||||
pushTransition,
|
||||
getOffscreenDeferredCache,
|
||||
getSuspendedTransitions,
|
||||
getPendingTransitions,
|
||||
} from './ReactFiberTransition.new';
|
||||
import {pushTracingMarker} from './ReactFiberTracingMarkerComponent.new';
|
||||
import {
|
||||
getTracingMarkers,
|
||||
pushTracingMarker,
|
||||
} from './ReactFiberTracingMarkerComponent.new';
|
||||
|
||||
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
||||
|
||||
@@ -891,6 +895,20 @@ function updateTracingMarkerComponent(
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: (luna) Only update the tracing marker if it's newly rendered or it's name changed.
|
||||
// A tracing marker is only associated with the transitions that rendered
|
||||
// or updated it, so we can create a new set of transitions each time
|
||||
if (current === null) {
|
||||
const currentTransitions = getPendingTransitions();
|
||||
if (currentTransitions !== null) {
|
||||
const markerInstance: TracingMarkerInstance = {
|
||||
transitions: new Set(currentTransitions),
|
||||
pendingSuspenseBoundaries: new Map(),
|
||||
};
|
||||
workInProgress.stateNode = markerInstance;
|
||||
}
|
||||
}
|
||||
|
||||
pushTracingMarker(workInProgress);
|
||||
const nextChildren = workInProgress.pendingProps.children;
|
||||
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
||||
@@ -2093,10 +2111,13 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
||||
);
|
||||
workInProgress.memoizedState = SUSPENDED_MARKER;
|
||||
if (enableTransitionTracing) {
|
||||
const currentTransitions = getSuspendedTransitions();
|
||||
const currentTransitions = getPendingTransitions();
|
||||
if (currentTransitions !== null) {
|
||||
// If there are no transitions, we don't need to keep track of tracing markers
|
||||
const currentTracingMarkers = getTracingMarkers();
|
||||
const primaryChildUpdateQueue: OffscreenQueue = {
|
||||
transitions: currentTransitions,
|
||||
tracingMarkers: currentTracingMarkers,
|
||||
};
|
||||
primaryChildFragment.updateQueue = primaryChildUpdateQueue;
|
||||
}
|
||||
@@ -2177,10 +2198,12 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
||||
? mountSuspenseOffscreenState(renderLanes)
|
||||
: updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
|
||||
if (enableTransitionTracing) {
|
||||
const currentTransitions = getSuspendedTransitions();
|
||||
const currentTransitions = getPendingTransitions();
|
||||
if (currentTransitions !== null) {
|
||||
const currentTracingMarkers = getTracingMarkers();
|
||||
const primaryChildUpdateQueue: OffscreenQueue = {
|
||||
transitions: currentTransitions,
|
||||
tracingMarkers: currentTracingMarkers,
|
||||
};
|
||||
primaryChildFragment.updateQueue = primaryChildUpdateQueue;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import type {
|
||||
} from './ReactFiberCacheComponent.old';
|
||||
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old';
|
||||
import type {RootState} from './ReactFiberRoot.old';
|
||||
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old';
|
||||
import {
|
||||
enableSuspenseAvoidThisFallback,
|
||||
enableCPUSuspense,
|
||||
@@ -255,9 +256,12 @@ import {
|
||||
getSuspendedCache,
|
||||
pushTransition,
|
||||
getOffscreenDeferredCache,
|
||||
getSuspendedTransitions,
|
||||
getPendingTransitions,
|
||||
} from './ReactFiberTransition.old';
|
||||
import {pushTracingMarker} from './ReactFiberTracingMarkerComponent.old';
|
||||
import {
|
||||
getTracingMarkers,
|
||||
pushTracingMarker,
|
||||
} from './ReactFiberTracingMarkerComponent.old';
|
||||
|
||||
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
||||
|
||||
@@ -891,6 +895,20 @@ function updateTracingMarkerComponent(
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: (luna) Only update the tracing marker if it's newly rendered or it's name changed.
|
||||
// A tracing marker is only associated with the transitions that rendered
|
||||
// or updated it, so we can create a new set of transitions each time
|
||||
if (current === null) {
|
||||
const currentTransitions = getPendingTransitions();
|
||||
if (currentTransitions !== null) {
|
||||
const markerInstance: TracingMarkerInstance = {
|
||||
transitions: new Set(currentTransitions),
|
||||
pendingSuspenseBoundaries: new Map(),
|
||||
};
|
||||
workInProgress.stateNode = markerInstance;
|
||||
}
|
||||
}
|
||||
|
||||
pushTracingMarker(workInProgress);
|
||||
const nextChildren = workInProgress.pendingProps.children;
|
||||
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
||||
@@ -2093,10 +2111,13 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
||||
);
|
||||
workInProgress.memoizedState = SUSPENDED_MARKER;
|
||||
if (enableTransitionTracing) {
|
||||
const currentTransitions = getSuspendedTransitions();
|
||||
const currentTransitions = getPendingTransitions();
|
||||
if (currentTransitions !== null) {
|
||||
// If there are no transitions, we don't need to keep track of tracing markers
|
||||
const currentTracingMarkers = getTracingMarkers();
|
||||
const primaryChildUpdateQueue: OffscreenQueue = {
|
||||
transitions: currentTransitions,
|
||||
tracingMarkers: currentTracingMarkers,
|
||||
};
|
||||
primaryChildFragment.updateQueue = primaryChildUpdateQueue;
|
||||
}
|
||||
@@ -2177,10 +2198,12 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
||||
? mountSuspenseOffscreenState(renderLanes)
|
||||
: updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
|
||||
if (enableTransitionTracing) {
|
||||
const currentTransitions = getSuspendedTransitions();
|
||||
const currentTransitions = getPendingTransitions();
|
||||
if (currentTransitions !== null) {
|
||||
const currentTracingMarkers = getTracingMarkers();
|
||||
const primaryChildUpdateQueue: OffscreenQueue = {
|
||||
transitions: currentTransitions,
|
||||
tracingMarkers: currentTracingMarkers,
|
||||
};
|
||||
primaryChildFragment.updateQueue = primaryChildUpdateQueue;
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ import {
|
||||
restorePendingUpdaters,
|
||||
addTransitionStartCallbackToPendingTransition,
|
||||
addTransitionCompleteCallbackToPendingTransition,
|
||||
addMarkerCompleteCallbackToPendingTransition,
|
||||
setIsRunningInsertionEffect,
|
||||
} from './ReactFiberWorkLoop.new';
|
||||
import {
|
||||
@@ -2910,6 +2911,7 @@ function commitPassiveMountOnFiber(
|
||||
instance.transitions = prevTransitions = new Set();
|
||||
}
|
||||
|
||||
// TODO(luna): Combine the root code with the tracing marker code
|
||||
if (transitions !== null) {
|
||||
transitions.forEach(transition => {
|
||||
// Add all the transitions saved in the update queue during
|
||||
@@ -2931,6 +2933,23 @@ function commitPassiveMountOnFiber(
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const tracingMarkers = queue.tracingMarkers;
|
||||
if (tracingMarkers !== null) {
|
||||
tracingMarkers.forEach(marker => {
|
||||
const markerInstance = marker.stateNode;
|
||||
// There should only be a few tracing marker transitions because
|
||||
// they should be only associated with the transition that
|
||||
// caused them
|
||||
markerInstance.transitions.forEach(transition => {
|
||||
if (instance.transitions.has(transition)) {
|
||||
instance.pendingMarkers.add(
|
||||
markerInstance.pendingSuspenseBoundaries,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
commitTransitionProgress(finishedWork);
|
||||
@@ -2967,6 +2986,28 @@ function commitPassiveMountOnFiber(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TracingMarkerComponent: {
|
||||
if (enableTransitionTracing) {
|
||||
// Get the transitions that were initiatized during the render
|
||||
// and add a start transition callback for each of them
|
||||
const instance = finishedWork.stateNode;
|
||||
if (
|
||||
instance.pendingSuspenseBoundaries === null ||
|
||||
instance.pendingSuspenseBoundaries.size === 0
|
||||
) {
|
||||
instance.transitions.forEach(transition => {
|
||||
addMarkerCompleteCallbackToPendingTransition({
|
||||
transitionName: transition.name,
|
||||
startTime: transition.startTime,
|
||||
markerName: finishedWork.memoizedProps.name,
|
||||
});
|
||||
});
|
||||
instance.transitions = null;
|
||||
instance.pendingSuspenseBoundaries = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ import {
|
||||
restorePendingUpdaters,
|
||||
addTransitionStartCallbackToPendingTransition,
|
||||
addTransitionCompleteCallbackToPendingTransition,
|
||||
addMarkerCompleteCallbackToPendingTransition,
|
||||
setIsRunningInsertionEffect,
|
||||
} from './ReactFiberWorkLoop.old';
|
||||
import {
|
||||
@@ -2910,6 +2911,7 @@ function commitPassiveMountOnFiber(
|
||||
instance.transitions = prevTransitions = new Set();
|
||||
}
|
||||
|
||||
// TODO(luna): Combine the root code with the tracing marker code
|
||||
if (transitions !== null) {
|
||||
transitions.forEach(transition => {
|
||||
// Add all the transitions saved in the update queue during
|
||||
@@ -2931,6 +2933,23 @@ function commitPassiveMountOnFiber(
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const tracingMarkers = queue.tracingMarkers;
|
||||
if (tracingMarkers !== null) {
|
||||
tracingMarkers.forEach(marker => {
|
||||
const markerInstance = marker.stateNode;
|
||||
// There should only be a few tracing marker transitions because
|
||||
// they should be only associated with the transition that
|
||||
// caused them
|
||||
markerInstance.transitions.forEach(transition => {
|
||||
if (instance.transitions.has(transition)) {
|
||||
instance.pendingMarkers.add(
|
||||
markerInstance.pendingSuspenseBoundaries,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
commitTransitionProgress(finishedWork);
|
||||
@@ -2967,6 +2986,28 @@ function commitPassiveMountOnFiber(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TracingMarkerComponent: {
|
||||
if (enableTransitionTracing) {
|
||||
// Get the transitions that were initiatized during the render
|
||||
// and add a start transition callback for each of them
|
||||
const instance = finishedWork.stateNode;
|
||||
if (
|
||||
instance.pendingSuspenseBoundaries === null ||
|
||||
instance.pendingSuspenseBoundaries.size === 0
|
||||
) {
|
||||
instance.transitions.forEach(transition => {
|
||||
addMarkerCompleteCallbackToPendingTransition({
|
||||
transitionName: transition.name,
|
||||
startTime: transition.startTime,
|
||||
markerName: finishedWork.memoizedProps.name,
|
||||
});
|
||||
});
|
||||
instance.transitions = null;
|
||||
instance.pendingSuspenseBoundaries = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1581,9 +1581,18 @@ function completeWork(
|
||||
}
|
||||
case TracingMarkerComponent: {
|
||||
if (enableTransitionTracing) {
|
||||
// Bubble subtree flags before so we can set the flag property
|
||||
popTracingMarker(workInProgress);
|
||||
bubbleProperties(workInProgress);
|
||||
|
||||
if (
|
||||
current === null ||
|
||||
(workInProgress.subtreeFlags & Visibility) !== NoFlags
|
||||
) {
|
||||
// If any of our suspense children toggle visibility, this means that
|
||||
// the pending boundaries array needs to be updated, which we only
|
||||
// do in the passive phase.
|
||||
workInProgress.flags |= Passive;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1581,9 +1581,18 @@ function completeWork(
|
||||
}
|
||||
case TracingMarkerComponent: {
|
||||
if (enableTransitionTracing) {
|
||||
// Bubble subtree flags before so we can set the flag property
|
||||
popTracingMarker(workInProgress);
|
||||
bubbleProperties(workInProgress);
|
||||
|
||||
if (
|
||||
current === null ||
|
||||
(workInProgress.subtreeFlags & Visibility) !== NoFlags
|
||||
) {
|
||||
// If any of our suspense children toggle visibility, this means that
|
||||
// the pending boundaries array needs to be updated, which we only
|
||||
// do in the passive phase.
|
||||
workInProgress.flags |= Passive;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import type {ReactNodeList, OffscreenMode} from 'shared/ReactTypes';
|
||||
import type {Fiber} from './ReactInternalTypes';
|
||||
import type {Lanes} from './ReactFiberLane.old';
|
||||
import type {SpawnedCachePool} from './ReactFiberCacheComponent.new';
|
||||
import type {
|
||||
@@ -38,6 +39,7 @@ export type OffscreenState = {|
|
||||
|
||||
export type OffscreenQueue = {|
|
||||
transitions: Array<Transition> | null,
|
||||
tracingMarkers: Array<Fiber> | null,
|
||||
|} | null;
|
||||
|
||||
export type OffscreenInstance = {|
|
||||
|
||||
@@ -39,6 +39,11 @@ export type BatchConfigTransition = {
|
||||
_updatedFibers?: Set<Fiber>,
|
||||
};
|
||||
|
||||
export type TracingMarkerInstance = {|
|
||||
pendingSuspenseBoundaries: PendingSuspenseBoundaries | null,
|
||||
transitions: Set<Transition> | null,
|
||||
|} | null;
|
||||
|
||||
export type PendingSuspenseBoundaries = Map<OffscreenInstance, SuspenseInfo>;
|
||||
|
||||
export function processTransitionCallbacks(
|
||||
|
||||
@@ -39,6 +39,11 @@ export type BatchConfigTransition = {
|
||||
_updatedFibers?: Set<Fiber>,
|
||||
};
|
||||
|
||||
export type TracingMarkerInstance = {|
|
||||
pendingSuspenseBoundaries: PendingSuspenseBoundaries | null,
|
||||
transitions: Set<Transition> | null,
|
||||
|} | null;
|
||||
|
||||
export type PendingSuspenseBoundaries = Map<OffscreenInstance, SuspenseInfo>;
|
||||
|
||||
export function processTransitionCallbacks(
|
||||
|
||||
@@ -149,7 +149,7 @@ export function popTransition(workInProgress: Fiber, current: Fiber | null) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getSuspendedTransitions(): Array<Transition> | null {
|
||||
export function getPendingTransitions(): Array<Transition> | null {
|
||||
if (!enableTransitionTracing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ export function popTransition(workInProgress: Fiber, current: Fiber | null) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getSuspendedTransitions(): Array<Transition> | null {
|
||||
export function getPendingTransitions(): Array<Transition> | null {
|
||||
if (!enableTransitionTracing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -477,4 +477,365 @@ describe('ReactInteractionTracing', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableTransitionTracing
|
||||
it('should correctly trace interactions for tracing markers complete', async () => {
|
||||
const transitionCallbacks = {
|
||||
onTransitionStart: (name, startTime) => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`onTransitionStart(${name}, ${startTime})`,
|
||||
);
|
||||
},
|
||||
onTransitionComplete: (name, startTime, endTime) => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`onTransitionComplete(${name}, ${startTime}, ${endTime})`,
|
||||
);
|
||||
},
|
||||
onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
|
||||
);
|
||||
},
|
||||
};
|
||||
let navigateToPageTwo;
|
||||
function App() {
|
||||
const [navigate, setNavigate] = useState(false);
|
||||
navigateToPageTwo = () => {
|
||||
setNavigate(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{navigate ? (
|
||||
<Suspense
|
||||
fallback={<Text text="Loading..." />}
|
||||
name="suspense page">
|
||||
<AsyncText text="Page Two" />
|
||||
<React.unstable_TracingMarker name="sync marker" />
|
||||
<React.unstable_TracingMarker name="async marker">
|
||||
<Suspense
|
||||
fallback={<Text text="Loading..." />}
|
||||
name="marker suspense">
|
||||
<AsyncText text="Marker Text" />
|
||||
</Suspense>
|
||||
</React.unstable_TracingMarker>
|
||||
</Suspense>
|
||||
) : (
|
||||
<Text text="Page One" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot({transitionCallbacks});
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Page One']);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
startTransition(() => navigateToPageTwo(), {name: 'page transition'});
|
||||
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Suspend [Page Two]',
|
||||
'Suspend [Marker Text]',
|
||||
'Loading...',
|
||||
'Loading...',
|
||||
'onTransitionStart(page transition, 1000)',
|
||||
]);
|
||||
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
await resolveText('Page Two');
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Page Two',
|
||||
'Suspend [Marker Text]',
|
||||
'Loading...',
|
||||
'onMarkerComplete(page transition, sync marker, 1000, 3000)',
|
||||
]);
|
||||
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
await resolveText('Marker Text');
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Marker Text',
|
||||
'onMarkerComplete(page transition, async marker, 1000, 4000)',
|
||||
'onTransitionComplete(page transition, 1000, 4000)',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableTransitionTracing
|
||||
it('trace interaction with multiple tracing markers', async () => {
|
||||
const transitionCallbacks = {
|
||||
onTransitionStart: (name, startTime) => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`onTransitionStart(${name}, ${startTime})`,
|
||||
);
|
||||
},
|
||||
onTransitionComplete: (name, startTime, endTime) => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`onTransitionComplete(${name}, ${startTime}, ${endTime})`,
|
||||
);
|
||||
},
|
||||
onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
let navigateToPageTwo;
|
||||
function App() {
|
||||
const [navigate, setNavigate] = useState(false);
|
||||
navigateToPageTwo = () => {
|
||||
setNavigate(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{navigate ? (
|
||||
<React.unstable_TracingMarker name="outer marker">
|
||||
<Suspense fallback={<Text text="Outer..." />}>
|
||||
<AsyncText text="Outer Text" />
|
||||
<Suspense fallback={<Text text="Inner One..." />}>
|
||||
<React.unstable_TracingMarker name="marker one">
|
||||
<AsyncText text="Inner Text One" />
|
||||
</React.unstable_TracingMarker>
|
||||
</Suspense>
|
||||
<Suspense fallback={<Text text="Inner Two..." />}>
|
||||
<React.unstable_TracingMarker name="marker two">
|
||||
<AsyncText text="Inner Text Two" />
|
||||
</React.unstable_TracingMarker>
|
||||
</Suspense>
|
||||
</Suspense>
|
||||
</React.unstable_TracingMarker>
|
||||
) : (
|
||||
<Text text="Page One" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot({transitionCallbacks});
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Page One']);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
startTransition(() => navigateToPageTwo(), {name: 'page transition'});
|
||||
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Suspend [Outer Text]',
|
||||
'Suspend [Inner Text One]',
|
||||
'Inner One...',
|
||||
'Suspend [Inner Text Two]',
|
||||
'Inner Two...',
|
||||
'Outer...',
|
||||
'onTransitionStart(page transition, 1000)',
|
||||
]);
|
||||
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
await resolveText('Inner Text Two');
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
await resolveText('Outer Text');
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Outer Text',
|
||||
'Suspend [Inner Text One]',
|
||||
'Inner One...',
|
||||
'Inner Text Two',
|
||||
'onMarkerComplete(page transition, marker two, 1000, 4000)',
|
||||
]);
|
||||
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
await resolveText('Inner Text One');
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Inner Text One',
|
||||
'onMarkerComplete(page transition, marker one, 1000, 5000)',
|
||||
'onMarkerComplete(page transition, outer marker, 1000, 5000)',
|
||||
'onTransitionComplete(page transition, 1000, 5000)',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableTransitionTracing
|
||||
it.skip('marker interaction cancelled when name changes', async () => {
|
||||
const transitionCallbacks = {
|
||||
onTransitionStart: (name, startTime) => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`onTransitionStart(${name}, ${startTime})`,
|
||||
);
|
||||
},
|
||||
onTransitionComplete: (name, startTime, endTime) => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`onTransitionComplete(${name}, ${startTime}, ${endTime})`,
|
||||
);
|
||||
},
|
||||
onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
let navigateToPageTwo;
|
||||
let setMarkerNameFn;
|
||||
function App() {
|
||||
const [navigate, setNavigate] = useState(false);
|
||||
navigateToPageTwo = () => {
|
||||
setNavigate(true);
|
||||
};
|
||||
|
||||
const [markerName, setMarkerName] = useState('old marker');
|
||||
setMarkerNameFn = () => setMarkerName('new marker');
|
||||
|
||||
return (
|
||||
<div>
|
||||
{navigate ? (
|
||||
<React.unstable_TracingMarker name={markerName}>
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<AsyncText text="Page Two" />
|
||||
</Suspense>
|
||||
</React.unstable_TracingMarker>
|
||||
) : (
|
||||
<Text text="Page One" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot({transitionCallbacks});
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Page One']);
|
||||
|
||||
startTransition(() => navigateToPageTwo(), {name: 'page transition'});
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Suspend [Page Two]',
|
||||
'Loading...',
|
||||
'onTransitionStart(page transition, 1000)',
|
||||
]);
|
||||
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
setMarkerNameFn();
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Suspend [Page Two]', 'Loading...']);
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
resolveText('Page Two');
|
||||
|
||||
// Marker complete is not called because the marker name changed
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Page Two',
|
||||
'onTransitionComplete(page transition, 1000, 3000)',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableTransitionTracing
|
||||
it.skip('marker changes to new interaction when name changes', async () => {
|
||||
const transitionCallbacks = {
|
||||
onTransitionStart: (name, startTime) => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`onTransitionStart(${name}, ${startTime})`,
|
||||
);
|
||||
},
|
||||
onTransitionComplete: (name, startTime, endTime) => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`onTransitionComplete(${name}, ${startTime}, ${endTime})`,
|
||||
);
|
||||
},
|
||||
onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
let navigateToPageTwo;
|
||||
let setMarkerNameFn;
|
||||
function App() {
|
||||
const [navigate, setNavigate] = useState(false);
|
||||
navigateToPageTwo = () => {
|
||||
setNavigate(true);
|
||||
};
|
||||
|
||||
const [markerName, setMarkerName] = useState('old marker');
|
||||
setMarkerNameFn = () => setMarkerName('new marker');
|
||||
|
||||
return (
|
||||
<div>
|
||||
{navigate ? (
|
||||
<React.unstable_TracingMarker name={markerName}>
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<AsyncText text="Page Two" />
|
||||
</Suspense>
|
||||
</React.unstable_TracingMarker>
|
||||
) : (
|
||||
<Text text="Page One" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot({transitionCallbacks});
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Page One']);
|
||||
|
||||
startTransition(() => navigateToPageTwo(), {name: 'page transition'});
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Suspend [Page Two]',
|
||||
'Loading...',
|
||||
'onTransitionStart(page transition, 1000)',
|
||||
]);
|
||||
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
startTransition(() => setMarkerNameFn(), {name: 'marker transition'});
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Suspend [Page Two]',
|
||||
'Loading...',
|
||||
'onTransitionStart(marker transition, 2000)',
|
||||
]);
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
resolveText('Page Two');
|
||||
|
||||
// Marker complete is not called because the marker name changed
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Page Two',
|
||||
'onMarkerComplete(new marker, 2000, 3000)',
|
||||
'onTransitionComplete(page transition, 1000, 3000)',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user