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;