@samitouri / QOS-React-2 / commits / 86b4070ddb

Cleaned up passive effects experimental flags (#19021)

Brian Vaughn committed May 28, 2020 at 08:32 UTC 86b4070ddb6bd2fcf22c904cfff07a04696b9495
15 files changed +2192 -2696
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+35 -137
@@ -27,7 +27,6 @@ import type {OffscreenState} from './ReactFiberOffscreenComponent';
27
28 import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
29 import {
30 - deferPassiveEffectCleanupDuringUnmount,
30 enableSchedulerTracing,
31 enableProfilerTimer,
32 enableProfilerCommitHooks,
@@ -36,7 +35,6 @@ import {
35 enableFundamentalAPI,
36 enableSuspenseCallback,
37 enableScopeAPI,
39 - runAllPassiveEffectDestroysBeforeCreates,
38 enableCreateEventHandleAPI,
39 } from 'shared/ReactFeatureFlags';
40 import {
@@ -71,7 +69,6 @@ import {
69 Placement,
70 Snapshot,
71 Update,
74 - Passive,
72 } from './ReactSideEffectTags';
73 import getComponentName from 'shared/getComponentName';
74 import invariant from 'shared/invariant';
@@ -81,9 +78,7 @@ import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
78 import {
79 getCommitTime,
80 recordLayoutEffectDuration,
84 - recordPassiveEffectDuration,
81 startLayoutEffectTimer,
86 - startPassiveEffectTimer,
82 } from './ReactProfilerTimer.new';
83 import {ProfileMode} from './ReactTypeOfMode';
84 import {commitUpdateQueue} from './ReactUpdateQueue.new';
@@ -134,10 +129,6 @@ import {
129 Passive as HookPassive,
130 } from './ReactHookEffectTags';
131 import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
137 -import {
138 - runWithPriority,
139 - NormalPriority,
140 -} from './SchedulerWithReactIntegration.new';
132 import {
133 updateDeprecatedEventListeners,
134 unmountDeprecatedResponderListeners,
@@ -394,67 +385,22 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
385 }
386
387 function schedulePassiveEffects(finishedWork: Fiber) {
397 - if (runAllPassiveEffectDestroysBeforeCreates) {
398 - const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
399 - const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
400 - if (lastEffect !== null) {
401 - const firstEffect = lastEffect.next;
402 - let effect = firstEffect;
403 - do {
404 - const {next, tag} = effect;
405 - if (
406 - (tag & HookPassive) !== NoHookEffect &&
407 - (tag & HookHasEffect) !== NoHookEffect
408 - ) {
409 - enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
410 - enqueuePendingPassiveHookEffectMount(finishedWork, effect);
411 - }
412 - effect = next;
413 - } while (effect !== firstEffect);
414 - }
415 - }
416 -}
417 -
418 -export function commitPassiveHookEffects(finishedWork: Fiber): void {
419 - if ((finishedWork.effectTag & Passive) !== NoEffect) {
420 - switch (finishedWork.tag) {
421 - case FunctionComponent:
422 - case ForwardRef:
423 - case SimpleMemoComponent:
424 - case Block: {
425 - // TODO (#17945) We should call all passive destroy functions (for all fibers)
426 - // before calling any create functions. The current approach only serializes
427 - // these for a single fiber.
428 - if (
429 - enableProfilerTimer &&
430 - enableProfilerCommitHooks &&
431 - finishedWork.mode & ProfileMode
432 - ) {
433 - try {
434 - startPassiveEffectTimer();
435 - commitHookEffectListUnmount(
436 - HookPassive | HookHasEffect,
437 - finishedWork,
438 - );
439 - commitHookEffectListMount(
440 - HookPassive | HookHasEffect,
441 - finishedWork,
442 - );
443 - } finally {
444 - recordPassiveEffectDuration(finishedWork);
445 - }
446 - } else {
447 - commitHookEffectListUnmount(
448 - HookPassive | HookHasEffect,
449 - finishedWork,
450 - );
451 - commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
452 - }
453 - break;
388 + const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
389 + const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
390 + if (lastEffect !== null) {
391 + const firstEffect = lastEffect.next;
392 + let effect = firstEffect;
393 + do {
394 + const {next, tag} = effect;
395 + if (
396 + (tag & HookPassive) !== NoHookEffect &&
397 + (tag & HookHasEffect) !== NoHookEffect
398 + ) {
399 + enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
400 + enqueuePendingPassiveHookEffectMount(finishedWork, effect);
401 }
455 - default:
456 - break;
457 - }
402 + effect = next;
403 + } while (effect !== firstEffect);
404 }
405 }
406
@@ -543,9 +489,7 @@ function commitLifeCycles(
489 commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
490 }
491
546 - if (runAllPassiveEffectDestroysBeforeCreates) {
547 - schedulePassiveEffects(finishedWork);
548 - }
492 + schedulePassiveEffects(finishedWork);
493 return;
494 }
495 case ClassComponent: {
@@ -946,74 +890,28 @@ function commitUnmount(
890 if (lastEffect !== null) {
891 const firstEffect = lastEffect.next;
892
949 - if (
950 - deferPassiveEffectCleanupDuringUnmount &&
951 - runAllPassiveEffectDestroysBeforeCreates
952 - ) {
953 - let effect = firstEffect;
954 - do {
955 - const {destroy, tag} = effect;
956 - if (destroy !== undefined) {
957 - if ((tag & HookPassive) !== NoHookEffect) {
958 - enqueuePendingPassiveHookEffectUnmount(current, effect);
893 + let effect = firstEffect;
894 + do {
895 + const {destroy, tag} = effect;
896 + if (destroy !== undefined) {
897 + if ((tag & HookPassive) !== NoHookEffect) {
898 + enqueuePendingPassiveHookEffectUnmount(current, effect);
899 + } else {
900 + if (
901 + enableProfilerTimer &&
902 + enableProfilerCommitHooks &&
903 + current.mode & ProfileMode
904 + ) {
905 + startLayoutEffectTimer();
906 + safelyCallDestroy(current, destroy);
907 + recordLayoutEffectDuration(current);
908 } else {
960 - if (
961 - enableProfilerTimer &&
962 - enableProfilerCommitHooks &&
963 - current.mode & ProfileMode
964 - ) {
965 - startLayoutEffectTimer();
966 - safelyCallDestroy(current, destroy);
967 - recordLayoutEffectDuration(current);
968 - } else {
969 - safelyCallDestroy(current, destroy);
970 - }
909 + safelyCallDestroy(current, destroy);
910 }
911 }
973 - effect = effect.next;
974 - } while (effect !== firstEffect);
975 - } else {
976 - // When the owner fiber is deleted, the destroy function of a passive
977 - // effect hook is called during the synchronous commit phase. This is
978 - // a concession to implementation complexity. Calling it in the
979 - // passive effect phase (like they usually are, when dependencies
980 - // change during an update) would require either traversing the
981 - // children of the deleted fiber again, or including unmount effects
982 - // as part of the fiber effect list.
983 - //
984 - // Because this is during the sync commit phase, we need to change
985 - // the priority.
986 - //
987 - // TODO: Reconsider this implementation trade off.
988 - const priorityLevel =
989 - renderPriorityLevel > NormalPriority
990 - ? NormalPriority
991 - : renderPriorityLevel;
992 - runWithPriority(priorityLevel, () => {
993 - let effect = firstEffect;
994 - do {
995 - const {destroy, tag} = effect;
996 - if (destroy !== undefined) {
997 - if (
998 - enableProfilerTimer &&
999 - enableProfilerCommitHooks &&
1000 - current.mode & ProfileMode
1001 - ) {
1002 - if ((tag & HookPassive) !== NoHookEffect) {
1003 - safelyCallDestroy(current, destroy);
1004 - } else {
1005 - startLayoutEffectTimer();
1006 - safelyCallDestroy(current, destroy);
1007 - recordLayoutEffectDuration(current);
1008 - }
1009 - } else {
1010 - safelyCallDestroy(current, destroy);
1011 - }
1012 - }
1013 - effect = effect.next;
1014 - } while (effect !== firstEffect);
1015 - });
1016 - }
912 + }
913 + effect = effect.next;
914 + } while (effect !== firstEffect);
915 }
916 }
917 return;
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+35 -137
@@ -26,7 +26,6 @@ import type {ReactPriorityLevel} from './ReactInternalTypes';
26
27 import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
28 import {
29 - deferPassiveEffectCleanupDuringUnmount,
29 enableSchedulerTracing,
30 enableProfilerTimer,
31 enableProfilerCommitHooks,
@@ -35,7 +34,6 @@ import {
34 enableFundamentalAPI,
35 enableSuspenseCallback,
36 enableScopeAPI,
38 - runAllPassiveEffectDestroysBeforeCreates,
37 enableCreateEventHandleAPI,
38 } from 'shared/ReactFeatureFlags';
39 import {
@@ -68,7 +66,6 @@ import {
66 Placement,
67 Snapshot,
68 Update,
71 - Passive,
69 } from './ReactSideEffectTags';
70 import getComponentName from 'shared/getComponentName';
71 import invariant from 'shared/invariant';
@@ -78,9 +75,7 @@ import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
75 import {
76 getCommitTime,
77 recordLayoutEffectDuration,
81 - recordPassiveEffectDuration,
78 startLayoutEffectTimer,
83 - startPassiveEffectTimer,
79 } from './ReactProfilerTimer.old';
80 import {ProfileMode} from './ReactTypeOfMode';
81 import {commitUpdateQueue} from './ReactUpdateQueue.old';
@@ -131,10 +126,6 @@ import {
126 Passive as HookPassive,
127 } from './ReactHookEffectTags';
128 import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.old';
134 -import {
135 - runWithPriority,
136 - NormalPriority,
137 -} from './SchedulerWithReactIntegration.old';
129 import {
130 updateDeprecatedEventListeners,
131 unmountDeprecatedResponderListeners,
@@ -391,67 +382,22 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
382 }
383
384 function schedulePassiveEffects(finishedWork: Fiber) {
394 - if (runAllPassiveEffectDestroysBeforeCreates) {
395 - const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
396 - const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
397 - if (lastEffect !== null) {
398 - const firstEffect = lastEffect.next;
399 - let effect = firstEffect;
400 - do {
401 - const {next, tag} = effect;
402 - if (
403 - (tag & HookPassive) !== NoHookEffect &&
404 - (tag & HookHasEffect) !== NoHookEffect
405 - ) {
406 - enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
407 - enqueuePendingPassiveHookEffectMount(finishedWork, effect);
408 - }
409 - effect = next;
410 - } while (effect !== firstEffect);
411 - }
412 - }
413 -}
414 -
415 -export function commitPassiveHookEffects(finishedWork: Fiber): void {
416 - if ((finishedWork.effectTag & Passive) !== NoEffect) {
417 - switch (finishedWork.tag) {
418 - case FunctionComponent:
419 - case ForwardRef:
420 - case SimpleMemoComponent:
421 - case Block: {
422 - // TODO (#17945) We should call all passive destroy functions (for all fibers)
423 - // before calling any create functions. The current approach only serializes
424 - // these for a single fiber.
425 - if (
426 - enableProfilerTimer &&
427 - enableProfilerCommitHooks &&
428 - finishedWork.mode & ProfileMode
429 - ) {
430 - try {
431 - startPassiveEffectTimer();
432 - commitHookEffectListUnmount(
433 - HookPassive | HookHasEffect,
434 - finishedWork,
435 - );
436 - commitHookEffectListMount(
437 - HookPassive | HookHasEffect,
438 - finishedWork,
439 - );
440 - } finally {
441 - recordPassiveEffectDuration(finishedWork);
442 - }
443 - } else {
444 - commitHookEffectListUnmount(
445 - HookPassive | HookHasEffect,
446 - finishedWork,
447 - );
448 - commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
449 - }
450 - break;
385 + const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
386 + const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
387 + if (lastEffect !== null) {
388 + const firstEffect = lastEffect.next;
389 + let effect = firstEffect;
390 + do {
391 + const {next, tag} = effect;
392 + if (
393 + (tag & HookPassive) !== NoHookEffect &&
394 + (tag & HookHasEffect) !== NoHookEffect
395 + ) {
396 + enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
397 + enqueuePendingPassiveHookEffectMount(finishedWork, effect);
398 }
452 - default:
453 - break;
454 - }
399 + effect = next;
400 + } while (effect !== firstEffect);
401 }
402 }
403
@@ -540,9 +486,7 @@ function commitLifeCycles(
486 commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
487 }
488
543 - if (runAllPassiveEffectDestroysBeforeCreates) {
544 - schedulePassiveEffects(finishedWork);
545 - }
489 + schedulePassiveEffects(finishedWork);
490 return;
491 }
492 case ClassComponent: {
@@ -944,74 +888,28 @@ function commitUnmount(
888 if (lastEffect !== null) {
889 const firstEffect = lastEffect.next;
890
947 - if (
948 - deferPassiveEffectCleanupDuringUnmount &&
949 - runAllPassiveEffectDestroysBeforeCreates
950 - ) {
951 - let effect = firstEffect;
952 - do {
953 - const {destroy, tag} = effect;
954 - if (destroy !== undefined) {
955 - if ((tag & HookPassive) !== NoHookEffect) {
956 - enqueuePendingPassiveHookEffectUnmount(current, effect);
891 + let effect = firstEffect;
892 + do {
893 + const {destroy, tag} = effect;
894 + if (destroy !== undefined) {
895 + if ((tag & HookPassive) !== NoHookEffect) {
896 + enqueuePendingPassiveHookEffectUnmount(current, effect);
897 + } else {
898 + if (
899 + enableProfilerTimer &&
900 + enableProfilerCommitHooks &&
901 + current.mode & ProfileMode
902 + ) {
903 + startLayoutEffectTimer();
904 + safelyCallDestroy(current, destroy);
905 + recordLayoutEffectDuration(current);
906 } else {
958 - if (
959 - enableProfilerTimer &&
960 - enableProfilerCommitHooks &&
961 - current.mode & ProfileMode
962 - ) {
963 - startLayoutEffectTimer();
964 - safelyCallDestroy(current, destroy);
965 - recordLayoutEffectDuration(current);
966 - } else {
967 - safelyCallDestroy(current, destroy);
968 - }
907 + safelyCallDestroy(current, destroy);
908 }
909 }
971 - effect = effect.next;
972 - } while (effect !== firstEffect);
973 - } else {
974 - // When the owner fiber is deleted, the destroy function of a passive
975 - // effect hook is called during the synchronous commit phase. This is
976 - // a concession to implementation complexity. Calling it in the
977 - // passive effect phase (like they usually are, when dependencies
978 - // change during an update) would require either traversing the
979 - // children of the deleted fiber again, or including unmount effects
980 - // as part of the fiber effect list.
981 - //
982 - // Because this is during the sync commit phase, we need to change
983 - // the priority.
984 - //
985 - // TODO: Reconsider this implementation trade off.
986 - const priorityLevel =
987 - renderPriorityLevel > NormalPriority
988 - ? NormalPriority
989 - : renderPriorityLevel;
990 - runWithPriority(priorityLevel, () => {
991 - let effect = firstEffect;
992 - do {
993 - const {destroy, tag} = effect;
994 - if (destroy !== undefined) {
995 - if (
996 - enableProfilerTimer &&
997 - enableProfilerCommitHooks &&
998 - current.mode & ProfileMode
999 - ) {
1000 - if ((tag & HookPassive) !== NoHookEffect) {
1001 - safelyCallDestroy(current, destroy);
1002 - } else {
1003 - startLayoutEffectTimer();
1004 - safelyCallDestroy(current, destroy);
1005 - recordLayoutEffectDuration(current);
1006 - }
1007 - } else {
1008 - safelyCallDestroy(current, destroy);
1009 - }
1010 - }
1011 - effect = effect.next;
1012 - } while (effect !== firstEffect);
1013 - });
1014 - }
910 + }
911 + effect = effect.next;
912 + } while (effect !== firstEffect);
913 }
914 }
915 return;
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+99 -138
@@ -19,8 +19,6 @@ import type {StackCursor} from './ReactFiberStack.new';
19
20 import {
21 warnAboutDeprecatedLifecycles,
22 - deferPassiveEffectCleanupDuringUnmount,
23 - runAllPassiveEffectDestroysBeforeCreates,
22 enableSuspenseServerRenderer,
23 replayFailedUnitOfWorkWithInvokeGuardedCallback,
24 enableProfilerTimer,
@@ -154,7 +152,6 @@ import {
152 import {
153 commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
154 commitLifeCycles as commitLayoutEffectOnFiber,
157 - commitPassiveHookEffects,
155 commitPlacement,
156 commitWork,
157 commitDeletion,
@@ -2260,15 +2257,13 @@ export function enqueuePendingPassiveHookEffectMount(
2257 fiber: Fiber,
2258 effect: HookEffect,
2259 ): void {
2263 - if (runAllPassiveEffectDestroysBeforeCreates) {
2264 - pendingPassiveHookEffectsMount.push(effect, fiber);
2265 - if (!rootDoesHavePassiveEffects) {
2266 - rootDoesHavePassiveEffects = true;
2267 - scheduleCallback(NormalSchedulerPriority, () => {
2268 - flushPassiveEffects();
2269 - return null;
2270 - });
2271 - }
2260 + pendingPassiveHookEffectsMount.push(effect, fiber);
2261 + if (!rootDoesHavePassiveEffects) {
2262 + rootDoesHavePassiveEffects = true;
2263 + scheduleCallback(NormalSchedulerPriority, () => {
2264 + flushPassiveEffects();
2265 + return null;
2266 + });
2267 }
2268 }
2269
@@ -2276,25 +2271,21 @@ export function enqueuePendingPassiveHookEffectUnmount(
2271 fiber: Fiber,
2272 effect: HookEffect,
2273 ): void {
2279 - if (runAllPassiveEffectDestroysBeforeCreates) {
2280 - pendingPassiveHookEffectsUnmount.push(effect, fiber);
2281 - if (__DEV__) {
2282 - if (deferPassiveEffectCleanupDuringUnmount) {
2283 - fiber.effectTag |= PassiveUnmountPendingDev;
2284 - const alternate = fiber.alternate;
2285 - if (alternate !== null) {
2286 - alternate.effectTag |= PassiveUnmountPendingDev;
2287 - }
2288 - }
2289 - }
2290 - if (!rootDoesHavePassiveEffects) {
2291 - rootDoesHavePassiveEffects = true;
2292 - scheduleCallback(NormalSchedulerPriority, () => {
2293 - flushPassiveEffects();
2294 - return null;
2295 - });
2274 + pendingPassiveHookEffectsUnmount.push(effect, fiber);
2275 + if (__DEV__) {
2276 + fiber.effectTag |= PassiveUnmountPendingDev;
2277 + const alternate = fiber.alternate;
2278 + if (alternate !== null) {
2279 + alternate.effectTag |= PassiveUnmountPendingDev;
2280 }
2281 }
2282 + if (!rootDoesHavePassiveEffects) {
2283 + rootDoesHavePassiveEffects = true;
2284 + scheduleCallback(NormalSchedulerPriority, () => {
2285 + flushPassiveEffects();
2286 + return null;
2287 + });
2288 + }
2289 }
2290
2291 function invokePassiveEffectCreate(effect: HookEffect): void {
@@ -2325,82 +2316,31 @@ function flushPassiveEffectsImpl() {
2316 executionContext |= CommitContext;
2317 const prevInteractions = pushInteractions(root);
2318
2328 - if (runAllPassiveEffectDestroysBeforeCreates) {
2329 - // It's important that ALL pending passive effect destroy functions are called
2330 - // before ANY passive effect create functions are called.
2331 - // Otherwise effects in sibling components might interfere with each other.
2332 - // e.g. a destroy function in one component may unintentionally override a ref
2333 - // value set by a create function in another component.
2334 - // Layout effects have the same constraint.
2335 -
2336 - // First pass: Destroy stale passive effects.
2337 - const unmountEffects = pendingPassiveHookEffectsUnmount;
2338 - pendingPassiveHookEffectsUnmount = [];
2339 - for (let i = 0; i < unmountEffects.length; i += 2) {
2340 - const effect = ((unmountEffects[i]: any): HookEffect);
2341 - const fiber = ((unmountEffects[i + 1]: any): Fiber);
2342 - const destroy = effect.destroy;
2343 - effect.destroy = undefined;
2344 -
2345 - if (__DEV__) {
2346 - if (deferPassiveEffectCleanupDuringUnmount) {
2347 - fiber.effectTag &= ~PassiveUnmountPendingDev;
2348 - const alternate = fiber.alternate;
2349 - if (alternate !== null) {
2350 - alternate.effectTag &= ~PassiveUnmountPendingDev;
2351 - }
2352 - }
2353 - }
2319 + // It's important that ALL pending passive effect destroy functions are called
2320 + // before ANY passive effect create functions are called.
2321 + // Otherwise effects in sibling components might interfere with each other.
2322 + // e.g. a destroy function in one component may unintentionally override a ref
2323 + // value set by a create function in another component.
2324 + // Layout effects have the same constraint.
2325 +
2326 + // First pass: Destroy stale passive effects.
2327 + const unmountEffects = pendingPassiveHookEffectsUnmount;
2328 + pendingPassiveHookEffectsUnmount = [];
2329 + for (let i = 0; i < unmountEffects.length; i += 2) {
2330 + const effect = ((unmountEffects[i]: any): HookEffect);
2331 + const fiber = ((unmountEffects[i + 1]: any): Fiber);
2332 + const destroy = effect.destroy;
2333 + effect.destroy = undefined;
2334
2355 - if (typeof destroy === 'function') {
2356 - if (__DEV__) {
2357 - setCurrentDebugFiberInDEV(fiber);
2358 - if (
2359 - enableProfilerTimer &&
2360 - enableProfilerCommitHooks &&
2361 - fiber.mode & ProfileMode
2362 - ) {
2363 - startPassiveEffectTimer();
2364 - invokeGuardedCallback(null, destroy, null);
2365 - recordPassiveEffectDuration(fiber);
2366 - } else {
2367 - invokeGuardedCallback(null, destroy, null);
2368 - }
2369 - if (hasCaughtError()) {
2370 - invariant(fiber !== null, 'Should be working on an effect.');
2371 - const error = clearCaughtError();
2372 - captureCommitPhaseError(fiber, error);
2373 - }
2374 - resetCurrentDebugFiberInDEV();
2375 - } else {
2376 - try {
2377 - if (
2378 - enableProfilerTimer &&
2379 - enableProfilerCommitHooks &&
2380 - fiber.mode & ProfileMode
2381 - ) {
2382 - try {
2383 - startPassiveEffectTimer();
2384 - destroy();
2385 - } finally {
2386 - recordPassiveEffectDuration(fiber);
2387 - }
2388 - } else {
2389 - destroy();
2390 - }
2391 - } catch (error) {
2392 - invariant(fiber !== null, 'Should be working on an effect.');
2393 - captureCommitPhaseError(fiber, error);
2394 - }
2395 - }
2335 + if (__DEV__) {
2336 + fiber.effectTag &= ~PassiveUnmountPendingDev;
2337 + const alternate = fiber.alternate;
2338 + if (alternate !== null) {
2339 + alternate.effectTag &= ~PassiveUnmountPendingDev;
2340 }
2341 }
2398 - // Second pass: Create new passive effects.
2399 - const mountEffects = pendingPassiveHookEffectsMount;
2400 - pendingPassiveHookEffectsMount = [];
2401 - for (let i = 0; i < mountEffects.length; i += 2) {
2402 - const effect = ((mountEffects[i]: any): HookEffect);
2403 - const fiber = ((mountEffects[i + 1]: any): Fiber);
2342 +
2343 + if (typeof destroy === 'function') {
2344 if (__DEV__) {
2345 setCurrentDebugFiberInDEV(fiber);
2346 if (
@@ -2409,10 +2349,10 @@ function flushPassiveEffectsImpl() {
2349 fiber.mode & ProfileMode
2350 ) {
2351 startPassiveEffectTimer();
2412 - invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2352 + invokeGuardedCallback(null, destroy, null);
2353 recordPassiveEffectDuration(fiber);
2354 } else {
2415 - invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2355 + invokeGuardedCallback(null, destroy, null);
2356 }
2357 if (hasCaughtError()) {
2358 invariant(fiber !== null, 'Should be working on an effect.');
@@ -2422,7 +2362,6 @@ function flushPassiveEffectsImpl() {
2362 resetCurrentDebugFiberInDEV();
2363 } else {
2364 try {
2425 - const create = effect.create;
2365 if (
2366 enableProfilerTimer &&
2367 enableProfilerCommitHooks &&
@@ -2430,12 +2369,12 @@ function flushPassiveEffectsImpl() {
2369 ) {
2370 try {
2371 startPassiveEffectTimer();
2433 - effect.destroy = create();
2372 + destroy();
2373 } finally {
2374 recordPassiveEffectDuration(fiber);
2375 }
2376 } else {
2438 - effect.destroy = create();
2377 + destroy();
2378 }
2379 } catch (error) {
2380 invariant(fiber !== null, 'Should be working on an effect.');
@@ -2444,33 +2383,60 @@ function flushPassiveEffectsImpl() {
2383 }
2384 }
2385 }
2447 - // Note: This currently assumes there are no passive effects on the root fiber
2448 - // because the root is not part of its own effect list.
2449 - // This could change in the future.
2450 - let effect = root.current.firstEffect;
2451 - while (effect !== null) {
2452 - // We do this work above if this flag is enabled, so we shouldn't be
2453 - // doing it here.
2454 - if (!runAllPassiveEffectDestroysBeforeCreates) {
2455 - if (__DEV__) {
2456 - setCurrentDebugFiberInDEV(effect);
2457 - invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
2458 - if (hasCaughtError()) {
2459 - invariant(effect !== null, 'Should be working on an effect.');
2460 - const error = clearCaughtError();
2461 - captureCommitPhaseError(effect, error);
2462 - }
2463 - resetCurrentDebugFiberInDEV();
2386 + // Second pass: Create new passive effects.
2387 + const mountEffects = pendingPassiveHookEffectsMount;
2388 + pendingPassiveHookEffectsMount = [];
2389 + for (let i = 0; i < mountEffects.length; i += 2) {
2390 + const effect = ((mountEffects[i]: any): HookEffect);
2391 + const fiber = ((mountEffects[i + 1]: any): Fiber);
2392 + if (__DEV__) {
2393 + setCurrentDebugFiberInDEV(fiber);
2394 + if (
2395 + enableProfilerTimer &&
2396 + enableProfilerCommitHooks &&
2397 + fiber.mode & ProfileMode
2398 + ) {
2399 + startPassiveEffectTimer();
2400 + invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2401 + recordPassiveEffectDuration(fiber);
2402 } else {
2465 - try {
2466 - commitPassiveHookEffects(effect);
2467 - } catch (error) {
2468 - invariant(effect !== null, 'Should be working on an effect.');
2469 - captureCommitPhaseError(effect, error);
2403 + invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2404 + }
2405 + if (hasCaughtError()) {
2406 + invariant(fiber !== null, 'Should be working on an effect.');
2407 + const error = clearCaughtError();
2408 + captureCommitPhaseError(fiber, error);
2409 + }
2410 + resetCurrentDebugFiberInDEV();
2411 + } else {
2412 + try {
2413 + const create = effect.create;
2414 + if (
2415 + enableProfilerTimer &&
2416 + enableProfilerCommitHooks &&
2417 + fiber.mode & ProfileMode
2418 + ) {
2419 + try {
2420 + startPassiveEffectTimer();
2421 + effect.destroy = create();
2422 + } finally {
2423 + recordPassiveEffectDuration(fiber);
2424 + }
2425 + } else {
2426 + effect.destroy = create();
2427 }
2428 + } catch (error) {
2429 + invariant(fiber !== null, 'Should be working on an effect.');
2430 + captureCommitPhaseError(fiber, error);
2431 }
2432 }
2433 + }
2434
2435 + // Note: This currently assumes there are no passive effects on the root fiber
2436 + // because the root is not part of its own effect list.
2437 + // This could change in the future.
2438 + let effect = root.current.firstEffect;
2439 + while (effect !== null) {
2440 const nextNextEffect = effect.nextEffect;
2441 // Remove nextEffect pointer to assist GC
2442 effect.nextEffect = null;
@@ -2868,15 +2834,10 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
2834 return;
2835 }
2836
2871 - if (
2872 - deferPassiveEffectCleanupDuringUnmount &&
2873 - runAllPassiveEffectDestroysBeforeCreates
2874 - ) {
2875 - // If there are pending passive effects unmounts for this Fiber,
2876 - // we can assume that they would have prevented this update.
2877 - if ((fiber.effectTag & PassiveUnmountPendingDev) !== NoEffect) {
2878 - return;
2879 - }
2837 + // If there are pending passive effects unmounts for this Fiber,
2838 + // we can assume that they would have prevented this update.
2839 + if ((fiber.effectTag & PassiveUnmountPendingDev) !== NoEffect) {
2840 + return;
2841 }
2842
2843 // We show the whole stack but dedupe on the top component's name because
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+99 -138
@@ -18,8 +18,6 @@ import type {Effect as HookEffect} from './ReactFiberHooks.old';
18
19 import {
20 warnAboutDeprecatedLifecycles,
21 - deferPassiveEffectCleanupDuringUnmount,
22 - runAllPassiveEffectDestroysBeforeCreates,
21 enableSuspenseServerRenderer,
22 replayFailedUnitOfWorkWithInvokeGuardedCallback,
23 enableProfilerTimer,
@@ -151,7 +149,6 @@ import {
149 import {
150 commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
151 commitLifeCycles as commitLayoutEffectOnFiber,
154 - commitPassiveHookEffects,
152 commitPlacement,
153 commitWork,
154 commitDeletion,
@@ -2400,15 +2397,13 @@ export function enqueuePendingPassiveHookEffectMount(
2397 fiber: Fiber,
2398 effect: HookEffect,
2399 ): void {
2403 - if (runAllPassiveEffectDestroysBeforeCreates) {
2404 - pendingPassiveHookEffectsMount.push(effect, fiber);
2405 - if (!rootDoesHavePassiveEffects) {
2406 - rootDoesHavePassiveEffects = true;
2407 - scheduleCallback(NormalPriority, () => {
2408 - flushPassiveEffects();
2409 - return null;
2410 - });
2411 - }
2400 + pendingPassiveHookEffectsMount.push(effect, fiber);
2401 + if (!rootDoesHavePassiveEffects) {
2402 + rootDoesHavePassiveEffects = true;
2403 + scheduleCallback(NormalPriority, () => {
2404 + flushPassiveEffects();
2405 + return null;
2406 + });
2407 }
2408 }
2409
@@ -2416,25 +2411,21 @@ export function enqueuePendingPassiveHookEffectUnmount(
2411 fiber: Fiber,
2412 effect: HookEffect,
2413 ): void {
2419 - if (runAllPassiveEffectDestroysBeforeCreates) {
2420 - pendingPassiveHookEffectsUnmount.push(effect, fiber);
2421 - if (__DEV__) {
2422 - if (deferPassiveEffectCleanupDuringUnmount) {
2423 - fiber.effectTag |= PassiveUnmountPendingDev;
2424 - const alternate = fiber.alternate;
2425 - if (alternate !== null) {
2426 - alternate.effectTag |= PassiveUnmountPendingDev;
2427 - }
2428 - }
2429 - }
2430 - if (!rootDoesHavePassiveEffects) {
2431 - rootDoesHavePassiveEffects = true;
2432 - scheduleCallback(NormalPriority, () => {
2433 - flushPassiveEffects();
2434 - return null;
2435 - });
2414 + pendingPassiveHookEffectsUnmount.push(effect, fiber);
2415 + if (__DEV__) {
2416 + fiber.effectTag |= PassiveUnmountPendingDev;
2417 + const alternate = fiber.alternate;
2418 + if (alternate !== null) {
2419 + alternate.effectTag |= PassiveUnmountPendingDev;
2420 }
2421 }
2422 + if (!rootDoesHavePassiveEffects) {
2423 + rootDoesHavePassiveEffects = true;
2424 + scheduleCallback(NormalPriority, () => {
2425 + flushPassiveEffects();
2426 + return null;
2427 + });
2428 + }
2429 }
2430
2431 function invokePassiveEffectCreate(effect: HookEffect): void {
@@ -2473,82 +2464,31 @@ function flushPassiveEffectsImpl() {
2464 executionContext |= CommitContext;
2465 const prevInteractions = pushInteractions(root);
2466
2476 - if (runAllPassiveEffectDestroysBeforeCreates) {
2477 - // It's important that ALL pending passive effect destroy functions are called
2478 - // before ANY passive effect create functions are called.
2479 - // Otherwise effects in sibling components might interfere with each other.
2480 - // e.g. a destroy function in one component may unintentionally override a ref
2481 - // value set by a create function in another component.
2482 - // Layout effects have the same constraint.
2483 -
2484 - // First pass: Destroy stale passive effects.
2485 - const unmountEffects = pendingPassiveHookEffectsUnmount;
2486 - pendingPassiveHookEffectsUnmount = [];
2487 - for (let i = 0; i < unmountEffects.length; i += 2) {
2488 - const effect = ((unmountEffects[i]: any): HookEffect);
2489 - const fiber = ((unmountEffects[i + 1]: any): Fiber);
2490 - const destroy = effect.destroy;
2491 - effect.destroy = undefined;
2492 -
2493 - if (__DEV__) {
2494 - if (deferPassiveEffectCleanupDuringUnmount) {
2495 - fiber.effectTag &= ~PassiveUnmountPendingDev;
2496 - const alternate = fiber.alternate;
2497 - if (alternate !== null) {
2498 - alternate.effectTag &= ~PassiveUnmountPendingDev;
2499 - }
2500 - }
2501 - }
2467 + // It's important that ALL pending passive effect destroy functions are called
2468 + // before ANY passive effect create functions are called.
2469 + // Otherwise effects in sibling components might interfere with each other.
2470 + // e.g. a destroy function in one component may unintentionally override a ref
2471 + // value set by a create function in another component.
2472 + // Layout effects have the same constraint.
2473 +
2474 + // First pass: Destroy stale passive effects.
2475 + const unmountEffects = pendingPassiveHookEffectsUnmount;
2476 + pendingPassiveHookEffectsUnmount = [];
2477 + for (let i = 0; i < unmountEffects.length; i += 2) {
2478 + const effect = ((unmountEffects[i]: any): HookEffect);
2479 + const fiber = ((unmountEffects[i + 1]: any): Fiber);
2480 + const destroy = effect.destroy;
2481 + effect.destroy = undefined;
2482
2503 - if (typeof destroy === 'function') {
2504 - if (__DEV__) {
2505 - setCurrentDebugFiberInDEV(fiber);
2506 - if (
2507 - enableProfilerTimer &&
2508 - enableProfilerCommitHooks &&
2509 - fiber.mode & ProfileMode
2510 - ) {
2511 - startPassiveEffectTimer();
2512 - invokeGuardedCallback(null, destroy, null);
2513 - recordPassiveEffectDuration(fiber);
2514 - } else {
2515 - invokeGuardedCallback(null, destroy, null);
2516 - }
2517 - if (hasCaughtError()) {
2518 - invariant(fiber !== null, 'Should be working on an effect.');
2519 - const error = clearCaughtError();
2520 - captureCommitPhaseError(fiber, error);
2521 - }
2522 - resetCurrentDebugFiberInDEV();
2523 - } else {
2524 - try {
2525 - if (
2526 - enableProfilerTimer &&
2527 - enableProfilerCommitHooks &&
2528 - fiber.mode & ProfileMode
2529 - ) {
2530 - try {
2531 - startPassiveEffectTimer();
2532 - destroy();
2533 - } finally {
2534 - recordPassiveEffectDuration(fiber);
2535 - }
2536 - } else {
2537 - destroy();
2538 - }
2539 - } catch (error) {
2540 - invariant(fiber !== null, 'Should be working on an effect.');
2541 - captureCommitPhaseError(fiber, error);
2542 - }
2543 - }
2483 + if (__DEV__) {
2484 + fiber.effectTag &= ~PassiveUnmountPendingDev;
2485 + const alternate = fiber.alternate;
2486 + if (alternate !== null) {
2487 + alternate.effectTag &= ~PassiveUnmountPendingDev;
2488 }
2489 }
2546 - // Second pass: Create new passive effects.
2547 - const mountEffects = pendingPassiveHookEffectsMount;
2548 - pendingPassiveHookEffectsMount = [];
2549 - for (let i = 0; i < mountEffects.length; i += 2) {
2550 - const effect = ((mountEffects[i]: any): HookEffect);
2551 - const fiber = ((mountEffects[i + 1]: any): Fiber);
2490 +
2491 + if (typeof destroy === 'function') {
2492 if (__DEV__) {
2493 setCurrentDebugFiberInDEV(fiber);
2494 if (
@@ -2557,10 +2497,10 @@ function flushPassiveEffectsImpl() {
2497 fiber.mode & ProfileMode
2498 ) {
2499 startPassiveEffectTimer();
2560 - invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2500 + invokeGuardedCallback(null, destroy, null);
2501 recordPassiveEffectDuration(fiber);
2502 } else {
2563 - invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2503 + invokeGuardedCallback(null, destroy, null);
2504 }
2505 if (hasCaughtError()) {
2506 invariant(fiber !== null, 'Should be working on an effect.');
@@ -2570,7 +2510,6 @@ function flushPassiveEffectsImpl() {
2510 resetCurrentDebugFiberInDEV();
2511 } else {
2512 try {
2573 - const create = effect.create;
2513 if (
2514 enableProfilerTimer &&
2515 enableProfilerCommitHooks &&
@@ -2578,12 +2517,12 @@ function flushPassiveEffectsImpl() {
2517 ) {
2518 try {
2519 startPassiveEffectTimer();
2581 - effect.destroy = create();
2520 + destroy();
2521 } finally {
2522 recordPassiveEffectDuration(fiber);
2523 }
2524 } else {
2586 - effect.destroy = create();
2525 + destroy();
2526 }
2527 } catch (error) {
2528 invariant(fiber !== null, 'Should be working on an effect.');
@@ -2592,33 +2531,60 @@ function flushPassiveEffectsImpl() {
2531 }
2532 }
2533 }
2595 - // Note: This currently assumes there are no passive effects on the root fiber
2596 - // because the root is not part of its own effect list.
2597 - // This could change in the future.
2598 - let effect = root.current.firstEffect;
2599 - while (effect !== null) {
2600 - // We do this work above if this flag is enabled, so we shouldn't be
2601 - // doing it here.
2602 - if (!runAllPassiveEffectDestroysBeforeCreates) {
2603 - if (__DEV__) {
2604 - setCurrentDebugFiberInDEV(effect);
2605 - invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
2606 - if (hasCaughtError()) {
2607 - invariant(effect !== null, 'Should be working on an effect.');
2608 - const error = clearCaughtError();
2609 - captureCommitPhaseError(effect, error);
2610 - }
2611 - resetCurrentDebugFiberInDEV();
2534 + // Second pass: Create new passive effects.
2535 + const mountEffects = pendingPassiveHookEffectsMount;
2536 + pendingPassiveHookEffectsMount = [];
2537 + for (let i = 0; i < mountEffects.length; i += 2) {
2538 + const effect = ((mountEffects[i]: any): HookEffect);
2539 + const fiber = ((mountEffects[i + 1]: any): Fiber);
2540 + if (__DEV__) {
2541 + setCurrentDebugFiberInDEV(fiber);
2542 + if (
2543 + enableProfilerTimer &&
2544 + enableProfilerCommitHooks &&
2545 + fiber.mode & ProfileMode
2546 + ) {
2547 + startPassiveEffectTimer();
2548 + invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2549 + recordPassiveEffectDuration(fiber);
2550 } else {
2613 - try {
2614 - commitPassiveHookEffects(effect);
2615 - } catch (error) {
2616 - invariant(effect !== null, 'Should be working on an effect.');
2617 - captureCommitPhaseError(effect, error);
2551 + invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2552 + }
2553 + if (hasCaughtError()) {
2554 + invariant(fiber !== null, 'Should be working on an effect.');
2555 + const error = clearCaughtError();
2556 + captureCommitPhaseError(fiber, error);
2557 + }
2558 + resetCurrentDebugFiberInDEV();
2559 + } else {
2560 + try {
2561 + const create = effect.create;
2562 + if (
2563 + enableProfilerTimer &&
2564 + enableProfilerCommitHooks &&
2565 + fiber.mode & ProfileMode
2566 + ) {
2567 + try {
2568 + startPassiveEffectTimer();
2569 + effect.destroy = create();
2570 + } finally {
2571 + recordPassiveEffectDuration(fiber);
2572 + }
2573 + } else {
2574 + effect.destroy = create();
2575 }
2576 + } catch (error) {
2577 + invariant(fiber !== null, 'Should be working on an effect.');
2578 + captureCommitPhaseError(fiber, error);
2579 }
2580 }
2581 + }
2582
2583 + // Note: This currently assumes there are no passive effects on the root fiber
2584 + // because the root is not part of its own effect list.
2585 + // This could change in the future.
2586 + let effect = root.current.firstEffect;
2587 + while (effect !== null) {
2588 const nextNextEffect = effect.nextEffect;
2589 // Remove nextEffect pointer to assist GC
2590 effect.nextEffect = null;
@@ -3037,15 +3003,10 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
3003 return;
3004 }
3005
3040 - if (
3041 - deferPassiveEffectCleanupDuringUnmount &&
3042 - runAllPassiveEffectDestroysBeforeCreates
3043 - ) {
3044 - // If there are pending passive effects unmounts for this Fiber,
3045 - // we can assume that they would have prevented this update.
3046 - if ((fiber.effectTag & PassiveUnmountPendingDev) !== NoEffect) {
3047 - return;
3048 - }
3006 + // If there are pending passive effects unmounts for this Fiber,
3007 + // we can assume that they would have prevented this update.
3008 + if ((fiber.effectTag & PassiveUnmountPendingDev) !== NoEffect) {
3009 + return;
3010 }
3011
3012 // We show the whole stack but dedupe on the top component's name because
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+91 -147
@@ -15,7 +15,6 @@
15 let React;
16 let ReactCache;
17 let TextResource;
18 -let ReactFeatureFlags;
18 let ReactNoop;
19 let Scheduler;
20 let SchedulerTracing;
@@ -33,21 +32,12 @@ let useDeferredValue;
32 let forwardRef;
33 let memo;
34 let act;
36 -let deferPassiveEffectCleanupDuringUnmount;
37 -let runAllPassiveEffectDestroysBeforeCreates;
35
36 describe('ReactHooksWithNoopRenderer', () => {
37 beforeEach(() => {
38 jest.resetModules();
39 jest.useFakeTimers();
40
44 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
45 -
46 - deferPassiveEffectCleanupDuringUnmount =
47 - ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount;
48 - runAllPassiveEffectDestroysBeforeCreates =
49 - ReactFeatureFlags.runAllPassiveEffectDestroysBeforeCreates;
50 -
41 React = require('react');
42 ReactNoop = require('react-noop-renderer');
43 Scheduler = require('scheduler');
@@ -1119,7 +1109,6 @@ describe('ReactHooksWithNoopRenderer', () => {
1109 },
1110 );
1111
1122 - // @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
1112 it('defers passive effect destroy functions during unmount', () => {
1113 function Child({bar, foo}) {
1114 React.useEffect(() => {
@@ -1204,7 +1193,6 @@ describe('ReactHooksWithNoopRenderer', () => {
1193 });
1194 });
1195
1207 - // @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
1196 it('does not warn about state updates for unmounted components with pending passive unmounts', () => {
1197 let completePendingRequest = null;
1198 function Component() {
@@ -1252,7 +1240,6 @@ describe('ReactHooksWithNoopRenderer', () => {
1240 });
1241 });
1242
1255 - // @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
1243 it('does not warn about state updates for unmounted components with pending passive unmounts for alternates', () => {
1244 let setParentState = null;
1245 const setChildStates = [];
@@ -1378,7 +1365,6 @@ describe('ReactHooksWithNoopRenderer', () => {
1365 });
1366 });
1367
1381 - // @gate deferPassiveEffectCleanupDuringUnmount && runAllPassiveEffectDestroysBeforeCreates
1368 it('still warns if there are pending passive unmount effects but not for the current fiber', () => {
1369 let completePendingRequest = null;
1370 function ComponentWithXHR() {
@@ -2085,80 +2071,66 @@ describe('ReactHooksWithNoopRenderer', () => {
2071 ]);
2072 });
2073
2088 - if (runAllPassiveEffectDestroysBeforeCreates) {
2089 - it('unmounts all previous effects between siblings before creating any new ones', () => {
2090 - function Counter({count, label}) {
2091 - useEffect(() => {
2092 - Scheduler.unstable_yieldValue(`Mount ${label} [${count}]`);
2093 - return () => {
2094 - Scheduler.unstable_yieldValue(`Unmount ${label} [${count}]`);
2095 - };
2096 - });
2097 - return <Text text={`${label} ${count}`} />;
2098 - }
2099 - act(() => {
2100 - ReactNoop.render(
2101 - <>
2102 - <Counter label="A" count={0} />
2103 - <Counter label="B" count={0} />
2104 - </>,
2105 - () => Scheduler.unstable_yieldValue('Sync effect'),
2106 - );
2107 - expect(Scheduler).toFlushAndYieldThrough([
2108 - 'A 0',
2109 - 'B 0',
2110 - 'Sync effect',
2111 - ]);
2112 - expect(ReactNoop.getChildren()).toEqual([span('A 0'), span('B 0')]);
2074 + it('unmounts all previous effects between siblings before creating any new ones', () => {
2075 + function Counter({count, label}) {
2076 + useEffect(() => {
2077 + Scheduler.unstable_yieldValue(`Mount ${label} [${count}]`);
2078 + return () => {
2079 + Scheduler.unstable_yieldValue(`Unmount ${label} [${count}]`);
2080 + };
2081 });
2082 + return <Text text={`${label} ${count}`} />;
2083 + }
2084 + act(() => {
2085 + ReactNoop.render(
2086 + <>
2087 + <Counter label="A" count={0} />
2088 + <Counter label="B" count={0} />
2089 + </>,
2090 + () => Scheduler.unstable_yieldValue('Sync effect'),
2091 + );
2092 + expect(Scheduler).toFlushAndYieldThrough(['A 0', 'B 0', 'Sync effect']);
2093 + expect(ReactNoop.getChildren()).toEqual([span('A 0'), span('B 0')]);
2094 + });
2095
2115 - expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']);
2096 + expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']);
2097
2117 - act(() => {
2118 - ReactNoop.render(
2119 - <>
2120 - <Counter label="A" count={1} />
2121 - <Counter label="B" count={1} />
2122 - </>,
2123 - () => Scheduler.unstable_yieldValue('Sync effect'),
2124 - );
2125 - expect(Scheduler).toFlushAndYieldThrough([
2126 - 'A 1',
2127 - 'B 1',
2128 - 'Sync effect',
2129 - ]);
2130 - expect(ReactNoop.getChildren()).toEqual([span('A 1'), span('B 1')]);
2131 - });
2132 - expect(Scheduler).toHaveYielded([
2133 - 'Unmount A [0]',
2134 - 'Unmount B [0]',
2135 - 'Mount A [1]',
2136 - 'Mount B [1]',
2137 - ]);
2098 + act(() => {
2099 + ReactNoop.render(
2100 + <>
2101 + <Counter label="A" count={1} />
2102 + <Counter label="B" count={1} />
2103 + </>,
2104 + () => Scheduler.unstable_yieldValue('Sync effect'),
2105 + );
2106 + expect(Scheduler).toFlushAndYieldThrough(['A 1', 'B 1', 'Sync effect']);
2107 + expect(ReactNoop.getChildren()).toEqual([span('A 1'), span('B 1')]);
2108 + });
2109 + expect(Scheduler).toHaveYielded([
2110 + 'Unmount A [0]',
2111 + 'Unmount B [0]',
2112 + 'Mount A [1]',
2113 + 'Mount B [1]',
2114 + ]);
2115
2139 - act(() => {
2140 - ReactNoop.render(
2141 - <>
2142 - <Counter label="B" count={2} />
2143 - <Counter label="C" count={0} />
2144 - </>,
2145 - () => Scheduler.unstable_yieldValue('Sync effect'),
2146 - );
2147 - expect(Scheduler).toFlushAndYieldThrough([
2148 - 'B 2',
2149 - 'C 0',
2150 - 'Sync effect',
2151 - ]);
2152 - expect(ReactNoop.getChildren()).toEqual([span('B 2'), span('C 0')]);
2153 - });
2154 - expect(Scheduler).toHaveYielded([
2155 - 'Unmount A [1]',
2156 - 'Unmount B [1]',
2157 - 'Mount B [2]',
2158 - 'Mount C [0]',
2159 - ]);
2116 + act(() => {
2117 + ReactNoop.render(
2118 + <>
2119 + <Counter label="B" count={2} />
2120 + <Counter label="C" count={0} />
2121 + </>,
2122 + () => Scheduler.unstable_yieldValue('Sync effect'),
2123 + );
2124 + expect(Scheduler).toFlushAndYieldThrough(['B 2', 'C 0', 'Sync effect']);
2125 + expect(ReactNoop.getChildren()).toEqual([span('B 2'), span('C 0')]);
2126 });
2161 - }
2127 + expect(Scheduler).toHaveYielded([
2128 + 'Unmount A [1]',
2129 + 'Unmount B [1]',
2130 + 'Mount B [2]',
2131 + 'Mount C [0]',
2132 + ]);
2133 + });
2134
2135 it('handles errors in create on mount', () => {
2136 function Counter(props) {
@@ -2236,30 +2208,19 @@ describe('ReactHooksWithNoopRenderer', () => {
2208 expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
2209 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
2210 expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops');
2239 - expect(Scheduler).toHaveYielded(
2240 - deferPassiveEffectCleanupDuringUnmount &&
2241 - runAllPassiveEffectDestroysBeforeCreates
2242 - ? ['Unmount A [0]', 'Unmount B [0]', 'Mount A [1]', 'Oops!']
2243 - : [
2244 - 'Unmount A [0]',
2245 - 'Unmount B [0]',
2246 - 'Mount A [1]',
2247 - 'Oops!',
2248 - 'Unmount A [1]',
2249 - ],
2250 - );
2251 - expect(ReactNoop.getChildren()).toEqual([]);
2252 - });
2253 - if (
2254 - deferPassiveEffectCleanupDuringUnmount &&
2255 - runAllPassiveEffectDestroysBeforeCreates
2256 - ) {
2211 expect(Scheduler).toHaveYielded([
2258 - // Clean up effect A runs passively on unmount.
2259 - // There's no effect B to clean-up, because it never mounted.
2260 - 'Unmount A [1]',
2212 + 'Unmount A [0]',
2213 + 'Unmount B [0]',
2214 + 'Mount A [1]',
2215 + 'Oops!',
2216 ]);
2262 - }
2217 + expect(ReactNoop.getChildren()).toEqual([]);
2218 + });
2219 + expect(Scheduler).toHaveYielded([
2220 + // Clean up effect A runs passively on unmount.
2221 + // There's no effect B to clean-up, because it never mounted.
2222 + 'Unmount A [1]',
2223 + ]);
2224 });
2225
2226 it('handles errors in destroy on update', () => {
@@ -2292,49 +2253,32 @@ describe('ReactHooksWithNoopRenderer', () => {
2253 expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']);
2254 });
2255
2295 - if (
2296 - deferPassiveEffectCleanupDuringUnmount &&
2297 - runAllPassiveEffectDestroysBeforeCreates
2298 - ) {
2299 - act(() => {
2300 - // This update will trigger an error during passive effect unmount
2301 - ReactNoop.render(<Counter count={1} />, () =>
2302 - Scheduler.unstable_yieldValue('Sync effect'),
2303 - );
2304 - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
2305 - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
2306 - expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops');
2307 -
2308 - // This branch enables a feature flag that flushes all passive destroys in a
2309 - // separate pass before flushing any passive creates.
2310 - // A result of this two-pass flush is that an error thrown from unmount does
2311 - // not block the subsequent create functions from being run.
2312 - expect(Scheduler).toHaveYielded([
2313 - 'Oops!',
2314 - 'Unmount B [0]',
2315 - 'Mount A [1]',
2316 - 'Mount B [1]',
2317 - ]);
2318 - });
2256 + act(() => {
2257 + // This update will trigger an error during passive effect unmount
2258 + ReactNoop.render(<Counter count={1} />, () =>
2259 + Scheduler.unstable_yieldValue('Sync effect'),
2260 + );
2261 + expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']);
2262 + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
2263 + expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops');
2264
2320 - // <Counter> gets unmounted because an error is thrown above.
2321 - // The remaining destroy functions are run later on unmount, since they're passive.
2322 - // In this case, one of them throws again (because of how the test is written).
2323 - expect(Scheduler).toHaveYielded(['Oops!', 'Unmount B [1]']);
2324 - expect(ReactNoop.getChildren()).toEqual([]);
2325 - } else {
2326 - act(() => {
2327 - // This update will trigger an error during passive effect unmount
2328 - ReactNoop.render(<Counter count={1} />, () =>
2329 - Scheduler.unstable_yieldValue('Sync effect'),
2330 - );
2331 - expect(() => {
2332 - expect(Scheduler).toFlushAndYield(['Count: 1', 'Sync effect']);
2333 - }).toThrow('Oops!');
2334 - expect(ReactNoop.getChildren()).toEqual([]);
2335 - ReactNoop.flushPassiveEffects();
2336 - });
2337 - }
2265 + // This branch enables a feature flag that flushes all passive destroys in a
2266 + // separate pass before flushing any passive creates.
2267 + // A result of this two-pass flush is that an error thrown from unmount does
2268 + // not block the subsequent create functions from being run.
2269 + expect(Scheduler).toHaveYielded([
2270 + 'Oops!',
2271 + 'Unmount B [0]',
2272 + 'Mount A [1]',
2273 + 'Mount B [1]',
2274 + ]);
2275 + });
2276 +
2277 + // <Counter> gets unmounted because an error is thrown above.
2278 + // The remaining destroy functions are run later on unmount, since they're passive.
2279 + // In this case, one of them throws again (because of how the test is written).
2280 + expect(Scheduler).toHaveYielded(['Oops!', 'Unmount B [1]']);
2281 + expect(ReactNoop.getChildren()).toEqual([]);
2282 });
2283
2284 it('works with memo', () => {
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+16 -46
@@ -1,5 +1,4 @@
1 let React;
2 -let ReactFeatureFlags;
2 let Fragment;
3 let ReactNoop;
4 let Scheduler;
@@ -14,7 +13,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
13 beforeEach(() => {
14 jest.resetModules();
15
17 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
16 React = require('react');
17 Fragment = React.Fragment;
18 ReactNoop = require('react-noop-renderer');
@@ -1737,26 +1735,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1735
1736 expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1737
1740 - if (
1741 - ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount &&
1742 - ReactFeatureFlags.runAllPassiveEffectDestroysBeforeCreates
1743 - ) {
1744 - expect(Scheduler).toFlushAndYield([
1745 - 'B',
1746 - 'Destroy Layout Effect [Loading...]',
1747 - 'Layout Effect [B]',
1748 - 'Destroy Effect [Loading...]',
1749 - 'Effect [B]',
1750 - ]);
1751 - } else {
1752 - expect(Scheduler).toFlushAndYield([
1753 - 'B',
1754 - 'Destroy Layout Effect [Loading...]',
1755 - 'Destroy Effect [Loading...]',
1756 - 'Layout Effect [B]',
1757 - 'Effect [B]',
1758 - ]);
1759 - }
1738 + expect(Scheduler).toFlushAndYield([
1739 + 'B',
1740 + 'Destroy Layout Effect [Loading...]',
1741 + 'Layout Effect [B]',
1742 + 'Destroy Effect [Loading...]',
1743 + 'Effect [B]',
1744 + ]);
1745
1746 // Update
1747 ReactNoop.renderLegacySyncRoot(<App text="B2" />, () =>
@@ -1786,30 +1771,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1771
1772 expect(Scheduler).toHaveYielded(['Promise resolved [B2]']);
1773
1789 - if (
1790 - ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount &&
1791 - ReactFeatureFlags.runAllPassiveEffectDestroysBeforeCreates
1792 - ) {
1793 - expect(Scheduler).toFlushAndYield([
1794 - 'B2',
1795 - 'Destroy Layout Effect [Loading...]',
1796 - 'Destroy Layout Effect [B]',
1797 - 'Layout Effect [B2]',
1798 - 'Destroy Effect [Loading...]',
1799 - 'Destroy Effect [B]',
1800 - 'Effect [B2]',
1801 - ]);
1802 - } else {
1803 - expect(Scheduler).toFlushAndYield([
1804 - 'B2',
1805 - 'Destroy Layout Effect [Loading...]',
1806 - 'Destroy Effect [Loading...]',
1807 - 'Destroy Layout Effect [B]',
1808 - 'Layout Effect [B2]',
1809 - 'Destroy Effect [B]',
1810 - 'Effect [B2]',
1811 - ]);
1812 - }
1774 + expect(Scheduler).toFlushAndYield([
1775 + 'B2',
1776 + 'Destroy Layout Effect [Loading...]',
1777 + 'Destroy Layout Effect [B]',
1778 + 'Layout Effect [B2]',
1779 + 'Destroy Effect [Loading...]',
1780 + 'Destroy Effect [B]',
1781 + 'Effect [B2]',
1782 + ]);
1783 });
1784
1785 it('suspends for longer if something took a long (CPU bound) time to render', async () => {
packages/react/src/__tests__/ReactProfiler-test.internal.js
+1817 -1923
@@ -24,7 +24,6 @@ let TextResource;
24 let resourcePromise;
25
26 function loadModules({
27 - deferPassiveEffectCleanupDuringUnmount = false,
27 enableProfilerTimer = true,
28 enableProfilerCommitHooks = true,
29 enableSchedulerTracing = true,
@@ -33,8 +32,6 @@ function loadModules({
32 } = {}) {
33 ReactFeatureFlags = require('shared/ReactFeatureFlags');
34
36 - ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount = deferPassiveEffectCleanupDuringUnmount;
37 - ReactFeatureFlags.runAllPassiveEffectDestroysBeforeCreates = deferPassiveEffectCleanupDuringUnmount;
35 ReactFeatureFlags.enableProfilerTimer = enableProfilerTimer;
36 ReactFeatureFlags.enableProfilerCommitHooks = enableProfilerCommitHooks;
37 ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing;
@@ -186,2296 +183,2193 @@ describe('Profiler', () => {
183 });
184 });
185
189 - [true, false].forEach(deferPassiveEffectCleanupDuringUnmount => {
190 - [true, false].forEach(enableSchedulerTracing => {
191 - describe(`onRender enableSchedulerTracing:${
192 - enableSchedulerTracing ? 'enabled' : 'disabled'
193 - } deferPassiveEffectCleanupDuringUnmount:${
194 - deferPassiveEffectCleanupDuringUnmount ? 'enabled' : 'disabled'
195 - }`, () => {
196 - beforeEach(() => {
197 - jest.resetModules();
198 -
199 - loadModules({
200 - deferPassiveEffectCleanupDuringUnmount,
201 - enableSchedulerTracing,
202 - });
203 - });
186 + [true, false].forEach(enableSchedulerTracing => {
187 + describe(`onRender enableSchedulerTracing:${
188 + enableSchedulerTracing ? 'enabled' : 'disabled'
189 + }`, () => {
190 + beforeEach(() => {
191 + jest.resetModules();
192
205 - it('should handle errors thrown', () => {
206 - const callback = jest.fn(id => {
207 - if (id === 'throw') {
208 - throw Error('expected');
209 - }
210 - });
193 + loadModules({
194 + enableSchedulerTracing,
195 + });
196 + });
197
212 - let didMount = false;
213 - class ClassComponent extends React.Component {
214 - componentDidMount() {
215 - didMount = true;
216 - }
217 - render() {
218 - return this.props.children;
219 - }
198 + it('should handle errors thrown', () => {
199 + const callback = jest.fn(id => {
200 + if (id === 'throw') {
201 + throw Error('expected');
202 }
221 -
222 - // Errors thrown from onRender should not break the commit phase,
223 - // Or prevent other lifecycles from being called.
224 - expect(() =>
225 - ReactTestRenderer.create(
226 - <ClassComponent>
227 - <React.Profiler id="do-not-throw" onRender={callback}>
228 - <React.Profiler id="throw" onRender={callback}>
229 - <div />
230 - </React.Profiler>
231 - </React.Profiler>
232 - </ClassComponent>,
233 - ),
234 - ).toThrow('expected');
235 - expect(didMount).toBe(true);
236 - expect(callback).toHaveBeenCalledTimes(2);
203 });
204
239 - it('is not invoked until the commit phase', () => {
240 - const callback = jest.fn();
241 -
242 - const Yield = ({value}) => {
243 - Scheduler.unstable_yieldValue(value);
244 - return null;
245 - };
205 + let didMount = false;
206 + class ClassComponent extends React.Component {
207 + componentDidMount() {
208 + didMount = true;
209 + }
210 + render() {
211 + return this.props.children;
212 + }
213 + }
214
215 + // Errors thrown from onRender should not break the commit phase,
216 + // Or prevent other lifecycles from being called.
217 + expect(() =>
218 ReactTestRenderer.create(
248 - <React.Profiler id="test" onRender={callback}>
249 - <Yield value="first" />
250 - <Yield value="last" />
251 - </React.Profiler>,
252 - {
253 - unstable_isConcurrent: true,
254 - },
255 - );
256 -
257 - // Times are logged until a render is committed.
258 - expect(Scheduler).toFlushAndYieldThrough(['first']);
259 - expect(callback).toHaveBeenCalledTimes(0);
260 - expect(Scheduler).toFlushAndYield(['last']);
261 - expect(callback).toHaveBeenCalledTimes(1);
262 - });
219 + <ClassComponent>
220 + <React.Profiler id="do-not-throw" onRender={callback}>
221 + <React.Profiler id="throw" onRender={callback}>
222 + <div />
223 + </React.Profiler>
224 + </React.Profiler>
225 + </ClassComponent>,
226 + ),
227 + ).toThrow('expected');
228 + expect(didMount).toBe(true);
229 + expect(callback).toHaveBeenCalledTimes(2);
230 + });
231
264 - it('does not record times for components outside of Profiler tree', () => {
265 - // Mock the Scheduler module so we can track how many times the current
266 - // time is read
267 - jest.mock('scheduler', obj => {
268 - const ActualScheduler = require.requireActual(
269 - 'scheduler/unstable_mock',
270 - );
271 - return {
272 - ...ActualScheduler,
273 - unstable_now: function mockUnstableNow() {
274 - ActualScheduler.unstable_yieldValue('read current time');
275 - return ActualScheduler.unstable_now();
276 - },
277 - };
278 - });
232 + it('is not invoked until the commit phase', () => {
233 + const callback = jest.fn();
234
280 - jest.resetModules();
235 + const Yield = ({value}) => {
236 + Scheduler.unstable_yieldValue(value);
237 + return null;
238 + };
239
282 - loadModules({enableSchedulerTracing});
240 + ReactTestRenderer.create(
241 + <React.Profiler id="test" onRender={callback}>
242 + <Yield value="first" />
243 + <Yield value="last" />
244 + </React.Profiler>,
245 + {
246 + unstable_isConcurrent: true,
247 + },
248 + );
249
284 - // Clear yields in case the current time is read during initialization.
285 - Scheduler.unstable_clearYields();
250 + // Times are logged until a render is committed.
251 + expect(Scheduler).toFlushAndYieldThrough(['first']);
252 + expect(callback).toHaveBeenCalledTimes(0);
253 + expect(Scheduler).toFlushAndYield(['last']);
254 + expect(callback).toHaveBeenCalledTimes(1);
255 + });
256
287 - ReactTestRenderer.create(
288 - <div>
289 - <AdvanceTime />
290 - <AdvanceTime />
291 - <AdvanceTime />
292 - <AdvanceTime />
293 - <AdvanceTime />
294 - </div>,
257 + it('does not record times for components outside of Profiler tree', () => {
258 + // Mock the Scheduler module so we can track how many times the current
259 + // time is read
260 + jest.mock('scheduler', obj => {
261 + const ActualScheduler = require.requireActual(
262 + 'scheduler/unstable_mock',
263 );
264 + return {
265 + ...ActualScheduler,
266 + unstable_now: function mockUnstableNow() {
267 + ActualScheduler.unstable_yieldValue('read current time');
268 + return ActualScheduler.unstable_now();
269 + },
270 + };
271 + });
272
297 - // Should be called two times:
298 - // 1. To compute the update expiration time
299 - // 2. To record the commit time
300 - // No additional calls from ProfilerTimer are expected.
301 - expect(Scheduler).toHaveYielded(
302 - gate(flags =>
303 - flags.new
304 - ? [
305 - // The new reconciler reads the current time in more places,
306 - // to detect starvation. This is unrelated to the profiler,
307 - // which happens to use the same Scheduler method that we
308 - // mocked above. We should rewrite this test so that it's
309 - // less fragile.
310 - 'read current time',
311 - 'read current time',
312 - 'read current time',
313 - 'read current time',
314 - ]
315 - : ['read current time', 'read current time'],
316 - ),
317 - );
273 + jest.resetModules();
274
319 - // Restore original mock
320 - jest.mock('scheduler', () =>
321 - require.requireActual('scheduler/unstable_mock'),
322 - );
323 - });
275 + loadModules({enableSchedulerTracing});
276
325 - it('does not report work done on a sibling', () => {
326 - const callback = jest.fn();
277 + // Clear yields in case the current time is read during initialization.
278 + Scheduler.unstable_clearYields();
279
328 - const DoesNotUpdate = React.memo(
329 - function DoesNotUpdateInner() {
330 - Scheduler.unstable_advanceTime(10);
331 - return null;
332 - },
333 - () => true,
334 - );
280 + ReactTestRenderer.create(
281 + <div>
282 + <AdvanceTime />
283 + <AdvanceTime />
284 + <AdvanceTime />
285 + <AdvanceTime />
286 + <AdvanceTime />
287 + </div>,
288 + );
289
336 - let updateProfilerSibling;
290 + // Should be called two times:
291 + // 1. To compute the update expiration time
292 + // 2. To record the commit time
293 + // No additional calls from ProfilerTimer are expected.
294 + expect(Scheduler).toHaveYielded(
295 + gate(flags =>
296 + flags.new
297 + ? [
298 + // The new reconciler reads the current time in more places,
299 + // to detect starvation. This is unrelated to the profiler,
300 + // which happens to use the same Scheduler method that we
301 + // mocked above. We should rewrite this test so that it's
302 + // less fragile.
303 + 'read current time',
304 + 'read current time',
305 + 'read current time',
306 + 'read current time',
307 + ]
308 + : ['read current time', 'read current time'],
309 + ),
310 + );
311 +
312 + // Restore original mock
313 + jest.mock('scheduler', () =>
314 + require.requireActual('scheduler/unstable_mock'),
315 + );
316 + });
317
338 - function ProfilerSibling() {
339 - const [count, setCount] = React.useState(0);
340 - updateProfilerSibling = () => setCount(count + 1);
318 + it('does not report work done on a sibling', () => {
319 + const callback = jest.fn();
320 +
321 + const DoesNotUpdate = React.memo(
322 + function DoesNotUpdateInner() {
323 + Scheduler.unstable_advanceTime(10);
324 return null;
342 - }
325 + },
326 + () => true,
327 + );
328
344 - function App() {
345 - return (
346 - <React.Fragment>
347 - <React.Profiler id="test" onRender={callback}>
348 - <DoesNotUpdate />
349 - </React.Profiler>
350 - <ProfilerSibling />
351 - </React.Fragment>
352 - );
353 - }
329 + let updateProfilerSibling;
330
355 - const renderer = ReactTestRenderer.create(<App />);
331 + function ProfilerSibling() {
332 + const [count, setCount] = React.useState(0);
333 + updateProfilerSibling = () => setCount(count + 1);
334 + return null;
335 + }
336
357 - expect(callback).toHaveBeenCalledTimes(1);
337 + function App() {
338 + return (
339 + <React.Fragment>
340 + <React.Profiler id="test" onRender={callback}>
341 + <DoesNotUpdate />
342 + </React.Profiler>
343 + <ProfilerSibling />
344 + </React.Fragment>
345 + );
346 + }
347
359 - let call = callback.mock.calls[0];
348 + const renderer = ReactTestRenderer.create(<App />);
349
361 - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
362 - expect(call[0]).toBe('test');
363 - expect(call[1]).toBe('mount');
364 - expect(call[2]).toBe(10); // actual time
365 - expect(call[3]).toBe(10); // base time
366 - expect(call[4]).toBe(0); // start time
367 - expect(call[5]).toBe(10); // commit time
368 - expect(call[6]).toEqual(
369 - enableSchedulerTracing ? new Set() : undefined,
370 - ); // interaction events
350 + expect(callback).toHaveBeenCalledTimes(1);
351
372 - callback.mockReset();
352 + let call = callback.mock.calls[0];
353
374 - Scheduler.unstable_advanceTime(20); // 10 -> 30
354 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
355 + expect(call[0]).toBe('test');
356 + expect(call[1]).toBe('mount');
357 + expect(call[2]).toBe(10); // actual time
358 + expect(call[3]).toBe(10); // base time
359 + expect(call[4]).toBe(0); // start time
360 + expect(call[5]).toBe(10); // commit time
361 + expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
362
376 - // Updating a parent should report a re-render,
377 - // since React technically did a little bit of work between the Profiler and the bailed out subtree.
378 - renderer.update(<App />);
363 + callback.mockReset();
364
380 - expect(callback).toHaveBeenCalledTimes(1);
365 + Scheduler.unstable_advanceTime(20); // 10 -> 30
366
382 - call = callback.mock.calls[0];
367 + // Updating a parent should report a re-render,
368 + // since React technically did a little bit of work between the Profiler and the bailed out subtree.
369 + renderer.update(<App />);
370
384 - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
385 - expect(call[0]).toBe('test');
386 - expect(call[1]).toBe('update');
387 - expect(call[2]).toBe(0); // actual time
388 - expect(call[3]).toBe(10); // base time
389 - expect(call[4]).toBe(30); // start time
390 - expect(call[5]).toBe(30); // commit time
391 - expect(call[6]).toEqual(
392 - enableSchedulerTracing ? new Set() : undefined,
393 - ); // interaction events
371 + expect(callback).toHaveBeenCalledTimes(1);
372
395 - callback.mockReset();
373 + call = callback.mock.calls[0];
374
397 - Scheduler.unstable_advanceTime(20); // 30 -> 50
375 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
376 + expect(call[0]).toBe('test');
377 + expect(call[1]).toBe('update');
378 + expect(call[2]).toBe(0); // actual time
379 + expect(call[3]).toBe(10); // base time
380 + expect(call[4]).toBe(30); // start time
381 + expect(call[5]).toBe(30); // commit time
382 + expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
383
399 - // Updating a sibling should not report a re-render.
400 - ReactTestRenderer.act(updateProfilerSibling);
384 + callback.mockReset();
385
402 - expect(callback).not.toHaveBeenCalled();
403 - });
386 + Scheduler.unstable_advanceTime(20); // 30 -> 50
387
405 - it('logs render times for both mount and update', () => {
406 - const callback = jest.fn();
388 + // Updating a sibling should not report a re-render.
389 + ReactTestRenderer.act(updateProfilerSibling);
390
408 - Scheduler.unstable_advanceTime(5); // 0 -> 5
391 + expect(callback).not.toHaveBeenCalled();
392 + });
393
410 - const renderer = ReactTestRenderer.create(
411 - <React.Profiler id="test" onRender={callback}>
412 - <AdvanceTime />
413 - </React.Profiler>,
414 - );
394 + it('logs render times for both mount and update', () => {
395 + const callback = jest.fn();
396
416 - expect(callback).toHaveBeenCalledTimes(1);
397 + Scheduler.unstable_advanceTime(5); // 0 -> 5
398
418 - let [call] = callback.mock.calls;
399 + const renderer = ReactTestRenderer.create(
400 + <React.Profiler id="test" onRender={callback}>
401 + <AdvanceTime />
402 + </React.Profiler>,
403 + );
404
420 - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
421 - expect(call[0]).toBe('test');
422 - expect(call[1]).toBe('mount');
423 - expect(call[2]).toBe(10); // actual time
424 - expect(call[3]).toBe(10); // base time
425 - expect(call[4]).toBe(5); // start time
426 - expect(call[5]).toBe(15); // commit time
427 - expect(call[6]).toEqual(
428 - enableSchedulerTracing ? new Set() : undefined,
429 - ); // interaction events
405 + expect(callback).toHaveBeenCalledTimes(1);
406
431 - callback.mockReset();
407 + let [call] = callback.mock.calls;
408
433 - Scheduler.unstable_advanceTime(20); // 15 -> 35
409 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
410 + expect(call[0]).toBe('test');
411 + expect(call[1]).toBe('mount');
412 + expect(call[2]).toBe(10); // actual time
413 + expect(call[3]).toBe(10); // base time
414 + expect(call[4]).toBe(5); // start time
415 + expect(call[5]).toBe(15); // commit time
416 + expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
417
435 - renderer.update(
436 - <React.Profiler id="test" onRender={callback}>
437 - <AdvanceTime />
438 - </React.Profiler>,
439 - );
418 + callback.mockReset();
419
441 - expect(callback).toHaveBeenCalledTimes(1);
420 + Scheduler.unstable_advanceTime(20); // 15 -> 35
421
443 - [call] = callback.mock.calls;
422 + renderer.update(
423 + <React.Profiler id="test" onRender={callback}>
424 + <AdvanceTime />
425 + </React.Profiler>,
426 + );
427
445 - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
446 - expect(call[0]).toBe('test');
447 - expect(call[1]).toBe('update');
448 - expect(call[2]).toBe(10); // actual time
449 - expect(call[3]).toBe(10); // base time
450 - expect(call[4]).toBe(35); // start time
451 - expect(call[5]).toBe(45); // commit time
452 - expect(call[6]).toEqual(
453 - enableSchedulerTracing ? new Set() : undefined,
454 - ); // interaction events
428 + expect(callback).toHaveBeenCalledTimes(1);
429
456 - callback.mockReset();
430 + [call] = callback.mock.calls;
431
458 - Scheduler.unstable_advanceTime(20); // 45 -> 65
432 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
433 + expect(call[0]).toBe('test');
434 + expect(call[1]).toBe('update');
435 + expect(call[2]).toBe(10); // actual time
436 + expect(call[3]).toBe(10); // base time
437 + expect(call[4]).toBe(35); // start time
438 + expect(call[5]).toBe(45); // commit time
439 + expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
440
460 - renderer.update(
461 - <React.Profiler id="test" onRender={callback}>
462 - <AdvanceTime byAmount={4} />
463 - </React.Profiler>,
464 - );
441 + callback.mockReset();
442
466 - expect(callback).toHaveBeenCalledTimes(1);
443 + Scheduler.unstable_advanceTime(20); // 45 -> 65
444
468 - [call] = callback.mock.calls;
445 + renderer.update(
446 + <React.Profiler id="test" onRender={callback}>
447 + <AdvanceTime byAmount={4} />
448 + </React.Profiler>,
449 + );
450
470 - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
471 - expect(call[0]).toBe('test');
472 - expect(call[1]).toBe('update');
473 - expect(call[2]).toBe(4); // actual time
474 - expect(call[3]).toBe(4); // base time
475 - expect(call[4]).toBe(65); // start time
476 - expect(call[5]).toBe(69); // commit time
477 - expect(call[6]).toEqual(
478 - enableSchedulerTracing ? new Set() : undefined,
479 - ); // interaction events
480 - });
451 + expect(callback).toHaveBeenCalledTimes(1);
452
482 - it('includes render times of nested Profilers in their parent times', () => {
483 - const callback = jest.fn();
453 + [call] = callback.mock.calls;
454
485 - Scheduler.unstable_advanceTime(5); // 0 -> 5
455 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
456 + expect(call[0]).toBe('test');
457 + expect(call[1]).toBe('update');
458 + expect(call[2]).toBe(4); // actual time
459 + expect(call[3]).toBe(4); // base time
460 + expect(call[4]).toBe(65); // start time
461 + expect(call[5]).toBe(69); // commit time
462 + expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
463 + });
464
487 - ReactTestRenderer.create(
488 - <React.Fragment>
489 - <React.Profiler id="parent" onRender={callback}>
490 - <AdvanceTime byAmount={10}>
491 - <React.Profiler id="child" onRender={callback}>
492 - <AdvanceTime byAmount={20} />
493 - </React.Profiler>
494 - </AdvanceTime>
495 - </React.Profiler>
496 - </React.Fragment>,
497 - );
465 + it('includes render times of nested Profilers in their parent times', () => {
466 + const callback = jest.fn();
467
499 - expect(callback).toHaveBeenCalledTimes(2);
468 + Scheduler.unstable_advanceTime(5); // 0 -> 5
469
501 - // Callbacks bubble (reverse order).
502 - const [childCall, parentCall] = callback.mock.calls;
503 - expect(childCall[0]).toBe('child');
504 - expect(parentCall[0]).toBe('parent');
505 -
506 - // Parent times should include child times
507 - expect(childCall[2]).toBe(20); // actual time
508 - expect(childCall[3]).toBe(20); // base time
509 - expect(childCall[4]).toBe(15); // start time
510 - expect(childCall[5]).toBe(35); // commit time
511 - expect(parentCall[2]).toBe(30); // actual time
512 - expect(parentCall[3]).toBe(30); // base time
513 - expect(parentCall[4]).toBe(5); // start time
514 - expect(parentCall[5]).toBe(35); // commit time
515 - });
470 + ReactTestRenderer.create(
471 + <React.Fragment>
472 + <React.Profiler id="parent" onRender={callback}>
473 + <AdvanceTime byAmount={10}>
474 + <React.Profiler id="child" onRender={callback}>
475 + <AdvanceTime byAmount={20} />
476 + </React.Profiler>
477 + </AdvanceTime>
478 + </React.Profiler>
479 + </React.Fragment>,
480 + );
481
517 - it('traces sibling Profilers separately', () => {
518 - const callback = jest.fn();
482 + expect(callback).toHaveBeenCalledTimes(2);
483 +
484 + // Callbacks bubble (reverse order).
485 + const [childCall, parentCall] = callback.mock.calls;
486 + expect(childCall[0]).toBe('child');
487 + expect(parentCall[0]).toBe('parent');
488 +
489 + // Parent times should include child times
490 + expect(childCall[2]).toBe(20); // actual time
491 + expect(childCall[3]).toBe(20); // base time
492 + expect(childCall[4]).toBe(15); // start time
493 + expect(childCall[5]).toBe(35); // commit time
494 + expect(parentCall[2]).toBe(30); // actual time
495 + expect(parentCall[3]).toBe(30); // base time
496 + expect(parentCall[4]).toBe(5); // start time
497 + expect(parentCall[5]).toBe(35); // commit time
498 + });
499
520 - Scheduler.unstable_advanceTime(5); // 0 -> 5
500 + it('traces sibling Profilers separately', () => {
501 + const callback = jest.fn();
502
522 - ReactTestRenderer.create(
523 - <React.Fragment>
524 - <React.Profiler id="first" onRender={callback}>
525 - <AdvanceTime byAmount={20} />
526 - </React.Profiler>
527 - <React.Profiler id="second" onRender={callback}>
528 - <AdvanceTime byAmount={5} />
529 - </React.Profiler>
530 - </React.Fragment>,
531 - );
503 + Scheduler.unstable_advanceTime(5); // 0 -> 5
504
533 - expect(callback).toHaveBeenCalledTimes(2);
505 + ReactTestRenderer.create(
506 + <React.Fragment>
507 + <React.Profiler id="first" onRender={callback}>
508 + <AdvanceTime byAmount={20} />
509 + </React.Profiler>
510 + <React.Profiler id="second" onRender={callback}>
511 + <AdvanceTime byAmount={5} />
512 + </React.Profiler>
513 + </React.Fragment>,
514 + );
515
535 - const [firstCall, secondCall] = callback.mock.calls;
536 - expect(firstCall[0]).toBe('first');
537 - expect(secondCall[0]).toBe('second');
538 -
539 - // Parent times should include child times
540 - expect(firstCall[2]).toBe(20); // actual time
541 - expect(firstCall[3]).toBe(20); // base time
542 - expect(firstCall[4]).toBe(5); // start time
543 - expect(firstCall[5]).toBe(30); // commit time
544 - expect(secondCall[2]).toBe(5); // actual time
545 - expect(secondCall[3]).toBe(5); // base time
546 - expect(secondCall[4]).toBe(25); // start time
547 - expect(secondCall[5]).toBe(30); // commit time
548 - });
516 + expect(callback).toHaveBeenCalledTimes(2);
517 +
518 + const [firstCall, secondCall] = callback.mock.calls;
519 + expect(firstCall[0]).toBe('first');
520 + expect(secondCall[0]).toBe('second');
521 +
522 + // Parent times should include child times
523 + expect(firstCall[2]).toBe(20); // actual time
524 + expect(firstCall[3]).toBe(20); // base time
525 + expect(firstCall[4]).toBe(5); // start time
526 + expect(firstCall[5]).toBe(30); // commit time
527 + expect(secondCall[2]).toBe(5); // actual time
528 + expect(secondCall[3]).toBe(5); // base time
529 + expect(secondCall[4]).toBe(25); // start time
530 + expect(secondCall[5]).toBe(30); // commit time
531 + });
532
550 - it('does not include time spent outside of profile root', () => {
551 - const callback = jest.fn();
533 + it('does not include time spent outside of profile root', () => {
534 + const callback = jest.fn();
535
553 - Scheduler.unstable_advanceTime(5); // 0 -> 5
536 + Scheduler.unstable_advanceTime(5); // 0 -> 5
537
555 - ReactTestRenderer.create(
556 - <React.Fragment>
557 - <AdvanceTime byAmount={20} />
558 - <React.Profiler id="test" onRender={callback}>
559 - <AdvanceTime byAmount={5} />
560 - </React.Profiler>
561 - <AdvanceTime byAmount={20} />
562 - </React.Fragment>,
563 - );
538 + ReactTestRenderer.create(
539 + <React.Fragment>
540 + <AdvanceTime byAmount={20} />
541 + <React.Profiler id="test" onRender={callback}>
542 + <AdvanceTime byAmount={5} />
543 + </React.Profiler>
544 + <AdvanceTime byAmount={20} />
545 + </React.Fragment>,
546 + );
547
565 - expect(callback).toHaveBeenCalledTimes(1);
548 + expect(callback).toHaveBeenCalledTimes(1);
549
567 - const [call] = callback.mock.calls;
568 - expect(call[0]).toBe('test');
569 - expect(call[2]).toBe(5); // actual time
570 - expect(call[3]).toBe(5); // base time
571 - expect(call[4]).toBe(25); // start time
572 - expect(call[5]).toBe(50); // commit time
573 - });
550 + const [call] = callback.mock.calls;
551 + expect(call[0]).toBe('test');
552 + expect(call[2]).toBe(5); // actual time
553 + expect(call[3]).toBe(5); // base time
554 + expect(call[4]).toBe(25); // start time
555 + expect(call[5]).toBe(50); // commit time
556 + });
557
575 - it('is not called when blocked by sCU false', () => {
576 - const callback = jest.fn();
558 + it('is not called when blocked by sCU false', () => {
559 + const callback = jest.fn();
560
578 - let instance;
579 - class Updater extends React.Component {
580 - state = {};
581 - render() {
582 - instance = this;
583 - return this.props.children;
584 - }
561 + let instance;
562 + class Updater extends React.Component {
563 + state = {};
564 + render() {
565 + instance = this;
566 + return this.props.children;
567 }
568 + }
569
587 - const renderer = ReactTestRenderer.create(
588 - <React.Profiler id="outer" onRender={callback}>
589 - <Updater>
590 - <React.Profiler id="inner" onRender={callback}>
591 - <div />
592 - </React.Profiler>
593 - </Updater>
594 - </React.Profiler>,
595 - );
570 + const renderer = ReactTestRenderer.create(
571 + <React.Profiler id="outer" onRender={callback}>
572 + <Updater>
573 + <React.Profiler id="inner" onRender={callback}>
574 + <div />
575 + </React.Profiler>
576 + </Updater>
577 + </React.Profiler>,
578 + );
579
597 - // All profile callbacks are called for initial render
598 - expect(callback).toHaveBeenCalledTimes(2);
580 + // All profile callbacks are called for initial render
581 + expect(callback).toHaveBeenCalledTimes(2);
582
600 - callback.mockReset();
583 + callback.mockReset();
584
602 - renderer.unstable_flushSync(() => {
603 - instance.setState({
604 - count: 1,
605 - });
585 + renderer.unstable_flushSync(() => {
586 + instance.setState({
587 + count: 1,
588 });
607 -
608 - // Only call onRender for paths that have re-rendered.
609 - // Since the Updater's props didn't change,
610 - // React does not re-render its children.
611 - expect(callback).toHaveBeenCalledTimes(1);
612 - expect(callback.mock.calls[0][0]).toBe('outer');
589 });
590
615 - it('decreases actual time but not base time when sCU prevents an update', () => {
616 - const callback = jest.fn();
591 + // Only call onRender for paths that have re-rendered.
592 + // Since the Updater's props didn't change,
593 + // React does not re-render its children.
594 + expect(callback).toHaveBeenCalledTimes(1);
595 + expect(callback.mock.calls[0][0]).toBe('outer');
596 + });
597
618 - Scheduler.unstable_advanceTime(5); // 0 -> 5
598 + it('decreases actual time but not base time when sCU prevents an update', () => {
599 + const callback = jest.fn();
600
620 - const renderer = ReactTestRenderer.create(
621 - <React.Profiler id="test" onRender={callback}>
622 - <AdvanceTime byAmount={10}>
623 - <AdvanceTime byAmount={13} shouldComponentUpdate={false} />
624 - </AdvanceTime>
625 - </React.Profiler>,
626 - );
601 + Scheduler.unstable_advanceTime(5); // 0 -> 5
602
628 - expect(callback).toHaveBeenCalledTimes(1);
603 + const renderer = ReactTestRenderer.create(
604 + <React.Profiler id="test" onRender={callback}>
605 + <AdvanceTime byAmount={10}>
606 + <AdvanceTime byAmount={13} shouldComponentUpdate={false} />
607 + </AdvanceTime>
608 + </React.Profiler>,
609 + );
610
630 - Scheduler.unstable_advanceTime(30); // 28 -> 58
611 + expect(callback).toHaveBeenCalledTimes(1);
612
632 - renderer.update(
633 - <React.Profiler id="test" onRender={callback}>
634 - <AdvanceTime byAmount={4}>
635 - <AdvanceTime byAmount={7} shouldComponentUpdate={false} />
636 - </AdvanceTime>
637 - </React.Profiler>,
638 - );
613 + Scheduler.unstable_advanceTime(30); // 28 -> 58
614
640 - expect(callback).toHaveBeenCalledTimes(2);
615 + renderer.update(
616 + <React.Profiler id="test" onRender={callback}>
617 + <AdvanceTime byAmount={4}>
618 + <AdvanceTime byAmount={7} shouldComponentUpdate={false} />
619 + </AdvanceTime>
620 + </React.Profiler>,
621 + );
622
642 - const [mountCall, updateCall] = callback.mock.calls;
623 + expect(callback).toHaveBeenCalledTimes(2);
624
644 - expect(mountCall[1]).toBe('mount');
645 - expect(mountCall[2]).toBe(23); // actual time
646 - expect(mountCall[3]).toBe(23); // base time
647 - expect(mountCall[4]).toBe(5); // start time
648 - expect(mountCall[5]).toBe(28); // commit time
625 + const [mountCall, updateCall] = callback.mock.calls;
626
650 - expect(updateCall[1]).toBe('update');
651 - expect(updateCall[2]).toBe(4); // actual time
652 - expect(updateCall[3]).toBe(17); // base time
653 - expect(updateCall[4]).toBe(58); // start time
654 - expect(updateCall[5]).toBe(62); // commit time
655 - });
627 + expect(mountCall[1]).toBe('mount');
628 + expect(mountCall[2]).toBe(23); // actual time
629 + expect(mountCall[3]).toBe(23); // base time
630 + expect(mountCall[4]).toBe(5); // start time
631 + expect(mountCall[5]).toBe(28); // commit time
632
657 - it('includes time spent in render phase lifecycles', () => {
658 - class WithLifecycles extends React.Component {
659 - state = {};
660 - static getDerivedStateFromProps() {
661 - Scheduler.unstable_advanceTime(3);
662 - return null;
663 - }
664 - shouldComponentUpdate() {
665 - Scheduler.unstable_advanceTime(7);
666 - return true;
667 - }
668 - render() {
669 - Scheduler.unstable_advanceTime(5);
670 - return null;
671 - }
633 + expect(updateCall[1]).toBe('update');
634 + expect(updateCall[2]).toBe(4); // actual time
635 + expect(updateCall[3]).toBe(17); // base time
636 + expect(updateCall[4]).toBe(58); // start time
637 + expect(updateCall[5]).toBe(62); // commit time
638 + });
639 +
640 + it('includes time spent in render phase lifecycles', () => {
641 + class WithLifecycles extends React.Component {
642 + state = {};
643 + static getDerivedStateFromProps() {
644 + Scheduler.unstable_advanceTime(3);
645 + return null;
646 + }
647 + shouldComponentUpdate() {
648 + Scheduler.unstable_advanceTime(7);
649 + return true;
650 + }
651 + render() {
652 + Scheduler.unstable_advanceTime(5);
653 + return null;
654 }
655 + }
656
674 - const callback = jest.fn();
657 + const callback = jest.fn();
658
676 - Scheduler.unstable_advanceTime(5); // 0 -> 5
659 + Scheduler.unstable_advanceTime(5); // 0 -> 5
660
678 - const renderer = ReactTestRenderer.create(
679 - <React.Profiler id="test" onRender={callback}>
680 - <WithLifecycles />
681 - </React.Profiler>,
682 - );
661 + const renderer = ReactTestRenderer.create(
662 + <React.Profiler id="test" onRender={callback}>
663 + <WithLifecycles />
664 + </React.Profiler>,
665 + );
666
684 - Scheduler.unstable_advanceTime(15); // 13 -> 28
667 + Scheduler.unstable_advanceTime(15); // 13 -> 28
668
686 - renderer.update(
687 - <React.Profiler id="test" onRender={callback}>
688 - <WithLifecycles />
689 - </React.Profiler>,
690 - );
669 + renderer.update(
670 + <React.Profiler id="test" onRender={callback}>
671 + <WithLifecycles />
672 + </React.Profiler>,
673 + );
674
692 - expect(callback).toHaveBeenCalledTimes(2);
675 + expect(callback).toHaveBeenCalledTimes(2);
676
694 - const [mountCall, updateCall] = callback.mock.calls;
677 + const [mountCall, updateCall] = callback.mock.calls;
678
696 - expect(mountCall[1]).toBe('mount');
697 - expect(mountCall[2]).toBe(8); // actual time
698 - expect(mountCall[3]).toBe(8); // base time
699 - expect(mountCall[4]).toBe(5); // start time
700 - expect(mountCall[5]).toBe(13); // commit time
679 + expect(mountCall[1]).toBe('mount');
680 + expect(mountCall[2]).toBe(8); // actual time
681 + expect(mountCall[3]).toBe(8); // base time
682 + expect(mountCall[4]).toBe(5); // start time
683 + expect(mountCall[5]).toBe(13); // commit time
684
702 - expect(updateCall[1]).toBe('update');
703 - expect(updateCall[2]).toBe(15); // actual time
704 - expect(updateCall[3]).toBe(15); // base time
705 - expect(updateCall[4]).toBe(28); // start time
706 - expect(updateCall[5]).toBe(43); // commit time
707 - });
685 + expect(updateCall[1]).toBe('update');
686 + expect(updateCall[2]).toBe(15); // actual time
687 + expect(updateCall[3]).toBe(15); // base time
688 + expect(updateCall[4]).toBe(28); // start time
689 + expect(updateCall[5]).toBe(43); // commit time
690 + });
691
709 - describe('with regard to interruptions', () => {
710 - it('should accumulate actual time after a scheduling interruptions', () => {
711 - const callback = jest.fn();
692 + describe('with regard to interruptions', () => {
693 + it('should accumulate actual time after a scheduling interruptions', () => {
694 + const callback = jest.fn();
695
713 - const Yield = ({renderTime}) => {
714 - Scheduler.unstable_advanceTime(renderTime);
715 - Scheduler.unstable_yieldValue('Yield:' + renderTime);
716 - return null;
717 - };
696 + const Yield = ({renderTime}) => {
697 + Scheduler.unstable_advanceTime(renderTime);
698 + Scheduler.unstable_yieldValue('Yield:' + renderTime);
699 + return null;
700 + };
701
719 - Scheduler.unstable_advanceTime(5); // 0 -> 5
720 -
721 - // Render partially, but run out of time before completing.
722 - ReactTestRenderer.create(
723 - <React.Profiler id="test" onRender={callback}>
724 - <Yield renderTime={2} />
725 - <Yield renderTime={3} />
726 - </React.Profiler>,
727 - {unstable_isConcurrent: true},
728 - );
729 - expect(Scheduler).toFlushAndYieldThrough(['Yield:2']);
730 - expect(callback).toHaveBeenCalledTimes(0);
731 -
732 - // Resume render for remaining children.
733 - expect(Scheduler).toFlushAndYield(['Yield:3']);
734 -
735 - // Verify that logged times include both durations above.
736 - expect(callback).toHaveBeenCalledTimes(1);
737 - const [call] = callback.mock.calls;
738 - expect(call[2]).toBe(5); // actual time
739 - expect(call[3]).toBe(5); // base time
740 - expect(call[4]).toBe(5); // start time
741 - expect(call[5]).toBe(10); // commit time
742 - });
743 -
744 - it('should not include time between frames', () => {
745 - const callback = jest.fn();
746 -
747 - const Yield = ({renderTime}) => {
748 - Scheduler.unstable_advanceTime(renderTime);
749 - Scheduler.unstable_yieldValue('Yield:' + renderTime);
750 - return null;
751 - };
702 + Scheduler.unstable_advanceTime(5); // 0 -> 5
703
753 - Scheduler.unstable_advanceTime(5); // 0 -> 5
704 + // Render partially, but run out of time before completing.
705 + ReactTestRenderer.create(
706 + <React.Profiler id="test" onRender={callback}>
707 + <Yield renderTime={2} />
708 + <Yield renderTime={3} />
709 + </React.Profiler>,
710 + {unstable_isConcurrent: true},
711 + );
712 + expect(Scheduler).toFlushAndYieldThrough(['Yield:2']);
713 + expect(callback).toHaveBeenCalledTimes(0);
714
755 - // Render partially, but don't finish.
756 - // This partial render should take 5ms of simulated time.
757 - ReactTestRenderer.create(
758 - <React.Profiler id="outer" onRender={callback}>
759 - <Yield renderTime={5} />
760 - <Yield renderTime={10} />
761 - <React.Profiler id="inner" onRender={callback}>
762 - <Yield renderTime={17} />
763 - </React.Profiler>
764 - </React.Profiler>,
765 - {unstable_isConcurrent: true},
766 - );
767 - expect(Scheduler).toFlushAndYieldThrough(['Yield:5']);
768 - expect(callback).toHaveBeenCalledTimes(0);
769 -
770 - // Simulate time moving forward while frame is paused.
771 - Scheduler.unstable_advanceTime(50); // 10 -> 60
772 -
773 - // Flush the remaining work,
774 - // Which should take an additional 10ms of simulated time.
775 - expect(Scheduler).toFlushAndYield(['Yield:10', 'Yield:17']);
776 - expect(callback).toHaveBeenCalledTimes(2);
777 -
778 - const [innerCall, outerCall] = callback.mock.calls;
779 -
780 - // Verify that the actual time includes all work times,
781 - // But not the time that elapsed between frames.
782 - expect(innerCall[0]).toBe('inner');
783 - expect(innerCall[2]).toBe(17); // actual time
784 - expect(innerCall[3]).toBe(17); // base time
785 - expect(innerCall[4]).toBe(70); // start time
786 - expect(innerCall[5]).toBe(87); // commit time
787 - expect(outerCall[0]).toBe('outer');
788 - expect(outerCall[2]).toBe(32); // actual time
789 - expect(outerCall[3]).toBe(32); // base time
790 - expect(outerCall[4]).toBe(5); // start time
791 - expect(outerCall[5]).toBe(87); // commit time
792 - });
715 + // Resume render for remaining children.
716 + expect(Scheduler).toFlushAndYield(['Yield:3']);
717
794 - it('should report the expected times when a high-pri update replaces a mount in-progress', () => {
795 - const callback = jest.fn();
718 + // Verify that logged times include both durations above.
719 + expect(callback).toHaveBeenCalledTimes(1);
720 + const [call] = callback.mock.calls;
721 + expect(call[2]).toBe(5); // actual time
722 + expect(call[3]).toBe(5); // base time
723 + expect(call[4]).toBe(5); // start time
724 + expect(call[5]).toBe(10); // commit time
725 + });
726
797 - const Yield = ({renderTime}) => {
798 - Scheduler.unstable_advanceTime(renderTime);
799 - Scheduler.unstable_yieldValue('Yield:' + renderTime);
800 - return null;
801 - };
727 + it('should not include time between frames', () => {
728 + const callback = jest.fn();
729
803 - Scheduler.unstable_advanceTime(5); // 0 -> 5
730 + const Yield = ({renderTime}) => {
731 + Scheduler.unstable_advanceTime(renderTime);
732 + Scheduler.unstable_yieldValue('Yield:' + renderTime);
733 + return null;
734 + };
735
805 - // Render a partially update, but don't finish.
806 - // This partial render should take 10ms of simulated time.
807 - const renderer = ReactTestRenderer.create(
808 - <React.Profiler id="test" onRender={callback}>
809 - <Yield renderTime={10} />
810 - <Yield renderTime={20} />
811 - </React.Profiler>,
812 - {unstable_isConcurrent: true},
813 - );
814 - expect(Scheduler).toFlushAndYieldThrough(['Yield:10']);
815 - expect(callback).toHaveBeenCalledTimes(0);
736 + Scheduler.unstable_advanceTime(5); // 0 -> 5
737
817 - // Simulate time moving forward while frame is paused.
818 - Scheduler.unstable_advanceTime(100); // 15 -> 115
738 + // Render partially, but don't finish.
739 + // This partial render should take 5ms of simulated time.
740 + ReactTestRenderer.create(
741 + <React.Profiler id="outer" onRender={callback}>
742 + <Yield renderTime={5} />
743 + <Yield renderTime={10} />
744 + <React.Profiler id="inner" onRender={callback}>
745 + <Yield renderTime={17} />
746 + </React.Profiler>
747 + </React.Profiler>,
748 + {unstable_isConcurrent: true},
749 + );
750 + expect(Scheduler).toFlushAndYieldThrough(['Yield:5']);
751 + expect(callback).toHaveBeenCalledTimes(0);
752
820 - // Interrupt with higher priority work.
821 - // The interrupted work simulates an additional 5ms of time.
822 - renderer.unstable_flushSync(() => {
823 - renderer.update(
824 - <React.Profiler id="test" onRender={callback}>
825 - <Yield renderTime={5} />
826 - </React.Profiler>,
827 - );
828 - });
829 - expect(Scheduler).toHaveYielded(['Yield:5']);
830 -
831 - // The initial work was thrown away in this case,
832 - // So the actual and base times should only include the final rendered tree times.
833 - expect(callback).toHaveBeenCalledTimes(1);
834 - const call = callback.mock.calls[0];
835 - expect(call[2]).toBe(5); // actual time
836 - expect(call[3]).toBe(5); // base time
837 - expect(call[4]).toBe(115); // start time
838 - expect(call[5]).toBe(120); // commit time
839 -
840 - callback.mockReset();
841 -
842 - // Verify no more unexpected callbacks from low priority work
843 - expect(Scheduler).toFlushWithoutYielding();
844 - expect(callback).toHaveBeenCalledTimes(0);
845 - });
753 + // Simulate time moving forward while frame is paused.
754 + Scheduler.unstable_advanceTime(50); // 10 -> 60
755
847 - it('should report the expected times when a high-priority update replaces a low-priority update', () => {
848 - const callback = jest.fn();
756 + // Flush the remaining work,
757 + // Which should take an additional 10ms of simulated time.
758 + expect(Scheduler).toFlushAndYield(['Yield:10', 'Yield:17']);
759 + expect(callback).toHaveBeenCalledTimes(2);
760
850 - const Yield = ({renderTime}) => {
851 - Scheduler.unstable_advanceTime(renderTime);
852 - Scheduler.unstable_yieldValue('Yield:' + renderTime);
853 - return null;
854 - };
761 + const [innerCall, outerCall] = callback.mock.calls;
762 +
763 + // Verify that the actual time includes all work times,
764 + // But not the time that elapsed between frames.
765 + expect(innerCall[0]).toBe('inner');
766 + expect(innerCall[2]).toBe(17); // actual time
767 + expect(innerCall[3]).toBe(17); // base time
768 + expect(innerCall[4]).toBe(70); // start time
769 + expect(innerCall[5]).toBe(87); // commit time
770 + expect(outerCall[0]).toBe('outer');
771 + expect(outerCall[2]).toBe(32); // actual time
772 + expect(outerCall[3]).toBe(32); // base time
773 + expect(outerCall[4]).toBe(5); // start time
774 + expect(outerCall[5]).toBe(87); // commit time
775 + });
776
856 - Scheduler.unstable_advanceTime(5); // 0 -> 5
777 + it('should report the expected times when a high-pri update replaces a mount in-progress', () => {
778 + const callback = jest.fn();
779
858 - const renderer = ReactTestRenderer.create(
859 - <React.Profiler id="test" onRender={callback}>
860 - <Yield renderTime={6} />
861 - <Yield renderTime={15} />
862 - </React.Profiler>,
863 - {unstable_isConcurrent: true},
864 - );
780 + const Yield = ({renderTime}) => {
781 + Scheduler.unstable_advanceTime(renderTime);
782 + Scheduler.unstable_yieldValue('Yield:' + renderTime);
783 + return null;
784 + };
785
866 - // Render everything initially.
867 - // This should take 21 seconds of actual and base time.
868 - expect(Scheduler).toFlushAndYield(['Yield:6', 'Yield:15']);
869 - expect(callback).toHaveBeenCalledTimes(1);
870 - let call = callback.mock.calls[0];
871 - expect(call[2]).toBe(21); // actual time
872 - expect(call[3]).toBe(21); // base time
873 - expect(call[4]).toBe(5); // start time
874 - expect(call[5]).toBe(26); // commit time
786 + Scheduler.unstable_advanceTime(5); // 0 -> 5
787
876 - callback.mockReset();
788 + // Render a partially update, but don't finish.
789 + // This partial render should take 10ms of simulated time.
790 + const renderer = ReactTestRenderer.create(
791 + <React.Profiler id="test" onRender={callback}>
792 + <Yield renderTime={10} />
793 + <Yield renderTime={20} />
794 + </React.Profiler>,
795 + {unstable_isConcurrent: true},
796 + );
797 + expect(Scheduler).toFlushAndYieldThrough(['Yield:10']);
798 + expect(callback).toHaveBeenCalledTimes(0);
799
878 - Scheduler.unstable_advanceTime(30); // 26 -> 56
800 + // Simulate time moving forward while frame is paused.
801 + Scheduler.unstable_advanceTime(100); // 15 -> 115
802
880 - // Render a partially update, but don't finish.
881 - // This partial render should take 3ms of simulated time.
803 + // Interrupt with higher priority work.
804 + // The interrupted work simulates an additional 5ms of time.
805 + renderer.unstable_flushSync(() => {
806 renderer.update(
807 <React.Profiler id="test" onRender={callback}>
884 - <Yield renderTime={3} />
808 <Yield renderTime={5} />
886 - <Yield renderTime={9} />
887 - </React.Profiler>,
888 - );
889 - expect(Scheduler).toFlushAndYieldThrough(['Yield:3']);
890 - expect(callback).toHaveBeenCalledTimes(0);
891 -
892 - // Simulate time moving forward while frame is paused.
893 - Scheduler.unstable_advanceTime(100); // 59 -> 159
894 -
895 - // Render another 5ms of simulated time.
896 - expect(Scheduler).toFlushAndYieldThrough(['Yield:5']);
897 - expect(callback).toHaveBeenCalledTimes(0);
898 -
899 - // Simulate time moving forward while frame is paused.
900 - Scheduler.unstable_advanceTime(100); // 164 -> 264
901 -
902 - // Interrupt with higher priority work.
903 - // The interrupted work simulates an additional 11ms of time.
904 - renderer.unstable_flushSync(() => {
905 - renderer.update(
906 - <React.Profiler id="test" onRender={callback}>
907 - <Yield renderTime={11} />
908 - </React.Profiler>,
909 - );
910 - });
911 - expect(Scheduler).toHaveYielded(['Yield:11']);
912 -
913 - // The actual time should include only the most recent render,
914 - // Because this lets us avoid a lot of commit phase reset complexity.
915 - // The base time includes only the final rendered tree times.
916 - expect(callback).toHaveBeenCalledTimes(1);
917 - call = callback.mock.calls[0];
918 - expect(call[2]).toBe(11); // actual time
919 - expect(call[3]).toBe(11); // base time
920 - expect(call[4]).toBe(264); // start time
921 - expect(call[5]).toBe(275); // commit time
922 -
923 - // Verify no more unexpected callbacks from low priority work
924 - expect(Scheduler).toFlushAndYield([]);
925 - expect(callback).toHaveBeenCalledTimes(1);
926 - });
927 -
928 - it('should report the expected times when a high-priority update interrupts a low-priority update', () => {
929 - const callback = jest.fn();
930 -
931 - const Yield = ({renderTime}) => {
932 - Scheduler.unstable_advanceTime(renderTime);
933 - Scheduler.unstable_yieldValue('Yield:' + renderTime);
934 - return null;
935 - };
936 -
937 - let first;
938 - class FirstComponent extends React.Component {
939 - state = {renderTime: 1};
940 - render() {
941 - first = this;
942 - Scheduler.unstable_advanceTime(this.state.renderTime);
943 - Scheduler.unstable_yieldValue(
944 - 'FirstComponent:' + this.state.renderTime,
945 - );
946 - return <Yield renderTime={4} />;
947 - }
948 - }
949 - let second;
950 - class SecondComponent extends React.Component {
951 - state = {renderTime: 2};
952 - render() {
953 - second = this;
954 - Scheduler.unstable_advanceTime(this.state.renderTime);
955 - Scheduler.unstable_yieldValue(
956 - 'SecondComponent:' + this.state.renderTime,
957 - );
958 - return <Yield renderTime={7} />;
959 - }
960 - }
961 -
962 - Scheduler.unstable_advanceTime(5); // 0 -> 5
963 -
964 - const renderer = ReactTestRenderer.create(
965 - <React.Profiler id="test" onRender={callback}>
966 - <FirstComponent />
967 - <SecondComponent />
809 </React.Profiler>,
969 - {unstable_isConcurrent: true},
970 - );
971 -
972 - // Render everything initially.
973 - // This simulates a total of 14ms of actual render time.
974 - // The base render time is also 14ms for the initial render.
975 - expect(Scheduler).toFlushAndYield([
976 - 'FirstComponent:1',
977 - 'Yield:4',
978 - 'SecondComponent:2',
979 - 'Yield:7',
980 - ]);
981 - expect(callback).toHaveBeenCalledTimes(1);
982 - let call = callback.mock.calls[0];
983 - expect(call[2]).toBe(14); // actual time
984 - expect(call[3]).toBe(14); // base time
985 - expect(call[4]).toBe(5); // start time
986 - expect(call[5]).toBe(19); // commit time
987 -
988 - callback.mockClear();
989 -
990 - Scheduler.unstable_advanceTime(100); // 19 -> 119
991 -
992 - // Render a partially update, but don't finish.
993 - // This partial render will take 10ms of actual render time.
994 - first.setState({renderTime: 10});
995 - expect(Scheduler).toFlushAndYieldThrough(['FirstComponent:10']);
996 - expect(callback).toHaveBeenCalledTimes(0);
997 -
998 - // Simulate time moving forward while frame is paused.
999 - Scheduler.unstable_advanceTime(100); // 129 -> 229
1000 -
1001 - // Interrupt with higher priority work.
1002 - // This simulates a total of 37ms of actual render time.
1003 - renderer.unstable_flushSync(() =>
1004 - second.setState({renderTime: 30}),
810 );
1006 - expect(Scheduler).toHaveYielded(['SecondComponent:30', 'Yield:7']);
1007 -
1008 - // The actual time should include only the most recent render (37ms),
1009 - // Because this greatly simplifies the commit phase logic.
1010 - // The base time should include the more recent times for the SecondComponent subtree,
1011 - // As well as the original times for the FirstComponent subtree.
1012 - expect(callback).toHaveBeenCalledTimes(1);
1013 - call = callback.mock.calls[0];
1014 - expect(call[2]).toBe(37); // actual time
1015 - expect(call[3]).toBe(42); // base time
1016 - expect(call[4]).toBe(229); // start time
1017 - expect(call[5]).toBe(266); // commit time
1018 -
1019 - callback.mockClear();
1020 -
1021 - // Simulate time moving forward while frame is paused.
1022 - Scheduler.unstable_advanceTime(100); // 266 -> 366
1023 -
1024 - // Resume the original low priority update, with rebased state.
1025 - // This simulates a total of 14ms of actual render time,
1026 - // And does not include the original (interrupted) 10ms.
1027 - // The tree contains 42ms of base render time at this point,
1028 - // Reflecting the most recent (longer) render durations.
1029 - // TODO: This actual time should decrease by 10ms once the scheduler supports resuming.
1030 - expect(Scheduler).toFlushAndYield(['FirstComponent:10', 'Yield:4']);
1031 - expect(callback).toHaveBeenCalledTimes(1);
1032 - call = callback.mock.calls[0];
1033 - expect(call[2]).toBe(14); // actual time
1034 - expect(call[3]).toBe(51); // base time
1035 - expect(call[4]).toBe(366); // start time
1036 - expect(call[5]).toBe(380); // commit time
811 });
812 + expect(Scheduler).toHaveYielded(['Yield:5']);
813
1039 - [true, false].forEach(
1040 - replayFailedUnitOfWorkWithInvokeGuardedCallback => {
1041 - describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${
1042 - replayFailedUnitOfWorkWithInvokeGuardedCallback
1043 - ? 'enabled'
1044 - : 'disabled'
1045 - }`, () => {
1046 - beforeEach(() => {
1047 - jest.resetModules();
1048 -
1049 - loadModules({
1050 - replayFailedUnitOfWorkWithInvokeGuardedCallback,
1051 - });
1052 - });
1053 -
1054 - it('should accumulate actual time after an error handled by componentDidCatch()', () => {
1055 - const callback = jest.fn();
1056 -
1057 - const ThrowsError = ({unused}) => {
1058 - Scheduler.unstable_advanceTime(3);
1059 - throw Error('expected error');
1060 - };
1061 -
1062 - class ErrorBoundary extends React.Component {
1063 - state = {error: null};
1064 - componentDidCatch(error) {
1065 - this.setState({error});
1066 - }
1067 - render() {
1068 - Scheduler.unstable_advanceTime(2);
1069 - return this.state.error === null ? (
1070 - this.props.children
1071 - ) : (
1072 - <AdvanceTime byAmount={20} />
1073 - );
1074 - }
1075 - }
1076 -
1077 - Scheduler.unstable_advanceTime(5); // 0 -> 5
1078 -
1079 - ReactTestRenderer.create(
1080 - <React.Profiler id="test" onRender={callback}>
1081 - <ErrorBoundary>
1082 - <AdvanceTime byAmount={9} />
1083 - <ThrowsError />
1084 - </ErrorBoundary>
1085 - </React.Profiler>,
1086 - );
1087 -
1088 - expect(callback).toHaveBeenCalledTimes(2);
1089 -
1090 - // Callbacks bubble (reverse order).
1091 - const [mountCall, updateCall] = callback.mock.calls;
1092 -
1093 - // The initial mount only includes the ErrorBoundary (which takes 2)
1094 - // But it spends time rendering all of the failed subtree also.
1095 - expect(mountCall[1]).toBe('mount');
1096 - // actual time includes: 2 (ErrorBoundary) + 9 (AdvanceTime) + 3 (ThrowsError)
1097 - // We don't count the time spent in replaying the failed unit of work (ThrowsError)
1098 - expect(mountCall[2]).toBe(14);
1099 - // base time includes: 2 (ErrorBoundary)
1100 - // Since the tree is empty for the initial commit
1101 - expect(mountCall[3]).toBe(2);
1102 - // start time
1103 - expect(mountCall[4]).toBe(5);
1104 - // commit time: 5 initially + 14 of work
1105 - // Add an additional 3 (ThrowsError) if we replayed the failed work
1106 - expect(mountCall[5]).toBe(
1107 - __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1108 - ? 22
1109 - : 19,
1110 - );
1111 -
1112 - // The update includes the ErrorBoundary and its fallback child
1113 - expect(updateCall[1]).toBe('update');
1114 - // actual time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
1115 - expect(updateCall[2]).toBe(22);
1116 - // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
1117 - expect(updateCall[3]).toBe(22);
1118 - // start time
1119 - expect(updateCall[4]).toBe(
1120 - __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1121 - ? 22
1122 - : 19,
1123 - );
1124 - // commit time: 19 (startTime) + 2 (ErrorBoundary) + 20 (AdvanceTime)
1125 - // Add an additional 3 (ThrowsError) if we replayed the failed work
1126 - expect(updateCall[5]).toBe(
1127 - __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1128 - ? 44
1129 - : 41,
1130 - );
1131 - });
1132 -
1133 - it('should accumulate actual time after an error handled by getDerivedStateFromError()', () => {
1134 - const callback = jest.fn();
1135 -
1136 - const ThrowsError = ({unused}) => {
1137 - Scheduler.unstable_advanceTime(10);
1138 - throw Error('expected error');
1139 - };
1140 -
1141 - class ErrorBoundary extends React.Component {
1142 - state = {error: null};
1143 - static getDerivedStateFromError(error) {
1144 - return {error};
1145 - }
1146 - render() {
1147 - Scheduler.unstable_advanceTime(2);
1148 - return this.state.error === null ? (
1149 - this.props.children
1150 - ) : (
1151 - <AdvanceTime byAmount={20} />
1152 - );
1153 - }
1154 - }
814 + // The initial work was thrown away in this case,
815 + // So the actual and base times should only include the final rendered tree times.
816 + expect(callback).toHaveBeenCalledTimes(1);
817 + const call = callback.mock.calls[0];
818 + expect(call[2]).toBe(5); // actual time
819 + expect(call[3]).toBe(5); // base time
820 + expect(call[4]).toBe(115); // start time
821 + expect(call[5]).toBe(120); // commit time
822
1156 - Scheduler.unstable_advanceTime(5); // 0 -> 5
1157 -
1158 - ReactTestRenderer.create(
1159 - <React.Profiler id="test" onRender={callback}>
1160 - <ErrorBoundary>
1161 - <AdvanceTime byAmount={5} />
1162 - <ThrowsError />
1163 - </ErrorBoundary>
1164 - </React.Profiler>,
1165 - );
1166 -
1167 - expect(callback).toHaveBeenCalledTimes(1);
1168 -
1169 - // Callbacks bubble (reverse order).
1170 - const [mountCall] = callback.mock.calls;
1171 -
1172 - // The initial mount includes the ErrorBoundary's error state,
1173 - // But it also spends actual time rendering UI that fails and isn't included.
1174 - expect(mountCall[1]).toBe('mount');
1175 - // actual time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError)
1176 - // Then the re-render: 2 (ErrorBoundary) + 20 (AdvanceTime)
1177 - // We don't count the time spent in replaying the failed unit of work (ThrowsError)
1178 - expect(mountCall[2]).toBe(39);
1179 - // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
1180 - expect(mountCall[3]).toBe(22);
1181 - // start time
1182 - expect(mountCall[4]).toBe(5);
1183 - // commit time
1184 - expect(mountCall[5]).toBe(
1185 - __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1186 - ? 54
1187 - : 44,
1188 - );
1189 - });
823 + callback.mockReset();
824
1191 - it('should reset the fiber stack correct after a "complete" phase error', () => {
1192 - jest.resetModules();
1193 -
1194 - loadModules({
1195 - useNoopRenderer: true,
1196 - replayFailedUnitOfWorkWithInvokeGuardedCallback,
1197 - });
1198 -
1199 - // Simulate a renderer error during the "complete" phase.
1200 - // This mimics behavior like React Native's View/Text nesting validation.
1201 - ReactNoop.render(
1202 - <React.Profiler id="profiler" onRender={jest.fn()}>
1203 - <errorInCompletePhase>hi</errorInCompletePhase>
1204 - </React.Profiler>,
1205 - );
1206 - expect(Scheduler).toFlushAndThrow('Error in host config.');
1207 -
1208 - // A similar case we've seen caused by an invariant in ReactDOM.
1209 - // It didn't reproduce without a host component inside.
1210 - ReactNoop.render(
1211 - <React.Profiler id="profiler" onRender={jest.fn()}>
1212 - <errorInCompletePhase>
1213 - <span>hi</span>
1214 - </errorInCompletePhase>
1215 - </React.Profiler>,
1216 - );
1217 - expect(Scheduler).toFlushAndThrow('Error in host config.');
1218 -
1219 - // So long as the profiler timer's fiber stack is reset correctly,
1220 - // Subsequent renders should not error.
1221 - ReactNoop.render(
1222 - <React.Profiler id="profiler" onRender={jest.fn()}>
1223 - <span>hi</span>
1224 - </React.Profiler>,
1225 - );
1226 - expect(Scheduler).toFlushWithoutYielding();
1227 - });
1228 - });
1229 - },
1230 - );
825 + // Verify no more unexpected callbacks from low priority work
826 + expect(Scheduler).toFlushWithoutYielding();
827 + expect(callback).toHaveBeenCalledTimes(0);
828 });
829
1233 - it('reflects the most recently rendered id value', () => {
830 + it('should report the expected times when a high-priority update replaces a low-priority update', () => {
831 const callback = jest.fn();
832
833 + const Yield = ({renderTime}) => {
834 + Scheduler.unstable_advanceTime(renderTime);
835 + Scheduler.unstable_yieldValue('Yield:' + renderTime);
836 + return null;
837 + };
838 +
839 Scheduler.unstable_advanceTime(5); // 0 -> 5
840
841 const renderer = ReactTestRenderer.create(
1239 - <React.Profiler id="one" onRender={callback}>
1240 - <AdvanceTime byAmount={2} />
842 + <React.Profiler id="test" onRender={callback}>
843 + <Yield renderTime={6} />
844 + <Yield renderTime={15} />
845 </React.Profiler>,
846 + {unstable_isConcurrent: true},
847 );
848
849 + // Render everything initially.
850 + // This should take 21 seconds of actual and base time.
851 + expect(Scheduler).toFlushAndYield(['Yield:6', 'Yield:15']);
852 expect(callback).toHaveBeenCalledTimes(1);
853 + let call = callback.mock.calls[0];
854 + expect(call[2]).toBe(21); // actual time
855 + expect(call[3]).toBe(21); // base time
856 + expect(call[4]).toBe(5); // start time
857 + expect(call[5]).toBe(26); // commit time
858 +
859 + callback.mockReset();
860
1246 - Scheduler.unstable_advanceTime(20); // 7 -> 27
861 + Scheduler.unstable_advanceTime(30); // 26 -> 56
862
863 + // Render a partially update, but don't finish.
864 + // This partial render should take 3ms of simulated time.
865 renderer.update(
1249 - <React.Profiler id="two" onRender={callback}>
1250 - <AdvanceTime byAmount={1} />
866 + <React.Profiler id="test" onRender={callback}>
867 + <Yield renderTime={3} />
868 + <Yield renderTime={5} />
869 + <Yield renderTime={9} />
870 </React.Profiler>,
871 );
872 + expect(Scheduler).toFlushAndYieldThrough(['Yield:3']);
873 + expect(callback).toHaveBeenCalledTimes(0);
874
1254 - expect(callback).toHaveBeenCalledTimes(2);
1255 -
1256 - const [mountCall, updateCall] = callback.mock.calls;
1257 -
1258 - expect(mountCall[0]).toBe('one');
1259 - expect(mountCall[1]).toBe('mount');
1260 - expect(mountCall[2]).toBe(2); // actual time
1261 - expect(mountCall[3]).toBe(2); // base time
1262 - expect(mountCall[4]).toBe(5); // start time
1263 -
1264 - expect(updateCall[0]).toBe('two');
1265 - expect(updateCall[1]).toBe('update');
1266 - expect(updateCall[2]).toBe(1); // actual time
1267 - expect(updateCall[3]).toBe(1); // base time
1268 - expect(updateCall[4]).toBe(27); // start time
1269 - });
875 + // Simulate time moving forward while frame is paused.
876 + Scheduler.unstable_advanceTime(100); // 59 -> 159
877
1271 - it('should not be called until after mutations', () => {
1272 - let classComponentMounted = false;
1273 - const callback = jest.fn(
1274 - (
1275 - id,
1276 - phase,
1277 - actualDuration,
1278 - baseDuration,
1279 - startTime,
1280 - commitTime,
1281 - ) => {
1282 - // Don't call this hook until after mutations
1283 - expect(classComponentMounted).toBe(true);
1284 - // But the commit time should reflect pre-mutation
1285 - expect(commitTime).toBe(2);
1286 - },
1287 - );
878 + // Render another 5ms of simulated time.
879 + expect(Scheduler).toFlushAndYieldThrough(['Yield:5']);
880 + expect(callback).toHaveBeenCalledTimes(0);
881
1289 - class ClassComponent extends React.Component {
1290 - componentDidMount() {
1291 - Scheduler.unstable_advanceTime(5);
1292 - classComponentMounted = true;
1293 - }
1294 - render() {
1295 - Scheduler.unstable_advanceTime(2);
1296 - return null;
1297 - }
1298 - }
882 + // Simulate time moving forward while frame is paused.
883 + Scheduler.unstable_advanceTime(100); // 164 -> 264
884
1300 - ReactTestRenderer.create(
1301 - <React.Profiler id="test" onRender={callback}>
1302 - <ClassComponent />
1303 - </React.Profiler>,
1304 - );
885 + // Interrupt with higher priority work.
886 + // The interrupted work simulates an additional 11ms of time.
887 + renderer.unstable_flushSync(() => {
888 + renderer.update(
889 + <React.Profiler id="test" onRender={callback}>
890 + <Yield renderTime={11} />
891 + </React.Profiler>,
892 + );
893 + });
894 + expect(Scheduler).toHaveYielded(['Yield:11']);
895
896 + // The actual time should include only the most recent render,
897 + // Because this lets us avoid a lot of commit phase reset complexity.
898 + // The base time includes only the final rendered tree times.
899 expect(callback).toHaveBeenCalledTimes(1);
1307 - });
1308 - });
900 + call = callback.mock.calls[0];
901 + expect(call[2]).toBe(11); // actual time
902 + expect(call[3]).toBe(11); // base time
903 + expect(call[4]).toBe(264); // start time
904 + expect(call[5]).toBe(275); // commit time
905
1310 - describe(`onCommit enableSchedulerTracing:${
1311 - enableSchedulerTracing ? 'enabled' : 'disabled'
1312 - } deferPassiveEffectCleanupDuringUnmount:${
1313 - deferPassiveEffectCleanupDuringUnmount ? 'enabled' : 'disabled'
1314 - }`, () => {
1315 - beforeEach(() => {
1316 - jest.resetModules();
1317 -
1318 - loadModules({
1319 - deferPassiveEffectCleanupDuringUnmount,
1320 - enableSchedulerTracing,
1321 - });
906 + // Verify no more unexpected callbacks from low priority work
907 + expect(Scheduler).toFlushAndYield([]);
908 + expect(callback).toHaveBeenCalledTimes(1);
909 });
910
1324 - it('should report time spent in layout effects and commit lifecycles', () => {
911 + it('should report the expected times when a high-priority update interrupts a low-priority update', () => {
912 const callback = jest.fn();
913
1327 - const ComponetWithEffects = () => {
1328 - React.useLayoutEffect(() => {
1329 - Scheduler.unstable_advanceTime(10);
1330 - return () => {
1331 - Scheduler.unstable_advanceTime(100);
1332 - };
1333 - }, []);
1334 - React.useLayoutEffect(() => {
1335 - Scheduler.unstable_advanceTime(1000);
1336 - return () => {
1337 - Scheduler.unstable_advanceTime(10000);
1338 - };
1339 - });
1340 - React.useEffect(() => {
1341 - // This passive effect is here to verify that its time isn't reported.
1342 - Scheduler.unstable_advanceTime(5);
1343 - return () => {
1344 - Scheduler.unstable_advanceTime(7);
1345 - };
1346 - });
914 + const Yield = ({renderTime}) => {
915 + Scheduler.unstable_advanceTime(renderTime);
916 + Scheduler.unstable_yieldValue('Yield:' + renderTime);
917 return null;
918 };
919
1350 - class ComponentWithCommitHooks extends React.Component {
1351 - componentDidMount() {
1352 - Scheduler.unstable_advanceTime(100000);
1353 - }
1354 - componentDidUpdate() {
1355 - Scheduler.unstable_advanceTime(1000000);
920 + let first;
921 + class FirstComponent extends React.Component {
922 + state = {renderTime: 1};
923 + render() {
924 + first = this;
925 + Scheduler.unstable_advanceTime(this.state.renderTime);
926 + Scheduler.unstable_yieldValue(
927 + 'FirstComponent:' + this.state.renderTime,
928 + );
929 + return <Yield renderTime={4} />;
930 }
931 + }
932 + let second;
933 + class SecondComponent extends React.Component {
934 + state = {renderTime: 2};
935 render() {
1358 - return null;
936 + second = this;
937 + Scheduler.unstable_advanceTime(this.state.renderTime);
938 + Scheduler.unstable_yieldValue(
939 + 'SecondComponent:' + this.state.renderTime,
940 + );
941 + return <Yield renderTime={7} />;
942 }
943 }
944
1362 - Scheduler.unstable_advanceTime(1);
945 + Scheduler.unstable_advanceTime(5); // 0 -> 5
946
947 const renderer = ReactTestRenderer.create(
1365 - <React.Profiler id="mount-test" onCommit={callback}>
1366 - <ComponetWithEffects />
1367 - <ComponentWithCommitHooks />
948 + <React.Profiler id="test" onRender={callback}>
949 + <FirstComponent />
950 + <SecondComponent />
951 </React.Profiler>,
952 + {unstable_isConcurrent: true},
953 );
954
955 + // Render everything initially.
956 + // This simulates a total of 14ms of actual render time.
957 + // The base render time is also 14ms for the initial render.
958 + expect(Scheduler).toFlushAndYield([
959 + 'FirstComponent:1',
960 + 'Yield:4',
961 + 'SecondComponent:2',
962 + 'Yield:7',
963 + ]);
964 expect(callback).toHaveBeenCalledTimes(1);
1372 -
965 let call = callback.mock.calls[0];
966 + expect(call[2]).toBe(14); // actual time
967 + expect(call[3]).toBe(14); // base time
968 + expect(call[4]).toBe(5); // start time
969 + expect(call[5]).toBe(19); // commit time
970
1375 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1376 - expect(call[0]).toBe('mount-test');
1377 - expect(call[1]).toBe('mount');
1378 - expect(call[2]).toBe(101010); // durations
1379 - expect(call[3]).toBe(1); // commit start time (before mutations or effects)
1380 - expect(call[4]).toEqual(
1381 - enableSchedulerTracing ? new Set() : undefined,
1382 - ); // interaction events
1383 -
1384 - Scheduler.unstable_advanceTime(1);
1385 -
1386 - renderer.update(
1387 - <React.Profiler id="update-test" onCommit={callback}>
1388 - <ComponetWithEffects />
1389 - <ComponentWithCommitHooks />
1390 - </React.Profiler>,
1391 - );
971 + callback.mockClear();
972
1393 - expect(callback).toHaveBeenCalledTimes(2);
973 + Scheduler.unstable_advanceTime(100); // 19 -> 119
974
1395 - call = callback.mock.calls[1];
975 + // Render a partially update, but don't finish.
976 + // This partial render will take 10ms of actual render time.
977 + first.setState({renderTime: 10});
978 + expect(Scheduler).toFlushAndYieldThrough(['FirstComponent:10']);
979 + expect(callback).toHaveBeenCalledTimes(0);
980
1397 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1398 - expect(call[0]).toBe('update-test');
1399 - expect(call[1]).toBe('update');
1400 - expect(call[2]).toBe(1011000); // durations
1401 - expect(call[3]).toBe(101017); // commit start time (before mutations or effects)
1402 - expect(call[4]).toEqual(
1403 - enableSchedulerTracing ? new Set() : undefined,
1404 - ); // interaction events
981 + // Simulate time moving forward while frame is paused.
982 + Scheduler.unstable_advanceTime(100); // 129 -> 229
983
1406 - Scheduler.unstable_advanceTime(1);
984 + // Interrupt with higher priority work.
985 + // This simulates a total of 37ms of actual render time.
986 + renderer.unstable_flushSync(() => second.setState({renderTime: 30}));
987 + expect(Scheduler).toHaveYielded(['SecondComponent:30', 'Yield:7']);
988
1408 - renderer.update(
1409 - <React.Profiler id="unmount-test" onCommit={callback} />,
1410 - );
989 + // The actual time should include only the most recent render (37ms),
990 + // Because this greatly simplifies the commit phase logic.
991 + // The base time should include the more recent times for the SecondComponent subtree,
992 + // As well as the original times for the FirstComponent subtree.
993 + expect(callback).toHaveBeenCalledTimes(1);
994 + call = callback.mock.calls[0];
995 + expect(call[2]).toBe(37); // actual time
996 + expect(call[3]).toBe(42); // base time
997 + expect(call[4]).toBe(229); // start time
998 + expect(call[5]).toBe(266); // commit time
999
1412 - expect(callback).toHaveBeenCalledTimes(3);
1000 + callback.mockClear();
1001
1414 - call = callback.mock.calls[2];
1002 + // Simulate time moving forward while frame is paused.
1003 + Scheduler.unstable_advanceTime(100); // 266 -> 366
1004
1416 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1417 - expect(call[0]).toBe('unmount-test');
1418 - expect(call[1]).toBe('update');
1419 - expect(call[2]).toBe(10100); // durations
1420 - expect(call[3]).toBe(1112030); // commit start time (before mutations or effects)
1421 - expect(call[4]).toEqual(
1422 - enableSchedulerTracing ? new Set() : undefined,
1423 - ); // interaction events
1005 + // Resume the original low priority update, with rebased state.
1006 + // This simulates a total of 14ms of actual render time,
1007 + // And does not include the original (interrupted) 10ms.
1008 + // The tree contains 42ms of base render time at this point,
1009 + // Reflecting the most recent (longer) render durations.
1010 + // TODO: This actual time should decrease by 10ms once the scheduler supports resuming.
1011 + expect(Scheduler).toFlushAndYield(['FirstComponent:10', 'Yield:4']);
1012 + expect(callback).toHaveBeenCalledTimes(1);
1013 + call = callback.mock.calls[0];
1014 + expect(call[2]).toBe(14); // actual time
1015 + expect(call[3]).toBe(51); // base time
1016 + expect(call[4]).toBe(366); // start time
1017 + expect(call[5]).toBe(380); // commit time
1018 });
1019
1426 - it('should report time spent in layout effects and commit lifecycles with cascading renders', () => {
1427 - const callback = jest.fn();
1020 + [true, false].forEach(
1021 + replayFailedUnitOfWorkWithInvokeGuardedCallback => {
1022 + describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${
1023 + replayFailedUnitOfWorkWithInvokeGuardedCallback
1024 + ? 'enabled'
1025 + : 'disabled'
1026 + }`, () => {
1027 + beforeEach(() => {
1028 + jest.resetModules();
1029 +
1030 + loadModules({
1031 + replayFailedUnitOfWorkWithInvokeGuardedCallback,
1032 + });
1033 + });
1034
1429 - const ComponetWithEffects = ({shouldCascade}) => {
1430 - const [didCascade, setDidCascade] = React.useState(false);
1431 - React.useLayoutEffect(() => {
1432 - if (shouldCascade && !didCascade) {
1433 - setDidCascade(true);
1434 - }
1435 - Scheduler.unstable_advanceTime(didCascade ? 30 : 10);
1436 - return () => {
1437 - Scheduler.unstable_advanceTime(100);
1438 - };
1439 - }, [didCascade, shouldCascade]);
1440 - return null;
1441 - };
1035 + it('should accumulate actual time after an error handled by componentDidCatch()', () => {
1036 + const callback = jest.fn();
1037
1443 - class ComponentWithCommitHooks extends React.Component {
1444 - state = {
1445 - didCascade: false,
1446 - };
1447 - componentDidMount() {
1448 - Scheduler.unstable_advanceTime(1000);
1449 - }
1450 - componentDidUpdate() {
1451 - Scheduler.unstable_advanceTime(10000);
1452 - if (this.props.shouldCascade && !this.state.didCascade) {
1453 - this.setState({didCascade: true});
1454 - }
1455 - }
1456 - render() {
1457 - return null;
1458 - }
1459 - }
1038 + const ThrowsError = ({unused}) => {
1039 + Scheduler.unstable_advanceTime(3);
1040 + throw Error('expected error');
1041 + };
1042
1461 - Scheduler.unstable_advanceTime(1);
1043 + class ErrorBoundary extends React.Component {
1044 + state = {error: null};
1045 + componentDidCatch(error) {
1046 + this.setState({error});
1047 + }
1048 + render() {
1049 + Scheduler.unstable_advanceTime(2);
1050 + return this.state.error === null ? (
1051 + this.props.children
1052 + ) : (
1053 + <AdvanceTime byAmount={20} />
1054 + );
1055 + }
1056 + }
1057
1463 - const renderer = ReactTestRenderer.create(
1464 - <React.Profiler id="mount-test" onCommit={callback}>
1465 - <ComponetWithEffects shouldCascade={true} />
1466 - <ComponentWithCommitHooks />
1467 - </React.Profiler>,
1468 - );
1058 + Scheduler.unstable_advanceTime(5); // 0 -> 5
1059
1470 - expect(callback).toHaveBeenCalledTimes(2);
1060 + ReactTestRenderer.create(
1061 + <React.Profiler id="test" onRender={callback}>
1062 + <ErrorBoundary>
1063 + <AdvanceTime byAmount={9} />
1064 + <ThrowsError />
1065 + </ErrorBoundary>
1066 + </React.Profiler>,
1067 + );
1068
1472 - let call = callback.mock.calls[0];
1069 + expect(callback).toHaveBeenCalledTimes(2);
1070 +
1071 + // Callbacks bubble (reverse order).
1072 + const [mountCall, updateCall] = callback.mock.calls;
1073 +
1074 + // The initial mount only includes the ErrorBoundary (which takes 2)
1075 + // But it spends time rendering all of the failed subtree also.
1076 + expect(mountCall[1]).toBe('mount');
1077 + // actual time includes: 2 (ErrorBoundary) + 9 (AdvanceTime) + 3 (ThrowsError)
1078 + // We don't count the time spent in replaying the failed unit of work (ThrowsError)
1079 + expect(mountCall[2]).toBe(14);
1080 + // base time includes: 2 (ErrorBoundary)
1081 + // Since the tree is empty for the initial commit
1082 + expect(mountCall[3]).toBe(2);
1083 + // start time
1084 + expect(mountCall[4]).toBe(5);
1085 + // commit time: 5 initially + 14 of work
1086 + // Add an additional 3 (ThrowsError) if we replayed the failed work
1087 + expect(mountCall[5]).toBe(
1088 + __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1089 + ? 22
1090 + : 19,
1091 + );
1092
1474 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1475 - expect(call[0]).toBe('mount-test');
1476 - expect(call[1]).toBe('mount');
1477 - expect(call[2]).toBe(1010); // durations
1478 - expect(call[3]).toBe(1); // commit start time (before mutations or effects)
1479 - expect(call[4]).toEqual(
1480 - enableSchedulerTracing ? new Set() : undefined,
1481 - ); // interaction events
1093 + // The update includes the ErrorBoundary and its fallback child
1094 + expect(updateCall[1]).toBe('update');
1095 + // actual time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
1096 + expect(updateCall[2]).toBe(22);
1097 + // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
1098 + expect(updateCall[3]).toBe(22);
1099 + // start time
1100 + expect(updateCall[4]).toBe(
1101 + __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1102 + ? 22
1103 + : 19,
1104 + );
1105 + // commit time: 19 (startTime) + 2 (ErrorBoundary) + 20 (AdvanceTime)
1106 + // Add an additional 3 (ThrowsError) if we replayed the failed work
1107 + expect(updateCall[5]).toBe(
1108 + __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1109 + ? 44
1110 + : 41,
1111 + );
1112 + });
1113
1483 - call = callback.mock.calls[1];
1114 + it('should accumulate actual time after an error handled by getDerivedStateFromError()', () => {
1115 + const callback = jest.fn();
1116
1485 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1486 - expect(call[0]).toBe('mount-test');
1487 - expect(call[1]).toBe('update');
1488 - expect(call[2]).toBe(130); // durations
1489 - expect(call[3]).toBe(1011); // commit start time (before mutations or effects)
1490 - expect(call[4]).toEqual(
1491 - enableSchedulerTracing ? new Set() : undefined,
1492 - ); // interaction events
1117 + const ThrowsError = ({unused}) => {
1118 + Scheduler.unstable_advanceTime(10);
1119 + throw Error('expected error');
1120 + };
1121
1494 - Scheduler.unstable_advanceTime(1);
1122 + class ErrorBoundary extends React.Component {
1123 + state = {error: null};
1124 + static getDerivedStateFromError(error) {
1125 + return {error};
1126 + }
1127 + render() {
1128 + Scheduler.unstable_advanceTime(2);
1129 + return this.state.error === null ? (
1130 + this.props.children
1131 + ) : (
1132 + <AdvanceTime byAmount={20} />
1133 + );
1134 + }
1135 + }
1136
1496 - renderer.update(
1497 - <React.Profiler id="update-test" onCommit={callback}>
1498 - <ComponetWithEffects />
1499 - <ComponentWithCommitHooks shouldCascade={true} />
1500 - </React.Profiler>,
1501 - );
1137 + Scheduler.unstable_advanceTime(5); // 0 -> 5
1138
1503 - expect(callback).toHaveBeenCalledTimes(4);
1139 + ReactTestRenderer.create(
1140 + <React.Profiler id="test" onRender={callback}>
1141 + <ErrorBoundary>
1142 + <AdvanceTime byAmount={5} />
1143 + <ThrowsError />
1144 + </ErrorBoundary>
1145 + </React.Profiler>,
1146 + );
1147
1505 - call = callback.mock.calls[2];
1148 + expect(callback).toHaveBeenCalledTimes(1);
1149 +
1150 + // Callbacks bubble (reverse order).
1151 + const [mountCall] = callback.mock.calls;
1152 +
1153 + // The initial mount includes the ErrorBoundary's error state,
1154 + // But it also spends actual time rendering UI that fails and isn't included.
1155 + expect(mountCall[1]).toBe('mount');
1156 + // actual time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError)
1157 + // Then the re-render: 2 (ErrorBoundary) + 20 (AdvanceTime)
1158 + // We don't count the time spent in replaying the failed unit of work (ThrowsError)
1159 + expect(mountCall[2]).toBe(39);
1160 + // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
1161 + expect(mountCall[3]).toBe(22);
1162 + // start time
1163 + expect(mountCall[4]).toBe(5);
1164 + // commit time
1165 + expect(mountCall[5]).toBe(
1166 + __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1167 + ? 54
1168 + : 44,
1169 + );
1170 + });
1171
1507 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1508 - expect(call[0]).toBe('update-test');
1509 - expect(call[1]).toBe('update');
1510 - expect(call[2]).toBe(10130); // durations
1511 - expect(call[3]).toBe(1142); // commit start time (before mutations or effects)
1512 - expect(call[4]).toEqual(
1513 - enableSchedulerTracing ? new Set() : undefined,
1514 - ); // interaction events
1172 + it('should reset the fiber stack correct after a "complete" phase error', () => {
1173 + jest.resetModules();
1174
1516 - call = callback.mock.calls[3];
1175 + loadModules({
1176 + useNoopRenderer: true,
1177 + replayFailedUnitOfWorkWithInvokeGuardedCallback,
1178 + });
1179
1518 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1519 - expect(call[0]).toBe('update-test');
1520 - expect(call[1]).toBe('update');
1521 - expect(call[2]).toBe(10000); // durations
1522 - expect(call[3]).toBe(11272); // commit start time (before mutations or effects)
1523 - expect(call[4]).toEqual(
1524 - enableSchedulerTracing ? new Set() : undefined,
1525 - ); // interaction events
1526 - });
1180 + // Simulate a renderer error during the "complete" phase.
1181 + // This mimics behavior like React Native's View/Text nesting validation.
1182 + ReactNoop.render(
1183 + <React.Profiler id="profiler" onRender={jest.fn()}>
1184 + <errorInCompletePhase>hi</errorInCompletePhase>
1185 + </React.Profiler>,
1186 + );
1187 + expect(Scheduler).toFlushAndThrow('Error in host config.');
1188
1528 - it('should bubble time spent in layout effects to higher profilers', () => {
1529 - const callback = jest.fn();
1189 + // A similar case we've seen caused by an invariant in ReactDOM.
1190 + // It didn't reproduce without a host component inside.
1191 + ReactNoop.render(
1192 + <React.Profiler id="profiler" onRender={jest.fn()}>
1193 + <errorInCompletePhase>
1194 + <span>hi</span>
1195 + </errorInCompletePhase>
1196 + </React.Profiler>,
1197 + );
1198 + expect(Scheduler).toFlushAndThrow('Error in host config.');
1199
1531 - const ComponetWithEffects = ({
1532 - cleanupDuration,
1533 - duration,
1534 - setCountRef,
1535 - }) => {
1536 - const setCount = React.useState(0)[1];
1537 - if (setCountRef != null) {
1538 - setCountRef.current = setCount;
1539 - }
1540 - React.useLayoutEffect(() => {
1541 - Scheduler.unstable_advanceTime(duration);
1542 - return () => {
1543 - Scheduler.unstable_advanceTime(cleanupDuration);
1544 - };
1200 + // So long as the profiler timer's fiber stack is reset correctly,
1201 + // Subsequent renders should not error.
1202 + ReactNoop.render(
1203 + <React.Profiler id="profiler" onRender={jest.fn()}>
1204 + <span>hi</span>
1205 + </React.Profiler>,
1206 + );
1207 + expect(Scheduler).toFlushWithoutYielding();
1208 + });
1209 });
1546 - Scheduler.unstable_advanceTime(1);
1210 + },
1211 + );
1212 + });
1213 +
1214 + it('reflects the most recently rendered id value', () => {
1215 + const callback = jest.fn();
1216 +
1217 + Scheduler.unstable_advanceTime(5); // 0 -> 5
1218 +
1219 + const renderer = ReactTestRenderer.create(
1220 + <React.Profiler id="one" onRender={callback}>
1221 + <AdvanceTime byAmount={2} />
1222 + </React.Profiler>,
1223 + );
1224 +
1225 + expect(callback).toHaveBeenCalledTimes(1);
1226 +
1227 + Scheduler.unstable_advanceTime(20); // 7 -> 27
1228 +
1229 + renderer.update(
1230 + <React.Profiler id="two" onRender={callback}>
1231 + <AdvanceTime byAmount={1} />
1232 + </React.Profiler>,
1233 + );
1234 +
1235 + expect(callback).toHaveBeenCalledTimes(2);
1236 +
1237 + const [mountCall, updateCall] = callback.mock.calls;
1238 +
1239 + expect(mountCall[0]).toBe('one');
1240 + expect(mountCall[1]).toBe('mount');
1241 + expect(mountCall[2]).toBe(2); // actual time
1242 + expect(mountCall[3]).toBe(2); // base time
1243 + expect(mountCall[4]).toBe(5); // start time
1244 +
1245 + expect(updateCall[0]).toBe('two');
1246 + expect(updateCall[1]).toBe('update');
1247 + expect(updateCall[2]).toBe(1); // actual time
1248 + expect(updateCall[3]).toBe(1); // base time
1249 + expect(updateCall[4]).toBe(27); // start time
1250 + });
1251 +
1252 + it('should not be called until after mutations', () => {
1253 + let classComponentMounted = false;
1254 + const callback = jest.fn(
1255 + (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
1256 + // Don't call this hook until after mutations
1257 + expect(classComponentMounted).toBe(true);
1258 + // But the commit time should reflect pre-mutation
1259 + expect(commitTime).toBe(2);
1260 + },
1261 + );
1262 +
1263 + class ClassComponent extends React.Component {
1264 + componentDidMount() {
1265 + Scheduler.unstable_advanceTime(5);
1266 + classComponentMounted = true;
1267 + }
1268 + render() {
1269 + Scheduler.unstable_advanceTime(2);
1270 return null;
1548 - };
1271 + }
1272 + }
1273
1550 - const setCountRef = React.createRef(null);
1274 + ReactTestRenderer.create(
1275 + <React.Profiler id="test" onRender={callback}>
1276 + <ClassComponent />
1277 + </React.Profiler>,
1278 + );
1279
1552 - let renderer = null;
1553 - ReactTestRenderer.act(() => {
1554 - renderer = ReactTestRenderer.create(
1555 - <React.Profiler id="root-mount" onCommit={callback}>
1556 - <React.Profiler id="a">
1557 - <ComponetWithEffects
1558 - duration={10}
1559 - cleanupDuration={100}
1560 - setCountRef={setCountRef}
1561 - />
1562 - </React.Profiler>
1563 - <React.Profiler id="b">
1564 - <ComponetWithEffects
1565 - duration={1000}
1566 - cleanupDuration={10000}
1567 - />
1568 - </React.Profiler>
1569 - </React.Profiler>,
1570 - );
1280 + expect(callback).toHaveBeenCalledTimes(1);
1281 + });
1282 + });
1283 +
1284 + describe(`onCommit enableSchedulerTracing:${
1285 + enableSchedulerTracing ? 'enabled' : 'disabled'
1286 + }`, () => {
1287 + beforeEach(() => {
1288 + jest.resetModules();
1289 +
1290 + loadModules({
1291 + enableSchedulerTracing,
1292 + });
1293 + });
1294 +
1295 + it('should report time spent in layout effects and commit lifecycles', () => {
1296 + const callback = jest.fn();
1297 +
1298 + const ComponetWithEffects = () => {
1299 + React.useLayoutEffect(() => {
1300 + Scheduler.unstable_advanceTime(10);
1301 + return () => {
1302 + Scheduler.unstable_advanceTime(100);
1303 + };
1304 + }, []);
1305 + React.useLayoutEffect(() => {
1306 + Scheduler.unstable_advanceTime(1000);
1307 + return () => {
1308 + Scheduler.unstable_advanceTime(10000);
1309 + };
1310 });
1311 + React.useEffect(() => {
1312 + // This passive effect is here to verify that its time isn't reported.
1313 + Scheduler.unstable_advanceTime(5);
1314 + return () => {
1315 + Scheduler.unstable_advanceTime(7);
1316 + };
1317 + });
1318 + return null;
1319 + };
1320
1573 - expect(callback).toHaveBeenCalledTimes(1);
1321 + class ComponentWithCommitHooks extends React.Component {
1322 + componentDidMount() {
1323 + Scheduler.unstable_advanceTime(100000);
1324 + }
1325 + componentDidUpdate() {
1326 + Scheduler.unstable_advanceTime(1000000);
1327 + }
1328 + render() {
1329 + return null;
1330 + }
1331 + }
1332
1575 - let call = callback.mock.calls[0];
1333 + Scheduler.unstable_advanceTime(1);
1334
1577 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1578 - expect(call[0]).toBe('root-mount');
1579 - expect(call[1]).toBe('mount');
1580 - expect(call[2]).toBe(1010); // durations
1581 - expect(call[3]).toBe(2); // commit start time (before mutations or effects)
1582 - expect(call[4]).toEqual(
1583 - enableSchedulerTracing ? new Set() : undefined,
1584 - ); // interaction events
1335 + const renderer = ReactTestRenderer.create(
1336 + <React.Profiler id="mount-test" onCommit={callback}>
1337 + <ComponetWithEffects />
1338 + <ComponentWithCommitHooks />
1339 + </React.Profiler>,
1340 + );
1341
1586 - ReactTestRenderer.act(() => setCountRef.current(count => count + 1));
1342 + expect(callback).toHaveBeenCalledTimes(1);
1343
1588 - expect(callback).toHaveBeenCalledTimes(2);
1344 + let call = callback.mock.calls[0];
1345
1590 - call = callback.mock.calls[1];
1346 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1347 + expect(call[0]).toBe('mount-test');
1348 + expect(call[1]).toBe('mount');
1349 + expect(call[2]).toBe(101010); // durations
1350 + expect(call[3]).toBe(1); // commit start time (before mutations or effects)
1351 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1352
1592 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1593 - expect(call[0]).toBe('root-mount');
1594 - expect(call[1]).toBe('update');
1595 - expect(call[2]).toBe(110); // durations
1596 - expect(call[3]).toBe(1013); // commit start time (before mutations or effects)
1597 - expect(call[4]).toEqual(
1598 - enableSchedulerTracing ? new Set() : undefined,
1599 - ); // interaction events
1353 + Scheduler.unstable_advanceTime(1);
1354
1601 - ReactTestRenderer.act(() => {
1602 - renderer.update(
1603 - <React.Profiler id="root-update" onCommit={callback}>
1604 - <React.Profiler id="b">
1605 - <ComponetWithEffects
1606 - duration={1000}
1607 - cleanupDuration={10000}
1608 - />
1609 - </React.Profiler>
1610 - </React.Profiler>,
1611 - );
1612 - });
1355 + renderer.update(
1356 + <React.Profiler id="update-test" onCommit={callback}>
1357 + <ComponetWithEffects />
1358 + <ComponentWithCommitHooks />
1359 + </React.Profiler>,
1360 + );
1361
1614 - expect(callback).toHaveBeenCalledTimes(3);
1362 + expect(callback).toHaveBeenCalledTimes(2);
1363
1616 - call = callback.mock.calls[2];
1364 + call = callback.mock.calls[1];
1365
1618 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1619 - expect(call[0]).toBe('root-update');
1620 - expect(call[1]).toBe('update');
1621 - expect(call[2]).toBe(1100); // durations
1622 - expect(call[3]).toBe(1124); // commit start time (before mutations or effects)
1623 - expect(call[4]).toEqual(
1624 - enableSchedulerTracing ? new Set() : undefined,
1625 - ); // interaction events
1626 - });
1366 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1367 + expect(call[0]).toBe('update-test');
1368 + expect(call[1]).toBe('update');
1369 + expect(call[2]).toBe(1011000); // durations
1370 + expect(call[3]).toBe(101017); // commit start time (before mutations or effects)
1371 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1372
1628 - it('should properly report time in layout effects even when there are errors', () => {
1629 - const callback = jest.fn();
1373 + Scheduler.unstable_advanceTime(1);
1374 +
1375 + renderer.update(
1376 + <React.Profiler id="unmount-test" onCommit={callback} />,
1377 + );
1378 +
1379 + expect(callback).toHaveBeenCalledTimes(3);
1380 +
1381 + call = callback.mock.calls[2];
1382
1631 - class ErrorBoundary extends React.Component {
1632 - state = {error: null};
1633 - static getDerivedStateFromError(error) {
1634 - return {error};
1383 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1384 + expect(call[0]).toBe('unmount-test');
1385 + expect(call[1]).toBe('update');
1386 + expect(call[2]).toBe(10100); // durations
1387 + expect(call[3]).toBe(1112030); // commit start time (before mutations or effects)
1388 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1389 + });
1390 +
1391 + it('should report time spent in layout effects and commit lifecycles with cascading renders', () => {
1392 + const callback = jest.fn();
1393 +
1394 + const ComponetWithEffects = ({shouldCascade}) => {
1395 + const [didCascade, setDidCascade] = React.useState(false);
1396 + React.useLayoutEffect(() => {
1397 + if (shouldCascade && !didCascade) {
1398 + setDidCascade(true);
1399 }
1636 - render() {
1637 - return this.state.error === null
1638 - ? this.props.children
1639 - : this.props.fallback;
1400 + Scheduler.unstable_advanceTime(didCascade ? 30 : 10);
1401 + return () => {
1402 + Scheduler.unstable_advanceTime(100);
1403 + };
1404 + }, [didCascade, shouldCascade]);
1405 + return null;
1406 + };
1407 +
1408 + class ComponentWithCommitHooks extends React.Component {
1409 + state = {
1410 + didCascade: false,
1411 + };
1412 + componentDidMount() {
1413 + Scheduler.unstable_advanceTime(1000);
1414 + }
1415 + componentDidUpdate() {
1416 + Scheduler.unstable_advanceTime(10000);
1417 + if (this.props.shouldCascade && !this.state.didCascade) {
1418 + this.setState({didCascade: true});
1419 }
1420 }
1642 -
1643 - const ComponetWithEffects = ({
1644 - cleanupDuration,
1645 - duration,
1646 - effectDuration,
1647 - shouldThrow,
1648 - }) => {
1649 - React.useLayoutEffect(() => {
1650 - Scheduler.unstable_advanceTime(effectDuration);
1651 - if (shouldThrow) {
1652 - throw Error('expected');
1653 - }
1654 - return () => {
1655 - Scheduler.unstable_advanceTime(cleanupDuration);
1656 - };
1657 - });
1658 - Scheduler.unstable_advanceTime(duration);
1421 + render() {
1422 return null;
1660 - };
1423 + }
1424 + }
1425 +
1426 + Scheduler.unstable_advanceTime(1);
1427 +
1428 + const renderer = ReactTestRenderer.create(
1429 + <React.Profiler id="mount-test" onCommit={callback}>
1430 + <ComponetWithEffects shouldCascade={true} />
1431 + <ComponentWithCommitHooks />
1432 + </React.Profiler>,
1433 + );
1434 +
1435 + expect(callback).toHaveBeenCalledTimes(2);
1436 +
1437 + let call = callback.mock.calls[0];
1438 +
1439 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1440 + expect(call[0]).toBe('mount-test');
1441 + expect(call[1]).toBe('mount');
1442 + expect(call[2]).toBe(1010); // durations
1443 + expect(call[3]).toBe(1); // commit start time (before mutations or effects)
1444 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1445 +
1446 + call = callback.mock.calls[1];
1447 +
1448 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1449 + expect(call[0]).toBe('mount-test');
1450 + expect(call[1]).toBe('update');
1451 + expect(call[2]).toBe(130); // durations
1452 + expect(call[3]).toBe(1011); // commit start time (before mutations or effects)
1453 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1454 +
1455 + Scheduler.unstable_advanceTime(1);
1456 +
1457 + renderer.update(
1458 + <React.Profiler id="update-test" onCommit={callback}>
1459 + <ComponetWithEffects />
1460 + <ComponentWithCommitHooks shouldCascade={true} />
1461 + </React.Profiler>,
1462 + );
1463 +
1464 + expect(callback).toHaveBeenCalledTimes(4);
1465 +
1466 + call = callback.mock.calls[2];
1467
1468 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1469 + expect(call[0]).toBe('update-test');
1470 + expect(call[1]).toBe('update');
1471 + expect(call[2]).toBe(10130); // durations
1472 + expect(call[3]).toBe(1142); // commit start time (before mutations or effects)
1473 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1474 +
1475 + call = callback.mock.calls[3];
1476 +
1477 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1478 + expect(call[0]).toBe('update-test');
1479 + expect(call[1]).toBe('update');
1480 + expect(call[2]).toBe(10000); // durations
1481 + expect(call[3]).toBe(11272); // commit start time (before mutations or effects)
1482 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1483 + });
1484 +
1485 + it('should bubble time spent in layout effects to higher profilers', () => {
1486 + const callback = jest.fn();
1487 +
1488 + const ComponetWithEffects = ({
1489 + cleanupDuration,
1490 + duration,
1491 + setCountRef,
1492 + }) => {
1493 + const setCount = React.useState(0)[1];
1494 + if (setCountRef != null) {
1495 + setCountRef.current = setCount;
1496 + }
1497 + React.useLayoutEffect(() => {
1498 + Scheduler.unstable_advanceTime(duration);
1499 + return () => {
1500 + Scheduler.unstable_advanceTime(cleanupDuration);
1501 + };
1502 + });
1503 Scheduler.unstable_advanceTime(1);
1504 + return null;
1505 + };
1506
1664 - // Test an error that happens during an effect
1507 + const setCountRef = React.createRef(null);
1508
1666 - ReactTestRenderer.act(() => {
1667 - ReactTestRenderer.create(
1668 - <React.Profiler id="root" onCommit={callback}>
1669 - <ErrorBoundary
1670 - fallback={
1671 - <ComponetWithEffects
1672 - duration={10000000}
1673 - effectDuration={100000000}
1674 - cleanupDuration={1000000000}
1675 - />
1676 - }>
1677 - <ComponetWithEffects
1678 - duration={10}
1679 - effectDuration={100}
1680 - cleanupDuration={1000}
1681 - shouldThrow={true}
1682 - />
1683 - </ErrorBoundary>
1509 + let renderer = null;
1510 + ReactTestRenderer.act(() => {
1511 + renderer = ReactTestRenderer.create(
1512 + <React.Profiler id="root-mount" onCommit={callback}>
1513 + <React.Profiler id="a">
1514 <ComponetWithEffects
1685 - duration={10000}
1686 - effectDuration={100000}
1687 - cleanupDuration={1000000}
1515 + duration={10}
1516 + cleanupDuration={100}
1517 + setCountRef={setCountRef}
1518 />
1689 - </React.Profiler>,
1690 - );
1691 - });
1519 + </React.Profiler>
1520 + <React.Profiler id="b">
1521 + <ComponetWithEffects duration={1000} cleanupDuration={10000} />
1522 + </React.Profiler>
1523 + </React.Profiler>,
1524 + );
1525 + });
1526
1693 - expect(callback).toHaveBeenCalledTimes(2);
1527 + expect(callback).toHaveBeenCalledTimes(1);
1528
1695 - let call = callback.mock.calls[0];
1529 + let call = callback.mock.calls[0];
1530
1697 - // Initial render (with error)
1698 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1699 - expect(call[0]).toBe('root');
1700 - expect(call[1]).toBe('mount');
1701 - expect(call[2]).toBe(100100); // durations
1702 - expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
1703 - expect(call[4]).toEqual(
1704 - enableSchedulerTracing ? new Set() : undefined,
1705 - ); // interaction events
1531 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1532 + expect(call[0]).toBe('root-mount');
1533 + expect(call[1]).toBe('mount');
1534 + expect(call[2]).toBe(1010); // durations
1535 + expect(call[3]).toBe(2); // commit start time (before mutations or effects)
1536 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1537
1707 - call = callback.mock.calls[1];
1538 + ReactTestRenderer.act(() => setCountRef.current(count => count + 1));
1539
1709 - // Cleanup render from error boundary
1710 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1711 - expect(call[0]).toBe('root');
1712 - expect(call[1]).toBe('update');
1713 - expect(call[2]).toBe(100000000); // durations
1714 - expect(call[3]).toBe(10110111); // commit start time (before mutations or effects)
1715 - expect(call[4]).toEqual(
1716 - enableSchedulerTracing ? new Set() : undefined,
1717 - ); // interaction events
1540 + expect(callback).toHaveBeenCalledTimes(2);
1541 +
1542 + call = callback.mock.calls[1];
1543 +
1544 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1545 + expect(call[0]).toBe('root-mount');
1546 + expect(call[1]).toBe('update');
1547 + expect(call[2]).toBe(110); // durations
1548 + expect(call[3]).toBe(1013); // commit start time (before mutations or effects)
1549 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1550 +
1551 + ReactTestRenderer.act(() => {
1552 + renderer.update(
1553 + <React.Profiler id="root-update" onCommit={callback}>
1554 + <React.Profiler id="b">
1555 + <ComponetWithEffects duration={1000} cleanupDuration={10000} />
1556 + </React.Profiler>
1557 + </React.Profiler>,
1558 + );
1559 });
1560
1720 - it('should properly report time in layout effect cleanup functions even when there are errors', () => {
1721 - const callback = jest.fn();
1561 + expect(callback).toHaveBeenCalledTimes(3);
1562
1723 - class ErrorBoundary extends React.Component {
1724 - state = {error: null};
1725 - static getDerivedStateFromError(error) {
1726 - return {error};
1727 - }
1728 - render() {
1729 - return this.state.error === null
1730 - ? this.props.children
1731 - : this.props.fallback;
1732 - }
1563 + call = callback.mock.calls[2];
1564 +
1565 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1566 + expect(call[0]).toBe('root-update');
1567 + expect(call[1]).toBe('update');
1568 + expect(call[2]).toBe(1100); // durations
1569 + expect(call[3]).toBe(1124); // commit start time (before mutations or effects)
1570 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1571 + });
1572 +
1573 + it('should properly report time in layout effects even when there are errors', () => {
1574 + const callback = jest.fn();
1575 +
1576 + class ErrorBoundary extends React.Component {
1577 + state = {error: null};
1578 + static getDerivedStateFromError(error) {
1579 + return {error};
1580 + }
1581 + render() {
1582 + return this.state.error === null
1583 + ? this.props.children
1584 + : this.props.fallback;
1585 }
1586 + }
1587
1735 - const ComponetWithEffects = ({
1736 - cleanupDuration,
1737 - duration,
1738 - effectDuration,
1739 - shouldThrow = false,
1740 - }) => {
1741 - React.useLayoutEffect(() => {
1742 - Scheduler.unstable_advanceTime(effectDuration);
1743 - return () => {
1744 - Scheduler.unstable_advanceTime(cleanupDuration);
1745 - if (shouldThrow) {
1746 - throw Error('expected');
1747 - }
1748 - };
1749 - });
1750 - Scheduler.unstable_advanceTime(duration);
1751 - return null;
1752 - };
1588 + const ComponetWithEffects = ({
1589 + cleanupDuration,
1590 + duration,
1591 + effectDuration,
1592 + shouldThrow,
1593 + }) => {
1594 + React.useLayoutEffect(() => {
1595 + Scheduler.unstable_advanceTime(effectDuration);
1596 + if (shouldThrow) {
1597 + throw Error('expected');
1598 + }
1599 + return () => {
1600 + Scheduler.unstable_advanceTime(cleanupDuration);
1601 + };
1602 + });
1603 + Scheduler.unstable_advanceTime(duration);
1604 + return null;
1605 + };
1606
1754 - Scheduler.unstable_advanceTime(1);
1607 + Scheduler.unstable_advanceTime(1);
1608
1756 - let renderer = null;
1609 + // Test an error that happens during an effect
1610
1758 - ReactTestRenderer.act(() => {
1759 - renderer = ReactTestRenderer.create(
1760 - <React.Profiler id="root" onCommit={callback}>
1761 - <ErrorBoundary
1762 - fallback={
1763 - <ComponetWithEffects
1764 - duration={10000000}
1765 - effectDuration={100000000}
1766 - cleanupDuration={1000000000}
1767 - />
1768 - }>
1611 + ReactTestRenderer.act(() => {
1612 + ReactTestRenderer.create(
1613 + <React.Profiler id="root" onCommit={callback}>
1614 + <ErrorBoundary
1615 + fallback={
1616 <ComponetWithEffects
1770 - duration={10}
1771 - effectDuration={100}
1772 - cleanupDuration={1000}
1773 - shouldThrow={true}
1617 + duration={10000000}
1618 + effectDuration={100000000}
1619 + cleanupDuration={1000000000}
1620 />
1775 - </ErrorBoundary>
1621 + }>
1622 <ComponetWithEffects
1777 - duration={10000}
1778 - effectDuration={100000}
1779 - cleanupDuration={1000000}
1623 + duration={10}
1624 + effectDuration={100}
1625 + cleanupDuration={1000}
1626 + shouldThrow={true}
1627 />
1781 - </React.Profiler>,
1782 - );
1783 - });
1628 + </ErrorBoundary>
1629 + <ComponetWithEffects
1630 + duration={10000}
1631 + effectDuration={100000}
1632 + cleanupDuration={1000000}
1633 + />
1634 + </React.Profiler>,
1635 + );
1636 + });
1637
1785 - expect(callback).toHaveBeenCalledTimes(1);
1638 + expect(callback).toHaveBeenCalledTimes(2);
1639
1787 - let call = callback.mock.calls[0];
1640 + let call = callback.mock.calls[0];
1641
1789 - // Initial render
1790 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1791 - expect(call[0]).toBe('root');
1792 - expect(call[1]).toBe('mount');
1793 - expect(call[2]).toBe(100100); // durations
1794 - expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
1795 - expect(call[4]).toEqual(
1796 - enableSchedulerTracing ? new Set() : undefined,
1797 - ); // interaction events
1642 + // Initial render (with error)
1643 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1644 + expect(call[0]).toBe('root');
1645 + expect(call[1]).toBe('mount');
1646 + expect(call[2]).toBe(100100); // durations
1647 + expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
1648 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1649
1799 - callback.mockClear();
1650 + call = callback.mock.calls[1];
1651
1801 - // Test an error that happens during an cleanup function
1652 + // Cleanup render from error boundary
1653 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1654 + expect(call[0]).toBe('root');
1655 + expect(call[1]).toBe('update');
1656 + expect(call[2]).toBe(100000000); // durations
1657 + expect(call[3]).toBe(10110111); // commit start time (before mutations or effects)
1658 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1659 + });
1660
1803 - ReactTestRenderer.act(() => {
1804 - renderer.update(
1805 - <React.Profiler id="root" onCommit={callback}>
1806 - <ErrorBoundary
1807 - fallback={
1808 - <ComponetWithEffects
1809 - duration={10000000}
1810 - effectDuration={100000000}
1811 - cleanupDuration={1000000000}
1812 - />
1813 - }>
1814 - <ComponetWithEffects
1815 - duration={10}
1816 - effectDuration={100}
1817 - cleanupDuration={1000}
1818 - shouldThrow={false}
1819 - />
1820 - </ErrorBoundary>
1821 - <ComponetWithEffects
1822 - duration={10000}
1823 - effectDuration={100000}
1824 - cleanupDuration={1000000}
1825 - />
1826 - </React.Profiler>,
1827 - );
1828 - });
1661 + it('should properly report time in layout effect cleanup functions even when there are errors', () => {
1662 + const callback = jest.fn();
1663
1830 - expect(callback).toHaveBeenCalledTimes(2);
1664 + class ErrorBoundary extends React.Component {
1665 + state = {error: null};
1666 + static getDerivedStateFromError(error) {
1667 + return {error};
1668 + }
1669 + render() {
1670 + return this.state.error === null
1671 + ? this.props.children
1672 + : this.props.fallback;
1673 + }
1674 + }
1675
1832 - call = callback.mock.calls[0];
1676 + const ComponetWithEffects = ({
1677 + cleanupDuration,
1678 + duration,
1679 + effectDuration,
1680 + shouldThrow = false,
1681 + }) => {
1682 + React.useLayoutEffect(() => {
1683 + Scheduler.unstable_advanceTime(effectDuration);
1684 + return () => {
1685 + Scheduler.unstable_advanceTime(cleanupDuration);
1686 + if (shouldThrow) {
1687 + throw Error('expected');
1688 + }
1689 + };
1690 + });
1691 + Scheduler.unstable_advanceTime(duration);
1692 + return null;
1693 + };
1694
1834 - // Update (that throws)
1835 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1836 - expect(call[0]).toBe('root');
1837 - expect(call[1]).toBe('update');
1838 - expect(call[2]).toBe(1101100); // durations
1839 - expect(call[3]).toBe(120121); // commit start time (before mutations or effects)
1840 - expect(call[4]).toEqual(
1841 - enableSchedulerTracing ? new Set() : undefined,
1842 - ); // interaction events
1695 + Scheduler.unstable_advanceTime(1);
1696
1844 - call = callback.mock.calls[1];
1697 + let renderer = null;
1698
1846 - // Cleanup render from error boundary
1847 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1848 - expect(call[0]).toBe('root');
1849 - expect(call[1]).toBe('update');
1850 - expect(call[2]).toBe(100001000); // durations
1851 - expect(call[3]).toBe(11221221); // commit start time (before mutations or effects)
1852 - expect(call[4]).toEqual(
1853 - enableSchedulerTracing ? new Set() : undefined,
1854 - ); // interaction events
1699 + ReactTestRenderer.act(() => {
1700 + renderer = ReactTestRenderer.create(
1701 + <React.Profiler id="root" onCommit={callback}>
1702 + <ErrorBoundary
1703 + fallback={
1704 + <ComponetWithEffects
1705 + duration={10000000}
1706 + effectDuration={100000000}
1707 + cleanupDuration={1000000000}
1708 + />
1709 + }>
1710 + <ComponetWithEffects
1711 + duration={10}
1712 + effectDuration={100}
1713 + cleanupDuration={1000}
1714 + shouldThrow={true}
1715 + />
1716 + </ErrorBoundary>
1717 + <ComponetWithEffects
1718 + duration={10000}
1719 + effectDuration={100000}
1720 + cleanupDuration={1000000}
1721 + />
1722 + </React.Profiler>,
1723 + );
1724 });
1725
1857 - if (enableSchedulerTracing) {
1858 - it('should report interactions that were active', () => {
1859 - const callback = jest.fn();
1726 + expect(callback).toHaveBeenCalledTimes(1);
1727
1861 - const ComponetWithEffects = () => {
1862 - const [didMount, setDidMount] = React.useState(false);
1863 - React.useLayoutEffect(() => {
1864 - Scheduler.unstable_advanceTime(didMount ? 1000 : 100);
1865 - if (!didMount) {
1866 - setDidMount(true);
1867 - }
1868 - return () => {
1869 - Scheduler.unstable_advanceTime(10000);
1870 - };
1871 - }, [didMount]);
1872 - Scheduler.unstable_advanceTime(10);
1873 - return null;
1874 - };
1728 + let call = callback.mock.calls[0];
1729
1876 - const interaction = {
1877 - id: 0,
1878 - name: 'mount',
1879 - timestamp: Scheduler.unstable_now(),
1880 - };
1730 + // Initial render
1731 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1732 + expect(call[0]).toBe('root');
1733 + expect(call[1]).toBe('mount');
1734 + expect(call[2]).toBe(100100); // durations
1735 + expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
1736 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1737
1882 - Scheduler.unstable_advanceTime(1);
1738 + callback.mockClear();
1739
1884 - SchedulerTracing.unstable_trace(
1885 - interaction.name,
1886 - interaction.timestamp,
1887 - () => {
1888 - ReactTestRenderer.create(
1889 - <React.Profiler id="root" onCommit={callback}>
1890 - <ComponetWithEffects />
1891 - </React.Profiler>,
1892 - );
1893 - },
1894 - );
1740 + // Test an error that happens during an cleanup function
1741
1896 - expect(callback).toHaveBeenCalledTimes(2);
1742 + ReactTestRenderer.act(() => {
1743 + renderer.update(
1744 + <React.Profiler id="root" onCommit={callback}>
1745 + <ErrorBoundary
1746 + fallback={
1747 + <ComponetWithEffects
1748 + duration={10000000}
1749 + effectDuration={100000000}
1750 + cleanupDuration={1000000000}
1751 + />
1752 + }>
1753 + <ComponetWithEffects
1754 + duration={10}
1755 + effectDuration={100}
1756 + cleanupDuration={1000}
1757 + shouldThrow={false}
1758 + />
1759 + </ErrorBoundary>
1760 + <ComponetWithEffects
1761 + duration={10000}
1762 + effectDuration={100000}
1763 + cleanupDuration={1000000}
1764 + />
1765 + </React.Profiler>,
1766 + );
1767 + });
1768
1898 - let call = callback.mock.calls[0];
1769 + expect(callback).toHaveBeenCalledTimes(2);
1770
1900 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1901 - expect(call[0]).toBe('root');
1902 - expect(call[1]).toBe('mount');
1903 - expect(call[4]).toMatchInteractions([interaction]);
1771 + call = callback.mock.calls[0];
1772
1905 - call = callback.mock.calls[1];
1773 + // Update (that throws)
1774 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1775 + expect(call[0]).toBe('root');
1776 + expect(call[1]).toBe('update');
1777 + expect(call[2]).toBe(1101100); // durations
1778 + expect(call[3]).toBe(120121); // commit start time (before mutations or effects)
1779 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1780
1907 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1908 - expect(call[0]).toBe('root');
1909 - expect(call[1]).toBe('update');
1910 - expect(call[4]).toMatchInteractions([interaction]);
1911 - });
1912 - }
1913 - });
1781 + call = callback.mock.calls[1];
1782
1915 - describe(`onPostCommit enableSchedulerTracing:${
1916 - enableSchedulerTracing ? 'enabled' : 'disabled'
1917 - } deferPassiveEffectCleanupDuringUnmount:${
1918 - deferPassiveEffectCleanupDuringUnmount ? 'enabled' : 'disabled'
1919 - }`, () => {
1920 - beforeEach(() => {
1921 - jest.resetModules();
1922 -
1923 - loadModules({
1924 - deferPassiveEffectCleanupDuringUnmount,
1925 - enableSchedulerTracing,
1926 - });
1927 - });
1783 + // Cleanup render from error boundary
1784 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1785 + expect(call[0]).toBe('root');
1786 + expect(call[1]).toBe('update');
1787 + expect(call[2]).toBe(100001000); // durations
1788 + expect(call[3]).toBe(11221221); // commit start time (before mutations or effects)
1789 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1790 + });
1791
1929 - it('should report time spent in passive effects', () => {
1792 + if (enableSchedulerTracing) {
1793 + it('should report interactions that were active', () => {
1794 const callback = jest.fn();
1795
1796 const ComponetWithEffects = () => {
1797 + const [didMount, setDidMount] = React.useState(false);
1798 React.useLayoutEffect(() => {
1934 - // This layout effect is here to verify that its time isn't reported.
1935 - Scheduler.unstable_advanceTime(5);
1936 - return () => {
1937 - Scheduler.unstable_advanceTime(7);
1938 - };
1939 - });
1940 - React.useEffect(() => {
1941 - Scheduler.unstable_advanceTime(10);
1942 - return () => {
1943 - Scheduler.unstable_advanceTime(100);
1944 - };
1945 - }, []);
1946 - React.useEffect(() => {
1947 - Scheduler.unstable_advanceTime(1000);
1799 + Scheduler.unstable_advanceTime(didMount ? 1000 : 100);
1800 + if (!didMount) {
1801 + setDidMount(true);
1802 + }
1803 return () => {
1804 Scheduler.unstable_advanceTime(10000);
1805 };
1951 - });
1806 + }, [didMount]);
1807 + Scheduler.unstable_advanceTime(10);
1808 return null;
1809 };
1810
1811 + const interaction = {
1812 + id: 0,
1813 + name: 'mount',
1814 + timestamp: Scheduler.unstable_now(),
1815 + };
1816 +
1817 Scheduler.unstable_advanceTime(1);
1818
1957 - let renderer;
1958 - ReactTestRenderer.act(() => {
1959 - renderer = ReactTestRenderer.create(
1960 - <React.Profiler id="mount-test" onPostCommit={callback}>
1961 - <ComponetWithEffects />
1962 - </React.Profiler>,
1963 - );
1964 - });
1965 - Scheduler.unstable_flushAll();
1819 + SchedulerTracing.unstable_trace(
1820 + interaction.name,
1821 + interaction.timestamp,
1822 + () => {
1823 + ReactTestRenderer.create(
1824 + <React.Profiler id="root" onCommit={callback}>
1825 + <ComponetWithEffects />
1826 + </React.Profiler>,
1827 + );
1828 + },
1829 + );
1830
1967 - expect(callback).toHaveBeenCalledTimes(1);
1831 + expect(callback).toHaveBeenCalledTimes(2);
1832
1833 let call = callback.mock.calls[0];
1834
1835 expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1972 - expect(call[0]).toBe('mount-test');
1836 + expect(call[0]).toBe('root');
1837 expect(call[1]).toBe('mount');
1974 - expect(call[2]).toBe(1010); // durations
1975 - expect(call[3]).toBe(1); // commit start time (before mutations or effects)
1976 - expect(call[4]).toEqual(
1977 - enableSchedulerTracing ? new Set() : undefined,
1978 - ); // interaction events
1838 + expect(call[4]).toMatchInteractions([interaction]);
1839
1980 - Scheduler.unstable_advanceTime(1);
1840 + call = callback.mock.calls[1];
1841 +
1842 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1843 + expect(call[0]).toBe('root');
1844 + expect(call[1]).toBe('update');
1845 + expect(call[4]).toMatchInteractions([interaction]);
1846 + });
1847 + }
1848 + });
1849 +
1850 + describe(`onPostCommit enableSchedulerTracing:${
1851 + enableSchedulerTracing ? 'enabled' : 'disabled'
1852 + }`, () => {
1853 + beforeEach(() => {
1854 + jest.resetModules();
1855 +
1856 + loadModules({
1857 + enableSchedulerTracing,
1858 + });
1859 + });
1860 +
1861 + it('should report time spent in passive effects', () => {
1862 + const callback = jest.fn();
1863 +
1864 + const ComponetWithEffects = () => {
1865 + React.useLayoutEffect(() => {
1866 + // This layout effect is here to verify that its time isn't reported.
1867 + Scheduler.unstable_advanceTime(5);
1868 + return () => {
1869 + Scheduler.unstable_advanceTime(7);
1870 + };
1871 + });
1872 + React.useEffect(() => {
1873 + Scheduler.unstable_advanceTime(10);
1874 + return () => {
1875 + Scheduler.unstable_advanceTime(100);
1876 + };
1877 + }, []);
1878 + React.useEffect(() => {
1879 + Scheduler.unstable_advanceTime(1000);
1880 + return () => {
1881 + Scheduler.unstable_advanceTime(10000);
1882 + };
1883 + });
1884 + return null;
1885 + };
1886 +
1887 + Scheduler.unstable_advanceTime(1);
1888 +
1889 + let renderer;
1890 + ReactTestRenderer.act(() => {
1891 + renderer = ReactTestRenderer.create(
1892 + <React.Profiler id="mount-test" onPostCommit={callback}>
1893 + <ComponetWithEffects />
1894 + </React.Profiler>,
1895 + );
1896 + });
1897 + Scheduler.unstable_flushAll();
1898 +
1899 + expect(callback).toHaveBeenCalledTimes(1);
1900 +
1901 + let call = callback.mock.calls[0];
1902 +
1903 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1904 + expect(call[0]).toBe('mount-test');
1905 + expect(call[1]).toBe('mount');
1906 + expect(call[2]).toBe(1010); // durations
1907 + expect(call[3]).toBe(1); // commit start time (before mutations or effects)
1908 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1909 +
1910 + Scheduler.unstable_advanceTime(1);
1911 +
1912 + ReactTestRenderer.act(() => {
1913 + renderer.update(
1914 + <React.Profiler id="update-test" onPostCommit={callback}>
1915 + <ComponetWithEffects />
1916 + </React.Profiler>,
1917 + );
1918 + });
1919 + Scheduler.unstable_flushAll();
1920
1982 - ReactTestRenderer.act(() => {
1983 - renderer.update(
1984 - <React.Profiler id="update-test" onPostCommit={callback}>
1985 - <ComponetWithEffects />
1986 - </React.Profiler>,
1987 - );
1988 - });
1989 - Scheduler.unstable_flushAll();
1921 + expect(callback).toHaveBeenCalledTimes(2);
1922
1991 - expect(callback).toHaveBeenCalledTimes(2);
1923 + call = callback.mock.calls[1];
1924
1993 - call = callback.mock.calls[1];
1925 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1926 + expect(call[0]).toBe('update-test');
1927 + expect(call[1]).toBe('update');
1928 + expect(call[2]).toBe(11000); // durations
1929 + expect(call[3]).toBe(1017); // commit start time (before mutations or effects)
1930 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1931
1995 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1996 - expect(call[0]).toBe('update-test');
1997 - expect(call[1]).toBe('update');
1998 - expect(call[2]).toBe(11000); // durations
1999 - expect(call[3]).toBe(1017); // commit start time (before mutations or effects)
2000 - expect(call[4]).toEqual(
2001 - enableSchedulerTracing ? new Set() : undefined,
2002 - ); // interaction events
1932 + Scheduler.unstable_advanceTime(1);
1933
2004 - Scheduler.unstable_advanceTime(1);
1934 + ReactTestRenderer.act(() => {
1935 + renderer.update(
1936 + <React.Profiler id="unmount-test" onPostCommit={callback} />,
1937 + );
1938 + });
1939 + Scheduler.unstable_flushAll();
1940 +
1941 + expect(callback).toHaveBeenCalledTimes(3);
1942 +
1943 + call = callback.mock.calls[2];
1944 +
1945 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1946 + expect(call[0]).toBe('unmount-test');
1947 + expect(call[1]).toBe('update');
1948 + // TODO (bvaughn) The duration reported below should be 10100, but is 0
1949 + // by the time the passive effect is flushed its parent Fiber pointer is gone.
1950 + // If we refactor to preserve the unmounted Fiber tree we could fix this.
1951 + // The current implementation would require too much extra overhead to track this.
1952 + expect(call[2]).toBe(0); // durations
1953 + expect(call[3]).toBe(12030); // commit start time (before mutations or effects)
1954 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1955 + });
1956
2006 - ReactTestRenderer.act(() => {
2007 - renderer.update(
2008 - <React.Profiler id="unmount-test" onPostCommit={callback} />,
2009 - );
2010 - });
2011 - Scheduler.unstable_flushAll();
1957 + it('should report time spent in passive effects with cascading renders', () => {
1958 + const callback = jest.fn();
1959
2013 - expect(callback).toHaveBeenCalledTimes(3);
1960 + const ComponetWithEffects = () => {
1961 + const [didMount, setDidMount] = React.useState(false);
1962 + React.useEffect(() => {
1963 + if (!didMount) {
1964 + setDidMount(true);
1965 + }
1966 + Scheduler.unstable_advanceTime(didMount ? 30 : 10);
1967 + return () => {
1968 + Scheduler.unstable_advanceTime(100);
1969 + };
1970 + }, [didMount]);
1971 + return null;
1972 + };
1973
2015 - call = callback.mock.calls[2];
1974 + Scheduler.unstable_advanceTime(1);
1975
2017 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2018 - expect(call[0]).toBe('unmount-test');
2019 - expect(call[1]).toBe('update');
2020 - // TODO (bvaughn) The duration reported below should be 10100, but is 0
2021 - // by the time the passive effect is flushed its parent Fiber pointer is gone.
2022 - // If we refactor to preserve the unmounted Fiber tree we could fix this.
2023 - // The current implementation would require too much extra overhead to track this.
2024 - expect(call[2]).toBe(0); // durations
2025 - expect(call[3]).toBe(12030); // commit start time (before mutations or effects)
2026 - expect(call[4]).toEqual(
2027 - enableSchedulerTracing ? new Set() : undefined,
2028 - ); // interaction events
1976 + ReactTestRenderer.act(() => {
1977 + ReactTestRenderer.create(
1978 + <React.Profiler id="mount-test" onPostCommit={callback}>
1979 + <ComponetWithEffects />
1980 + </React.Profiler>,
1981 + );
1982 });
1983
2031 - it('should report time spent in passive effects with cascading renders', () => {
2032 - const callback = jest.fn();
1984 + expect(callback).toHaveBeenCalledTimes(2);
1985
2034 - const ComponetWithEffects = () => {
2035 - const [didMount, setDidMount] = React.useState(false);
2036 - React.useEffect(() => {
2037 - if (!didMount) {
2038 - setDidMount(true);
2039 - }
2040 - Scheduler.unstable_advanceTime(didMount ? 30 : 10);
2041 - return () => {
2042 - Scheduler.unstable_advanceTime(100);
2043 - };
2044 - }, [didMount]);
2045 - return null;
2046 - };
1986 + let call = callback.mock.calls[0];
1987
2048 - Scheduler.unstable_advanceTime(1);
1988 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1989 + expect(call[0]).toBe('mount-test');
1990 + expect(call[1]).toBe('mount');
1991 + expect(call[2]).toBe(10); // durations
1992 + expect(call[3]).toBe(1); // commit start time (before mutations or effects)
1993 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
1994
2050 - ReactTestRenderer.act(() => {
2051 - ReactTestRenderer.create(
2052 - <React.Profiler id="mount-test" onPostCommit={callback}>
2053 - <ComponetWithEffects />
2054 - </React.Profiler>,
2055 - );
1995 + call = callback.mock.calls[1];
1996 +
1997 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1998 + expect(call[0]).toBe('mount-test');
1999 + expect(call[1]).toBe('update');
2000 + expect(call[2]).toBe(130); // durations
2001 + expect(call[3]).toBe(11); // commit start time (before mutations or effects)
2002 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
2003 + });
2004 +
2005 + it('should bubble time spent in effects to higher profilers', () => {
2006 + const callback = jest.fn();
2007 +
2008 + const ComponetWithEffects = ({
2009 + cleanupDuration,
2010 + duration,
2011 + setCountRef,
2012 + }) => {
2013 + const setCount = React.useState(0)[1];
2014 + if (setCountRef != null) {
2015 + setCountRef.current = setCount;
2016 + }
2017 + React.useEffect(() => {
2018 + Scheduler.unstable_advanceTime(duration);
2019 + return () => {
2020 + Scheduler.unstable_advanceTime(cleanupDuration);
2021 + };
2022 });
2023 + Scheduler.unstable_advanceTime(1);
2024 + return null;
2025 + };
2026
2058 - expect(callback).toHaveBeenCalledTimes(2);
2027 + const setCountRef = React.createRef(null);
2028
2060 - let call = callback.mock.calls[0];
2029 + let renderer = null;
2030 + ReactTestRenderer.act(() => {
2031 + renderer = ReactTestRenderer.create(
2032 + <React.Profiler id="root-mount" onPostCommit={callback}>
2033 + <React.Profiler id="a">
2034 + <ComponetWithEffects
2035 + duration={10}
2036 + cleanupDuration={100}
2037 + setCountRef={setCountRef}
2038 + />
2039 + </React.Profiler>
2040 + <React.Profiler id="b">
2041 + <ComponetWithEffects duration={1000} cleanupDuration={10000} />
2042 + </React.Profiler>
2043 + </React.Profiler>,
2044 + );
2045 + });
2046
2062 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2063 - expect(call[0]).toBe('mount-test');
2064 - expect(call[1]).toBe('mount');
2065 - expect(call[2]).toBe(10); // durations
2066 - expect(call[3]).toBe(1); // commit start time (before mutations or effects)
2067 - expect(call[4]).toEqual(
2068 - enableSchedulerTracing ? new Set() : undefined,
2069 - ); // interaction events
2047 + expect(callback).toHaveBeenCalledTimes(1);
2048
2071 - call = callback.mock.calls[1];
2049 + let call = callback.mock.calls[0];
2050
2073 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2074 - expect(call[0]).toBe('mount-test');
2075 - expect(call[1]).toBe('update');
2076 - expect(call[2]).toBe(130); // durations
2077 - expect(call[3]).toBe(11); // commit start time (before mutations or effects)
2078 - expect(call[4]).toEqual(
2079 - enableSchedulerTracing ? new Set() : undefined,
2080 - ); // interaction events
2081 - });
2051 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2052 + expect(call[0]).toBe('root-mount');
2053 + expect(call[1]).toBe('mount');
2054 + expect(call[2]).toBe(1010); // durations
2055 + expect(call[3]).toBe(2); // commit start time (before mutations or effects)
2056 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
2057
2083 - it('should bubble time spent in effects to higher profilers', () => {
2084 - const callback = jest.fn();
2058 + ReactTestRenderer.act(() => setCountRef.current(count => count + 1));
2059
2086 - const ComponetWithEffects = ({
2087 - cleanupDuration,
2088 - duration,
2089 - setCountRef,
2090 - }) => {
2091 - const setCount = React.useState(0)[1];
2092 - if (setCountRef != null) {
2093 - setCountRef.current = setCount;
2094 - }
2095 - React.useEffect(() => {
2096 - Scheduler.unstable_advanceTime(duration);
2097 - return () => {
2098 - Scheduler.unstable_advanceTime(cleanupDuration);
2099 - };
2100 - });
2101 - Scheduler.unstable_advanceTime(1);
2102 - return null;
2103 - };
2060 + expect(callback).toHaveBeenCalledTimes(2);
2061
2105 - const setCountRef = React.createRef(null);
2062 + call = callback.mock.calls[1];
2063
2107 - let renderer = null;
2108 - ReactTestRenderer.act(() => {
2109 - renderer = ReactTestRenderer.create(
2110 - <React.Profiler id="root-mount" onPostCommit={callback}>
2111 - <React.Profiler id="a">
2112 - <ComponetWithEffects
2113 - duration={10}
2114 - cleanupDuration={100}
2115 - setCountRef={setCountRef}
2116 - />
2117 - </React.Profiler>
2118 - <React.Profiler id="b">
2119 - <ComponetWithEffects
2120 - duration={1000}
2121 - cleanupDuration={10000}
2122 - />
2123 - </React.Profiler>
2124 - </React.Profiler>,
2125 - );
2126 - });
2064 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2065 + expect(call[0]).toBe('root-mount');
2066 + expect(call[1]).toBe('update');
2067 + expect(call[2]).toBe(110); // durations
2068 + expect(call[3]).toBe(1013); // commit start time (before mutations or effects)
2069 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
2070
2128 - expect(callback).toHaveBeenCalledTimes(1);
2071 + ReactTestRenderer.act(() => {
2072 + renderer.update(
2073 + <React.Profiler id="root-update" onPostCommit={callback}>
2074 + <React.Profiler id="b">
2075 + <ComponetWithEffects duration={1000} cleanupDuration={10000} />
2076 + </React.Profiler>
2077 + </React.Profiler>,
2078 + );
2079 + });
2080
2130 - let call = callback.mock.calls[0];
2081 + expect(callback).toHaveBeenCalledTimes(3);
2082
2132 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2133 - expect(call[0]).toBe('root-mount');
2134 - expect(call[1]).toBe('mount');
2135 - expect(call[2]).toBe(1010); // durations
2136 - expect(call[3]).toBe(2); // commit start time (before mutations or effects)
2137 - expect(call[4]).toEqual(
2138 - enableSchedulerTracing ? new Set() : undefined,
2139 - ); // interaction events
2083 + call = callback.mock.calls[2];
2084
2141 - ReactTestRenderer.act(() => setCountRef.current(count => count + 1));
2085 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2086 + expect(call[0]).toBe('root-update');
2087 + expect(call[1]).toBe('update');
2088 + expect(call[2]).toBe(1100); // durations
2089 + expect(call[3]).toBe(1124); // commit start time (before mutations or effects)
2090 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
2091 + });
2092
2143 - expect(callback).toHaveBeenCalledTimes(2);
2093 + it('should properly report time in passive effects even when there are errors', () => {
2094 + const callback = jest.fn();
2095
2145 - call = callback.mock.calls[1];
2096 + class ErrorBoundary extends React.Component {
2097 + state = {error: null};
2098 + static getDerivedStateFromError(error) {
2099 + return {error};
2100 + }
2101 + render() {
2102 + return this.state.error === null
2103 + ? this.props.children
2104 + : this.props.fallback;
2105 + }
2106 + }
2107
2147 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2148 - expect(call[0]).toBe('root-mount');
2149 - expect(call[1]).toBe('update');
2150 - expect(call[2]).toBe(110); // durations
2151 - expect(call[3]).toBe(1013); // commit start time (before mutations or effects)
2152 - expect(call[4]).toEqual(
2153 - enableSchedulerTracing ? new Set() : undefined,
2154 - ); // interaction events
2108 + const ComponetWithEffects = ({
2109 + cleanupDuration,
2110 + duration,
2111 + effectDuration,
2112 + shouldThrow,
2113 + }) => {
2114 + React.useEffect(() => {
2115 + Scheduler.unstable_advanceTime(effectDuration);
2116 + if (shouldThrow) {
2117 + throw Error('expected');
2118 + }
2119 + return () => {
2120 + Scheduler.unstable_advanceTime(cleanupDuration);
2121 + };
2122 + });
2123 + Scheduler.unstable_advanceTime(duration);
2124 + return null;
2125 + };
2126
2156 - ReactTestRenderer.act(() => {
2157 - renderer.update(
2158 - <React.Profiler id="root-update" onPostCommit={callback}>
2159 - <React.Profiler id="b">
2127 + Scheduler.unstable_advanceTime(1);
2128 +
2129 + // Test an error that happens during an effect
2130 +
2131 + ReactTestRenderer.act(() => {
2132 + ReactTestRenderer.create(
2133 + <React.Profiler id="root" onPostCommit={callback}>
2134 + <ErrorBoundary
2135 + fallback={
2136 <ComponetWithEffects
2161 - duration={1000}
2162 - cleanupDuration={10000}
2137 + duration={10000000}
2138 + effectDuration={100000000}
2139 + cleanupDuration={1000000000}
2140 />
2164 - </React.Profiler>
2165 - </React.Profiler>,
2166 - );
2167 - });
2141 + }>
2142 + <ComponetWithEffects
2143 + duration={10}
2144 + effectDuration={100}
2145 + cleanupDuration={1000}
2146 + shouldThrow={true}
2147 + />
2148 + </ErrorBoundary>
2149 + <ComponetWithEffects
2150 + duration={10000}
2151 + effectDuration={100000}
2152 + cleanupDuration={1000000}
2153 + />
2154 + </React.Profiler>,
2155 + );
2156 + });
2157
2169 - expect(callback).toHaveBeenCalledTimes(3);
2158 + expect(callback).toHaveBeenCalledTimes(2);
2159
2171 - call = callback.mock.calls[2];
2160 + let call = callback.mock.calls[0];
2161
2173 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2174 - expect(call[0]).toBe('root-update');
2175 - expect(call[1]).toBe('update');
2176 - expect(call[2]).toBe(1100); // durations
2177 - expect(call[3]).toBe(1124); // commit start time (before mutations or effects)
2178 - expect(call[4]).toEqual(
2179 - enableSchedulerTracing ? new Set() : undefined,
2180 - ); // interaction events
2181 - });
2162 + // Initial render (with error)
2163 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2164 + expect(call[0]).toBe('root');
2165 + expect(call[1]).toBe('mount');
2166 + expect(call[2]).toBe(100100); // durations
2167 + expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
2168 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
2169
2183 - it('should properly report time in passive effects even when there are errors', () => {
2184 - const callback = jest.fn();
2170 + call = callback.mock.calls[1];
2171
2186 - class ErrorBoundary extends React.Component {
2187 - state = {error: null};
2188 - static getDerivedStateFromError(error) {
2189 - return {error};
2190 - }
2191 - render() {
2192 - return this.state.error === null
2193 - ? this.props.children
2194 - : this.props.fallback;
2195 - }
2172 + // Cleanup render from error boundary
2173 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2174 + expect(call[0]).toBe('root');
2175 + expect(call[1]).toBe('update');
2176 + expect(call[2]).toBe(100000000); // durations
2177 + expect(call[3]).toBe(10110111); // commit start time (before mutations or effects)
2178 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
2179 + });
2180 +
2181 + it('should properly report time in passive effect cleanup functions even when there are errors', () => {
2182 + const callback = jest.fn();
2183 +
2184 + class ErrorBoundary extends React.Component {
2185 + state = {error: null};
2186 + static getDerivedStateFromError(error) {
2187 + return {error};
2188 + }
2189 + render() {
2190 + return this.state.error === null
2191 + ? this.props.children
2192 + : this.props.fallback;
2193 }
2194 + }
2195
2198 - const ComponetWithEffects = ({
2199 - cleanupDuration,
2200 - duration,
2201 - effectDuration,
2202 - shouldThrow,
2203 - }) => {
2204 - React.useEffect(() => {
2205 - Scheduler.unstable_advanceTime(effectDuration);
2196 + const ComponetWithEffects = ({
2197 + cleanupDuration,
2198 + duration,
2199 + effectDuration,
2200 + shouldThrow = false,
2201 + id,
2202 + }) => {
2203 + React.useEffect(() => {
2204 + Scheduler.unstable_advanceTime(effectDuration);
2205 + return () => {
2206 + Scheduler.unstable_advanceTime(cleanupDuration);
2207 if (shouldThrow) {
2208 throw Error('expected');
2209 }
2209 - return () => {
2210 - Scheduler.unstable_advanceTime(cleanupDuration);
2211 - };
2212 - });
2213 - Scheduler.unstable_advanceTime(duration);
2214 - return null;
2215 - };
2210 + };
2211 + });
2212 + Scheduler.unstable_advanceTime(duration);
2213 + return null;
2214 + };
2215
2217 - Scheduler.unstable_advanceTime(1);
2216 + Scheduler.unstable_advanceTime(1);
2217
2219 - // Test an error that happens during an effect
2218 + let renderer = null;
2219
2221 - ReactTestRenderer.act(() => {
2222 - ReactTestRenderer.create(
2223 - <React.Profiler id="root" onPostCommit={callback}>
2224 - <ErrorBoundary
2225 - fallback={
2226 - <ComponetWithEffects
2227 - duration={10000000}
2228 - effectDuration={100000000}
2229 - cleanupDuration={1000000000}
2230 - />
2231 - }>
2220 + ReactTestRenderer.act(() => {
2221 + renderer = ReactTestRenderer.create(
2222 + <React.Profiler id="root" onPostCommit={callback}>
2223 + <ErrorBoundary
2224 + fallback={
2225 <ComponetWithEffects
2233 - duration={10}
2234 - effectDuration={100}
2235 - cleanupDuration={1000}
2236 - shouldThrow={true}
2226 + duration={10000000}
2227 + effectDuration={100000000}
2228 + cleanupDuration={1000000000}
2229 />
2238 - </ErrorBoundary>
2230 + }>
2231 <ComponetWithEffects
2240 - duration={10000}
2241 - effectDuration={100000}
2242 - cleanupDuration={1000000}
2232 + duration={10}
2233 + effectDuration={100}
2234 + cleanupDuration={1000}
2235 + shouldThrow={true}
2236 />
2244 - </React.Profiler>,
2245 - );
2246 - });
2237 + </ErrorBoundary>
2238 + <ComponetWithEffects
2239 + duration={10000}
2240 + effectDuration={100000}
2241 + cleanupDuration={1000000}
2242 + />
2243 + </React.Profiler>,
2244 + );
2245 + });
2246
2248 - expect(callback).toHaveBeenCalledTimes(2);
2247 + expect(callback).toHaveBeenCalledTimes(1);
2248
2250 - let call = callback.mock.calls[0];
2249 + let call = callback.mock.calls[0];
2250
2252 - // Initial render (with error)
2253 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2254 - expect(call[0]).toBe('root');
2255 - expect(call[1]).toBe('mount');
2256 - expect(call[2]).toBe(100100); // durations
2257 - expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
2258 - expect(call[4]).toEqual(
2259 - enableSchedulerTracing ? new Set() : undefined,
2260 - ); // interaction events
2251 + // Initial render
2252 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2253 + expect(call[0]).toBe('root');
2254 + expect(call[1]).toBe('mount');
2255 + expect(call[2]).toBe(100100); // durations
2256 + expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
2257 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
2258
2262 - call = callback.mock.calls[1];
2259 + callback.mockClear();
2260
2264 - // Cleanup render from error boundary
2265 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2266 - expect(call[0]).toBe('root');
2267 - expect(call[1]).toBe('update');
2268 - expect(call[2]).toBe(100000000); // durations
2269 - expect(call[3]).toBe(10110111); // commit start time (before mutations or effects)
2270 - expect(call[4]).toEqual(
2271 - enableSchedulerTracing ? new Set() : undefined,
2272 - ); // interaction events
2261 + // Test an error that happens during an cleanup function
2262 +
2263 + ReactTestRenderer.act(() => {
2264 + renderer.update(
2265 + <React.Profiler id="root" onPostCommit={callback}>
2266 + <ErrorBoundary
2267 + fallback={
2268 + <ComponetWithEffects
2269 + duration={10000000}
2270 + effectDuration={100000000}
2271 + cleanupDuration={1000000000}
2272 + />
2273 + }>
2274 + <ComponetWithEffects
2275 + duration={10}
2276 + effectDuration={100}
2277 + cleanupDuration={1000}
2278 + shouldThrow={false}
2279 + />
2280 + </ErrorBoundary>
2281 + <ComponetWithEffects
2282 + duration={10000}
2283 + effectDuration={100000}
2284 + cleanupDuration={1000000}
2285 + />
2286 + </React.Profiler>,
2287 + );
2288 });
2289
2275 - it('should properly report time in passive effect cleanup functions even when there are errors', () => {
2276 - const callback = jest.fn();
2290 + expect(callback).toHaveBeenCalledTimes(2);
2291 +
2292 + call = callback.mock.calls[0];
2293 +
2294 + // Update (that throws)
2295 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2296 + expect(call[0]).toBe('root');
2297 + expect(call[1]).toBe('update');
2298 + // We continue flushing pending effects even if one throws.
2299 + expect(call[2]).toBe(1101100); // durations
2300 + expect(call[3]).toBe(120121); // commit start time (before mutations or effects)
2301 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
2302 +
2303 + call = callback.mock.calls[1];
2304 +
2305 + // Cleanup render from error boundary
2306 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2307 + expect(call[0]).toBe('root');
2308 + expect(call[1]).toBe('update');
2309 + expect(call[2]).toBe(100000000); // durations
2310 + // The commit time varies because the above duration time varies
2311 + expect(call[3]).toBe(11221221); // commit start time (before mutations or effects)
2312 + expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
2313 + });
2314
2278 - class ErrorBoundary extends React.Component {
2279 - state = {error: null};
2280 - static getDerivedStateFromError(error) {
2281 - return {error};
2282 - }
2283 - render() {
2284 - return this.state.error === null
2285 - ? this.props.children
2286 - : this.props.fallback;
2287 - }
2288 - }
2315 + if (enableSchedulerTracing) {
2316 + it('should report interactions that were active', () => {
2317 + const callback = jest.fn();
2318
2290 - const ComponetWithEffects = ({
2291 - cleanupDuration,
2292 - duration,
2293 - effectDuration,
2294 - shouldThrow = false,
2295 - id,
2296 - }) => {
2319 + const ComponetWithEffects = () => {
2320 + const [didMount, setDidMount] = React.useState(false);
2321 React.useEffect(() => {
2298 - Scheduler.unstable_advanceTime(effectDuration);
2322 + Scheduler.unstable_advanceTime(didMount ? 1000 : 100);
2323 + if (!didMount) {
2324 + setDidMount(true);
2325 + }
2326 return () => {
2300 - Scheduler.unstable_advanceTime(cleanupDuration);
2301 - if (shouldThrow) {
2302 - throw Error('expected');
2303 - }
2327 + Scheduler.unstable_advanceTime(10000);
2328 };
2305 - });
2306 - Scheduler.unstable_advanceTime(duration);
2329 + }, [didMount]);
2330 + Scheduler.unstable_advanceTime(10);
2331 return null;
2332 };
2333
2310 - Scheduler.unstable_advanceTime(1);
2334 + const interaction = {
2335 + id: 0,
2336 + name: 'mount',
2337 + timestamp: Scheduler.unstable_now(),
2338 + };
2339
2312 - let renderer = null;
2340 + Scheduler.unstable_advanceTime(1);
2341
2342 ReactTestRenderer.act(() => {
2315 - renderer = ReactTestRenderer.create(
2316 - <React.Profiler id="root" onPostCommit={callback}>
2317 - <ErrorBoundary
2318 - fallback={
2319 - <ComponetWithEffects
2320 - duration={10000000}
2321 - effectDuration={100000000}
2322 - cleanupDuration={1000000000}
2323 - />
2324 - }>
2325 - <ComponetWithEffects
2326 - duration={10}
2327 - effectDuration={100}
2328 - cleanupDuration={1000}
2329 - shouldThrow={true}
2330 - />
2331 - </ErrorBoundary>
2332 - <ComponetWithEffects
2333 - duration={10000}
2334 - effectDuration={100000}
2335 - cleanupDuration={1000000}
2336 - />
2337 - </React.Profiler>,
2343 + SchedulerTracing.unstable_trace(
2344 + interaction.name,
2345 + interaction.timestamp,
2346 + () => {
2347 + ReactTestRenderer.create(
2348 + <React.Profiler id="root" onPostCommit={callback}>
2349 + <ComponetWithEffects />
2350 + </React.Profiler>,
2351 + );
2352 + },
2353 );
2354 });
2355
2341 - expect(callback).toHaveBeenCalledTimes(1);
2356 + expect(callback).toHaveBeenCalledTimes(2);
2357
2358 let call = callback.mock.calls[0];
2359
2345 - // Initial render
2360 expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2361 expect(call[0]).toBe('root');
2362 expect(call[1]).toBe('mount');
2349 - expect(call[2]).toBe(100100); // durations
2350 - expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
2351 - expect(call[4]).toEqual(
2352 - enableSchedulerTracing ? new Set() : undefined,
2353 - ); // interaction events
2354 -
2355 - callback.mockClear();
2356 -
2357 - // Test an error that happens during an cleanup function
2358 -
2359 - ReactTestRenderer.act(() => {
2360 - renderer.update(
2361 - <React.Profiler id="root" onPostCommit={callback}>
2362 - <ErrorBoundary
2363 - fallback={
2364 - <ComponetWithEffects
2365 - duration={10000000}
2366 - effectDuration={100000000}
2367 - cleanupDuration={1000000000}
2368 - />
2369 - }>
2370 - <ComponetWithEffects
2371 - duration={10}
2372 - effectDuration={100}
2373 - cleanupDuration={1000}
2374 - shouldThrow={false}
2375 - />
2376 - </ErrorBoundary>
2377 - <ComponetWithEffects
2378 - duration={10000}
2379 - effectDuration={100000}
2380 - cleanupDuration={1000000}
2381 - />
2382 - </React.Profiler>,
2383 - );
2384 - });
2385 -
2386 - expect(callback).toHaveBeenCalledTimes(2);
2387 -
2388 - call = callback.mock.calls[0];
2389 -
2390 - // Update (that throws)
2391 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2392 - expect(call[0]).toBe('root');
2393 - expect(call[1]).toBe('update');
2394 - // The duration varies because the flushing behavior varies when this flag is on.
2395 - // We continue flushing pending effects even if one throws.
2396 - expect(call[2]).toBe(
2397 - deferPassiveEffectCleanupDuringUnmount ? 1101100 : 1101000,
2398 - ); // durations
2399 - expect(call[3]).toBe(120121); // commit start time (before mutations or effects)
2400 - expect(call[4]).toEqual(
2401 - enableSchedulerTracing ? new Set() : undefined,
2402 - ); // interaction events
2363 + expect(call[4]).toMatchInteractions([interaction]);
2364
2365 call = callback.mock.calls[1];
2366
2406 - // Cleanup render from error boundary
2367 expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2368 expect(call[0]).toBe('root');
2369 expect(call[1]).toBe('update');
2410 - expect(call[2]).toBe(100000000); // durations
2411 - // The commit time varies because the above duration time varies
2412 - expect(call[3]).toBe(
2413 - deferPassiveEffectCleanupDuringUnmount ? 11221221 : 11221121,
2414 - ); // commit start time (before mutations or effects)
2415 - expect(call[4]).toEqual(
2416 - enableSchedulerTracing ? new Set() : undefined,
2417 - ); // interaction events
2370 + expect(call[4]).toMatchInteractions([interaction]);
2371 });
2419 -
2420 - if (enableSchedulerTracing) {
2421 - it('should report interactions that were active', () => {
2422 - const callback = jest.fn();
2423 -
2424 - const ComponetWithEffects = () => {
2425 - const [didMount, setDidMount] = React.useState(false);
2426 - React.useEffect(() => {
2427 - Scheduler.unstable_advanceTime(didMount ? 1000 : 100);
2428 - if (!didMount) {
2429 - setDidMount(true);
2430 - }
2431 - return () => {
2432 - Scheduler.unstable_advanceTime(10000);
2433 - };
2434 - }, [didMount]);
2435 - Scheduler.unstable_advanceTime(10);
2436 - return null;
2437 - };
2438 -
2439 - const interaction = {
2440 - id: 0,
2441 - name: 'mount',
2442 - timestamp: Scheduler.unstable_now(),
2443 - };
2444 -
2445 - Scheduler.unstable_advanceTime(1);
2446 -
2447 - ReactTestRenderer.act(() => {
2448 - SchedulerTracing.unstable_trace(
2449 - interaction.name,
2450 - interaction.timestamp,
2451 - () => {
2452 - ReactTestRenderer.create(
2453 - <React.Profiler id="root" onPostCommit={callback}>
2454 - <ComponetWithEffects />
2455 - </React.Profiler>,
2456 - );
2457 - },
2458 - );
2459 - });
2460 -
2461 - expect(callback).toHaveBeenCalledTimes(2);
2462 -
2463 - let call = callback.mock.calls[0];
2464 -
2465 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2466 - expect(call[0]).toBe('root');
2467 - expect(call[1]).toBe('mount');
2468 - expect(call[4]).toMatchInteractions([interaction]);
2469 -
2470 - call = callback.mock.calls[1];
2471 -
2472 - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2473 - expect(call[0]).toBe('root');
2474 - expect(call[1]).toBe('update');
2475 - expect(call[4]).toMatchInteractions([interaction]);
2476 - });
2477 - }
2478 - });
2372 + }
2373 });
2374 });
2375
packages/shared/ReactFeatureFlags.js
-15
@@ -82,21 +82,6 @@ export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
82
83 export const enableTrustedTypesIntegration = false;
84
85 -// Controls sequence of passive effect destroy and create functions.
86 -// If this flag is off, destroy and create functions may be interleaved.
87 -// When the flag is on, all destroy functions will be run (for all fibers)
88 -// before any create functions are run, similar to how layout effects work.
89 -// This flag provides a killswitch if that proves to break existing code somehow.
90 -export const runAllPassiveEffectDestroysBeforeCreates = false;
91 -
92 -// Controls behavior of deferred effect destroy functions during unmount.
93 -// Previously these functions were run during commit (along with layout effects).
94 -// Ideally we should delay these until after commit for performance reasons.
95 -// This flag provides a killswitch if that proves to break existing code somehow.
96 -//
97 -// WARNING This flag only has an affect if used with runAllPassiveEffectDestroysBeforeCreates.
98 -export const deferPassiveEffectCleanupDuringUnmount = false;
99 -
85 // Enables a warning when trying to spread a 'key' to an element;
86 // a deprecated pattern we want to get rid of in the future
87 export const warnAboutSpreadingKeyToJSX = false;
packages/shared/forks/ReactFeatureFlags.native-fb.js
-2
@@ -38,8 +38,6 @@ export const enableTrustedTypesIntegration = false;
38 export const disableTextareaChildren = false;
39 export const disableModulePatternComponents = false;
40 export const warnUnstableRenderSubtreeIntoContainer = false;
41 -export const deferPassiveEffectCleanupDuringUnmount = false;
42 -export const runAllPassiveEffectDestroysBeforeCreates = false;
41 export const enableModernEventSystem = false;
42 export const warnAboutSpreadingKeyToJSX = false;
43 export const enableComponentStackLocations = false;
packages/shared/forks/ReactFeatureFlags.native-oss.js
-2
@@ -37,8 +37,6 @@ export const enableTrustedTypesIntegration = false;
37 export const disableTextareaChildren = false;
38 export const disableModulePatternComponents = false;
39 export const warnUnstableRenderSubtreeIntoContainer = false;
40 -export const deferPassiveEffectCleanupDuringUnmount = false;
41 -export const runAllPassiveEffectDestroysBeforeCreates = false;
40 export const enableModernEventSystem = false;
41 export const warnAboutSpreadingKeyToJSX = false;
42 export const enableComponentStackLocations = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
-2
@@ -37,8 +37,6 @@ export const enableTrustedTypesIntegration = false;
37 export const disableTextareaChildren = false;
38 export const disableModulePatternComponents = false;
39 export const warnUnstableRenderSubtreeIntoContainer = false;
40 -export const deferPassiveEffectCleanupDuringUnmount = false;
41 -export const runAllPassiveEffectDestroysBeforeCreates = false;
40 export const enableModernEventSystem = false;
41 export const warnAboutSpreadingKeyToJSX = false;
42 export const enableComponentStackLocations = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
-2
@@ -37,8 +37,6 @@ export const enableTrustedTypesIntegration = false;
37 export const disableTextareaChildren = false;
38 export const disableModulePatternComponents = true;
39 export const warnUnstableRenderSubtreeIntoContainer = false;
40 -export const deferPassiveEffectCleanupDuringUnmount = true;
41 -export const runAllPassiveEffectDestroysBeforeCreates = true;
40 export const enableModernEventSystem = false;
41 export const warnAboutSpreadingKeyToJSX = false;
42 export const enableComponentStackLocations = false;
packages/shared/forks/ReactFeatureFlags.testing.js
-2
@@ -37,8 +37,6 @@ export const enableTrustedTypesIntegration = false;
37 export const disableTextareaChildren = false;
38 export const disableModulePatternComponents = false;
39 export const warnUnstableRenderSubtreeIntoContainer = false;
40 -export const deferPassiveEffectCleanupDuringUnmount = false;
41 -export const runAllPassiveEffectDestroysBeforeCreates = false;
40 export const enableModernEventSystem = false;
41 export const warnAboutSpreadingKeyToJSX = false;
42 export const enableComponentStackLocations = false;
packages/shared/forks/ReactFeatureFlags.testing.www.js
-2
@@ -37,8 +37,6 @@ export const enableTrustedTypesIntegration = false;
37 export const disableTextareaChildren = __EXPERIMENTAL__;
38 export const disableModulePatternComponents = true;
39 export const warnUnstableRenderSubtreeIntoContainer = false;
40 -export const deferPassiveEffectCleanupDuringUnmount = true;
41 -export const runAllPassiveEffectDestroysBeforeCreates = true;
40 export const enableModernEventSystem = false;
41 export const warnAboutSpreadingKeyToJSX = false;
42 export const enableComponentStackLocations = false;
packages/shared/forks/ReactFeatureFlags.www.js
-3
@@ -76,9 +76,6 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
76 // to the correct value.
77 export const enableNewReconciler = __VARIANT__;
78
79 -export const deferPassiveEffectCleanupDuringUnmount = true;
80 -export const runAllPassiveEffectDestroysBeforeCreates = true;
81 -
79 // Flow magic to verify the exports of this file match the original version.
80 // eslint-disable-next-line no-unused-vars
81 type Check<_X, Y: _X, X: Y = _X> = null;