diff --git a/packages/react-reconciler/src/ReactFiber.new.js b/packages/react-reconciler/src/ReactFiber.new.js index c5a5919494d86..e78db21baf74e 100644 --- a/packages/react-reconciler/src/ReactFiber.new.js +++ b/packages/react-reconciler/src/ReactFiber.new.js @@ -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; } diff --git a/packages/react-reconciler/src/ReactFiber.old.js b/packages/react-reconciler/src/ReactFiber.old.js index 9a4e3e2aa0e4c..15019976d0ab2 100644 --- a/packages/react-reconciler/src/ReactFiber.old.js +++ b/packages/react-reconciler/src/ReactFiber.old.js @@ -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; } diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index 0d575e14332ea..9f60e0ab227e9 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -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; } diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 9bbaf40bd1adf..ca25b272e473f 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -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; } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 6f983e8d249dd..da00d7761f9c5 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -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; + } } } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 147a25d74a807..677db2f10b0ea 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -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; + } } } diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index c5230bce92028..f6a54052f92d5 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -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; } diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 4dbc447dd989a..b99d3451d3324 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -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; } diff --git a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js index 3f5ef38424d7b..00904d9fdd8ed 100644 --- a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js +++ b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js @@ -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 | null, + tracingMarkers: Array | null, |} | null; export type OffscreenInstance = {| diff --git a/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.new.js b/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.new.js index 593ebfc2d7da3..3a136cda686fd 100644 --- a/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.new.js +++ b/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.new.js @@ -39,6 +39,11 @@ export type BatchConfigTransition = { _updatedFibers?: Set, }; +export type TracingMarkerInstance = {| + pendingSuspenseBoundaries: PendingSuspenseBoundaries | null, + transitions: Set | null, +|} | null; + export type PendingSuspenseBoundaries = Map; export function processTransitionCallbacks( diff --git a/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js b/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js index a632af8e1cb91..5f119b3a4b66a 100644 --- a/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js +++ b/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js @@ -39,6 +39,11 @@ export type BatchConfigTransition = { _updatedFibers?: Set, }; +export type TracingMarkerInstance = {| + pendingSuspenseBoundaries: PendingSuspenseBoundaries | null, + transitions: Set | null, +|} | null; + export type PendingSuspenseBoundaries = Map; export function processTransitionCallbacks( diff --git a/packages/react-reconciler/src/ReactFiberTransition.new.js b/packages/react-reconciler/src/ReactFiberTransition.new.js index bb5f5d93d2026..527cb89877f62 100644 --- a/packages/react-reconciler/src/ReactFiberTransition.new.js +++ b/packages/react-reconciler/src/ReactFiberTransition.new.js @@ -149,7 +149,7 @@ export function popTransition(workInProgress: Fiber, current: Fiber | null) { } } -export function getSuspendedTransitions(): Array | null { +export function getPendingTransitions(): Array | null { if (!enableTransitionTracing) { return null; } diff --git a/packages/react-reconciler/src/ReactFiberTransition.old.js b/packages/react-reconciler/src/ReactFiberTransition.old.js index 1abb0e2180ab5..8323559d58f8b 100644 --- a/packages/react-reconciler/src/ReactFiberTransition.old.js +++ b/packages/react-reconciler/src/ReactFiberTransition.old.js @@ -149,7 +149,7 @@ export function popTransition(workInProgress: Fiber, current: Fiber | null) { } } -export function getSuspendedTransitions(): Array | null { +export function getPendingTransitions(): Array | null { if (!enableTransitionTracing) { return null; } diff --git a/packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js b/packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js index 9f5648b85b539..2fd5a3633e426 100644 --- a/packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js +++ b/packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js @@ -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 ( +
+ {navigate ? ( + } + name="suspense page"> + + + + } + name="marker suspense"> + + + + + ) : ( + + )} +
+ ); + } + + const root = ReactNoop.createRoot({transitionCallbacks}); + await act(async () => { + root.render(); + 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 ( +
+ {navigate ? ( + + }> + + }> + + + + + }> + + + + + + + ) : ( + + )} +
+ ); + } + + const root = ReactNoop.createRoot({transitionCallbacks}); + await act(async () => { + root.render(); + 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 ( +
+ {navigate ? ( + + }> + + + + ) : ( + + )} +
+ ); + } + + const root = ReactNoop.createRoot({transitionCallbacks}); + await act(async () => { + root.render(); + 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 ( +
+ {navigate ? ( + + }> + + + + ) : ( + + )} +
+ ); + } + + const root = ReactNoop.createRoot({transitionCallbacks}); + await act(async () => { + root.render(); + 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)', + ]); + }); + }); });