@samitouri / QOS-React-2 / commits / 81aaee56af

Don't call onCommit et al if there are no effects (#19863)

* Don't call onCommit et al if there are no effects Checks `subtreeFlags` before scheduling an effect on the Profiler. * Fix failing Profiler tests The change to conditionally call Profiler commit hooks only if updates were scheduled broke a few of the Profiler tests. I've fixed the tests by either: * Adding a no-op passive effect into the subtree or * Converting onPostCommit to onCommit When possible, I opted to add the no-op passive effect to the tests since that that hook is called later (during passive phase) so the test is a little broader. In a few cases, this required adding awkward act() wrappers so I opted to go with onCommit instead. Co-authored-by: Brian Vaughn <bvaughn@fb.com>

Andrew Clark committed Sep 22, 2020 at 13:20 UTC 81aaee56afba2bb3558f2aaa484b594f23b59d4c
4 files changed +795 -714
packages/react-reconciler/src/ReactFiberBeginWork.new.js
-15
@@ -60,8 +60,6 @@ import {
60 Hydrating,
61 ContentReset,
62 DidCapture,
63 - Update,
64 - Passive,
63 Ref,
64 Deletion,
65 ForceUpdateForLegacySuspense,
@@ -675,9 +673,6 @@ function updateProfiler(
673 renderLanes: Lanes,
674 ) {
675 if (enableProfilerTimer) {
678 - // TODO: Only call onRender et al if subtree has effects
679 - workInProgress.flags |= Update | Passive;
680 -
676 // Reset effect durations for the next eventual effect phase.
677 // These are reset during render to allow the DevTools commit hook a chance to read them,
678 const stateNode = workInProgress.stateNode;
@@ -3117,16 +3112,6 @@ function beginWork(
3112 }
3113 case Profiler:
3114 if (enableProfilerTimer) {
3120 - // Profiler should only call onRender when one of its descendants actually rendered.
3121 - // TODO: Only call onRender et al if subtree has effects
3122 - const hasChildWork = includesSomeLane(
3123 - renderLanes,
3124 - workInProgress.childLanes,
3125 - );
3126 - if (hasChildWork) {
3127 - workInProgress.flags |= Passive | Update;
3128 - }
3129 -
3115 // Reset effect durations for the next eventual effect phase.
3116 // These are reset during render to allow the DevTools commit hook a chance to read them,
3117 const stateNode = workInProgress.stateNode;
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+13 -2
@@ -68,6 +68,7 @@ import {
68 Placement,
69 Snapshot,
70 Update,
71 + Callback,
72 PassiveMask,
73 } from './ReactFiberFlags';
74 import getComponentName from 'shared/getComponentName';
@@ -676,10 +677,17 @@ function commitLifeCycles(
677 if (enableProfilerTimer) {
678 const {onCommit, onRender} = finishedWork.memoizedProps;
679 const {effectDuration} = finishedWork.stateNode;
680 + const flags = finishedWork.flags;
681
682 const commitTime = getCommitTime();
683
682 - if (typeof onRender === 'function') {
684 + const OnRenderFlag = Update;
685 + const OnCommitFlag = Callback;
686 +
687 + if (
688 + (flags & OnRenderFlag) !== NoFlags &&
689 + typeof onRender === 'function'
690 + ) {
691 if (enableSchedulerTracing) {
692 onRender(
693 finishedWork.memoizedProps.id,
@@ -703,7 +711,10 @@ function commitLifeCycles(
711 }
712
713 if (enableProfilerCommitHooks) {
706 - if (typeof onCommit === 'function') {
714 + if (
715 + (flags & OnCommitFlag) !== NoFlags &&
716 + typeof onCommit === 'function'
717 + ) {
718 if (enableSchedulerTracing) {
719 onCommit(
720 finishedWork.memoizedProps.id,
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+54 -1
@@ -67,10 +67,15 @@ import {
67 import {
68 Ref,
69 Update,
70 + Callback,
71 + Passive,
72 + Deletion,
73 NoFlags,
74 DidCapture,
75 Snapshot,
76 MutationMask,
77 + LayoutMask,
78 + PassiveMask,
79 StaticMask,
80 } from './ReactFiberFlags';
81 import invariant from 'shared/invariant';
@@ -787,6 +792,8 @@ function bubbleProperties(completedWork: Fiber) {
792 }
793
794 completedWork.childLanes = newChildLanes;
795 +
796 + return didBailout;
797 }
798
799 function completeWork(
@@ -804,7 +811,6 @@ function completeWork(
811 case ForwardRef:
812 case Fragment:
813 case Mode:
807 - case Profiler:
814 case ContextConsumer:
815 case MemoComponent:
816 bubbleProperties(workInProgress);
@@ -966,6 +972,53 @@ function completeWork(
972 bubbleProperties(workInProgress);
973 return null;
974 }
975 + case Profiler: {
976 + const didBailout = bubbleProperties(workInProgress);
977 + if (!didBailout) {
978 + // Use subtreeFlags to determine which commit callbacks should fire.
979 + // TODO: Move this logic to the commit phase, since we already check if
980 + // a fiber's subtree contains effects. Refactor the commit phase's
981 + // depth-first traversal so that we can put work tag-specific logic
982 + // before or after committing a subtree's effects.
983 + const OnRenderFlag = Update;
984 + const OnCommitFlag = Callback;
985 + const OnPostCommitFlag = Passive;
986 + const subtreeFlags = workInProgress.subtreeFlags;
987 + const flags = workInProgress.flags;
988 + let newFlags = flags;
989 +
990 + // Call onRender any time this fiber or its subtree are worked on, even
991 + // if there are no effects
992 + newFlags |= OnRenderFlag;
993 +
994 + // Call onCommit only if the subtree contains layout work, or if it
995 + // contains deletions, since those might result in unmount work, which
996 + // we include in the same measure.
997 + // TODO: Can optimize by using a static flag to track whether a tree
998 + // contains layout effects, like we do for passive effects.
999 + if (
1000 + (flags & (LayoutMask | Deletion)) !== NoFlags ||
1001 + (subtreeFlags & (LayoutMask | Deletion)) !== NoFlags
1002 + ) {
1003 + newFlags |= OnCommitFlag;
1004 + }
1005 +
1006 + // Call onPostCommit only if the subtree contains passive work.
1007 + // Don't have to check for deletions, because Deletion is already
1008 + // a passive flag.
1009 + if (
1010 + (flags & PassiveMask) !== NoFlags ||
1011 + (subtreeFlags & PassiveMask) !== NoFlags
1012 + ) {
1013 + newFlags |= OnPostCommitFlag;
1014 + }
1015 + workInProgress.flags = newFlags;
1016 + } else {
1017 + // This fiber and its subtree bailed out, so don't fire any callbacks.
1018 + }
1019 +
1020 + return null;
1021 + }
1022 case SuspenseComponent: {
1023 popSuspenseContext(workInProgress);
1024 const nextState: null | SuspenseState = workInProgress.memoizedState;
packages/react/src/__tests__/ReactProfiler-test.internal.js
+728 -696
@@ -20,6 +20,7 @@ let ReactTestRendererAct;
20 let SchedulerTracing;
21 let AdvanceTime;
22 let AsyncText;
23 +let ComponentWithPassiveEffect;
24 let Text;
25 let TextResource;
26 let resourcePromise;
@@ -102,6 +103,12 @@ function loadModules({
103 Scheduler.unstable_yieldValue(`Text [${text}]`);
104 return text;
105 };
106 +
107 + ComponentWithPassiveEffect = () => {
108 + // Intentionally schedule a passive effect so the onPostCommit hook will be called.
109 + React.useEffect(() => {});
110 + return null;
111 + };
112 }
113
114 describe('Profiler', () => {
@@ -2622,251 +2629,256 @@ describe('Profiler', () => {
2629 <React.Fragment>
2630 <Yield value="first" />
2631 {this.state.count}
2632 + <ComponentWithPassiveEffect count={this.state.count} />
2633 <Yield value="last" />
2634 </React.Fragment>
2635 );
2636 }
2637 }
2638
2631 - Scheduler.unstable_advanceTime(1);
2632 -
2633 - const interactionCreation = {
2634 - id: 0,
2635 - name: 'creation event',
2636 - timestamp: Scheduler.unstable_now(),
2637 - };
2639 + ReactTestRendererAct(() => {
2640 + Scheduler.unstable_advanceTime(1);
2641
2639 - const onPostCommit = jest.fn(() => {
2640 - Scheduler.unstable_yieldValue('onPostCommit');
2641 - });
2642 - let renderer;
2643 - SchedulerTracing.unstable_trace(
2644 - interactionCreation.name,
2645 - Scheduler.unstable_now(),
2646 - () => {
2647 - renderer = ReactTestRenderer.create(
2648 - <React.Profiler id="test-profiler" onPostCommit={onPostCommit}>
2649 - <Example />
2650 - </React.Profiler>,
2651 - {
2652 - unstable_isConcurrent: true,
2653 - },
2654 - );
2655 - },
2656 - );
2642 + const interactionCreation = {
2643 + id: 0,
2644 + name: 'creation event',
2645 + timestamp: Scheduler.unstable_now(),
2646 + };
2647
2658 - expect(onInteractionTraced).toHaveBeenCalledTimes(1);
2659 - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
2660 - interactionCreation,
2661 - );
2662 - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
2648 + const onPostCommit = jest.fn(() => {
2649 + Scheduler.unstable_yieldValue('onPostCommit');
2650 + });
2651 + let renderer;
2652 + SchedulerTracing.unstable_trace(
2653 + interactionCreation.name,
2654 + Scheduler.unstable_now(),
2655 + () => {
2656 + renderer = ReactTestRenderer.create(
2657 + <React.Profiler id="test-profiler" onPostCommit={onPostCommit}>
2658 + <Example />
2659 + </React.Profiler>,
2660 + {
2661 + unstable_isConcurrent: true,
2662 + },
2663 + );
2664 + },
2665 + );
2666
2664 - // The scheduler/tracing package will notify of work started for the default thread,
2665 - // But React shouldn't notify until it's been flushed.
2666 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
2667 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2668 -
2669 - // Work may have been scheduled multiple times.
2670 - // We only care that the subscriber was notified at least once.
2671 - // As for the thread ID- the actual value isn't important, only that there was one.
2672 - expect(onWorkScheduled).toHaveBeenCalled();
2673 - expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([
2674 - interactionCreation,
2675 - ]);
2676 - expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true);
2677 -
2678 - // Mount
2679 - expect(Scheduler).toFlushAndYield(['first', 'last', 'onPostCommit']);
2680 - expect(onPostCommit).toHaveBeenCalledTimes(1);
2681 - let call = onPostCommit.mock.calls[0];
2682 - expect(call[0]).toEqual('test-profiler');
2683 - expect(call[3]).toEqual(Scheduler.unstable_now());
2684 - if (ReactFeatureFlags.enableSchedulerTracing) {
2685 - expect(call[4]).toMatchInteractions([interactionCreation]);
2686 - }
2667 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
2668 + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
2669 + interactionCreation,
2670 + );
2671 + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
2672
2688 - expect(onInteractionTraced).toHaveBeenCalledTimes(1);
2689 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
2690 - expect(
2691 - onInteractionScheduledWorkCompleted,
2692 - ).toHaveBeenLastNotifiedOfInteraction(interactionCreation);
2693 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
2694 - expect(getWorkForReactThreads(onWorkStarted)[0][0]).toMatchInteractions([
2695 - interactionCreation,
2696 - ]);
2697 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1);
2698 - expect(getWorkForReactThreads(onWorkStopped)[0][0]).toMatchInteractions([
2699 - interactionCreation,
2700 - ]);
2673 + // The scheduler/tracing package will notify of work started for the default thread,
2674 + // But React shouldn't notify until it's been flushed.
2675 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
2676 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2677
2702 - onPostCommit.mockClear();
2703 - onWorkScheduled.mockClear();
2704 - onWorkStarted.mockClear();
2705 - onWorkStopped.mockClear();
2678 + // Work may have been scheduled multiple times.
2679 + // We only care that the subscriber was notified at least once.
2680 + // As for the thread ID- the actual value isn't important, only that there was one.
2681 + expect(onWorkScheduled).toHaveBeenCalled();
2682 + expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([
2683 + interactionCreation,
2684 + ]);
2685 + expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true);
2686
2707 - Scheduler.unstable_advanceTime(3);
2687 + // Mount
2688 + expect(Scheduler).toFlushAndYield(['first', 'last', 'onPostCommit']);
2689 + expect(onPostCommit).toHaveBeenCalledTimes(1);
2690 + let call = onPostCommit.mock.calls[0];
2691 + expect(call[0]).toEqual('test-profiler');
2692 + expect(call[3]).toEqual(Scheduler.unstable_now());
2693 + if (ReactFeatureFlags.enableSchedulerTracing) {
2694 + expect(call[4]).toMatchInteractions([interactionCreation]);
2695 + }
2696
2709 - let didRunCallback = false;
2697 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
2698 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
2699 + expect(
2700 + onInteractionScheduledWorkCompleted,
2701 + ).toHaveBeenLastNotifiedOfInteraction(interactionCreation);
2702 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
2703 + expect(
2704 + getWorkForReactThreads(onWorkStarted)[0][0],
2705 + ).toMatchInteractions([interactionCreation]);
2706 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1);
2707 + expect(
2708 + getWorkForReactThreads(onWorkStopped)[0][0],
2709 + ).toMatchInteractions([interactionCreation]);
2710
2711 - const interactionOne = {
2712 - id: 1,
2713 - name: 'initial event',
2714 - timestamp: Scheduler.unstable_now(),
2715 - };
2716 - SchedulerTracing.unstable_trace(
2717 - interactionOne.name,
2718 - Scheduler.unstable_now(),
2719 - () => {
2720 - instance.setState({count: 1});
2711 + onPostCommit.mockClear();
2712 + onWorkScheduled.mockClear();
2713 + onWorkStarted.mockClear();
2714 + onWorkStopped.mockClear();
2715
2722 - // Update state again to verify our traced interaction isn't registered twice
2723 - instance.setState({count: 2});
2716 + Scheduler.unstable_advanceTime(3);
2717
2725 - // The scheduler/tracing package will notify of work started for the default thread,
2726 - // But React shouldn't notify until it's been flushed.
2727 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
2728 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2718 + let didRunCallback = false;
2719
2730 - // Work may have been scheduled multiple times.
2731 - // We only care that the subscriber was notified at least once.
2732 - // As for the thread ID- the actual value isn't important, only that there was one.
2733 - expect(onWorkScheduled).toHaveBeenCalled();
2734 - expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([
2735 - interactionOne,
2736 - ]);
2737 - expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true);
2720 + const interactionOne = {
2721 + id: 1,
2722 + name: 'initial event',
2723 + timestamp: Scheduler.unstable_now(),
2724 + };
2725 + SchedulerTracing.unstable_trace(
2726 + interactionOne.name,
2727 + Scheduler.unstable_now(),
2728 + () => {
2729 + instance.setState({count: 1});
2730 +
2731 + // Update state again to verify our traced interaction isn't registered twice
2732 + instance.setState({count: 2});
2733 +
2734 + // The scheduler/tracing package will notify of work started for the default thread,
2735 + // But React shouldn't notify until it's been flushed.
2736 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
2737 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2738 +
2739 + // Work may have been scheduled multiple times.
2740 + // We only care that the subscriber was notified at least once.
2741 + // As for the thread ID- the actual value isn't important, only that there was one.
2742 + expect(onWorkScheduled).toHaveBeenCalled();
2743 + expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([
2744 + interactionOne,
2745 + ]);
2746 + expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true);
2747 +
2748 + expect(Scheduler).toFlushAndYieldThrough(['first']);
2749 + expect(onPostCommit).not.toHaveBeenCalled();
2750 +
2751 + expect(onInteractionTraced).toHaveBeenCalledTimes(2);
2752 + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
2753 + interactionOne,
2754 + );
2755 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(
2756 + 1,
2757 + );
2758 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
2759 + expect(
2760 + getWorkForReactThreads(onWorkStarted)[0][0],
2761 + ).toMatchInteractions([interactionOne]);
2762 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2763 +
2764 + expect(Scheduler).toFlushAndYield(['last', 'onPostCommit']);
2765 + expect(onPostCommit).toHaveBeenCalledTimes(1);
2766 +
2767 + call = onPostCommit.mock.calls[0];
2768 + expect(call[0]).toEqual('test-profiler');
2769 + expect(call[3]).toEqual(Scheduler.unstable_now());
2770 + if (ReactFeatureFlags.enableSchedulerTracing) {
2771 + expect(call[4]).toMatchInteractions([interactionOne]);
2772 + }
2773
2739 - expect(Scheduler).toFlushAndYieldThrough(['first']);
2740 - expect(onPostCommit).not.toHaveBeenCalled();
2774 + didRunCallback = true;
2775
2742 - expect(onInteractionTraced).toHaveBeenCalledTimes(2);
2743 - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
2744 - interactionOne,
2745 - );
2746 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
2747 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
2748 - expect(
2749 - getWorkForReactThreads(onWorkStarted)[0][0],
2750 - ).toMatchInteractions([interactionOne]);
2751 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2776 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
2777 + expect(
2778 + getWorkForReactThreads(onWorkStarted)[0][0],
2779 + ).toMatchInteractions([interactionOne]);
2780 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1);
2781 + expect(
2782 + getWorkForReactThreads(onWorkStopped)[0][0],
2783 + ).toMatchInteractions([interactionOne]);
2784 + },
2785 + );
2786
2753 - expect(Scheduler).toFlushAndYield(['last', 'onPostCommit']);
2754 - expect(onPostCommit).toHaveBeenCalledTimes(1);
2787 + expect(didRunCallback).toBe(true);
2788
2756 - call = onPostCommit.mock.calls[0];
2757 - expect(call[0]).toEqual('test-profiler');
2758 - expect(call[3]).toEqual(Scheduler.unstable_now());
2759 - if (ReactFeatureFlags.enableSchedulerTracing) {
2760 - expect(call[4]).toMatchInteractions([interactionOne]);
2761 - }
2789 + onPostCommit.mockClear();
2790 + onWorkScheduled.mockClear();
2791 + onWorkStarted.mockClear();
2792 + onWorkStopped.mockClear();
2793
2763 - didRunCallback = true;
2794 + Scheduler.unstable_advanceTime(17);
2795
2765 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
2766 - expect(
2767 - getWorkForReactThreads(onWorkStarted)[0][0],
2768 - ).toMatchInteractions([interactionOne]);
2769 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1);
2770 - expect(
2771 - getWorkForReactThreads(onWorkStopped)[0][0],
2772 - ).toMatchInteractions([interactionOne]);
2773 - },
2774 - );
2796 + // Verify that updating state again does not re-log our interaction.
2797 + instance.setState({count: 3});
2798 + expect(Scheduler).toFlushAndYield(['first', 'last', 'onPostCommit']);
2799
2776 - expect(didRunCallback).toBe(true);
2800 + expect(onPostCommit).toHaveBeenCalledTimes(1);
2801 + call = onPostCommit.mock.calls[0];
2802 + expect(call[0]).toEqual('test-profiler');
2803 + expect(call[3]).toEqual(Scheduler.unstable_now());
2804 + if (ReactFeatureFlags.enableSchedulerTracing) {
2805 + expect(call[4]).toMatchInteractions([]);
2806 + }
2807
2778 - onPostCommit.mockClear();
2779 - onWorkScheduled.mockClear();
2780 - onWorkStarted.mockClear();
2781 - onWorkStopped.mockClear();
2808 + expect(onInteractionTraced).toHaveBeenCalledTimes(2);
2809 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
2810 + expect(
2811 + onInteractionScheduledWorkCompleted,
2812 + ).toHaveBeenLastNotifiedOfInteraction(interactionOne);
2813 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
2814 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2815
2783 - Scheduler.unstable_advanceTime(17);
2816 + onPostCommit.mockClear();
2817
2785 - // Verify that updating state again does not re-log our interaction.
2786 - instance.setState({count: 3});
2787 - expect(Scheduler).toFlushAndYield(['first', 'last', 'onPostCommit']);
2818 + Scheduler.unstable_advanceTime(3);
2819
2789 - expect(onPostCommit).toHaveBeenCalledTimes(1);
2790 - call = onPostCommit.mock.calls[0];
2791 - expect(call[0]).toEqual('test-profiler');
2792 - expect(call[3]).toEqual(Scheduler.unstable_now());
2793 - if (ReactFeatureFlags.enableSchedulerTracing) {
2794 - expect(call[4]).toMatchInteractions([]);
2795 - }
2820 + // Verify that root updates are also associated with traced events.
2821 + const interactionTwo = {
2822 + id: 2,
2823 + name: 'root update event',
2824 + timestamp: Scheduler.unstable_now(),
2825 + };
2826 + SchedulerTracing.unstable_trace(
2827 + interactionTwo.name,
2828 + Scheduler.unstable_now(),
2829 + () => {
2830 + renderer.update(
2831 + <React.Profiler id="test-profiler" onPostCommit={onPostCommit}>
2832 + <Example />
2833 + </React.Profiler>,
2834 + );
2835 + },
2836 + );
2837
2797 - expect(onInteractionTraced).toHaveBeenCalledTimes(2);
2798 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
2799 - expect(
2800 - onInteractionScheduledWorkCompleted,
2801 - ).toHaveBeenLastNotifiedOfInteraction(interactionOne);
2802 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
2803 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2838 + expect(onInteractionTraced).toHaveBeenCalledTimes(3);
2839 + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
2840 + interactionTwo,
2841 + );
2842 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
2843
2805 - onPostCommit.mockClear();
2844 + // The scheduler/tracing package will notify of work started for the default thread,
2845 + // But React shouldn't notify until it's been flushed.
2846 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
2847 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2848
2807 - Scheduler.unstable_advanceTime(3);
2849 + // Work may have been scheduled multiple times.
2850 + // We only care that the subscriber was notified at least once.
2851 + // As for the thread ID- the actual value isn't important, only that there was one.
2852 + expect(onWorkScheduled).toHaveBeenCalled();
2853 + expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([
2854 + interactionTwo,
2855 + ]);
2856 + expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true);
2857
2809 - // Verify that root updates are also associated with traced events.
2810 - const interactionTwo = {
2811 - id: 2,
2812 - name: 'root update event',
2813 - timestamp: Scheduler.unstable_now(),
2814 - };
2815 - SchedulerTracing.unstable_trace(
2816 - interactionTwo.name,
2817 - Scheduler.unstable_now(),
2818 - () => {
2819 - renderer.update(
2820 - <React.Profiler id="test-profiler" onPostCommit={onPostCommit}>
2821 - <Example />
2822 - </React.Profiler>,
2823 - );
2824 - },
2825 - );
2858 + expect(Scheduler).toFlushAndYield(['first', 'last', 'onPostCommit']);
2859
2827 - expect(onInteractionTraced).toHaveBeenCalledTimes(3);
2828 - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
2829 - interactionTwo,
2830 - );
2831 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
2832 -
2833 - // The scheduler/tracing package will notify of work started for the default thread,
2834 - // But React shouldn't notify until it's been flushed.
2835 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
2836 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2837 -
2838 - // Work may have been scheduled multiple times.
2839 - // We only care that the subscriber was notified at least once.
2840 - // As for the thread ID- the actual value isn't important, only that there was one.
2841 - expect(onWorkScheduled).toHaveBeenCalled();
2842 - expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([
2843 - interactionTwo,
2844 - ]);
2845 - expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true);
2846 -
2847 - expect(Scheduler).toFlushAndYield(['first', 'last', 'onPostCommit']);
2848 -
2849 - expect(onPostCommit).toHaveBeenCalledTimes(1);
2850 - call = onPostCommit.mock.calls[0];
2851 - expect(call[0]).toEqual('test-profiler');
2852 - expect(call[3]).toEqual(Scheduler.unstable_now());
2853 - if (ReactFeatureFlags.enableSchedulerTracing) {
2854 - expect(call[4]).toMatchInteractions([interactionTwo]);
2855 - }
2860 + expect(onPostCommit).toHaveBeenCalledTimes(1);
2861 + call = onPostCommit.mock.calls[0];
2862 + expect(call[0]).toEqual('test-profiler');
2863 + expect(call[3]).toEqual(Scheduler.unstable_now());
2864 + if (ReactFeatureFlags.enableSchedulerTracing) {
2865 + expect(call[4]).toMatchInteractions([interactionTwo]);
2866 + }
2867
2857 - expect(onInteractionTraced).toHaveBeenCalledTimes(3);
2858 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(3);
2859 - expect(
2860 - onInteractionScheduledWorkCompleted,
2861 - ).toHaveBeenLastNotifiedOfInteraction(interactionTwo);
2862 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
2863 - expect(getWorkForReactThreads(onWorkStarted)[0][0]).toMatchInteractions([
2864 - interactionTwo,
2865 - ]);
2866 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1);
2867 - expect(getWorkForReactThreads(onWorkStopped)[0][0]).toMatchInteractions([
2868 - interactionTwo,
2869 - ]);
2868 + expect(onInteractionTraced).toHaveBeenCalledTimes(3);
2869 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(3);
2870 + expect(
2871 + onInteractionScheduledWorkCompleted,
2872 + ).toHaveBeenLastNotifiedOfInteraction(interactionTwo);
2873 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
2874 + expect(
2875 + getWorkForReactThreads(onWorkStarted)[0][0],
2876 + ).toMatchInteractions([interactionTwo]);
2877 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1);
2878 + expect(
2879 + getWorkForReactThreads(onWorkStopped)[0][0],
2880 + ).toMatchInteractions([interactionTwo]);
2881 + });
2882 });
2883
2884 it('should not mark an interaction complete while passive effects are outstanding', () => {
@@ -2921,7 +2933,7 @@ describe('Profiler', () => {
2933 render() {
2934 first = this;
2935 Scheduler.unstable_yieldValue('FirstComponent');
2924 - return null;
2936 + return <ComponentWithPassiveEffect count={this.state.count} />;
2937 }
2938 }
2939 let second;
@@ -2930,165 +2942,176 @@ describe('Profiler', () => {
2942 render() {
2943 second = this;
2944 Scheduler.unstable_yieldValue('SecondComponent');
2933 - return null;
2945 + return <ComponentWithPassiveEffect count={this.state.count} />;
2946 }
2947 }
2948
2949 Scheduler.unstable_advanceTime(5);
2950
2939 - const renderer = ReactTestRenderer.create(
2940 - <React.Profiler id="test" onPostCommit={onPostCommit}>
2941 - <FirstComponent />
2942 - <SecondComponent />
2943 - </React.Profiler>,
2944 - {unstable_isConcurrent: true},
2945 - );
2946 -
2947 - // Initial mount.
2948 - expect(Scheduler).toFlushAndYield([
2949 - 'FirstComponent',
2950 - 'SecondComponent',
2951 - 'onPostCommit',
2952 - ]);
2953 -
2954 - expect(onInteractionTraced).not.toHaveBeenCalled();
2955 - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
2956 -
2957 - onPostCommit.mockClear();
2958 -
2959 - Scheduler.unstable_advanceTime(100);
2960 -
2961 - const interactionLowPri = {
2962 - id: 0,
2963 - name: 'lowPri',
2964 - timestamp: Scheduler.unstable_now(),
2965 - };
2951 + ReactTestRendererAct(() => {
2952 + const renderer = ReactTestRenderer.create(
2953 + <React.Profiler id="test" onPostCommit={onPostCommit}>
2954 + <FirstComponent />
2955 + <SecondComponent />
2956 + </React.Profiler>,
2957 + {unstable_isConcurrent: true},
2958 + );
2959
2967 - SchedulerTracing.unstable_trace(
2968 - interactionLowPri.name,
2969 - Scheduler.unstable_now(),
2970 - () => {
2971 - // Render a partially update, but don't finish.
2972 - first.setState({count: 1});
2960 + // Initial mount.
2961 + expect(Scheduler).toFlushAndYield([
2962 + 'FirstComponent',
2963 + 'SecondComponent',
2964 + 'onPostCommit',
2965 + ]);
2966
2974 - expect(onWorkScheduled).toHaveBeenCalled();
2975 - expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([
2976 - interactionLowPri,
2977 - ]);
2967 + expect(onInteractionTraced).not.toHaveBeenCalled();
2968 + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
2969
2979 - expect(Scheduler).toFlushAndYieldThrough(['FirstComponent']);
2980 - expect(onPostCommit).not.toHaveBeenCalled();
2970 + onPostCommit.mockClear();
2971
2982 - expect(onInteractionTraced).toHaveBeenCalledTimes(1);
2983 - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
2984 - interactionLowPri,
2985 - );
2986 - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
2987 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
2988 - expect(
2989 - getWorkForReactThreads(onWorkStarted)[0][0],
2990 - ).toMatchInteractions([interactionLowPri]);
2991 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2972 + Scheduler.unstable_advanceTime(100);
2973
2993 - Scheduler.unstable_advanceTime(100);
2974 + const interactionLowPri = {
2975 + id: 0,
2976 + name: 'lowPri',
2977 + timestamp: Scheduler.unstable_now(),
2978 + };
2979
2995 - const interactionHighPri = {
2996 - id: 1,
2997 - name: 'highPri',
2998 - timestamp: Scheduler.unstable_now(),
2999 - };
2980 + SchedulerTracing.unstable_trace(
2981 + interactionLowPri.name,
2982 + Scheduler.unstable_now(),
2983 + () => {
2984 + // Render a partially update, but don't finish.
2985 + first.setState({count: 1});
2986
3001 - // Interrupt with higher priority work.
3002 - // This simulates a total of 37ms of actual render time.
3003 - renderer.unstable_flushSync(() => {
3004 - SchedulerTracing.unstable_trace(
3005 - interactionHighPri.name,
3006 - Scheduler.unstable_now(),
3007 - () => {
3008 - second.setState({count: 1});
2987 + expect(onWorkScheduled).toHaveBeenCalled();
2988 + expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([
2989 + interactionLowPri,
2990 + ]);
2991
3010 - expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3011 - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
3012 - interactionHighPri,
3013 - );
3014 - expect(
3015 - onInteractionScheduledWorkCompleted,
3016 - ).not.toHaveBeenCalled();
2992 + expect(Scheduler).toFlushAndYieldThrough(['FirstComponent']);
2993 + expect(onPostCommit).not.toHaveBeenCalled();
2994
3018 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
3019 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
3020 - },
2995 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
2996 + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
2997 + interactionLowPri,
2998 );
3022 - });
2999 + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3000 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
3001 + expect(
3002 + getWorkForReactThreads(onWorkStarted)[0][0],
3003 + ).toMatchInteractions([interactionLowPri]);
3004 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
3005
3024 - // Profiler tag causes passive effects to be scheduled,
3025 - // so the interactions are still not completed.
3026 - expect(Scheduler).toHaveYielded(['SecondComponent']);
3027 - expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3028 - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3029 - expect(Scheduler).toFlushAndYieldThrough(['onPostCommit']);
3006 + Scheduler.unstable_advanceTime(100);
3007
3031 - expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3032 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3033 - expect(
3034 - onInteractionScheduledWorkCompleted,
3035 - ).toHaveBeenLastNotifiedOfInteraction(interactionHighPri);
3008 + const interactionHighPri = {
3009 + id: 1,
3010 + name: 'highPri',
3011 + timestamp: Scheduler.unstable_now(),
3012 + };
3013
3037 - // Verify the high priority update was associated with the high priority event.
3038 - expect(onPostCommit).toHaveBeenCalledTimes(1);
3039 - let call = onPostCommit.mock.calls[0];
3040 - expect(call[0]).toEqual('test');
3041 - expect(call[3]).toEqual(Scheduler.unstable_now());
3042 - expect(call[4]).toMatchInteractions(
3043 - ReactFeatureFlags.enableSchedulerTracing
3044 - ? [interactionLowPri, interactionHighPri]
3045 - : [],
3046 - );
3014 + // Interrupt with higher priority work.
3015 + // This simulates a total of 37ms of actual render time.
3016 + renderer.unstable_flushSync(() => {
3017 + SchedulerTracing.unstable_trace(
3018 + interactionHighPri.name,
3019 + Scheduler.unstable_now(),
3020 + () => {
3021 + second.setState({count: 1});
3022 +
3023 + expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3024 + expect(
3025 + onInteractionTraced,
3026 + ).toHaveBeenLastNotifiedOfInteraction(interactionHighPri);
3027 + expect(
3028 + onInteractionScheduledWorkCompleted,
3029 + ).not.toHaveBeenCalled();
3030 +
3031 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
3032 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
3033 + },
3034 + );
3035 + });
3036
3048 - onPostCommit.mockClear();
3037 + // Profiler tag causes passive effects to be scheduled,
3038 + // so the interactions are still not completed.
3039 + expect(Scheduler).toHaveYielded(['SecondComponent']);
3040 + expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3041 + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3042 + expect(Scheduler).toFlushAndYieldThrough(['onPostCommit']);
3043
3050 - Scheduler.unstable_advanceTime(100);
3044 + expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3045 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(
3046 + 1,
3047 + );
3048 + expect(
3049 + onInteractionScheduledWorkCompleted,
3050 + ).toHaveBeenLastNotifiedOfInteraction(interactionHighPri);
3051 +
3052 + // Verify the high priority update was associated with the high priority event.
3053 + expect(onPostCommit).toHaveBeenCalledTimes(1);
3054 + let call = onPostCommit.mock.calls[0];
3055 + expect(call[0]).toEqual('test');
3056 + expect(call[3]).toEqual(Scheduler.unstable_now());
3057 + expect(call[4]).toMatchInteractions(
3058 + ReactFeatureFlags.enableSchedulerTracing
3059 + ? [interactionLowPri, interactionHighPri]
3060 + : [],
3061 + );
3062
3052 - // Resume the original low priority update, with rebased state.
3053 - // Verify the low priority update was retained.
3054 - expect(Scheduler).toFlushAndYield(['FirstComponent', 'onPostCommit']);
3055 - expect(onPostCommit).toHaveBeenCalledTimes(1);
3056 - call = onPostCommit.mock.calls[0];
3057 - expect(call[0]).toEqual('test');
3058 - expect(call[3]).toEqual(Scheduler.unstable_now());
3059 - expect(call[4]).toMatchInteractions(
3060 - ReactFeatureFlags.enableSchedulerTracing ? [interactionLowPri] : [],
3061 - );
3063 + onPostCommit.mockClear();
3064 +
3065 + Scheduler.unstable_advanceTime(100);
3066 +
3067 + // Resume the original low priority update, with rebased state.
3068 + // Verify the low priority update was retained.
3069 + expect(Scheduler).toFlushAndYield([
3070 + 'FirstComponent',
3071 + 'onPostCommit',
3072 + ]);
3073 + expect(onPostCommit).toHaveBeenCalledTimes(1);
3074 + call = onPostCommit.mock.calls[0];
3075 + expect(call[0]).toEqual('test');
3076 + expect(call[3]).toEqual(Scheduler.unstable_now());
3077 + expect(call[4]).toMatchInteractions(
3078 + ReactFeatureFlags.enableSchedulerTracing
3079 + ? [interactionLowPri]
3080 + : [],
3081 + );
3082
3063 - expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3064 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3083 + expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3084 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(
3085 + 1,
3086 + );
3087
3066 - // Work might be started multiple times before being completed.
3067 - // This is okay; it's part of the scheduler/tracing contract.
3068 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(3);
3069 - expect(
3070 - getWorkForReactThreads(onWorkStarted)[1][0],
3071 - ).toMatchInteractions([interactionLowPri, interactionHighPri]);
3072 - expect(
3073 - getWorkForReactThreads(onWorkStarted)[2][0],
3074 - ).toMatchInteractions([interactionLowPri]);
3075 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2);
3076 - expect(
3077 - getWorkForReactThreads(onWorkStopped)[0][0],
3078 - ).toMatchInteractions([interactionLowPri, interactionHighPri]);
3079 - expect(
3080 - getWorkForReactThreads(onWorkStopped)[1][0],
3081 - ).toMatchInteractions([interactionLowPri]);
3082 - },
3083 - );
3088 + // Work might be started multiple times before being completed.
3089 + // This is okay; it's part of the scheduler/tracing contract.
3090 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(3);
3091 + expect(
3092 + getWorkForReactThreads(onWorkStarted)[1][0],
3093 + ).toMatchInteractions([interactionLowPri, interactionHighPri]);
3094 + expect(
3095 + getWorkForReactThreads(onWorkStarted)[2][0],
3096 + ).toMatchInteractions([interactionLowPri]);
3097 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2);
3098 + expect(
3099 + getWorkForReactThreads(onWorkStopped)[0][0],
3100 + ).toMatchInteractions([interactionLowPri, interactionHighPri]);
3101 + expect(
3102 + getWorkForReactThreads(onWorkStopped)[1][0],
3103 + ).toMatchInteractions([interactionLowPri]);
3104 + },
3105 + );
3106
3085 - expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3086 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
3087 - expect(
3088 - onInteractionScheduledWorkCompleted,
3089 - ).toHaveBeenLastNotifiedOfInteraction(interactionLowPri);
3090 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(3);
3091 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2);
3107 + expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3108 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
3109 + expect(
3110 + onInteractionScheduledWorkCompleted,
3111 + ).toHaveBeenLastNotifiedOfInteraction(interactionLowPri);
3112 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(3);
3113 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2);
3114 + });
3115 });
3116
3117 it('should trace work spawned by a commit phase lifecycle and setState callback', () => {
@@ -3110,7 +3133,7 @@ describe('Profiler', () => {
3133 render() {
3134 instance = this;
3135 Scheduler.unstable_yieldValue('Example:' + this.state.count);
3113 - return null;
3136 + return <ComponentWithPassiveEffect count={this.state.count} />;
3137 }
3138 }
3139
@@ -3124,214 +3147,217 @@ describe('Profiler', () => {
3147 const onPostCommit = jest.fn(() => {
3148 Scheduler.unstable_yieldValue('onPostCommit');
3149 });
3127 - let firstCommitTime = Scheduler.unstable_now();
3128 - SchedulerTracing.unstable_trace(
3129 - interactionOne.name,
3130 - Scheduler.unstable_now(),
3131 - () => {
3132 - ReactTestRenderer.create(
3133 - <React.Profiler id="test" onPostCommit={onPostCommit}>
3134 - <Example />
3135 - </React.Profiler>,
3136 - {unstable_isConcurrent: true},
3137 - );
3138 - },
3139 - );
3150
3141 - expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3142 - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
3143 - interactionOne,
3144 - );
3145 - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3146 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
3147 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
3151 + ReactTestRendererAct(() => {
3152 + let firstCommitTime = Scheduler.unstable_now();
3153 + SchedulerTracing.unstable_trace(
3154 + interactionOne.name,
3155 + Scheduler.unstable_now(),
3156 + () => {
3157 + ReactTestRenderer.create(
3158 + <React.Profiler id="test" onPostCommit={onPostCommit}>
3159 + <Example />
3160 + </React.Profiler>,
3161 + {unstable_isConcurrent: true},
3162 + );
3163 + },
3164 + );
3165
3149 - expect(Scheduler).toFlushAndYield([
3150 - 'Example:0',
3151 - 'onPostCommit',
3152 - 'Example:1',
3153 - 'onPostCommit',
3154 - ]);
3166 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3167 + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
3168 + interactionOne,
3169 + );
3170 + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3171 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
3172 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
3173
3156 - expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3157 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3158 - expect(
3159 - onInteractionScheduledWorkCompleted,
3160 - ).toHaveBeenLastNotifiedOfInteraction(interactionOne);
3161 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(2);
3162 - expect(getWorkForReactThreads(onWorkStarted)[0][0]).toMatchInteractions([
3163 - interactionOne,
3164 - ]);
3165 - expect(getWorkForReactThreads(onWorkStarted)[1][0]).toMatchInteractions([
3166 - interactionOne,
3167 - ]);
3168 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2);
3169 - expect(getWorkForReactThreads(onWorkStopped)[0][0]).toMatchInteractions([
3170 - interactionOne,
3171 - ]);
3172 - expect(getWorkForReactThreads(onWorkStopped)[1][0]).toMatchInteractions([
3173 - interactionOne,
3174 - ]);
3175 -
3176 - expect(onPostCommit).toHaveBeenCalledTimes(2);
3177 - let call = onPostCommit.mock.calls[0];
3178 - expect(call[0]).toEqual('test');
3179 - expect(call[3]).toEqual(firstCommitTime);
3180 - expect(call[4]).toMatchInteractions(
3181 - ReactFeatureFlags.enableSchedulerTracing ? [interactionOne] : [],
3182 - );
3183 - call = onPostCommit.mock.calls[1];
3184 - expect(call[0]).toEqual('test');
3185 - expect(call[3]).toEqual(Scheduler.unstable_now());
3186 - expect(call[4]).toMatchInteractions(
3187 - ReactFeatureFlags.enableSchedulerTracing ? [interactionOne] : [],
3188 - );
3174 + expect(Scheduler).toFlushAndYield([
3175 + 'Example:0',
3176 + 'onPostCommit',
3177 + 'Example:1',
3178 + 'onPostCommit',
3179 + ]);
3180
3190 - onPostCommit.mockClear();
3181 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3182 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3183 + expect(
3184 + onInteractionScheduledWorkCompleted,
3185 + ).toHaveBeenLastNotifiedOfInteraction(interactionOne);
3186 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(2);
3187 + expect(
3188 + getWorkForReactThreads(onWorkStarted)[0][0],
3189 + ).toMatchInteractions([interactionOne]);
3190 + expect(
3191 + getWorkForReactThreads(onWorkStarted)[1][0],
3192 + ).toMatchInteractions([interactionOne]);
3193 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2);
3194 + expect(
3195 + getWorkForReactThreads(onWorkStopped)[0][0],
3196 + ).toMatchInteractions([interactionOne]);
3197 + expect(
3198 + getWorkForReactThreads(onWorkStopped)[1][0],
3199 + ).toMatchInteractions([interactionOne]);
3200
3192 - const interactionTwo = {
3193 - id: 1,
3194 - name: 'componentDidUpdate test',
3195 - timestamp: Scheduler.unstable_now(),
3196 - };
3201 + expect(onPostCommit).toHaveBeenCalledTimes(2);
3202 + let call = onPostCommit.mock.calls[0];
3203 + expect(call[0]).toEqual('test');
3204 + expect(call[3]).toEqual(firstCommitTime);
3205 + expect(call[4]).toMatchInteractions(
3206 + ReactFeatureFlags.enableSchedulerTracing ? [interactionOne] : [],
3207 + );
3208 + call = onPostCommit.mock.calls[1];
3209 + expect(call[0]).toEqual('test');
3210 + expect(call[3]).toEqual(Scheduler.unstable_now());
3211 + expect(call[4]).toMatchInteractions(
3212 + ReactFeatureFlags.enableSchedulerTracing ? [interactionOne] : [],
3213 + );
3214
3198 - // Cause an traced, async update
3199 - SchedulerTracing.unstable_trace(
3200 - interactionTwo.name,
3201 - Scheduler.unstable_now(),
3202 - () => {
3203 - instance.setState({count: 2});
3204 - },
3205 - );
3206 - expect(onPostCommit).not.toHaveBeenCalled();
3207 - expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3208 - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
3209 - interactionTwo,
3210 - );
3211 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3212 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(2);
3213 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2);
3215 + onPostCommit.mockClear();
3216
3215 - Scheduler.unstable_advanceTime(5);
3217 + const interactionTwo = {
3218 + id: 1,
3219 + name: 'componentDidUpdate test',
3220 + timestamp: Scheduler.unstable_now(),
3221 + };
3222
3217 - // Flush async work (outside of traced scope)
3218 - // This will cause an intentional cascading update from did-update
3219 - firstCommitTime = Scheduler.unstable_now();
3220 - expect(Scheduler).toFlushAndYield([
3221 - 'Example:2',
3222 - 'onPostCommit',
3223 - 'Example:3',
3224 - 'onPostCommit',
3225 - ]);
3226 -
3227 - expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3228 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
3229 - expect(
3230 - onInteractionScheduledWorkCompleted,
3231 - ).toHaveBeenLastNotifiedOfInteraction(interactionTwo);
3232 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(4);
3233 - expect(getWorkForReactThreads(onWorkStarted)[2][0]).toMatchInteractions([
3234 - interactionTwo,
3235 - ]);
3236 - expect(getWorkForReactThreads(onWorkStarted)[3][0]).toMatchInteractions([
3237 - interactionTwo,
3238 - ]);
3239 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(4);
3240 - expect(getWorkForReactThreads(onWorkStopped)[2][0]).toMatchInteractions([
3241 - interactionTwo,
3242 - ]);
3243 - expect(getWorkForReactThreads(onWorkStopped)[3][0]).toMatchInteractions([
3244 - interactionTwo,
3245 - ]);
3246 -
3247 - // Verify the cascading commit is associated with the origin event
3248 - expect(onPostCommit).toHaveBeenCalledTimes(2);
3249 - call = onPostCommit.mock.calls[0];
3250 - expect(call[0]).toEqual('test');
3251 - expect(call[3]).toEqual(firstCommitTime);
3252 - expect(call[4]).toMatchInteractions(
3253 - ReactFeatureFlags.enableSchedulerTracing ? [interactionTwo] : [],
3254 - );
3255 - call = onPostCommit.mock.calls[1];
3256 - expect(call[0]).toEqual('test');
3257 - expect(call[3]).toEqual(Scheduler.unstable_now());
3258 - expect(call[4]).toMatchInteractions(
3259 - ReactFeatureFlags.enableSchedulerTracing ? [interactionTwo] : [],
3260 - );
3223 + // Cause an traced, async update
3224 + SchedulerTracing.unstable_trace(
3225 + interactionTwo.name,
3226 + Scheduler.unstable_now(),
3227 + () => {
3228 + instance.setState({count: 2});
3229 + },
3230 + );
3231 + expect(onPostCommit).not.toHaveBeenCalled();
3232 + expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3233 + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
3234 + interactionTwo,
3235 + );
3236 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3237 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(2);
3238 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2);
3239
3262 - onPostCommit.mockClear();
3240 + Scheduler.unstable_advanceTime(5);
3241
3264 - const interactionThree = {
3265 - id: 2,
3266 - name: 'setState callback test',
3267 - timestamp: Scheduler.unstable_now(),
3268 - };
3242 + // Flush async work (outside of traced scope)
3243 + // This will cause an intentional cascading update from did-update
3244 + firstCommitTime = Scheduler.unstable_now();
3245 + expect(Scheduler).toFlushAndYield([
3246 + 'Example:2',
3247 + 'onPostCommit',
3248 + 'Example:3',
3249 + 'onPostCommit',
3250 + ]);
3251
3270 - // Cause a cascading update from the setState callback
3271 - function callback() {
3272 - instance.setState({count: 6});
3273 - }
3274 - SchedulerTracing.unstable_trace(
3275 - interactionThree.name,
3276 - Scheduler.unstable_now(),
3277 - () => {
3278 - instance.setState({count: 5}, callback);
3279 - },
3280 - );
3281 - expect(onPostCommit).not.toHaveBeenCalled();
3252 + expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3253 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
3254 + expect(
3255 + onInteractionScheduledWorkCompleted,
3256 + ).toHaveBeenLastNotifiedOfInteraction(interactionTwo);
3257 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(4);
3258 + expect(
3259 + getWorkForReactThreads(onWorkStarted)[2][0],
3260 + ).toMatchInteractions([interactionTwo]);
3261 + expect(
3262 + getWorkForReactThreads(onWorkStarted)[3][0],
3263 + ).toMatchInteractions([interactionTwo]);
3264 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(4);
3265 + expect(
3266 + getWorkForReactThreads(onWorkStopped)[2][0],
3267 + ).toMatchInteractions([interactionTwo]);
3268 + expect(
3269 + getWorkForReactThreads(onWorkStopped)[3][0],
3270 + ).toMatchInteractions([interactionTwo]);
3271
3283 - expect(onInteractionTraced).toHaveBeenCalledTimes(3);
3284 - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
3285 - interactionThree,
3286 - );
3287 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
3288 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(4);
3289 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(4);
3290 -
3291 - // Flush async work (outside of traced scope)
3292 - // This will cause an intentional cascading update from the setState callback
3293 - firstCommitTime = Scheduler.unstable_now();
3294 - expect(Scheduler).toFlushAndYield([
3295 - 'Example:5',
3296 - 'onPostCommit',
3297 - 'Example:6',
3298 - 'onPostCommit',
3299 - ]);
3300 -
3301 - expect(onInteractionTraced).toHaveBeenCalledTimes(3);
3302 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(3);
3303 - expect(
3304 - onInteractionScheduledWorkCompleted,
3305 - ).toHaveBeenLastNotifiedOfInteraction(interactionThree);
3306 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(6);
3307 - expect(getWorkForReactThreads(onWorkStarted)[4][0]).toMatchInteractions([
3308 - interactionThree,
3309 - ]);
3310 - expect(getWorkForReactThreads(onWorkStarted)[5][0]).toMatchInteractions([
3311 - interactionThree,
3312 - ]);
3313 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(6);
3314 - expect(getWorkForReactThreads(onWorkStopped)[4][0]).toMatchInteractions([
3315 - interactionThree,
3316 - ]);
3317 - expect(getWorkForReactThreads(onWorkStopped)[5][0]).toMatchInteractions([
3318 - interactionThree,
3319 - ]);
3320 -
3321 - // Verify the cascading commit is associated with the origin event
3322 - expect(onPostCommit).toHaveBeenCalledTimes(2);
3323 - call = onPostCommit.mock.calls[0];
3324 - expect(call[0]).toEqual('test');
3325 - expect(call[3]).toEqual(firstCommitTime);
3326 - expect(call[4]).toMatchInteractions(
3327 - ReactFeatureFlags.enableSchedulerTracing ? [interactionThree] : [],
3328 - );
3329 - call = onPostCommit.mock.calls[1];
3330 - expect(call[0]).toEqual('test');
3331 - expect(call[3]).toEqual(Scheduler.unstable_now());
3332 - expect(call[4]).toMatchInteractions(
3333 - ReactFeatureFlags.enableSchedulerTracing ? [interactionThree] : [],
3334 - );
3272 + // Verify the cascading commit is associated with the origin event
3273 + expect(onPostCommit).toHaveBeenCalledTimes(2);
3274 + call = onPostCommit.mock.calls[0];
3275 + expect(call[0]).toEqual('test');
3276 + expect(call[3]).toEqual(firstCommitTime);
3277 + expect(call[4]).toMatchInteractions(
3278 + ReactFeatureFlags.enableSchedulerTracing ? [interactionTwo] : [],
3279 + );
3280 + call = onPostCommit.mock.calls[1];
3281 + expect(call[0]).toEqual('test');
3282 + expect(call[3]).toEqual(Scheduler.unstable_now());
3283 + expect(call[4]).toMatchInteractions(
3284 + ReactFeatureFlags.enableSchedulerTracing ? [interactionTwo] : [],
3285 + );
3286 +
3287 + onPostCommit.mockClear();
3288 +
3289 + const interactionThree = {
3290 + id: 2,
3291 + name: 'setState callback test',
3292 + timestamp: Scheduler.unstable_now(),
3293 + };
3294 +
3295 + // Cause a cascading update from the setState callback
3296 + function callback() {
3297 + instance.setState({count: 6});
3298 + }
3299 + SchedulerTracing.unstable_trace(
3300 + interactionThree.name,
3301 + Scheduler.unstable_now(),
3302 + () => {
3303 + instance.setState({count: 5}, callback);
3304 + },
3305 + );
3306 + expect(onPostCommit).not.toHaveBeenCalled();
3307 +
3308 + expect(onInteractionTraced).toHaveBeenCalledTimes(3);
3309 + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
3310 + interactionThree,
3311 + );
3312 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
3313 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(4);
3314 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(4);
3315 +
3316 + // Flush async work (outside of traced scope)
3317 + // This will cause an intentional cascading update from the setState callback
3318 + firstCommitTime = Scheduler.unstable_now();
3319 + expect(Scheduler).toFlushAndYield([
3320 + 'Example:5',
3321 + 'onPostCommit',
3322 + 'Example:6',
3323 + 'onPostCommit',
3324 + ]);
3325 +
3326 + expect(onInteractionTraced).toHaveBeenCalledTimes(3);
3327 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(3);
3328 + expect(
3329 + onInteractionScheduledWorkCompleted,
3330 + ).toHaveBeenLastNotifiedOfInteraction(interactionThree);
3331 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(6);
3332 + expect(
3333 + getWorkForReactThreads(onWorkStarted)[4][0],
3334 + ).toMatchInteractions([interactionThree]);
3335 + expect(
3336 + getWorkForReactThreads(onWorkStarted)[5][0],
3337 + ).toMatchInteractions([interactionThree]);
3338 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(6);
3339 + expect(
3340 + getWorkForReactThreads(onWorkStopped)[4][0],
3341 + ).toMatchInteractions([interactionThree]);
3342 + expect(
3343 + getWorkForReactThreads(onWorkStopped)[5][0],
3344 + ).toMatchInteractions([interactionThree]);
3345 +
3346 + // Verify the cascading commit is associated with the origin event
3347 + expect(onPostCommit).toHaveBeenCalledTimes(2);
3348 + call = onPostCommit.mock.calls[0];
3349 + expect(call[0]).toEqual('test');
3350 + expect(call[3]).toEqual(firstCommitTime);
3351 + expect(call[4]).toMatchInteractions(
3352 + ReactFeatureFlags.enableSchedulerTracing ? [interactionThree] : [],
3353 + );
3354 + call = onPostCommit.mock.calls[1];
3355 + expect(call[0]).toEqual('test');
3356 + expect(call[3]).toEqual(Scheduler.unstable_now());
3357 + expect(call[4]).toMatchInteractions(
3358 + ReactFeatureFlags.enableSchedulerTracing ? [interactionThree] : [],
3359 + );
3360 + });
3361 });
3362
3363 it('should trace interactions associated with a parent component state update', () => {
@@ -3343,7 +3369,7 @@ describe('Profiler', () => {
3369 class Child extends React.Component {
3370 render() {
3371 Scheduler.unstable_yieldValue('Child:' + this.props.count);
3346 - return null;
3372 + return <ComponentWithPassiveEffect count={this.props.count} />;
3373 }
3374 }
3375
@@ -3363,56 +3389,58 @@ describe('Profiler', () => {
3389
3390 Scheduler.unstable_advanceTime(1);
3391
3366 - ReactTestRenderer.create(<Parent />, {
3367 - unstable_isConcurrent: true,
3368 - });
3369 - expect(Scheduler).toFlushAndYield(['Child:0', 'onPostCommit']);
3370 - onPostCommit.mockClear();
3392 + ReactTestRendererAct(() => {
3393 + ReactTestRenderer.create(<Parent />, {
3394 + unstable_isConcurrent: true,
3395 + });
3396 + expect(Scheduler).toFlushAndYield(['Child:0', 'onPostCommit']);
3397 + onPostCommit.mockClear();
3398
3372 - const interaction = {
3373 - id: 0,
3374 - name: 'parent interaction',
3375 - timestamp: Scheduler.unstable_now(),
3376 - };
3399 + const interaction = {
3400 + id: 0,
3401 + name: 'parent interaction',
3402 + timestamp: Scheduler.unstable_now(),
3403 + };
3404
3378 - SchedulerTracing.unstable_trace(
3379 - interaction.name,
3380 - Scheduler.unstable_now(),
3381 - () => {
3382 - parentInstance.setState({count: 1});
3383 - },
3384 - );
3405 + SchedulerTracing.unstable_trace(
3406 + interaction.name,
3407 + Scheduler.unstable_now(),
3408 + () => {
3409 + parentInstance.setState({count: 1});
3410 + },
3411 + );
3412
3386 - expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3387 - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
3388 - interaction,
3389 - );
3390 - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3391 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
3392 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
3413 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3414 + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
3415 + interaction,
3416 + );
3417 + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3418 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
3419 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
3420
3394 - expect(onPostCommit).not.toHaveBeenCalled();
3395 - expect(Scheduler).toFlushAndYield(['Child:1', 'onPostCommit']);
3396 - expect(onPostCommit).toHaveBeenCalledTimes(1);
3397 - const call = onPostCommit.mock.calls[0];
3398 - expect(call[0]).toEqual('test-profiler');
3399 - expect(call[4]).toMatchInteractions(
3400 - ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [],
3401 - );
3421 + expect(onPostCommit).not.toHaveBeenCalled();
3422 + expect(Scheduler).toFlushAndYield(['Child:1', 'onPostCommit']);
3423 + expect(onPostCommit).toHaveBeenCalledTimes(1);
3424 + const call = onPostCommit.mock.calls[0];
3425 + expect(call[0]).toEqual('test-profiler');
3426 + expect(call[4]).toMatchInteractions(
3427 + ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [],
3428 + );
3429
3403 - expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3404 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3405 - expect(
3406 - onInteractionScheduledWorkCompleted,
3407 - ).toHaveBeenLastNotifiedOfInteraction(interaction);
3408 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
3409 - expect(getWorkForReactThreads(onWorkStarted)[0][0]).toMatchInteractions([
3410 - interaction,
3411 - ]);
3412 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1);
3413 - expect(getWorkForReactThreads(onWorkStopped)[0][0]).toMatchInteractions([
3414 - interaction,
3415 - ]);
3430 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3431 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3432 + expect(
3433 + onInteractionScheduledWorkCompleted,
3434 + ).toHaveBeenLastNotifiedOfInteraction(interaction);
3435 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1);
3436 + expect(
3437 + getWorkForReactThreads(onWorkStarted)[0][0],
3438 + ).toMatchInteractions([interaction]);
3439 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1);
3440 + expect(
3441 + getWorkForReactThreads(onWorkStopped)[0][0],
3442 + ).toMatchInteractions([interaction]);
3443 + });
3444 });
3445
3446 describe('suspense', () => {
@@ -3443,76 +3471,79 @@ describe('Profiler', () => {
3471 const onPostCommit = jest.fn(() => {
3472 Scheduler.unstable_yieldValue('onPostCommit');
3473 });
3446 - SchedulerTracing.unstable_trace(
3447 - interaction.name,
3448 - Scheduler.unstable_now(),
3449 - () => {
3450 - ReactNoop.render(
3451 - <React.Profiler id="test-profiler" onPostCommit={onPostCommit}>
3452 - <React.Suspense fallback={<Text text="Loading..." />}>
3453 - <AsyncText text="Async" ms={20000} />
3454 - </React.Suspense>
3455 - <Text text="Sync" />
3456 - <Monkey ref={monkey} />
3457 - </React.Profiler>,
3458 - );
3459 - },
3460 - );
3474 + ReactNoop.act(async () => {
3475 + SchedulerTracing.unstable_trace(
3476 + interaction.name,
3477 + Scheduler.unstable_now(),
3478 + () => {
3479 + ReactNoop.render(
3480 + <React.Profiler id="test-profiler" onPostCommit={onPostCommit}>
3481 + <React.Suspense fallback={<Text text="Loading..." />}>
3482 + <AsyncText text="Async" ms={20000} />
3483 + </React.Suspense>
3484 + <Text text="Sync" />
3485 + <Monkey ref={monkey} />
3486 + <ComponentWithPassiveEffect />
3487 + </React.Profiler>,
3488 + );
3489 + },
3490 + );
3491
3462 - expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3463 - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
3464 - interaction,
3465 - );
3466 - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3467 - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
3468 - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
3492 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3493 + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
3494 + interaction,
3495 + );
3496 + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3497 + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
3498 + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
3499
3470 - expect(Scheduler).toFlushAndYield([
3471 - 'Suspend [Async]',
3472 - 'Text [Loading...]',
3473 - 'Text [Sync]',
3474 - 'Monkey',
3475 - 'onPostCommit',
3476 - ]);
3477 - // Should have committed the placeholder.
3478 - expect(ReactNoop.getChildrenAsJSX()).toEqual('Loading...Sync');
3479 - expect(onPostCommit).toHaveBeenCalledTimes(1);
3500 + expect(Scheduler).toFlushAndYield([
3501 + 'Suspend [Async]',
3502 + 'Text [Loading...]',
3503 + 'Text [Sync]',
3504 + 'Monkey',
3505 + 'onPostCommit',
3506 + ]);
3507 + // Should have committed the placeholder.
3508 + expect(ReactNoop.getChildrenAsJSX()).toEqual('Loading...Sync');
3509 + expect(onPostCommit).toHaveBeenCalledTimes(1);
3510
3481 - let call = onPostCommit.mock.calls[0];
3482 - expect(call[0]).toEqual('test-profiler');
3483 - expect(call[4]).toMatchInteractions(
3484 - ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [],
3485 - );
3511 + let call = onPostCommit.mock.calls[0];
3512 + expect(call[0]).toEqual('test-profiler');
3513 + expect(call[4]).toMatchInteractions(
3514 + ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [],
3515 + );
3516
3487 - expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3488 - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3517 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3518 + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3519
3490 - // An unrelated update in the middle shouldn't affect things...
3491 - monkey.current.forceUpdate();
3492 - expect(Scheduler).toFlushAndYield(['Monkey', 'onPostCommit']);
3493 - expect(onPostCommit).toHaveBeenCalledTimes(2);
3520 + // An unrelated update in the middle shouldn't affect things...
3521 + monkey.current.forceUpdate();
3522 + expect(Scheduler).toFlushAndYield(['Monkey', 'onPostCommit']);
3523 + expect(onPostCommit).toHaveBeenCalledTimes(2);
3524
3495 - // Once the promise resolves, we render the suspended view
3496 - await awaitableAdvanceTimers(20000);
3497 - expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
3498 - expect(Scheduler).toFlushAndYield([
3499 - 'AsyncText [Async]',
3500 - 'onPostCommit',
3501 - ]);
3502 - expect(ReactNoop.getChildrenAsJSX()).toEqual('AsyncSync');
3503 - expect(onPostCommit).toHaveBeenCalledTimes(3);
3525 + // Once the promise resolves, we render the suspended view
3526 + await awaitableAdvanceTimers(20000);
3527 + expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
3528 + expect(Scheduler).toFlushAndYield([
3529 + 'AsyncText [Async]',
3530 + 'onPostCommit',
3531 + ]);
3532 + expect(ReactNoop.getChildrenAsJSX()).toEqual('AsyncSync');
3533 + expect(onPostCommit).toHaveBeenCalledTimes(3);
3534
3505 - call = onPostCommit.mock.calls[2];
3506 - expect(call[0]).toEqual('test-profiler');
3507 - expect(call[4]).toMatchInteractions(
3508 - ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [],
3509 - );
3535 + call = onPostCommit.mock.calls[2];
3536 + expect(call[0]).toEqual('test-profiler');
3537 + expect(call[4]).toMatchInteractions(
3538 + ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [],
3539 + );
3540
3511 - expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3512 - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3513 - expect(
3514 - onInteractionScheduledWorkCompleted,
3515 - ).toHaveBeenLastNotifiedOfInteraction(interaction);
3541 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3542 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3543 + expect(
3544 + onInteractionScheduledWorkCompleted,
3545 + ).toHaveBeenLastNotifiedOfInteraction(interaction);
3546 + });
3547 });
3548
3549 it('does not prematurely complete for suspended sync renders', async () => {
@@ -3533,6 +3564,7 @@ describe('Profiler', () => {
3564 <React.Profiler id="app" onPostCommit={onPostCommit}>
3565 <React.Suspense fallback={<Text text="loading" />}>
3566 <AsyncText text="loaded" ms={500} />
3567 + <ComponentWithPassiveEffect />
3568 </React.Suspense>
3569 </React.Profiler>,
3570 );
@@ -3594,6 +3626,7 @@ describe('Profiler', () => {
3626 () => {
3627 ReactTestRenderer.create(
3628 <React.Profiler id="app" onPostCommit={onPostCommit}>
3629 + <ComponentWithPassiveEffect />
3630 <React.Suspense fallback={<Text text="loading" />}>
3631 <AsyncComponentWithCascadingWork text="loaded" ms={500} />
3632 </React.Suspense>
@@ -3616,7 +3649,16 @@ describe('Profiler', () => {
3649
3650 wrappedCascadingFn();
3651 expect(Scheduler).toHaveYielded(['onPostCommit', 'render']);
3619 - expect(Scheduler).toFlushAndYield(['onPostCommit']);
3652 +
3653 + // The new reconciler does not call onPostCommit again
3654 + // because the resolved suspended subtree doesn't contain any passive effects.
3655 + // If <AsyncComponentWithCascadingWork> or its decendents had a passive effect,
3656 + // onPostCommit would be called again.
3657 + if (gate(flags => flags.new)) {
3658 + expect(Scheduler).toFlushAndYield([]);
3659 + } else {
3660 + expect(Scheduler).toFlushAndYield(['onPostCommit']);
3661 + }
3662
3663 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3664 expect(
@@ -3631,15 +3673,15 @@ describe('Profiler', () => {
3673 timestamp: Scheduler.unstable_now(),
3674 };
3675
3634 - const onPostCommit = jest.fn(() => {
3635 - Scheduler.unstable_yieldValue('onPostCommit');
3676 + const onCommit = jest.fn(() => {
3677 + Scheduler.unstable_yieldValue('onCommit');
3678 });
3679 SchedulerTracing.unstable_trace(
3680 interaction.name,
3681 interaction.timestamp,
3682 () => {
3683 ReactTestRenderer.create(
3642 - <React.Profiler id="app" onPostCommit={onPostCommit}>
3684 + <React.Profiler id="app" onCommit={onCommit}>
3685 <React.Suspense fallback={<Text text="loading" />}>
3686 <AsyncText text="loaded" ms={500} />
3687 </React.Suspense>
@@ -3657,7 +3699,7 @@ describe('Profiler', () => {
3699 expect(Scheduler).toFlushAndYield([
3700 'Suspend [loaded]',
3701 'Text [loading]',
3660 - 'onPostCommit',
3702 + 'onCommit',
3703 ]);
3704 expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3705
@@ -3665,10 +3707,7 @@ describe('Profiler', () => {
3707 await awaitableAdvanceTimers(500);
3708
3709 expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
3668 - expect(Scheduler).toFlushAndYield([
3669 - 'AsyncText [loaded]',
3670 - 'onPostCommit',
3671 - ]);
3710 + expect(Scheduler).toFlushAndYield(['AsyncText [loaded]', 'onCommit']);
3711 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3712 expect(
3713 onInteractionScheduledWorkCompleted,
@@ -3682,15 +3721,15 @@ describe('Profiler', () => {
3721 timestamp: Scheduler.unstable_now(),
3722 };
3723
3685 - const onPostCommit = jest.fn(() => {
3686 - Scheduler.unstable_yieldValue('onPostCommit');
3724 + const onCommit = jest.fn(() => {
3725 + Scheduler.unstable_yieldValue('onCommit');
3726 });
3727 SchedulerTracing.unstable_trace(
3728 interaction.name,
3729 interaction.timestamp,
3730 () => {
3731 ReactTestRenderer.create(
3693 - <React.Profiler id="app" onPostCommit={onPostCommit}>
3732 + <React.Profiler id="app" onCommit={onCommit}>
3733 <React.Suspense fallback={<Text text="loading" />}>
3734 <AsyncText text="loaded" ms={100} />
3735 </React.Suspense>
@@ -3702,7 +3741,7 @@ describe('Profiler', () => {
3741 expect(Scheduler).toFlushAndYield([
3742 'Suspend [loaded]',
3743 'Text [loading]',
3705 - 'onPostCommit',
3744 + 'onCommit',
3745 ]);
3746
3747 expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
@@ -3710,10 +3749,7 @@ describe('Profiler', () => {
3749 jest.advanceTimersByTime(100);
3750 await resourcePromise;
3751 expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
3713 - expect(Scheduler).toFlushAndYield([
3714 - 'AsyncText [loaded]',
3715 - 'onPostCommit',
3716 - ]);
3752 + expect(Scheduler).toFlushAndYield(['AsyncText [loaded]', 'onCommit']);
3753
3754 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3755 expect(
@@ -3738,6 +3774,7 @@ describe('Profiler', () => {
3774 () => {
3775 renderer = ReactTestRenderer.create(
3776 <React.Profiler id="app" onPostCommit={onPostCommit}>
3777 + <ComponentWithPassiveEffect />
3778 <React.Suspense fallback={<Text text="loading" />}>
3779 <AsyncText text="loaded" ms={100} />
3780 </React.Suspense>
@@ -3776,6 +3813,7 @@ describe('Profiler', () => {
3813 () => {
3814 renderer.update(
3815 <React.Profiler id="app" onPostCommit={onPostCommit}>
3816 + <ComponentWithPassiveEffect />
3817 <React.Suspense fallback={<Text text="loading" />}>
3818 <AsyncText text="loaded" ms={100} />
3819 </React.Suspense>
@@ -3842,15 +3880,15 @@ describe('Profiler', () => {
3880 timestamp: Scheduler.unstable_now(),
3881 };
3882
3845 - const onPostCommit = jest.fn(() => {
3846 - Scheduler.unstable_yieldValue('onPostCommit');
3883 + const onCommit = jest.fn(() => {
3884 + Scheduler.unstable_yieldValue('onCommit');
3885 });
3886 SchedulerTracing.unstable_trace(
3887 initialRenderInteraction.name,
3888 initialRenderInteraction.timestamp,
3889 () => {
3890 renderer.update(
3853 - <React.Profiler id="app" onPostCommit={onPostCommit}>
3891 + <React.Profiler id="app" onCommit={onCommit}>
3892 <React.Suspense fallback={<Text text="loading" />}>
3893 <AsyncText text="loaded" ms={100} />
3894 </React.Suspense>
@@ -3866,7 +3904,7 @@ describe('Profiler', () => {
3904 ]);
3905
3906 expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3869 - expect(onPostCommit).not.toHaveBeenCalled();
3907 + expect(onCommit).not.toHaveBeenCalled();
3908
3909 Scheduler.unstable_advanceTime(50);
3910 jest.advanceTimersByTime(50);
@@ -3885,7 +3923,7 @@ describe('Profiler', () => {
3923 highPriUpdateInteraction.timestamp,
3924 () => {
3925 renderer.update(
3888 - <React.Profiler id="app" onPostCommit={onPostCommit}>
3926 + <React.Profiler id="app" onCommit={onCommit}>
3927 <React.Suspense fallback={<Text text="loading" />}>
3928 <AsyncText text="loaded" ms={100} />
3929 </React.Suspense>
@@ -3899,15 +3937,15 @@ describe('Profiler', () => {
3937 'Suspend [loaded]',
3938 'Text [loading]',
3939 'Text [updated]',
3940 + 'onCommit',
3941 ]);
3903 - expect(Scheduler).toFlushAndYieldThrough(['onPostCommit']);
3942 expect(renderer.toJSON()).toEqual(['loading', 'updated']);
3943
3906 - expect(onPostCommit).toHaveBeenCalledTimes(1);
3907 - expect(onPostCommit.mock.calls[0][4]).toMatchInteractions([
3944 + expect(onCommit).toHaveBeenCalledTimes(1);
3945 + expect(onCommit.mock.calls[0][4]).toMatchInteractions([
3946 highPriUpdateInteraction,
3947 ]);
3910 - onPostCommit.mockClear();
3948 + onCommit.mockClear();
3949
3950 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(0);
3951
@@ -3915,14 +3953,11 @@ describe('Profiler', () => {
3953 jest.advanceTimersByTime(50);
3954 await originalPromise;
3955 expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
3918 - expect(Scheduler).toFlushAndYield([
3919 - 'AsyncText [loaded]',
3920 - 'onPostCommit',
3921 - ]);
3956 + expect(Scheduler).toFlushAndYield(['AsyncText [loaded]', 'onCommit']);
3957 expect(renderer.toJSON()).toEqual(['loaded', 'updated']);
3958
3924 - expect(onPostCommit).toHaveBeenCalledTimes(1);
3925 - expect(onPostCommit.mock.calls[0][4]).toMatchInteractions([
3959 + expect(onCommit).toHaveBeenCalledTimes(1);
3960 + expect(onCommit.mock.calls[0][4]).toMatchInteractions([
3961 highPriUpdateInteraction,
3962 ]);
3963
@@ -3961,15 +3996,15 @@ describe('Profiler', () => {
3996 }
3997 };
3998
3964 - const onPostCommit = jest.fn(() => {
3965 - Scheduler.unstable_yieldValue('onPostCommit');
3999 + const onCommit = jest.fn(() => {
4000 + Scheduler.unstable_yieldValue('onCommit');
4001 });
4002 SchedulerTracing.unstable_trace(
4003 interaction.name,
4004 Scheduler.unstable_now(),
4005 () => {
4006 ReactNoop.render(
3972 - <React.Profiler id="test-profiler" onPostCommit={onPostCommit}>
4007 + <React.Profiler id="test-profiler" onCommit={onCommit}>
4008 <React.Suspense fallback={<Text text="Loading..." />}>
4009 <AsyncText text="Async" ms={20000} />
4010 </React.Suspense>
@@ -3991,13 +4026,13 @@ describe('Profiler', () => {
4026 'Suspend [Async]',
4027 'Text [Loading...]',
4028 'Text [Sync]',
3994 - 'onPostCommit',
4029 + 'onCommit',
4030 ]);
4031 // Should have committed the placeholder.
4032 expect(ReactNoop.getChildrenAsJSX()).toEqual('Loading...Sync');
3998 - expect(onPostCommit).toHaveBeenCalledTimes(1);
4033 + expect(onCommit).toHaveBeenCalledTimes(1);
4034
4000 - let call = onPostCommit.mock.calls[0];
4035 + let call = onCommit.mock.calls[0];
4036 expect(call[0]).toEqual('test-profiler');
4037 expect(call[4]).toMatchInteractions(
4038 ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [],
@@ -4013,15 +4048,12 @@ describe('Profiler', () => {
4048 // Once the promise resolves, we render the suspended view
4049 await awaitableAdvanceTimers(20000);
4050 expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
4016 - expect(Scheduler).toFlushAndYield([
4017 - 'AsyncText [Async]',
4018 - 'onPostCommit',
4019 - ]);
4051 + expect(Scheduler).toFlushAndYield(['AsyncText [Async]', 'onCommit']);
4052 expect(ReactNoop.getChildrenAsJSX()).toEqual('AsyncSync');
4021 - expect(onPostCommit).toHaveBeenCalledTimes(2);
4053 + expect(onCommit).toHaveBeenCalledTimes(2);
4054
4055 // No interactions should be associated with this update.
4024 - call = onPostCommit.mock.calls[1];
4056 + call = onCommit.mock.calls[1];
4057 expect(call[0]).toEqual('test-profiler');
4058 expect(call[4]).toMatchInteractions([]);
4059 });