mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Rewrite ReactFiberScheduler for better integration with Scheduler package (#15151)
* Rewrite ReactFiberScheduler Adds a new implementation of ReactFiberScheduler behind a feature flag. We will maintain both implementations in parallel until the new one is proven stable enough to replace the old one. The main difference between the implementations is that the new one is integrated with the Scheduler package's priority levels. * Conditionally add fields to FiberRoot Some fields only used by the old scheduler, and some by the new. * Add separate build that enables new scheduler * Re-enable skipped test If synchronous updates are scheduled by a passive effect, that work should be flushed synchronously, even if flushPassiveEffects is called inside batchedUpdates. * Passive effects have same priority as render * Revert ability to cancel the current callback React doesn't need this anyway because it never schedules callbacks if it's already rendering. * Revert change to FiberDebugPerf Turns out this isn't neccessary. * Fix ReactFiberScheduler dead code elimination Should initialize to nothing, then assign the exports conditionally, instead of initializing to the old exports and then reassigning to the new ones. * Don't yield before commit during sync error retry * Call Scheduler.flushAll unconditionally in tests Instead of wrapping in enableNewScheduler flag.
This commit is contained in:
parent
aed0e1c30c
commit
4d5cb64aa2
7
packages/react-cache/src/LRU.js
vendored
7
packages/react-cache/src/LRU.js
vendored
|
|
@ -11,7 +11,10 @@ import * as Scheduler from 'scheduler';
|
|||
|
||||
// Intentionally not named imports because Rollup would
|
||||
// use dynamic dispatch for CommonJS interop named imports.
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_IdlePriority: IdlePriority,
|
||||
} = Scheduler;
|
||||
|
||||
type Entry<T> = {|
|
||||
value: T,
|
||||
|
|
@ -34,7 +37,7 @@ export function createLRU<T>(limit: number) {
|
|||
// The cache size exceeds the limit. Schedule a callback to delete the
|
||||
// least recently used entries.
|
||||
cleanUpIsScheduled = true;
|
||||
scheduleCallback(cleanUp);
|
||||
scheduleCallback(IdlePriority, cleanUp);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
let React;
|
||||
let ReactTestRenderer;
|
||||
let Scheduler;
|
||||
let ReactDebugTools;
|
||||
let act;
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ describe('ReactHooksInspectionIntegration', () => {
|
|||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
act = ReactTestRenderer.act;
|
||||
ReactDebugTools = require('react-debug-tools');
|
||||
});
|
||||
|
|
@ -618,6 +620,8 @@ describe('ReactHooksInspectionIntegration', () => {
|
|||
|
||||
await LazyFoo;
|
||||
|
||||
Scheduler.flushAll();
|
||||
|
||||
let childFiber = renderer.root._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
let ReactFeatureFlags;
|
||||
let enableNewScheduler;
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let Scheduler;
|
||||
|
|
@ -19,6 +21,8 @@ describe('ReactDOMHooks', () => {
|
|||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
Scheduler = require('scheduler');
|
||||
|
|
@ -97,15 +101,30 @@ describe('ReactDOMHooks', () => {
|
|||
}
|
||||
|
||||
ReactDOM.render(<Foo />, container);
|
||||
ReactDOM.unstable_batchedUpdates(() => {
|
||||
_set(0); // Forces the effect to be flushed
|
||||
expect(otherContainer.textContent).toBe('');
|
||||
ReactDOM.render(<B />, otherContainer);
|
||||
expect(otherContainer.textContent).toBe('');
|
||||
});
|
||||
expect(otherContainer.textContent).toBe('B');
|
||||
expect(calledA).toBe(false); // It was in a batch
|
||||
expect(calledB).toBe(true);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
// The old behavior was accidental; in the new scheduler, flushing passive
|
||||
// effects also flushes synchronous work, even inside batchedUpdates.
|
||||
ReactDOM.unstable_batchedUpdates(() => {
|
||||
_set(0); // Forces the effect to be flushed
|
||||
expect(otherContainer.textContent).toBe('A');
|
||||
ReactDOM.render(<B />, otherContainer);
|
||||
expect(otherContainer.textContent).toBe('A');
|
||||
});
|
||||
expect(otherContainer.textContent).toBe('B');
|
||||
expect(calledA).toBe(true);
|
||||
expect(calledB).toBe(true);
|
||||
} else {
|
||||
ReactDOM.unstable_batchedUpdates(() => {
|
||||
_set(0); // Forces the effect to be flushed
|
||||
expect(otherContainer.textContent).toBe('');
|
||||
ReactDOM.render(<B />, otherContainer);
|
||||
expect(otherContainer.textContent).toBe('');
|
||||
});
|
||||
expect(otherContainer.textContent).toBe('B');
|
||||
expect(calledA).toBe(false); // It was in a batch
|
||||
expect(calledB).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not bail out when an update is scheduled from within an event handler', () => {
|
||||
|
|
@ -14,6 +14,7 @@ let ReactDOM;
|
|||
let Suspense;
|
||||
let ReactCache;
|
||||
let ReactTestUtils;
|
||||
let Scheduler;
|
||||
let TextResource;
|
||||
let act;
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
|
|||
ReactDOM = require('react-dom');
|
||||
ReactCache = require('react-cache');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
Scheduler = require('scheduler');
|
||||
act = ReactTestUtils.act;
|
||||
Suspense = React.Suspense;
|
||||
container = document.createElement('div');
|
||||
|
|
@ -94,6 +96,8 @@ describe('ReactDOMSuspensePlaceholder', () => {
|
|||
|
||||
await advanceTimers(500);
|
||||
|
||||
Scheduler.flushAll();
|
||||
|
||||
expect(divs[0].current.style.display).toEqual('');
|
||||
expect(divs[1].current.style.display).toEqual('');
|
||||
// This div's display was set with a prop.
|
||||
|
|
@ -115,6 +119,8 @@ describe('ReactDOMSuspensePlaceholder', () => {
|
|||
|
||||
await advanceTimers(500);
|
||||
|
||||
Scheduler.flushAll();
|
||||
|
||||
expect(container.textContent).toEqual('ABC');
|
||||
});
|
||||
|
||||
|
|
@ -160,6 +166,8 @@ describe('ReactDOMSuspensePlaceholder', () => {
|
|||
|
||||
await advanceTimers(500);
|
||||
|
||||
Scheduler.flushAll();
|
||||
|
||||
expect(container.innerHTML).toEqual(
|
||||
'<span style="display: inline;">Sibling</span><span style="">Async</span>',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
let React;
|
||||
let ReactDOM;
|
||||
let ReactDOMServer;
|
||||
let Scheduler;
|
||||
|
||||
// These tests rely both on ReactDOMServer and ReactDOM.
|
||||
// If a test only needs ReactDOMServer, put it in ReactServerRendering-test instead.
|
||||
|
|
@ -21,6 +22,7 @@ describe('ReactDOMServerHydration', () => {
|
|||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
Scheduler = require('scheduler');
|
||||
});
|
||||
|
||||
it('should have the correct mounting behavior (old hydrate API)', () => {
|
||||
|
|
@ -498,6 +500,7 @@ describe('ReactDOMServerHydration', () => {
|
|||
|
||||
jest.runAllTimers();
|
||||
await Promise.resolve();
|
||||
Scheduler.flushAll();
|
||||
expect(element.textContent).toBe('Hello world');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
16
packages/react-dom/unstable-new-scheduler.js
vendored
Normal file
16
packages/react-dom/unstable-new-scheduler.js
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactDOM = require('./src/client/ReactDOM');
|
||||
|
||||
// TODO: decide on the top-level export form.
|
||||
// This is hacky but makes it work with both Rollup and Jest.
|
||||
module.exports = ReactDOM.default || ReactDOM;
|
||||
|
|
@ -7,8 +7,17 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import type {ReactPriorityLevel} from './SchedulerWithReactIntegration';
|
||||
|
||||
import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
|
||||
|
||||
import {
|
||||
ImmediatePriority,
|
||||
UserBlockingPriority,
|
||||
NormalPriority,
|
||||
IdlePriority,
|
||||
} from './SchedulerWithReactIntegration';
|
||||
|
||||
export type ExpirationTime = number;
|
||||
|
||||
export const NoWork = 0;
|
||||
|
|
@ -46,6 +55,8 @@ function computeExpirationBucket(
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: This corresponds to Scheduler's NormalPriority, not LowPriority. Update
|
||||
// the names to reflect.
|
||||
export const LOW_PRIORITY_EXPIRATION = 5000;
|
||||
export const LOW_PRIORITY_BATCH_SIZE = 250;
|
||||
|
||||
|
|
@ -80,3 +91,31 @@ export function computeInteractiveExpiration(currentTime: ExpirationTime) {
|
|||
HIGH_PRIORITY_BATCH_SIZE,
|
||||
);
|
||||
}
|
||||
|
||||
export function inferPriorityFromExpirationTime(
|
||||
currentTime: ExpirationTime,
|
||||
expirationTime: ExpirationTime,
|
||||
): ReactPriorityLevel {
|
||||
if (expirationTime === Sync) {
|
||||
return ImmediatePriority;
|
||||
}
|
||||
if (expirationTime === Never) {
|
||||
return IdlePriority;
|
||||
}
|
||||
const msUntil =
|
||||
msToExpirationTime(expirationTime) - msToExpirationTime(currentTime);
|
||||
if (msUntil <= 0) {
|
||||
return ImmediatePriority;
|
||||
}
|
||||
if (msUntil <= HIGH_PRIORITY_EXPIRATION) {
|
||||
return UserBlockingPriority;
|
||||
}
|
||||
if (msUntil <= LOW_PRIORITY_EXPIRATION) {
|
||||
return NormalPriority;
|
||||
}
|
||||
|
||||
// TODO: Handle LowPriority
|
||||
|
||||
// Assume anything lower has idle priority
|
||||
return IdlePriority;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ import {
|
|||
requestCurrentTime,
|
||||
computeExpirationForFiber,
|
||||
scheduleWork,
|
||||
requestWork,
|
||||
flushRoot,
|
||||
batchedUpdates,
|
||||
unbatchedUpdates,
|
||||
|
|
@ -300,7 +299,6 @@ export function updateContainer(
|
|||
|
||||
export {
|
||||
flushRoot,
|
||||
requestWork,
|
||||
computeUniqueAsyncExpiration,
|
||||
batchedUpdates,
|
||||
unbatchedUpdates,
|
||||
|
|
|
|||
121
packages/react-reconciler/src/ReactFiberRoot.js
vendored
121
packages/react-reconciler/src/ReactFiberRoot.js
vendored
|
|
@ -16,7 +16,10 @@ import type {Interaction} from 'scheduler/src/Tracing';
|
|||
import {noTimeout} from './ReactFiberHostConfig';
|
||||
import {createHostRootFiber} from './ReactFiber';
|
||||
import {NoWork} from './ReactFiberExpirationTime';
|
||||
import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
enableSchedulerTracing,
|
||||
enableNewScheduler,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {unstable_getThreadID} from 'scheduler/tracing';
|
||||
|
||||
// TODO: This should be lifted into the renderer.
|
||||
|
|
@ -83,6 +86,13 @@ type BaseFiberRootProperties = {|
|
|||
firstBatch: Batch | null,
|
||||
// Linked-list of roots
|
||||
nextScheduledRoot: FiberRoot | null,
|
||||
|
||||
// New Scheduler fields
|
||||
callbackNode: *,
|
||||
callbackExpirationTime: ExpirationTime,
|
||||
firstPendingTime: ExpirationTime,
|
||||
lastPendingTime: ExpirationTime,
|
||||
pingTime: ExpirationTime,
|
||||
|};
|
||||
|
||||
// The following attributes are only used by interaction tracing builds.
|
||||
|
|
@ -105,81 +115,56 @@ export type FiberRoot = {
|
|||
...ProfilingOnlyFiberRootProperties,
|
||||
};
|
||||
|
||||
function FiberRootNode(containerInfo, hydrate) {
|
||||
this.current = null;
|
||||
this.containerInfo = containerInfo;
|
||||
this.pendingChildren = null;
|
||||
this.pingCache = null;
|
||||
this.pendingCommitExpirationTime = NoWork;
|
||||
this.finishedWork = null;
|
||||
this.timeoutHandle = noTimeout;
|
||||
this.context = null;
|
||||
this.pendingContext = null;
|
||||
this.hydrate = hydrate;
|
||||
this.firstBatch = null;
|
||||
|
||||
if (enableNewScheduler) {
|
||||
this.callbackNode = null;
|
||||
this.callbackExpirationTime = NoWork;
|
||||
this.firstPendingTime = NoWork;
|
||||
this.lastPendingTime = NoWork;
|
||||
this.pingTime = NoWork;
|
||||
} else {
|
||||
this.earliestPendingTime = NoWork;
|
||||
this.latestPendingTime = NoWork;
|
||||
this.earliestSuspendedTime = NoWork;
|
||||
this.latestSuspendedTime = NoWork;
|
||||
this.latestPingedTime = NoWork;
|
||||
this.didError = false;
|
||||
this.nextExpirationTimeToWorkOn = NoWork;
|
||||
this.expirationTime = NoWork;
|
||||
this.nextScheduledRoot = null;
|
||||
}
|
||||
|
||||
if (enableSchedulerTracing) {
|
||||
this.interactionThreadID = unstable_getThreadID();
|
||||
this.memoizedInteractions = new Set();
|
||||
this.pendingInteractionMap = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
export function createFiberRoot(
|
||||
containerInfo: any,
|
||||
isConcurrent: boolean,
|
||||
hydrate: boolean,
|
||||
): FiberRoot {
|
||||
const root: FiberRoot = (new FiberRootNode(containerInfo, hydrate): any);
|
||||
|
||||
// Cyclic construction. This cheats the type system right now because
|
||||
// stateNode is any.
|
||||
const uninitializedFiber = createHostRootFiber(isConcurrent);
|
||||
|
||||
let root;
|
||||
if (enableSchedulerTracing) {
|
||||
root = ({
|
||||
current: uninitializedFiber,
|
||||
containerInfo: containerInfo,
|
||||
pendingChildren: null,
|
||||
|
||||
earliestPendingTime: NoWork,
|
||||
latestPendingTime: NoWork,
|
||||
earliestSuspendedTime: NoWork,
|
||||
latestSuspendedTime: NoWork,
|
||||
latestPingedTime: NoWork,
|
||||
|
||||
pingCache: null,
|
||||
|
||||
didError: false,
|
||||
|
||||
pendingCommitExpirationTime: NoWork,
|
||||
finishedWork: null,
|
||||
timeoutHandle: noTimeout,
|
||||
context: null,
|
||||
pendingContext: null,
|
||||
hydrate,
|
||||
nextExpirationTimeToWorkOn: NoWork,
|
||||
expirationTime: NoWork,
|
||||
firstBatch: null,
|
||||
nextScheduledRoot: null,
|
||||
|
||||
interactionThreadID: unstable_getThreadID(),
|
||||
memoizedInteractions: new Set(),
|
||||
pendingInteractionMap: new Map(),
|
||||
}: FiberRoot);
|
||||
} else {
|
||||
root = ({
|
||||
current: uninitializedFiber,
|
||||
containerInfo: containerInfo,
|
||||
pendingChildren: null,
|
||||
|
||||
pingCache: null,
|
||||
|
||||
earliestPendingTime: NoWork,
|
||||
latestPendingTime: NoWork,
|
||||
earliestSuspendedTime: NoWork,
|
||||
latestSuspendedTime: NoWork,
|
||||
latestPingedTime: NoWork,
|
||||
|
||||
didError: false,
|
||||
|
||||
pendingCommitExpirationTime: NoWork,
|
||||
finishedWork: null,
|
||||
timeoutHandle: noTimeout,
|
||||
context: null,
|
||||
pendingContext: null,
|
||||
hydrate,
|
||||
nextExpirationTimeToWorkOn: NoWork,
|
||||
expirationTime: NoWork,
|
||||
firstBatch: null,
|
||||
nextScheduledRoot: null,
|
||||
}: BaseFiberRootProperties);
|
||||
}
|
||||
|
||||
root.current = uninitializedFiber;
|
||||
uninitializedFiber.stateNode = root;
|
||||
|
||||
// The reason for the way the Flow types are structured in this file,
|
||||
// Is to avoid needing :any casts everywhere interaction tracing fields are used.
|
||||
// Unfortunately that requires an :any cast for non-interaction tracing capable builds.
|
||||
// $FlowFixMe Remove this :any cast and replace it with something better.
|
||||
return ((root: any): FiberRoot);
|
||||
return root;
|
||||
}
|
||||
|
|
|
|||
128
packages/react-reconciler/src/ReactFiberScheduler.js
vendored
128
packages/react-reconciler/src/ReactFiberScheduler.js
vendored
|
|
@ -22,7 +22,6 @@ import {
|
|||
markLegacyErrorBoundaryAsFailed as markLegacyErrorBoundaryAsFailed_old,
|
||||
isAlreadyFailedLegacyErrorBoundary as isAlreadyFailedLegacyErrorBoundary_old,
|
||||
scheduleWork as scheduleWork_old,
|
||||
requestWork as requestWork_old,
|
||||
flushRoot as flushRoot_old,
|
||||
batchedUpdates as batchedUpdates_old,
|
||||
unbatchedUpdates as unbatchedUpdates_old,
|
||||
|
|
@ -35,6 +34,7 @@ import {
|
|||
computeUniqueAsyncExpiration as computeUniqueAsyncExpiration_old,
|
||||
flushPassiveEffects as flushPassiveEffects_old,
|
||||
warnIfNotCurrentlyActingUpdatesInDev as warnIfNotCurrentlyActingUpdatesInDev_old,
|
||||
inferStartTimeFromExpirationTime as inferStartTimeFromExpirationTime_old,
|
||||
} from './ReactFiberScheduler.old';
|
||||
|
||||
import {
|
||||
|
|
@ -50,7 +50,6 @@ import {
|
|||
markLegacyErrorBoundaryAsFailed as markLegacyErrorBoundaryAsFailed_new,
|
||||
isAlreadyFailedLegacyErrorBoundary as isAlreadyFailedLegacyErrorBoundary_new,
|
||||
scheduleWork as scheduleWork_new,
|
||||
requestWork as requestWork_new,
|
||||
flushRoot as flushRoot_new,
|
||||
batchedUpdates as batchedUpdates_new,
|
||||
unbatchedUpdates as unbatchedUpdates_new,
|
||||
|
|
@ -63,61 +62,80 @@ import {
|
|||
computeUniqueAsyncExpiration as computeUniqueAsyncExpiration_new,
|
||||
flushPassiveEffects as flushPassiveEffects_new,
|
||||
warnIfNotCurrentlyActingUpdatesInDev as warnIfNotCurrentlyActingUpdatesInDev_new,
|
||||
inferStartTimeFromExpirationTime as inferStartTimeFromExpirationTime_new,
|
||||
} from './ReactFiberScheduler.new';
|
||||
|
||||
export let requestCurrentTime = requestCurrentTime_old;
|
||||
export let computeExpirationForFiber = computeExpirationForFiber_old;
|
||||
export let captureCommitPhaseError = captureCommitPhaseError_old;
|
||||
export let onUncaughtError = onUncaughtError_old;
|
||||
export let renderDidSuspend = renderDidSuspend_old;
|
||||
export let renderDidError = renderDidError_old;
|
||||
export let pingSuspendedRoot = pingSuspendedRoot_old;
|
||||
export let retryTimedOutBoundary = retryTimedOutBoundary_old;
|
||||
export let resolveRetryThenable = resolveRetryThenable_old;
|
||||
export let markLegacyErrorBoundaryAsFailed = markLegacyErrorBoundaryAsFailed_old;
|
||||
export let isAlreadyFailedLegacyErrorBoundary = isAlreadyFailedLegacyErrorBoundary_old;
|
||||
export let scheduleWork = scheduleWork_old;
|
||||
export let requestWork = requestWork_old;
|
||||
export let flushRoot = flushRoot_old;
|
||||
export let batchedUpdates = batchedUpdates_old;
|
||||
export let unbatchedUpdates = unbatchedUpdates_old;
|
||||
export let flushSync = flushSync_old;
|
||||
export let flushControlled = flushControlled_old;
|
||||
export let deferredUpdates = deferredUpdates_old;
|
||||
export let syncUpdates = syncUpdates_old;
|
||||
export let interactiveUpdates = interactiveUpdates_old;
|
||||
export let flushInteractiveUpdates = flushInteractiveUpdates_old;
|
||||
export let computeUniqueAsyncExpiration = computeUniqueAsyncExpiration_old;
|
||||
export let flushPassiveEffects = flushPassiveEffects_old;
|
||||
export let warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDev_old;
|
||||
|
||||
if (enableNewScheduler) {
|
||||
requestCurrentTime = requestCurrentTime_new;
|
||||
computeExpirationForFiber = computeExpirationForFiber_new;
|
||||
captureCommitPhaseError = captureCommitPhaseError_new;
|
||||
onUncaughtError = onUncaughtError_new;
|
||||
renderDidSuspend = renderDidSuspend_new;
|
||||
renderDidError = renderDidError_new;
|
||||
pingSuspendedRoot = pingSuspendedRoot_new;
|
||||
retryTimedOutBoundary = retryTimedOutBoundary_new;
|
||||
resolveRetryThenable = resolveRetryThenable_new;
|
||||
markLegacyErrorBoundaryAsFailed = markLegacyErrorBoundaryAsFailed_new;
|
||||
isAlreadyFailedLegacyErrorBoundary = isAlreadyFailedLegacyErrorBoundary_new;
|
||||
scheduleWork = scheduleWork_new;
|
||||
requestWork = requestWork_new;
|
||||
flushRoot = flushRoot_new;
|
||||
batchedUpdates = batchedUpdates_new;
|
||||
unbatchedUpdates = unbatchedUpdates_new;
|
||||
flushSync = flushSync_new;
|
||||
flushControlled = flushControlled_new;
|
||||
deferredUpdates = deferredUpdates_new;
|
||||
syncUpdates = syncUpdates_new;
|
||||
interactiveUpdates = interactiveUpdates_new;
|
||||
flushInteractiveUpdates = flushInteractiveUpdates_new;
|
||||
computeUniqueAsyncExpiration = computeUniqueAsyncExpiration_new;
|
||||
flushPassiveEffects = flushPassiveEffects_new;
|
||||
warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDev_new;
|
||||
}
|
||||
export const requestCurrentTime = enableNewScheduler
|
||||
? requestCurrentTime_new
|
||||
: requestCurrentTime_old;
|
||||
export const computeExpirationForFiber = enableNewScheduler
|
||||
? computeExpirationForFiber_new
|
||||
: computeExpirationForFiber_old;
|
||||
export const captureCommitPhaseError = enableNewScheduler
|
||||
? captureCommitPhaseError_new
|
||||
: captureCommitPhaseError_old;
|
||||
export const onUncaughtError = enableNewScheduler
|
||||
? onUncaughtError_new
|
||||
: onUncaughtError_old;
|
||||
export const renderDidSuspend = enableNewScheduler
|
||||
? renderDidSuspend_new
|
||||
: renderDidSuspend_old;
|
||||
export const renderDidError = enableNewScheduler
|
||||
? renderDidError_new
|
||||
: renderDidError_old;
|
||||
export const pingSuspendedRoot = enableNewScheduler
|
||||
? pingSuspendedRoot_new
|
||||
: pingSuspendedRoot_old;
|
||||
export const retryTimedOutBoundary = enableNewScheduler
|
||||
? retryTimedOutBoundary_new
|
||||
: retryTimedOutBoundary_old;
|
||||
export const resolveRetryThenable = enableNewScheduler
|
||||
? resolveRetryThenable_new
|
||||
: resolveRetryThenable_old;
|
||||
export const markLegacyErrorBoundaryAsFailed = enableNewScheduler
|
||||
? markLegacyErrorBoundaryAsFailed_new
|
||||
: markLegacyErrorBoundaryAsFailed_old;
|
||||
export const isAlreadyFailedLegacyErrorBoundary = enableNewScheduler
|
||||
? isAlreadyFailedLegacyErrorBoundary_new
|
||||
: isAlreadyFailedLegacyErrorBoundary_old;
|
||||
export const scheduleWork = enableNewScheduler
|
||||
? scheduleWork_new
|
||||
: scheduleWork_old;
|
||||
export const flushRoot = enableNewScheduler ? flushRoot_new : flushRoot_old;
|
||||
export const batchedUpdates = enableNewScheduler
|
||||
? batchedUpdates_new
|
||||
: batchedUpdates_old;
|
||||
export const unbatchedUpdates = enableNewScheduler
|
||||
? unbatchedUpdates_new
|
||||
: unbatchedUpdates_old;
|
||||
export const flushSync = enableNewScheduler ? flushSync_new : flushSync_old;
|
||||
export const flushControlled = enableNewScheduler
|
||||
? flushControlled_new
|
||||
: flushControlled_old;
|
||||
export const deferredUpdates = enableNewScheduler
|
||||
? deferredUpdates_new
|
||||
: deferredUpdates_old;
|
||||
export const syncUpdates = enableNewScheduler
|
||||
? syncUpdates_new
|
||||
: syncUpdates_old;
|
||||
export const interactiveUpdates = enableNewScheduler
|
||||
? interactiveUpdates_new
|
||||
: interactiveUpdates_old;
|
||||
export const flushInteractiveUpdates = enableNewScheduler
|
||||
? flushInteractiveUpdates_new
|
||||
: flushInteractiveUpdates_old;
|
||||
export const computeUniqueAsyncExpiration = enableNewScheduler
|
||||
? computeUniqueAsyncExpiration_new
|
||||
: computeUniqueAsyncExpiration_old;
|
||||
export const flushPassiveEffects = enableNewScheduler
|
||||
? flushPassiveEffects_new
|
||||
: flushPassiveEffects_old;
|
||||
export const warnIfNotCurrentlyActingUpdatesInDev = enableNewScheduler
|
||||
? warnIfNotCurrentlyActingUpdatesInDev_new
|
||||
: warnIfNotCurrentlyActingUpdatesInDev_old;
|
||||
export const inferStartTimeFromExpirationTime = enableNewScheduler
|
||||
? inferStartTimeFromExpirationTime_new
|
||||
: inferStartTimeFromExpirationTime_old;
|
||||
|
||||
export type Thenable = {
|
||||
then(resolve: () => mixed, reject?: () => mixed): void | Thenable,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -123,6 +123,7 @@ import {
|
|||
expirationTimeToMs,
|
||||
computeAsyncExpiration,
|
||||
computeInteractiveExpiration,
|
||||
LOW_PRIORITY_EXPIRATION,
|
||||
} from './ReactFiberExpirationTime';
|
||||
import {ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
|
||||
import {enqueueUpdate, resetCurrentlyProcessingQueue} from './ReactUpdateQueue';
|
||||
|
|
@ -173,6 +174,8 @@ const {
|
|||
unstable_cancelCallback: cancelCallback,
|
||||
unstable_shouldYield: shouldYield,
|
||||
unstable_now: now,
|
||||
unstable_getCurrentPriorityLevel: getCurrentPriorityLevel,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
|
||||
export type Thenable = {
|
||||
|
|
@ -826,7 +829,7 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
|
|||
// here because that code is still in flux.
|
||||
callback = Scheduler_tracing_wrap(callback);
|
||||
}
|
||||
passiveEffectCallbackHandle = scheduleCallback(callback);
|
||||
passiveEffectCallbackHandle = scheduleCallback(NormalPriority, callback);
|
||||
passiveEffectCallback = callback;
|
||||
}
|
||||
|
||||
|
|
@ -1677,6 +1680,25 @@ function renderDidError() {
|
|||
nextRenderDidError = true;
|
||||
}
|
||||
|
||||
function inferStartTimeFromExpirationTime(
|
||||
root: FiberRoot,
|
||||
expirationTime: ExpirationTime,
|
||||
) {
|
||||
// We don't know exactly when the update was scheduled, but we can infer an
|
||||
// approximate start time from the expiration time. First, find the earliest
|
||||
// uncommitted expiration time in the tree, including work that is suspended.
|
||||
// Then subtract the offset used to compute an async update's expiration time.
|
||||
// This will cause high priority (interactive) work to expire earlier than
|
||||
// necessary, but we can account for this by adjusting for the Just
|
||||
// Noticeable Difference.
|
||||
const earliestExpirationTime = findEarliestOutstandingPriorityLevel(
|
||||
root,
|
||||
expirationTime,
|
||||
);
|
||||
const earliestExpirationTimeMs = expirationTimeToMs(earliestExpirationTime);
|
||||
return earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION;
|
||||
}
|
||||
|
||||
function pingSuspendedRoot(
|
||||
root: FiberRoot,
|
||||
thenable: Thenable,
|
||||
|
|
@ -2044,7 +2066,8 @@ function scheduleCallbackWithExpirationTime(
|
|||
const currentMs = now() - originalStartTimeMs;
|
||||
const expirationTimeMs = expirationTimeToMs(expirationTime);
|
||||
const timeout = expirationTimeMs - currentMs;
|
||||
callbackID = scheduleCallback(performAsyncWork, {timeout});
|
||||
const priorityLevel = getCurrentPriorityLevel();
|
||||
callbackID = scheduleCallback(priorityLevel, performAsyncWork, {timeout});
|
||||
}
|
||||
|
||||
// For every call to renderRoot, one of onFatal, onComplete, onSuspend, and
|
||||
|
|
@ -2677,7 +2700,6 @@ export {
|
|||
markLegacyErrorBoundaryAsFailed,
|
||||
isAlreadyFailedLegacyErrorBoundary,
|
||||
scheduleWork,
|
||||
requestWork,
|
||||
flushRoot,
|
||||
batchedUpdates,
|
||||
unbatchedUpdates,
|
||||
|
|
@ -2689,4 +2711,5 @@ export {
|
|||
flushInteractiveUpdates,
|
||||
computeUniqueAsyncExpiration,
|
||||
flushPassiveEffects,
|
||||
inferStartTimeFromExpirationTime,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -70,16 +70,12 @@ import {
|
|||
isAlreadyFailedLegacyErrorBoundary,
|
||||
pingSuspendedRoot,
|
||||
resolveRetryThenable,
|
||||
inferStartTimeFromExpirationTime,
|
||||
} from './ReactFiberScheduler';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
import maxSigned31BitInt from './maxSigned31BitInt';
|
||||
import {
|
||||
Sync,
|
||||
expirationTimeToMs,
|
||||
LOW_PRIORITY_EXPIRATION,
|
||||
} from './ReactFiberExpirationTime';
|
||||
import {findEarliestOutstandingPriorityLevel} from './ReactFiberPendingPriority';
|
||||
import {Sync, expirationTimeToMs} from './ReactFiberExpirationTime';
|
||||
|
||||
const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
|
||||
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
|
||||
|
|
@ -322,21 +318,12 @@ function throwException(
|
|||
if (startTimeMs === -1) {
|
||||
// This suspend happened outside of any already timed-out
|
||||
// placeholders. We don't know exactly when the update was
|
||||
// scheduled, but we can infer an approximate start time from the
|
||||
// expiration time. First, find the earliest uncommitted expiration
|
||||
// time in the tree, including work that is suspended. Then subtract
|
||||
// the offset used to compute an async update's expiration time.
|
||||
// This will cause high priority (interactive) work to expire
|
||||
// earlier than necessary, but we can account for this by adjusting
|
||||
// for the Just Noticeable Difference.
|
||||
const earliestExpirationTime = findEarliestOutstandingPriorityLevel(
|
||||
// scheduled, but we can infer an approximate start time based on
|
||||
// the expiration time and the priority.
|
||||
startTimeMs = inferStartTimeFromExpirationTime(
|
||||
root,
|
||||
renderExpirationTime,
|
||||
);
|
||||
const earliestExpirationTimeMs = expirationTimeToMs(
|
||||
earliestExpirationTime,
|
||||
);
|
||||
startTimeMs = earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION;
|
||||
}
|
||||
absoluteTimeoutMs = startTimeMs + earliestTimeoutMs;
|
||||
}
|
||||
|
|
|
|||
171
packages/react-reconciler/src/SchedulerWithReactIntegration.js
vendored
Normal file
171
packages/react-reconciler/src/SchedulerWithReactIntegration.js
vendored
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
// Intentionally not named imports because Rollup would use dynamic dispatch for
|
||||
// CommonJS interop named imports.
|
||||
import * as Scheduler from 'scheduler';
|
||||
|
||||
import {disableYielding} from 'shared/ReactFeatureFlags';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
const {
|
||||
unstable_runWithPriority: Scheduler_runWithPriority,
|
||||
unstable_scheduleCallback: Scheduler_scheduleCallback,
|
||||
unstable_cancelCallback: Scheduler_cancelCallback,
|
||||
unstable_shouldYield: Scheduler_shouldYield,
|
||||
unstable_now: Scheduler_now,
|
||||
unstable_getCurrentPriorityLevel: Scheduler_getCurrentPriorityLevel,
|
||||
unstable_ImmediatePriority: Scheduler_ImmediatePriority,
|
||||
unstable_UserBlockingPriority: Scheduler_UserBlockingPriority,
|
||||
unstable_NormalPriority: Scheduler_NormalPriority,
|
||||
unstable_LowPriority: Scheduler_LowPriority,
|
||||
unstable_IdlePriority: Scheduler_IdlePriority,
|
||||
} = Scheduler;
|
||||
|
||||
export opaque type ReactPriorityLevel = 99 | 98 | 97 | 96 | 95 | 90;
|
||||
export type SchedulerCallback = (isSync: boolean) => SchedulerCallback | null;
|
||||
|
||||
type SchedulerCallbackOptions = {
|
||||
timeout?: number,
|
||||
};
|
||||
|
||||
const fakeCallbackNode = {};
|
||||
|
||||
// Except for NoPriority, these correspond to Scheduler priorities. We use
|
||||
// ascending numbers so we can compare them like numbers. They start at 90 to
|
||||
// avoid clashing with Scheduler's priorities.
|
||||
export const ImmediatePriority: ReactPriorityLevel = 99;
|
||||
export const UserBlockingPriority: ReactPriorityLevel = 98;
|
||||
export const NormalPriority: ReactPriorityLevel = 97;
|
||||
export const LowPriority: ReactPriorityLevel = 96;
|
||||
export const IdlePriority: ReactPriorityLevel = 95;
|
||||
// NoPriority is the absence of priority. Also React-only.
|
||||
export const NoPriority: ReactPriorityLevel = 90;
|
||||
|
||||
export const now = Scheduler_now;
|
||||
export const shouldYield = disableYielding
|
||||
? () => false // Never yield when `disableYielding` is on
|
||||
: Scheduler_shouldYield;
|
||||
|
||||
let immediateQueue: Array<SchedulerCallback> | null = null;
|
||||
let immediateQueueCallbackNode: mixed | null = null;
|
||||
let isFlushingImmediate: boolean = false;
|
||||
|
||||
export function getCurrentPriorityLevel(): ReactPriorityLevel {
|
||||
switch (Scheduler_getCurrentPriorityLevel()) {
|
||||
case Scheduler_ImmediatePriority:
|
||||
return ImmediatePriority;
|
||||
case Scheduler_UserBlockingPriority:
|
||||
return UserBlockingPriority;
|
||||
case Scheduler_NormalPriority:
|
||||
return NormalPriority;
|
||||
case Scheduler_LowPriority:
|
||||
return LowPriority;
|
||||
case Scheduler_IdlePriority:
|
||||
return IdlePriority;
|
||||
default:
|
||||
invariant(false, 'Unknown priority level.');
|
||||
}
|
||||
}
|
||||
|
||||
function reactPriorityToSchedulerPriority(reactPriorityLevel) {
|
||||
switch (reactPriorityLevel) {
|
||||
case ImmediatePriority:
|
||||
return Scheduler_ImmediatePriority;
|
||||
case UserBlockingPriority:
|
||||
return Scheduler_UserBlockingPriority;
|
||||
case NormalPriority:
|
||||
return Scheduler_NormalPriority;
|
||||
case LowPriority:
|
||||
return Scheduler_LowPriority;
|
||||
case IdlePriority:
|
||||
return Scheduler_IdlePriority;
|
||||
default:
|
||||
invariant(false, 'Unknown priority level.');
|
||||
}
|
||||
}
|
||||
|
||||
export function runWithPriority<T>(
|
||||
reactPriorityLevel: ReactPriorityLevel,
|
||||
fn: () => T,
|
||||
): T {
|
||||
const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
|
||||
return Scheduler_runWithPriority(priorityLevel, fn);
|
||||
}
|
||||
|
||||
export function scheduleCallback(
|
||||
reactPriorityLevel: ReactPriorityLevel,
|
||||
callback: SchedulerCallback,
|
||||
options: SchedulerCallbackOptions | void | null,
|
||||
) {
|
||||
if (reactPriorityLevel === ImmediatePriority) {
|
||||
// Push this callback into an internal queue. We'll flush these either in
|
||||
// the next tick, or earlier if something calls `flushImmediateQueue`.
|
||||
if (immediateQueue === null) {
|
||||
immediateQueue = [callback];
|
||||
// Flush the queue in the next tick, at the earliest.
|
||||
immediateQueueCallbackNode = Scheduler_scheduleCallback(
|
||||
Scheduler_ImmediatePriority,
|
||||
flushImmediateQueueImpl,
|
||||
);
|
||||
} else {
|
||||
// Push onto existing queue. Don't need to schedule a callback because
|
||||
// we already scheduled one when we created the queue.
|
||||
immediateQueue.push(callback);
|
||||
}
|
||||
return fakeCallbackNode;
|
||||
}
|
||||
// Otherwise pass through to Scheduler.
|
||||
const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
|
||||
return Scheduler_scheduleCallback(priorityLevel, callback, options);
|
||||
}
|
||||
|
||||
export function cancelCallback(callbackNode: mixed) {
|
||||
if (callbackNode !== fakeCallbackNode) {
|
||||
Scheduler_cancelCallback(callbackNode);
|
||||
}
|
||||
}
|
||||
|
||||
export function flushImmediateQueue() {
|
||||
if (immediateQueueCallbackNode !== null) {
|
||||
Scheduler_cancelCallback(immediateQueueCallbackNode);
|
||||
}
|
||||
flushImmediateQueueImpl();
|
||||
}
|
||||
|
||||
function flushImmediateQueueImpl() {
|
||||
if (!isFlushingImmediate && immediateQueue !== null) {
|
||||
// Prevent re-entrancy.
|
||||
isFlushingImmediate = true;
|
||||
let i = 0;
|
||||
try {
|
||||
const isSync = true;
|
||||
for (; i < immediateQueue.length; i++) {
|
||||
let callback = immediateQueue[i];
|
||||
do {
|
||||
callback = callback(isSync);
|
||||
} while (callback !== null);
|
||||
}
|
||||
immediateQueue = null;
|
||||
} catch (error) {
|
||||
// If something throws, leave the remaining callbacks on the queue.
|
||||
if (immediateQueue !== null) {
|
||||
immediateQueue = immediateQueue.slice(i + 1);
|
||||
}
|
||||
// Resume flushing in the next tick
|
||||
Scheduler_scheduleCallback(
|
||||
Scheduler_ImmediatePriority,
|
||||
flushImmediateQueue,
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
isFlushingImmediate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1747,6 +1747,7 @@ describe('ReactHooks', () => {
|
|||
);
|
||||
expect(root).toMatchRenderedOutput('loading');
|
||||
await Promise.resolve();
|
||||
Scheduler.flushAll();
|
||||
expect(root).toMatchRenderedOutput('hello');
|
||||
});
|
||||
|
||||
|
|
@ -1778,6 +1779,7 @@ describe('ReactHooks', () => {
|
|||
);
|
||||
expect(root).toMatchRenderedOutput('loading');
|
||||
await Promise.resolve();
|
||||
Scheduler.flushAll();
|
||||
expect(root).toMatchRenderedOutput('hello');
|
||||
});
|
||||
|
||||
|
|
@ -1809,6 +1811,7 @@ describe('ReactHooks', () => {
|
|||
);
|
||||
expect(root).toMatchRenderedOutput('loading');
|
||||
await Promise.resolve();
|
||||
Scheduler.flushAll();
|
||||
expect(root).toMatchRenderedOutput('hello');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ let ReactFeatureFlags;
|
|||
let React;
|
||||
let ReactNoop;
|
||||
let Scheduler;
|
||||
let enableNewScheduler;
|
||||
|
||||
describe('ReactIncrementalErrorHandling', () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -22,6 +23,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
|||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
|
||||
enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
|
||||
PropTypes = require('prop-types');
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
|
|
@ -131,6 +133,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
|||
'ErrorBoundary (catch)',
|
||||
'ErrorMessage',
|
||||
]);
|
||||
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Caught an error: oops!')]);
|
||||
});
|
||||
|
||||
|
|
@ -306,21 +309,17 @@ describe('ReactIncrementalErrorHandling', () => {
|
|||
});
|
||||
|
||||
it('retries one more time before handling error', () => {
|
||||
let ops = [];
|
||||
function BadRender() {
|
||||
ops.push('BadRender');
|
||||
Scheduler.yieldValue('BadRender');
|
||||
throw new Error('oops');
|
||||
}
|
||||
|
||||
function Sibling() {
|
||||
ops.push('Sibling');
|
||||
Scheduler.yieldValue('Sibling');
|
||||
return <span prop="Sibling" />;
|
||||
}
|
||||
|
||||
function Parent() {
|
||||
ops.push('Parent');
|
||||
Scheduler.yieldValue('Parent');
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
|
@ -338,10 +337,15 @@ describe('ReactIncrementalErrorHandling', () => {
|
|||
// Finish the rest of the async work
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Sibling']);
|
||||
|
||||
// React retries once, synchronously, before throwing.
|
||||
ops = [];
|
||||
expect(() => ReactNoop.flushNextYield()).toThrow('oops');
|
||||
expect(ops).toEqual(['Parent', 'BadRender', 'Sibling']);
|
||||
// Old scheduler renders, commits, and throws synchronously
|
||||
expect(() => Scheduler.unstable_flushNumberOfYields(1)).toThrow('oops');
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Parent',
|
||||
'BadRender',
|
||||
'Sibling',
|
||||
'commit',
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
});
|
||||
|
||||
// TODO: This is currently unobservable, but will be once we lift renderRoot
|
||||
|
|
@ -744,7 +748,8 @@ describe('ReactIncrementalErrorHandling', () => {
|
|||
expect(ReactNoop.getChildren()).toEqual([span('a:5')]);
|
||||
});
|
||||
|
||||
it('applies sync updates regardless despite errors in scheduling', () => {
|
||||
// TODO: Is this a breaking change?
|
||||
it('defers additional sync work to a separate event after an error', () => {
|
||||
ReactNoop.render(<span prop="a:1" />);
|
||||
expect(() => {
|
||||
ReactNoop.flushSync(() => {
|
||||
|
|
@ -755,6 +760,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
|||
});
|
||||
});
|
||||
}).toThrow('Hello');
|
||||
Scheduler.flushAll();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('a:3')]);
|
||||
});
|
||||
|
||||
|
|
@ -962,43 +968,46 @@ describe('ReactIncrementalErrorHandling', () => {
|
|||
expect(ReactNoop.getChildren('a')).toEqual([
|
||||
span('Caught an error: Hello.'),
|
||||
]);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
|
||||
});
|
||||
|
||||
it('continues work on other roots despite uncaught errors', () => {
|
||||
function BrokenRender(props) {
|
||||
throw new Error('Hello');
|
||||
throw new Error(props.label);
|
||||
}
|
||||
|
||||
ReactNoop.renderToRootWithID(<BrokenRender />, 'a');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender label="a" />, 'a');
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toThrow('Hello');
|
||||
}).toThrow('a');
|
||||
expect(ReactNoop.getChildren('a')).toEqual([]);
|
||||
|
||||
ReactNoop.renderToRootWithID(<BrokenRender />, 'a');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender label="a" />, 'a');
|
||||
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toThrow('Hello');
|
||||
}).toThrow('a');
|
||||
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(ReactNoop.getChildren('a')).toEqual([]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
|
||||
|
||||
ReactNoop.renderToRootWithID(<span prop="a:3" />, 'a');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender />, 'b');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender label="b" />, 'b');
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toThrow('Hello');
|
||||
}).toThrow('b');
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:3')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([]);
|
||||
|
||||
ReactNoop.renderToRootWithID(<span prop="a:4" />, 'a');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender />, 'b');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender label="b" />, 'b');
|
||||
ReactNoop.renderToRootWithID(<span prop="c:4" />, 'c');
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toThrow('Hello');
|
||||
}).toThrow('b');
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:4')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:4')]);
|
||||
|
|
@ -1007,25 +1016,43 @@ describe('ReactIncrementalErrorHandling', () => {
|
|||
ReactNoop.renderToRootWithID(<span prop="b:5" />, 'b');
|
||||
ReactNoop.renderToRootWithID(<span prop="c:5" />, 'c');
|
||||
ReactNoop.renderToRootWithID(<span prop="d:5" />, 'd');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender />, 'e');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender label="e" />, 'e');
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toThrow('Hello');
|
||||
}).toThrow('e');
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:5')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:5')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:5')]);
|
||||
expect(ReactNoop.getChildren('d')).toEqual([span('d:5')]);
|
||||
expect(ReactNoop.getChildren('e')).toEqual([]);
|
||||
|
||||
ReactNoop.renderToRootWithID(<BrokenRender />, 'a');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender label="a" />, 'a');
|
||||
ReactNoop.renderToRootWithID(<span prop="b:6" />, 'b');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender />, 'c');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender label="c" />, 'c');
|
||||
ReactNoop.renderToRootWithID(<span prop="d:6" />, 'd');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender />, 'e');
|
||||
ReactNoop.renderToRootWithID(<BrokenRender label="e" />, 'e');
|
||||
ReactNoop.renderToRootWithID(<span prop="f:6" />, 'f');
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toThrow('Hello');
|
||||
|
||||
if (enableNewScheduler) {
|
||||
// The new scheduler will throw all three errors.
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toThrow('a');
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toThrow('c');
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toThrow('e');
|
||||
} else {
|
||||
// The old scheduler only throws the first one.
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toThrow('a');
|
||||
}
|
||||
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(ReactNoop.getChildren('a')).toEqual([]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:6')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([]);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -5,6 +5,7 @@ let Scheduler;
|
|||
let ReactFeatureFlags;
|
||||
let Suspense;
|
||||
let lazy;
|
||||
let enableNewScheduler;
|
||||
|
||||
describe('ReactLazy', () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -18,6 +19,7 @@ describe('ReactLazy', () => {
|
|||
lazy = React.lazy;
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
|
||||
});
|
||||
|
||||
function Text(props) {
|
||||
|
|
@ -485,19 +487,33 @@ describe('ReactLazy', () => {
|
|||
|
||||
await Promise.resolve();
|
||||
|
||||
if (enableNewScheduler) {
|
||||
// The new scheduler pings in a separate task
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
} else {
|
||||
// The old scheduler pings synchronously
|
||||
expect(Scheduler).toHaveYielded(['UNSAFE_componentWillMount: A', 'A1']);
|
||||
}
|
||||
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyClass num={2} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'UNSAFE_componentWillMount: A',
|
||||
'A1',
|
||||
'UNSAFE_componentWillReceiveProps: A -> A',
|
||||
'UNSAFE_componentWillUpdate: A -> A',
|
||||
'A2',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
// Because this ping happens in a new task, the ping and the update
|
||||
// are batched together
|
||||
expect(Scheduler).toHaveYielded(['UNSAFE_componentWillMount: A', 'A2']);
|
||||
} else {
|
||||
// The old scheduler must do two separate renders, no batching.
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'UNSAFE_componentWillReceiveProps: A -> A',
|
||||
'UNSAFE_componentWillUpdate: A -> A',
|
||||
'A2',
|
||||
]);
|
||||
}
|
||||
|
||||
expect(root).toMatchRenderedOutput('A2');
|
||||
|
||||
root.update(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactNoop;
|
||||
let Scheduler;
|
||||
let ImmediatePriority;
|
||||
let UserBlockingPriority;
|
||||
let NormalPriority;
|
||||
let LowPriority;
|
||||
let IdlePriority;
|
||||
let runWithPriority;
|
||||
|
||||
describe('ReactSchedulerIntegration', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.enableNewScheduler = true;
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
ImmediatePriority = Scheduler.unstable_ImmediatePriority;
|
||||
UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
|
||||
NormalPriority = Scheduler.unstable_NormalPriority;
|
||||
LowPriority = Scheduler.unstable_LowPriority;
|
||||
IdlePriority = Scheduler.unstable_IdlePriority;
|
||||
runWithPriority = Scheduler.unstable_runWithPriority;
|
||||
});
|
||||
|
||||
function getCurrentPriorityAsString() {
|
||||
const priorityLevel = Scheduler.unstable_getCurrentPriorityLevel();
|
||||
switch (priorityLevel) {
|
||||
case ImmediatePriority:
|
||||
return 'Immediate';
|
||||
case UserBlockingPriority:
|
||||
return 'UserBlocking';
|
||||
case NormalPriority:
|
||||
return 'Normal';
|
||||
case LowPriority:
|
||||
return 'Low';
|
||||
case IdlePriority:
|
||||
return 'Idle';
|
||||
default:
|
||||
throw Error('Unknown priority level: ' + priorityLevel);
|
||||
}
|
||||
}
|
||||
|
||||
it('has correct priority during rendering', () => {
|
||||
function ReadPriority() {
|
||||
Scheduler.yieldValue('Priority: ' + getCurrentPriorityAsString());
|
||||
return null;
|
||||
}
|
||||
ReactNoop.render(<ReadPriority />);
|
||||
expect(Scheduler).toFlushAndYield(['Priority: Normal']);
|
||||
|
||||
runWithPriority(UserBlockingPriority, () => {
|
||||
ReactNoop.render(<ReadPriority />);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYield(['Priority: UserBlocking']);
|
||||
|
||||
runWithPriority(IdlePriority, () => {
|
||||
ReactNoop.render(<ReadPriority />);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYield(['Priority: Idle']);
|
||||
});
|
||||
|
||||
it('has correct priority when continuing a render after yielding', () => {
|
||||
function ReadPriority() {
|
||||
Scheduler.yieldValue('Priority: ' + getCurrentPriorityAsString());
|
||||
return null;
|
||||
}
|
||||
|
||||
runWithPriority(UserBlockingPriority, () => {
|
||||
ReactNoop.render(
|
||||
<React.Fragment>
|
||||
<ReadPriority />
|
||||
<ReadPriority />
|
||||
<ReadPriority />
|
||||
</React.Fragment>,
|
||||
);
|
||||
});
|
||||
|
||||
// Render part of the tree
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Priority: UserBlocking']);
|
||||
|
||||
// Priority is set back to normal when yielding
|
||||
expect(getCurrentPriorityAsString()).toEqual('Normal');
|
||||
|
||||
// Priority is restored to user-blocking when continuing
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Priority: UserBlocking',
|
||||
'Priority: UserBlocking',
|
||||
]);
|
||||
});
|
||||
|
||||
it('layout effects have immediate priority', () => {
|
||||
const {useLayoutEffect} = React;
|
||||
function ReadPriority() {
|
||||
Scheduler.yieldValue('Render priority: ' + getCurrentPriorityAsString());
|
||||
useLayoutEffect(() => {
|
||||
Scheduler.yieldValue(
|
||||
'Layout priority: ' + getCurrentPriorityAsString(),
|
||||
);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
ReactNoop.render(<ReadPriority />);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Render priority: Normal',
|
||||
'Layout priority: Immediate',
|
||||
]);
|
||||
});
|
||||
|
||||
it('passive effects have the same priority as render', () => {
|
||||
const {useEffect} = React;
|
||||
function ReadPriority() {
|
||||
Scheduler.yieldValue('Render priority: ' + getCurrentPriorityAsString());
|
||||
useEffect(() => {
|
||||
Scheduler.yieldValue(
|
||||
'Passive priority: ' + getCurrentPriorityAsString(),
|
||||
);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
ReactNoop.render(<ReadPriority />);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Render priority: Normal',
|
||||
'Passive priority: Normal',
|
||||
]);
|
||||
|
||||
runWithPriority(UserBlockingPriority, () => {
|
||||
ReactNoop.render(<ReadPriority />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Render priority: UserBlocking',
|
||||
'Passive priority: UserBlocking',
|
||||
]);
|
||||
});
|
||||
|
||||
// TODO
|
||||
it.skip('passive effects have render priority even if they are flushed early', () => {});
|
||||
});
|
||||
|
|
@ -5,6 +5,7 @@ let Scheduler;
|
|||
let ReactCache;
|
||||
let Suspense;
|
||||
let act;
|
||||
let enableNewScheduler;
|
||||
|
||||
let TextResource;
|
||||
let textResourceShouldFail;
|
||||
|
|
@ -22,6 +23,7 @@ describe('ReactSuspense', () => {
|
|||
act = ReactTestRenderer.act;
|
||||
Scheduler = require('scheduler');
|
||||
ReactCache = require('react-cache');
|
||||
enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
|
||||
|
||||
Suspense = React.Suspense;
|
||||
|
||||
|
|
@ -265,7 +267,11 @@ describe('ReactSuspense', () => {
|
|||
|
||||
await LazyClass;
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Hi', 'Did mount: Hi']);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toFlushExpired(['Hi', 'Did mount: Hi']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded(['Hi', 'Did mount: Hi']);
|
||||
}
|
||||
expect(root).toMatchRenderedOutput('Hi');
|
||||
});
|
||||
|
||||
|
|
@ -393,13 +399,24 @@ describe('ReactSuspense', () => {
|
|||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
jest.advanceTimersByTime(100);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [B:1]',
|
||||
'B:1',
|
||||
'Unmount [Loading...]',
|
||||
// Should be a mount, not an update
|
||||
'Mount [B:1]',
|
||||
]);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B:1]']);
|
||||
expect(Scheduler).toFlushExpired([
|
||||
'B:1',
|
||||
'Unmount [Loading...]',
|
||||
// Should be a mount, not an update
|
||||
'Mount [B:1]',
|
||||
]);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [B:1]',
|
||||
'B:1',
|
||||
'Unmount [Loading...]',
|
||||
// Should be a mount, not an update
|
||||
'Mount [B:1]',
|
||||
]);
|
||||
}
|
||||
|
||||
expect(root).toMatchRenderedOutput('AB:1C');
|
||||
|
||||
|
|
@ -413,12 +430,21 @@ describe('ReactSuspense', () => {
|
|||
|
||||
jest.advanceTimersByTime(100);
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [B:2]',
|
||||
'B:2',
|
||||
'Unmount [Loading...]',
|
||||
'Update [B:2]',
|
||||
]);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B:2]']);
|
||||
expect(Scheduler).toFlushExpired([
|
||||
'B:2',
|
||||
'Unmount [Loading...]',
|
||||
'Update [B:2]',
|
||||
]);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [B:2]',
|
||||
'B:2',
|
||||
'Unmount [Loading...]',
|
||||
'Update [B:2]',
|
||||
]);
|
||||
}
|
||||
expect(root).toMatchRenderedOutput('AB:2C');
|
||||
});
|
||||
|
||||
|
|
@ -450,7 +476,14 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]', 'A']);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
|
||||
expect(Scheduler).toFlushExpired(['A']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]', 'A']);
|
||||
}
|
||||
|
||||
expect(root).toMatchRenderedOutput('Stateful: 1A');
|
||||
|
||||
root.update(<App text="B" />);
|
||||
|
|
@ -466,7 +499,14 @@ describe('ReactSuspense', () => {
|
|||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
|
||||
expect(Scheduler).toFlushExpired(['B']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
|
||||
}
|
||||
|
||||
expect(root).toMatchRenderedOutput('Stateful: 2B');
|
||||
});
|
||||
|
||||
|
|
@ -506,7 +546,13 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]', 'A']);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
|
||||
expect(Scheduler).toFlushExpired(['A']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]', 'A']);
|
||||
}
|
||||
expect(root).toMatchRenderedOutput('Stateful: 1A');
|
||||
|
||||
root.update(<App text="B" />);
|
||||
|
|
@ -529,7 +575,14 @@ describe('ReactSuspense', () => {
|
|||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
|
||||
expect(Scheduler).toFlushExpired(['B']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
|
||||
}
|
||||
|
||||
expect(root).toMatchRenderedOutput('Stateful: 2B');
|
||||
});
|
||||
|
||||
|
|
@ -610,11 +663,17 @@ describe('ReactSuspense', () => {
|
|||
ReactTestRenderer.create(<App text="A" />);
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [A]', 'Loading...']);
|
||||
jest.advanceTimersByTime(500);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [A]',
|
||||
'A',
|
||||
'Did commit: A',
|
||||
]);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
|
||||
expect(Scheduler).toFlushExpired(['A', 'Did commit: A']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [A]',
|
||||
'A',
|
||||
'Did commit: A',
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('retries when an update is scheduled on a timed out tree', () => {
|
||||
|
|
@ -698,23 +757,42 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Child 1]',
|
||||
'Child 1',
|
||||
'Suspend! [Child 2]',
|
||||
'Suspend! [Child 3]',
|
||||
]);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
|
||||
expect(Scheduler).toFlushExpired([
|
||||
'Child 1',
|
||||
'Suspend! [Child 2]',
|
||||
'Suspend! [Child 3]',
|
||||
]);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Child 1]',
|
||||
'Child 1',
|
||||
'Suspend! [Child 2]',
|
||||
'Suspend! [Child 3]',
|
||||
]);
|
||||
}
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Child 2]',
|
||||
'Child 2',
|
||||
'Suspend! [Child 3]',
|
||||
]);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
|
||||
expect(Scheduler).toFlushExpired(['Child 2', 'Suspend! [Child 3]']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Child 2]',
|
||||
'Child 2',
|
||||
'Suspend! [Child 3]',
|
||||
]);
|
||||
}
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Child 3]',
|
||||
'Child 3',
|
||||
]);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Child 3]']);
|
||||
expect(Scheduler).toFlushExpired(['Child 3']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Child 3]',
|
||||
'Child 3',
|
||||
]);
|
||||
}
|
||||
expect(root).toMatchRenderedOutput(
|
||||
['Child 1', 'Child 2', 'Child 3'].join(''),
|
||||
);
|
||||
|
|
@ -773,7 +851,16 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 0]', 'Tab: 0']);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 0]']);
|
||||
expect(Scheduler).toFlushExpired(['Tab: 0']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Tab: 0]',
|
||||
'Tab: 0',
|
||||
]);
|
||||
}
|
||||
expect(root).toMatchRenderedOutput('Tab: 0 + sibling');
|
||||
|
||||
act(() => setTab(1));
|
||||
|
|
@ -784,7 +871,17 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 1]', 'Tab: 1']);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 1]']);
|
||||
expect(Scheduler).toFlushExpired(['Tab: 1']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Tab: 1]',
|
||||
'Tab: 1',
|
||||
]);
|
||||
}
|
||||
|
||||
expect(root).toMatchRenderedOutput('Tab: 1 + sibling');
|
||||
|
||||
act(() => setTab(2));
|
||||
|
|
@ -795,7 +892,17 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 2]', 'Tab: 2']);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 2]']);
|
||||
expect(Scheduler).toFlushExpired(['Tab: 2']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Tab: 2]',
|
||||
'Tab: 2',
|
||||
]);
|
||||
}
|
||||
|
||||
expect(root).toMatchRenderedOutput('Tab: 2 + sibling');
|
||||
});
|
||||
|
||||
|
|
@ -831,7 +938,14 @@ describe('ReactSuspense', () => {
|
|||
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [A:0]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A:0]', 'A:0']);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A:0]']);
|
||||
expect(Scheduler).toFlushExpired(['A:0']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A:0]', 'A:0']);
|
||||
}
|
||||
|
||||
expect(root).toMatchRenderedOutput('A:0');
|
||||
|
||||
act(() => setStep(1));
|
||||
|
|
@ -868,34 +982,65 @@ describe('ReactSuspense', () => {
|
|||
// Resolve A
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [A]',
|
||||
'A',
|
||||
// The promises for B and C have now been thrown twice
|
||||
'Suspend! [B]',
|
||||
'Suspend! [C]',
|
||||
]);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
|
||||
expect(Scheduler).toFlushExpired([
|
||||
'A',
|
||||
// The promises for B and C have now been thrown twice
|
||||
'Suspend! [B]',
|
||||
'Suspend! [C]',
|
||||
]);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [A]',
|
||||
'A',
|
||||
// The promises for B and C have now been thrown twice
|
||||
'Suspend! [B]',
|
||||
'Suspend! [C]',
|
||||
]);
|
||||
}
|
||||
|
||||
// Resolve B
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [B]',
|
||||
// Even though the promise for B was thrown twice, we should only
|
||||
// re-render once.
|
||||
'B',
|
||||
// The promise for C has now been thrown three times
|
||||
'Suspend! [C]',
|
||||
]);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
|
||||
expect(Scheduler).toFlushExpired([
|
||||
// Even though the promise for B was thrown twice, we should only
|
||||
// re-render once.
|
||||
'B',
|
||||
// The promise for C has now been thrown three times
|
||||
'Suspend! [C]',
|
||||
]);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [B]',
|
||||
// Even though the promise for B was thrown twice, we should only
|
||||
// re-render once.
|
||||
'B',
|
||||
// The promise for C has now been thrown three times
|
||||
'Suspend! [C]',
|
||||
]);
|
||||
}
|
||||
|
||||
// Resolve C
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [C]',
|
||||
// Even though the promise for C was thrown three times, we should only
|
||||
// re-render once.
|
||||
'C',
|
||||
]);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
|
||||
expect(Scheduler).toFlushExpired([
|
||||
// Even though the promise for C was thrown three times, we should only
|
||||
// re-render once.
|
||||
'C',
|
||||
]);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [C]',
|
||||
// Even though the promise for C was thrown three times, we should only
|
||||
// re-render once.
|
||||
'C',
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('#14162', () => {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ let ReactCache;
|
|||
let Suspense;
|
||||
let TextResource;
|
||||
let textResourceShouldFail;
|
||||
let enableNewScheduler;
|
||||
|
||||
describe('ReactSuspensePlaceholder', () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -30,6 +31,7 @@ describe('ReactSuspensePlaceholder', () => {
|
|||
ReactNoop = require('react-noop-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
ReactCache = require('react-cache');
|
||||
enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
|
||||
|
||||
Profiler = React.Profiler;
|
||||
Suspense = React.Suspense;
|
||||
|
|
@ -323,10 +325,16 @@ describe('ReactSuspensePlaceholder', () => {
|
|||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Loaded]',
|
||||
'Loaded',
|
||||
]);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Loaded]']);
|
||||
expect(Scheduler).toFlushExpired(['Loaded']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Loaded]',
|
||||
'Loaded',
|
||||
]);
|
||||
}
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput('LoadedText');
|
||||
expect(onRender).toHaveBeenCalledTimes(2);
|
||||
|
||||
|
|
@ -426,10 +434,16 @@ describe('ReactSuspensePlaceholder', () => {
|
|||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Loaded]',
|
||||
'Loaded',
|
||||
]);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Loaded]']);
|
||||
expect(Scheduler).toFlushExpired(['Loaded']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Loaded]',
|
||||
'Loaded',
|
||||
]);
|
||||
}
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput('LoadedNew');
|
||||
expect(onRender).toHaveBeenCalledTimes(4);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ let ReactCache;
|
|||
let Suspense;
|
||||
let StrictMode;
|
||||
let ConcurrentMode;
|
||||
let enableNewScheduler;
|
||||
|
||||
let TextResource;
|
||||
let textResourceShouldFail;
|
||||
|
|
@ -28,6 +29,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
Suspense = React.Suspense;
|
||||
StrictMode = React.StrictMode;
|
||||
ConcurrentMode = React.unstable_ConcurrentMode;
|
||||
enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
|
||||
|
||||
TextResource = ReactCache.unstable_createResource(([text, ms = 0]) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
|
|
@ -842,7 +844,16 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
|
||||
ReactNoop.expire(100);
|
||||
await advanceTimers(100);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Result]', 'Result']);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Result]']);
|
||||
expect(Scheduler).toFlushExpired(['Result']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Result]',
|
||||
'Result',
|
||||
]);
|
||||
}
|
||||
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Result')]);
|
||||
});
|
||||
|
|
@ -880,15 +891,27 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
// Initial mount. This is synchronous, because the root is sync.
|
||||
ReactNoop.renderLegacySyncRoot(<App />);
|
||||
await advanceTimers(100);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Suspend! [Step: 1]',
|
||||
'Sibling',
|
||||
'Loading (1)',
|
||||
'Loading (2)',
|
||||
'Loading (3)',
|
||||
'Promise resolved [Step: 1]',
|
||||
'Step: 1',
|
||||
]);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Suspend! [Step: 1]',
|
||||
'Sibling',
|
||||
'Loading (1)',
|
||||
'Loading (2)',
|
||||
'Loading (3)',
|
||||
'Promise resolved [Step: 1]',
|
||||
]);
|
||||
expect(Scheduler).toFlushExpired(['Step: 1']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Suspend! [Step: 1]',
|
||||
'Sibling',
|
||||
'Loading (1)',
|
||||
'Loading (2)',
|
||||
'Loading (3)',
|
||||
'Promise resolved [Step: 1]',
|
||||
'Step: 1',
|
||||
]);
|
||||
}
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<React.Fragment>
|
||||
<span prop="Step: 1" />
|
||||
|
|
@ -920,10 +943,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
);
|
||||
|
||||
await advanceTimers(100);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Step: 2]',
|
||||
'Step: 2',
|
||||
]);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Step: 2]']);
|
||||
expect(Scheduler).toFlushExpired(['Step: 2']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Step: 2]',
|
||||
'Step: 2',
|
||||
]);
|
||||
}
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<React.Fragment>
|
||||
<span prop="Step: 2" />
|
||||
|
|
@ -981,18 +1009,34 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
Scheduler.yieldValue('Did mount'),
|
||||
);
|
||||
await advanceTimers(100);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Before',
|
||||
'Suspend! [Async: 1]',
|
||||
'After',
|
||||
'Loading...',
|
||||
'Before',
|
||||
'Sync: 1',
|
||||
'After',
|
||||
'Did mount',
|
||||
'Promise resolved [Async: 1]',
|
||||
'Async: 1',
|
||||
]);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Before',
|
||||
'Suspend! [Async: 1]',
|
||||
'After',
|
||||
'Loading...',
|
||||
'Before',
|
||||
'Sync: 1',
|
||||
'After',
|
||||
'Did mount',
|
||||
'Promise resolved [Async: 1]',
|
||||
]);
|
||||
expect(Scheduler).toFlushExpired(['Async: 1']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Before',
|
||||
'Suspend! [Async: 1]',
|
||||
'After',
|
||||
'Loading...',
|
||||
'Before',
|
||||
'Sync: 1',
|
||||
'After',
|
||||
'Did mount',
|
||||
'Promise resolved [Async: 1]',
|
||||
'Async: 1',
|
||||
]);
|
||||
}
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<React.Fragment>
|
||||
<span prop="Before" />
|
||||
|
|
@ -1046,10 +1090,16 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
// When the placeholder is pinged, the boundary must be re-rendered
|
||||
// synchronously.
|
||||
await advanceTimers(100);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Async: 2]',
|
||||
'Async: 2',
|
||||
]);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Async: 2]']);
|
||||
expect(Scheduler).toFlushExpired(['Async: 2']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Async: 2]',
|
||||
'Async: 2',
|
||||
]);
|
||||
}
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<React.Fragment>
|
||||
|
|
@ -1114,18 +1164,33 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
Scheduler.yieldValue('Did mount'),
|
||||
);
|
||||
await advanceTimers(100);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Before',
|
||||
'Suspend! [Async: 1]',
|
||||
'After',
|
||||
'Loading...',
|
||||
'Before',
|
||||
'Sync: 1',
|
||||
'After',
|
||||
'Did mount',
|
||||
'Promise resolved [Async: 1]',
|
||||
'Async: 1',
|
||||
]);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Before',
|
||||
'Suspend! [Async: 1]',
|
||||
'After',
|
||||
'Loading...',
|
||||
'Before',
|
||||
'Sync: 1',
|
||||
'After',
|
||||
'Did mount',
|
||||
'Promise resolved [Async: 1]',
|
||||
]);
|
||||
expect(Scheduler).toFlushExpired(['Async: 1']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Before',
|
||||
'Suspend! [Async: 1]',
|
||||
'After',
|
||||
'Loading...',
|
||||
'Before',
|
||||
'Sync: 1',
|
||||
'After',
|
||||
'Did mount',
|
||||
'Promise resolved [Async: 1]',
|
||||
'Async: 1',
|
||||
]);
|
||||
}
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<React.Fragment>
|
||||
<span prop="Before" />
|
||||
|
|
@ -1179,10 +1244,16 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
// When the placeholder is pinged, the boundary must be re-rendered
|
||||
// synchronously.
|
||||
await advanceTimers(100);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Async: 2]',
|
||||
'Async: 2',
|
||||
]);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Async: 2]']);
|
||||
expect(Scheduler).toFlushExpired(['Async: 2']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Async: 2]',
|
||||
'Async: 2',
|
||||
]);
|
||||
}
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<React.Fragment>
|
||||
|
|
@ -1260,7 +1331,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
|
||||
expect(Scheduler).toFlushExpired(['B']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
|
||||
}
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<React.Fragment>
|
||||
|
|
@ -1312,12 +1389,22 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
|
||||
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Hi]',
|
||||
'constructor',
|
||||
'Hi',
|
||||
'componentDidMount',
|
||||
]);
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
|
||||
expect(Scheduler).toFlushExpired([
|
||||
'constructor',
|
||||
'Hi',
|
||||
'componentDidMount',
|
||||
]);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Hi]',
|
||||
'constructor',
|
||||
'Hi',
|
||||
'componentDidMount',
|
||||
]);
|
||||
}
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
|
||||
});
|
||||
|
||||
|
|
@ -1356,7 +1443,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
|
||||
await advanceTimers(100);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
|
||||
expect(Scheduler).toFlushExpired(['Hi']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
|
||||
}
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
|
||||
});
|
||||
|
||||
|
|
@ -1400,7 +1492,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
|
||||
await advanceTimers(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
|
||||
expect(Scheduler).toFlushExpired(['Hi']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
it('hides/unhides suspended children before layout effects fire (mutation)', async () => {
|
||||
|
|
@ -1439,7 +1536,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
|
||||
await advanceTimers(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
|
||||
expect(Scheduler).toFlushExpired(['Hi']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ReactDebugFiberPerf captures all lifecycles 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler captures all lifecycles 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Mount
|
||||
|
|
@ -44,7 +44,7 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduce overhead 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler deduplicates lifecycle names during commit to reduce overhead 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// The commit phase should mention A and B just once
|
||||
|
|
@ -91,7 +91,7 @@ exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduc
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf does not include ConcurrentMode, StrictMode, or Profiler components in measurements 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler does not include ConcurrentMode, StrictMode, or Profiler components in measurements 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Mount
|
||||
|
|
@ -107,7 +107,7 @@ exports[`ReactDebugFiberPerf does not include ConcurrentMode, StrictMode, or Pro
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf does not include context provider or consumer in measurements 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler does not include context provider or consumer in measurements 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Mount
|
||||
|
|
@ -122,7 +122,7 @@ exports[`ReactDebugFiberPerf does not include context provider or consumer in me
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf does not schedule an extra callback if setState is called during a synchronous commit phase 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler does not schedule an extra callback if setState is called during a synchronous commit phase 1`] = `
|
||||
"⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Component [mount]
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ exports[`ReactDebugFiberPerf does not schedule an extra callback if setState is
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascading 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler does not treat setState from cWM or cWRP as cascading 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Should not print a warning
|
||||
|
|
@ -171,7 +171,7 @@ exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascadi
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler measures a simple reconciliation 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Mount
|
||||
|
|
@ -208,7 +208,7 @@ exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler measures deferred work in chunks 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Start rendering through B
|
||||
|
|
@ -235,7 +235,7 @@ exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf measures deprioritized work 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler measures deprioritized work 1`] = `
|
||||
"// Flush the parent
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
|
|
@ -258,7 +258,7 @@ exports[`ReactDebugFiberPerf measures deprioritized work 1`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf properly displays the forwardRef component in measurements 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler properly displays the forwardRef component in measurements 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Mount
|
||||
|
|
@ -278,7 +278,7 @@ exports[`ReactDebugFiberPerf properly displays the forwardRef component in measu
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf recovers from caught errors 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler recovers from caught errors 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Stop on Baddie and restart from Boundary
|
||||
|
|
@ -312,7 +312,7 @@ exports[`ReactDebugFiberPerf recovers from caught errors 1`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler recovers from fatal errors 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Will fatal
|
||||
|
|
@ -343,7 +343,7 @@ exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler skips parents during setState 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Should include just A and B, no Parents
|
||||
|
|
@ -358,7 +358,7 @@ exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf supports Suspense and lazy 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler supports Suspense and lazy 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
|
|
@ -369,7 +369,7 @@ exports[`ReactDebugFiberPerf supports Suspense and lazy 1`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf supports Suspense and lazy 2`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler supports Suspense and lazy 2`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
|
|
@ -392,7 +392,7 @@ exports[`ReactDebugFiberPerf supports Suspense and lazy 2`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf supports memo 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler supports memo 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
|
|
@ -406,7 +406,7 @@ exports[`ReactDebugFiberPerf supports memo 1`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf supports portals 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler supports portals 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
|
|
@ -420,7 +420,7 @@ exports[`ReactDebugFiberPerf supports portals 1`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf warns if an in-progress update is interrupted 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler warns if an in-progress update is interrupted 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation: Yielded)
|
||||
|
|
@ -443,12 +443,9 @@ exports[`ReactDebugFiberPerf warns if an in-progress update is interrupted 1`] =
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf warns if async work expires (starvation) 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler warns if async work expires (starvation) 1`] = `
|
||||
"⛔ (Waiting for async callback... will force flush in 5250 ms) Warning: React was blocked by main thread
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Foo [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
|
|
@ -456,7 +453,7 @@ exports[`ReactDebugFiberPerf warns if async work expires (starvation) 1`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler warns on cascading renders from setState 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Should print a warning
|
||||
|
|
@ -480,7 +477,511 @@ exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = `
|
|||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1`] = `
|
||||
exports[`ReactDebugFiberPerf new scheduler warns on cascading renders from top-level render 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Rendering the first root
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Cascading [mount]
|
||||
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
|
||||
|
||||
// Scheduling another root from componentDidMount
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler captures all lifecycles 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Mount
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ AllLifecycles [mount]
|
||||
⚛ AllLifecycles.componentWillMount
|
||||
⚛ AllLifecycles.getChildContext
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
⚛ AllLifecycles.componentDidMount
|
||||
|
||||
⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Update
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ AllLifecycles [update]
|
||||
⚛ AllLifecycles.componentWillReceiveProps
|
||||
⚛ AllLifecycles.shouldComponentUpdate
|
||||
⚛ AllLifecycles.componentWillUpdate
|
||||
⚛ AllLifecycles.getChildContext
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
⚛ AllLifecycles.componentDidUpdate
|
||||
|
||||
⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Unmount
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ AllLifecycles.componentWillUnmount
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler deduplicates lifecycle names during commit to reduce overhead 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// The commit phase should mention A and B just once
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [update]
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 9 Total)
|
||||
⚛ (Calling Lifecycle Methods: 9 Total)
|
||||
⚛ A.componentDidUpdate
|
||||
⚛ B.componentDidUpdate
|
||||
|
||||
⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Because of deduplication, we don't know B was cascading,
|
||||
// but we should still see the warning for the commit phase.
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [update]
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 9 Total)
|
||||
⚛ (Calling Lifecycle Methods: 9 Total)
|
||||
⚛ A.componentDidUpdate
|
||||
⚛ B.componentDidUpdate
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ B [update]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
⚛ B.componentDidUpdate
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler does not include ConcurrentMode, StrictMode, or Profiler components in measurements 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Mount
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Profiler [mount]
|
||||
⚛ Parent [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler does not include context provider or consumer in measurements 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Mount
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler does not schedule an extra callback if setState is called during a synchronous commit phase 1`] = `
|
||||
"⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Component [mount]
|
||||
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
⛔ Component.componentDidMount Warning: Scheduled a cascading update
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Component [update]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler does not treat setState from cWM or cWRP as cascading 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Should not print a warning
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ NotCascading [mount]
|
||||
⚛ NotCascading.componentWillMount
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Should not print a warning
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [update]
|
||||
⚛ NotCascading [update]
|
||||
⚛ NotCascading.componentWillReceiveProps
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler measures a simple reconciliation 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Mount
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Update
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [update]
|
||||
⚛ Child [update]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
|
||||
⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Unmount
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler measures deferred work in chunks 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Start rendering through B
|
||||
⚛ (React Tree Reconciliation: Yielded)
|
||||
⚛ Parent [mount]
|
||||
⚛ A [mount]
|
||||
⚛ Child [mount]
|
||||
⚛ B [mount]
|
||||
|
||||
⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Complete the rest
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ B [mount]
|
||||
⚛ Child [mount]
|
||||
⚛ C [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler measures deprioritized work 1`] = `
|
||||
"// Flush the parent
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
⚛ (Waiting for async callback... will force flush in 10737418210 ms)
|
||||
|
||||
// Flush the child
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler properly displays the forwardRef component in measurements 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Mount
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ ForwardRef [mount]
|
||||
⚛ Child [mount]
|
||||
⚛ ForwardRef(refForwarder) [mount]
|
||||
⚛ Child [mount]
|
||||
⚛ ForwardRef(OverriddenName) [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler recovers from caught errors 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Stop on Baddie and restart from Boundary
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⛔ Boundary [mount] Warning: An error was thrown inside this error boundary
|
||||
⚛ Parent [mount]
|
||||
⚛ Baddie [mount]
|
||||
⚛ Boundary [mount]
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⛔ Boundary [mount] Warning: An error was thrown inside this error boundary
|
||||
⚛ Parent [mount]
|
||||
⚛ Baddie [mount]
|
||||
⚛ Boundary [mount]
|
||||
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Boundary [update]
|
||||
⚛ ErrorReport [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler recovers from fatal errors 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Will fatal
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Baddie [mount]
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Baddie [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
|
||||
⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Will reconcile from a clean state
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler skips parents during setState 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Should include just A and B, no Parents
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler supports Suspense and lazy 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⛔ Suspense [mount] Warning: Rendering was suspended
|
||||
⚛ Suspense [mount]
|
||||
⚛ Spinner [mount]
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler supports Suspense and lazy 2`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⛔ Suspense [mount] Warning: Rendering was suspended
|
||||
⚛ Suspense [mount]
|
||||
⚛ Spinner [mount]
|
||||
|
||||
⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Suspense [mount]
|
||||
⚛ Foo [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler supports memo 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Foo [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler supports portals 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler warns if an in-progress update is interrupted 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation: Yielded)
|
||||
⚛ Foo [mount]
|
||||
|
||||
⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
⛔ (React Tree Reconciliation: Completed Root) Warning: A top-level update interrupted the previous render
|
||||
⚛ Foo [mount]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 0 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler warns if async work expires (starvation) 1`] = `
|
||||
"⛔ (Waiting for async callback... will force flush in 5250 ms) Warning: React was blocked by main thread
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Foo [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler warns on cascading renders from setState 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Should print a warning
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Cascading [mount]
|
||||
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
|
||||
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Cascading [update]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf old scheduler warns on cascading renders from top-level render 1`] = `
|
||||
"⚛ (Waiting for async callback... will force flush in 5250 ms)
|
||||
|
||||
// Rendering the first root
|
||||
|
|
|
|||
|
|
@ -9,15 +9,25 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
const ReactDOM = require('react-dom');
|
||||
|
||||
// Isolate test renderer.
|
||||
jest.resetModules();
|
||||
const React = require('react');
|
||||
const ReactCache = require('react-cache');
|
||||
const ReactTestRenderer = require('react-test-renderer');
|
||||
let ReactDOM;
|
||||
let React;
|
||||
let ReactCache;
|
||||
let ReactTestRenderer;
|
||||
let Scheduler;
|
||||
|
||||
describe('ReactTestRenderer', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactDOM = require('react-dom');
|
||||
|
||||
// Isolate test renderer.
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactCache = require('react-cache');
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
});
|
||||
|
||||
it('should warn if used to render a ReactDOM portal', () => {
|
||||
const container = document.createElement('div');
|
||||
expect(() => {
|
||||
|
|
@ -62,6 +72,7 @@ describe('ReactTestRenderer', () => {
|
|||
const root = ReactTestRenderer.create(<App text="initial" />);
|
||||
PendingResources.initial('initial');
|
||||
await Promise.resolve();
|
||||
Scheduler.flushAll();
|
||||
expect(root.toJSON()).toEqual('initial');
|
||||
|
||||
root.update(<App text="dynamic" />);
|
||||
|
|
@ -69,6 +80,7 @@ describe('ReactTestRenderer', () => {
|
|||
|
||||
PendingResources.dynamic('dynamic');
|
||||
await Promise.resolve();
|
||||
Scheduler.flushAll();
|
||||
expect(root.toJSON()).toEqual('dynamic');
|
||||
|
||||
done();
|
||||
|
|
@ -88,6 +100,7 @@ describe('ReactTestRenderer', () => {
|
|||
const root = ReactTestRenderer.create(<App text="initial" />);
|
||||
PendingResources.initial('initial');
|
||||
await Promise.resolve();
|
||||
Scheduler.flushAll();
|
||||
expect(root.toJSON().children).toEqual(['initial']);
|
||||
|
||||
root.update(<App text="dynamic" />);
|
||||
|
|
@ -95,6 +108,7 @@ describe('ReactTestRenderer', () => {
|
|||
|
||||
PendingResources.dynamic('dynamic');
|
||||
await Promise.resolve();
|
||||
Scheduler.flushAll();
|
||||
expect(root.toJSON().children).toEqual(['dynamic']);
|
||||
|
||||
done();
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let enableNewScheduler;
|
||||
let ReactNoop;
|
||||
let Scheduler;
|
||||
let ReactCache;
|
||||
|
|
@ -35,6 +36,7 @@ function loadModules({
|
|||
ReactFeatureFlags.enableProfilerTimer = enableProfilerTimer;
|
||||
ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing;
|
||||
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = replayFailedUnitOfWorkWithInvokeGuardedCallback;
|
||||
enableNewScheduler = ReactFeatureFlags.enableNewScheduler;
|
||||
|
||||
React = require('react');
|
||||
Scheduler = require('scheduler');
|
||||
|
|
@ -1352,6 +1354,9 @@ describe('Profiler', () => {
|
|||
},
|
||||
);
|
||||
}).toThrow('Expected error onWorkScheduled');
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toFlushAndYield(['Component:fail']);
|
||||
}
|
||||
throwInOnWorkScheduled = false;
|
||||
expect(onWorkScheduled).toHaveBeenCalled();
|
||||
|
||||
|
|
@ -1386,7 +1391,14 @@ describe('Profiler', () => {
|
|||
// Errors that happen inside of a subscriber should throw,
|
||||
throwInOnWorkStarted = true;
|
||||
expect(Scheduler).toFlushAndThrow('Expected error onWorkStarted');
|
||||
expect(Scheduler).toHaveYielded(['Component:text']);
|
||||
if (enableNewScheduler) {
|
||||
// Rendering was interrupted by the error that was thrown
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
// Rendering continues in the next task
|
||||
expect(Scheduler).toFlushAndYield(['Component:text']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded(['Component:text']);
|
||||
}
|
||||
throwInOnWorkStarted = false;
|
||||
expect(onWorkStarted).toHaveBeenCalled();
|
||||
|
||||
|
|
@ -2370,11 +2382,23 @@ describe('Profiler', () => {
|
|||
},
|
||||
);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Suspend [loaded]', 'Text [loading]']);
|
||||
|
||||
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
|
||||
|
||||
jest.runAllTimers();
|
||||
await resourcePromise;
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
|
||||
expect(Scheduler).toFlushExpired(['AsyncText [loaded]']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [loaded]',
|
||||
'AsyncText [loaded]',
|
||||
]);
|
||||
}
|
||||
|
||||
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
onInteractionScheduledWorkCompleted,
|
||||
|
|
@ -2424,9 +2448,16 @@ describe('Profiler', () => {
|
|||
|
||||
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Text [loading]']);
|
||||
|
||||
jest.runAllTimers();
|
||||
await resourcePromise;
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toFlushExpired([]);
|
||||
}
|
||||
|
||||
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
|
||||
|
||||
wrappedCascadingFn();
|
||||
|
|
@ -2578,6 +2609,14 @@ describe('Profiler', () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Suspend [loaded]',
|
||||
'Text [loading]',
|
||||
'Text [initial]',
|
||||
'Suspend [loaded]',
|
||||
'Text [loading]',
|
||||
'Text [updated]',
|
||||
]);
|
||||
expect(renderer.toJSON()).toEqual(['loading', 'updated']);
|
||||
|
||||
expect(onRender).toHaveBeenCalledTimes(1);
|
||||
|
|
@ -2591,6 +2630,17 @@ describe('Profiler', () => {
|
|||
Scheduler.advanceTime(100);
|
||||
jest.advanceTimersByTime(100);
|
||||
await originalPromise;
|
||||
|
||||
if (enableNewScheduler) {
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
|
||||
expect(Scheduler).toFlushExpired(['AsyncText [loaded]']);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [loaded]',
|
||||
'AsyncText [loaded]',
|
||||
]);
|
||||
}
|
||||
|
||||
expect(renderer.toJSON()).toEqual(['loaded', 'updated']);
|
||||
|
||||
expect(onRender).toHaveBeenCalledTimes(1);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ var IDLE_PRIORITY = maxSigned31BitInt;
|
|||
// Callbacks are stored as a circular, doubly linked list.
|
||||
var firstCallbackNode = null;
|
||||
|
||||
var currentDidTimeout = false;
|
||||
var currentHostCallbackDidTimeout = false;
|
||||
// Pausing the scheduler is useful for debugging.
|
||||
var isSchedulerPaused = false;
|
||||
|
||||
|
|
@ -48,29 +48,31 @@ var currentPriorityLevel = NormalPriority;
|
|||
var currentEventStartTime = -1;
|
||||
var currentExpirationTime = -1;
|
||||
|
||||
// This is set when a callback is being executed, to prevent re-entrancy.
|
||||
var isExecutingCallback = false;
|
||||
// This is set while performing work, to prevent re-entrancy.
|
||||
var isPerformingWork = false;
|
||||
|
||||
var isHostCallbackScheduled = false;
|
||||
|
||||
function ensureHostCallbackIsScheduled() {
|
||||
if (isExecutingCallback) {
|
||||
function scheduleHostCallbackIfNeeded() {
|
||||
if (isPerformingWork) {
|
||||
// Don't schedule work yet; wait until the next time we yield.
|
||||
return;
|
||||
}
|
||||
// Schedule the host callback using the earliest expiration in the list.
|
||||
var expirationTime = firstCallbackNode.expirationTime;
|
||||
if (!isHostCallbackScheduled) {
|
||||
isHostCallbackScheduled = true;
|
||||
} else {
|
||||
// Cancel the existing host callback.
|
||||
cancelHostCallback();
|
||||
if (firstCallbackNode !== null) {
|
||||
// Schedule the host callback using the earliest expiration in the list.
|
||||
var expirationTime = firstCallbackNode.expirationTime;
|
||||
if (isHostCallbackScheduled) {
|
||||
// Cancel the existing host callback.
|
||||
cancelHostCallback();
|
||||
} else {
|
||||
isHostCallbackScheduled = true;
|
||||
}
|
||||
requestHostCallback(flushWork, expirationTime);
|
||||
}
|
||||
requestHostCallback(flushWork, expirationTime);
|
||||
}
|
||||
|
||||
function flushFirstCallback() {
|
||||
var flushedNode = firstCallbackNode;
|
||||
const currentlyFlushingCallback = firstCallbackNode;
|
||||
|
||||
// Remove the node from the list before calling the callback. That way the
|
||||
// list is in a consistent state even if the callback throws.
|
||||
|
|
@ -85,19 +87,25 @@ function flushFirstCallback() {
|
|||
next.previous = lastCallbackNode;
|
||||
}
|
||||
|
||||
flushedNode.next = flushedNode.previous = null;
|
||||
currentlyFlushingCallback.next = currentlyFlushingCallback.previous = null;
|
||||
|
||||
// Now it's safe to call the callback.
|
||||
var callback = flushedNode.callback;
|
||||
var expirationTime = flushedNode.expirationTime;
|
||||
var priorityLevel = flushedNode.priorityLevel;
|
||||
var callback = currentlyFlushingCallback.callback;
|
||||
var expirationTime = currentlyFlushingCallback.expirationTime;
|
||||
var priorityLevel = currentlyFlushingCallback.priorityLevel;
|
||||
var previousPriorityLevel = currentPriorityLevel;
|
||||
var previousExpirationTime = currentExpirationTime;
|
||||
currentPriorityLevel = priorityLevel;
|
||||
currentExpirationTime = expirationTime;
|
||||
var continuationCallback;
|
||||
try {
|
||||
continuationCallback = callback(currentDidTimeout);
|
||||
const didUserCallbackTimeout =
|
||||
currentHostCallbackDidTimeout ||
|
||||
// Immediate priority callbacks are always called as if they timed out
|
||||
priorityLevel === ImmediatePriority;
|
||||
continuationCallback = callback(didUserCallbackTimeout);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
currentPriorityLevel = previousPriorityLevel;
|
||||
currentExpirationTime = previousExpirationTime;
|
||||
|
|
@ -141,7 +149,7 @@ function flushFirstCallback() {
|
|||
} else if (nextAfterContinuation === firstCallbackNode) {
|
||||
// The new callback is the highest priority callback in the list.
|
||||
firstCallbackNode = continuationNode;
|
||||
ensureHostCallbackIsScheduled();
|
||||
scheduleHostCallbackIfNeeded();
|
||||
}
|
||||
|
||||
var previous = nextAfterContinuation.previous;
|
||||
|
|
@ -152,46 +160,20 @@ function flushFirstCallback() {
|
|||
}
|
||||
}
|
||||
|
||||
function flushImmediateWork() {
|
||||
if (
|
||||
// Confirm we've exited the outer most event handler
|
||||
currentEventStartTime === -1 &&
|
||||
firstCallbackNode !== null &&
|
||||
firstCallbackNode.priorityLevel === ImmediatePriority
|
||||
) {
|
||||
isExecutingCallback = true;
|
||||
try {
|
||||
do {
|
||||
flushFirstCallback();
|
||||
} while (
|
||||
// Keep flushing until there are no more immediate callbacks
|
||||
firstCallbackNode !== null &&
|
||||
firstCallbackNode.priorityLevel === ImmediatePriority
|
||||
);
|
||||
} finally {
|
||||
isExecutingCallback = false;
|
||||
if (firstCallbackNode !== null) {
|
||||
// There's still work remaining. Request another callback.
|
||||
ensureHostCallbackIsScheduled();
|
||||
} else {
|
||||
isHostCallbackScheduled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function flushWork(didTimeout) {
|
||||
function flushWork(didUserCallbackTimeout) {
|
||||
// Exit right away if we're currently paused
|
||||
|
||||
if (enableSchedulerDebugging && isSchedulerPaused) {
|
||||
return;
|
||||
}
|
||||
|
||||
isExecutingCallback = true;
|
||||
const previousDidTimeout = currentDidTimeout;
|
||||
currentDidTimeout = didTimeout;
|
||||
// We'll need a new host callback the next time work is scheduled.
|
||||
isHostCallbackScheduled = false;
|
||||
|
||||
isPerformingWork = true;
|
||||
const previousDidTimeout = currentHostCallbackDidTimeout;
|
||||
currentHostCallbackDidTimeout = didUserCallbackTimeout;
|
||||
try {
|
||||
if (didTimeout) {
|
||||
if (didUserCallbackTimeout) {
|
||||
// Flush all the expired callbacks without yielding.
|
||||
while (
|
||||
firstCallbackNode !== null &&
|
||||
|
|
@ -226,16 +208,10 @@ function flushWork(didTimeout) {
|
|||
}
|
||||
}
|
||||
} finally {
|
||||
isExecutingCallback = false;
|
||||
currentDidTimeout = previousDidTimeout;
|
||||
if (firstCallbackNode !== null) {
|
||||
// There's still work remaining. Request another callback.
|
||||
ensureHostCallbackIsScheduled();
|
||||
} else {
|
||||
isHostCallbackScheduled = false;
|
||||
}
|
||||
// Before exiting, flush all the immediate work that was scheduled.
|
||||
flushImmediateWork();
|
||||
isPerformingWork = false;
|
||||
currentHostCallbackDidTimeout = previousDidTimeout;
|
||||
// There's still work remaining. Request another callback.
|
||||
scheduleHostCallbackIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -258,12 +234,13 @@ function unstable_runWithPriority(priorityLevel, eventHandler) {
|
|||
|
||||
try {
|
||||
return eventHandler();
|
||||
} catch (error) {
|
||||
// There's still work remaining. Request another callback.
|
||||
scheduleHostCallbackIfNeeded();
|
||||
throw error;
|
||||
} finally {
|
||||
currentPriorityLevel = previousPriorityLevel;
|
||||
currentEventStartTime = previousEventStartTime;
|
||||
|
||||
// Before exiting, flush all the immediate work that was scheduled.
|
||||
flushImmediateWork();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -289,12 +266,13 @@ function unstable_next(eventHandler) {
|
|||
|
||||
try {
|
||||
return eventHandler();
|
||||
} catch (error) {
|
||||
// There's still work remaining. Request another callback.
|
||||
scheduleHostCallbackIfNeeded();
|
||||
throw error;
|
||||
} finally {
|
||||
currentPriorityLevel = previousPriorityLevel;
|
||||
currentEventStartTime = previousEventStartTime;
|
||||
|
||||
// Before exiting, flush all the immediate work that was scheduled.
|
||||
flushImmediateWork();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -309,15 +287,22 @@ function unstable_wrapCallback(callback) {
|
|||
|
||||
try {
|
||||
return callback.apply(this, arguments);
|
||||
} catch (error) {
|
||||
// There's still work remaining. Request another callback.
|
||||
scheduleHostCallbackIfNeeded();
|
||||
throw error;
|
||||
} finally {
|
||||
currentPriorityLevel = previousPriorityLevel;
|
||||
currentEventStartTime = previousEventStartTime;
|
||||
flushImmediateWork();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function unstable_scheduleCallback(callback, deprecated_options) {
|
||||
function unstable_scheduleCallback(
|
||||
priorityLevel,
|
||||
callback,
|
||||
deprecated_options,
|
||||
) {
|
||||
var startTime =
|
||||
currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();
|
||||
|
||||
|
|
@ -330,7 +315,7 @@ function unstable_scheduleCallback(callback, deprecated_options) {
|
|||
// FIXME: Remove this branch once we lift expiration times out of React.
|
||||
expirationTime = startTime + deprecated_options.timeout;
|
||||
} else {
|
||||
switch (currentPriorityLevel) {
|
||||
switch (priorityLevel) {
|
||||
case ImmediatePriority:
|
||||
expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT;
|
||||
break;
|
||||
|
|
@ -351,7 +336,7 @@ function unstable_scheduleCallback(callback, deprecated_options) {
|
|||
|
||||
var newNode = {
|
||||
callback,
|
||||
priorityLevel: currentPriorityLevel,
|
||||
priorityLevel: priorityLevel,
|
||||
expirationTime,
|
||||
next: null,
|
||||
previous: null,
|
||||
|
|
@ -363,7 +348,7 @@ function unstable_scheduleCallback(callback, deprecated_options) {
|
|||
if (firstCallbackNode === null) {
|
||||
// This is the first callback in the list.
|
||||
firstCallbackNode = newNode.next = newNode.previous = newNode;
|
||||
ensureHostCallbackIsScheduled();
|
||||
scheduleHostCallbackIfNeeded();
|
||||
} else {
|
||||
var next = null;
|
||||
var node = firstCallbackNode;
|
||||
|
|
@ -383,7 +368,7 @@ function unstable_scheduleCallback(callback, deprecated_options) {
|
|||
} else if (next === firstCallbackNode) {
|
||||
// The new callback has the earliest expiration in the entire list.
|
||||
firstCallbackNode = newNode;
|
||||
ensureHostCallbackIsScheduled();
|
||||
scheduleHostCallbackIfNeeded();
|
||||
}
|
||||
|
||||
var previous = next.previous;
|
||||
|
|
@ -402,7 +387,7 @@ function unstable_pauseExecution() {
|
|||
function unstable_continueExecution() {
|
||||
isSchedulerPaused = false;
|
||||
if (firstCallbackNode !== null) {
|
||||
ensureHostCallbackIsScheduled();
|
||||
scheduleHostCallbackIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -439,7 +424,7 @@ function unstable_getCurrentPriorityLevel() {
|
|||
|
||||
function unstable_shouldYield() {
|
||||
return (
|
||||
!currentDidTimeout &&
|
||||
!currentHostCallbackDidTimeout &&
|
||||
((firstCallbackNode !== null &&
|
||||
firstCallbackNode.expirationTime < currentExpirationTime) ||
|
||||
shouldYieldToHost())
|
||||
|
|
|
|||
|
|
@ -39,10 +39,10 @@ describe('Scheduler', () => {
|
|||
});
|
||||
|
||||
it('flushes work incrementally', () => {
|
||||
scheduleCallback(() => Scheduler.yieldValue('A'));
|
||||
scheduleCallback(() => Scheduler.yieldValue('B'));
|
||||
scheduleCallback(() => Scheduler.yieldValue('C'));
|
||||
scheduleCallback(() => Scheduler.yieldValue('D'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('A'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('B'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('C'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('D'));
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A', 'B']);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['C']);
|
||||
|
|
@ -50,9 +50,11 @@ describe('Scheduler', () => {
|
|||
});
|
||||
|
||||
it('cancels work', () => {
|
||||
scheduleCallback(() => Scheduler.yieldValue('A'));
|
||||
const callbackHandleB = scheduleCallback(() => Scheduler.yieldValue('B'));
|
||||
scheduleCallback(() => Scheduler.yieldValue('C'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('A'));
|
||||
const callbackHandleB = scheduleCallback(NormalPriority, () =>
|
||||
Scheduler.yieldValue('B'),
|
||||
);
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('C'));
|
||||
|
||||
cancelCallback(callbackHandleB);
|
||||
|
||||
|
|
@ -64,37 +66,31 @@ describe('Scheduler', () => {
|
|||
});
|
||||
|
||||
it('executes the highest priority callbacks first', () => {
|
||||
scheduleCallback(() => Scheduler.yieldValue('A'));
|
||||
scheduleCallback(() => Scheduler.yieldValue('B'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('A'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('B'));
|
||||
|
||||
// Yield before B is flushed
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A']);
|
||||
|
||||
runWithPriority(UserBlockingPriority, () => {
|
||||
scheduleCallback(() => Scheduler.yieldValue('C'));
|
||||
scheduleCallback(() => Scheduler.yieldValue('D'));
|
||||
});
|
||||
scheduleCallback(UserBlockingPriority, () => Scheduler.yieldValue('C'));
|
||||
scheduleCallback(UserBlockingPriority, () => Scheduler.yieldValue('D'));
|
||||
|
||||
// C and D should come first, because they are higher priority
|
||||
expect(Scheduler).toFlushAndYield(['C', 'D', 'B']);
|
||||
});
|
||||
|
||||
it('expires work', () => {
|
||||
scheduleCallback(didTimeout => {
|
||||
scheduleCallback(NormalPriority, didTimeout => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue(`A (did timeout: ${didTimeout})`);
|
||||
});
|
||||
runWithPriority(UserBlockingPriority, () => {
|
||||
scheduleCallback(didTimeout => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue(`B (did timeout: ${didTimeout})`);
|
||||
});
|
||||
scheduleCallback(UserBlockingPriority, didTimeout => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue(`B (did timeout: ${didTimeout})`);
|
||||
});
|
||||
runWithPriority(UserBlockingPriority, () => {
|
||||
scheduleCallback(didTimeout => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue(`C (did timeout: ${didTimeout})`);
|
||||
});
|
||||
scheduleCallback(UserBlockingPriority, didTimeout => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue(`C (did timeout: ${didTimeout})`);
|
||||
});
|
||||
|
||||
// Advance time, but not by enough to expire any work
|
||||
|
|
@ -102,11 +98,11 @@ describe('Scheduler', () => {
|
|||
expect(Scheduler).toHaveYielded([]);
|
||||
|
||||
// Schedule a few more callbacks
|
||||
scheduleCallback(didTimeout => {
|
||||
scheduleCallback(NormalPriority, didTimeout => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue(`D (did timeout: ${didTimeout})`);
|
||||
});
|
||||
scheduleCallback(didTimeout => {
|
||||
scheduleCallback(NormalPriority, didTimeout => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue(`E (did timeout: ${didTimeout})`);
|
||||
});
|
||||
|
|
@ -130,7 +126,7 @@ describe('Scheduler', () => {
|
|||
});
|
||||
|
||||
it('has a default expiration of ~5 seconds', () => {
|
||||
scheduleCallback(() => Scheduler.yieldValue('A'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('A'));
|
||||
|
||||
Scheduler.advanceTime(4999);
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
|
|
@ -140,11 +136,11 @@ describe('Scheduler', () => {
|
|||
});
|
||||
|
||||
it('continues working on same task after yielding', () => {
|
||||
scheduleCallback(() => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('A');
|
||||
});
|
||||
scheduleCallback(() => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('B');
|
||||
});
|
||||
|
|
@ -163,13 +159,13 @@ describe('Scheduler', () => {
|
|||
}
|
||||
};
|
||||
|
||||
scheduleCallback(C);
|
||||
scheduleCallback(NormalPriority, C);
|
||||
|
||||
scheduleCallback(() => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('D');
|
||||
});
|
||||
scheduleCallback(() => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('E');
|
||||
});
|
||||
|
|
@ -197,7 +193,7 @@ describe('Scheduler', () => {
|
|||
};
|
||||
|
||||
// Schedule a high priority callback
|
||||
runWithPriority(UserBlockingPriority, () => scheduleCallback(work));
|
||||
scheduleCallback(UserBlockingPriority, work);
|
||||
|
||||
// Flush until just before the expiration time
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A', 'B']);
|
||||
|
|
@ -207,26 +203,6 @@ describe('Scheduler', () => {
|
|||
expect(Scheduler).toHaveYielded(['C', 'D']);
|
||||
});
|
||||
|
||||
it('nested callbacks inherit the priority of the currently executing callback', () => {
|
||||
runWithPriority(UserBlockingPriority, () => {
|
||||
scheduleCallback(() => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('Parent callback');
|
||||
scheduleCallback(() => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('Nested callback');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Parent callback']);
|
||||
|
||||
// The nested callback has user-blocking priority, so it should
|
||||
// expire quickly.
|
||||
Scheduler.advanceTime(250 + 100);
|
||||
expect(Scheduler).toHaveYielded(['Nested callback']);
|
||||
});
|
||||
|
||||
it('continuations are interrupted by higher priority work', () => {
|
||||
const tasks = [['A', 100], ['B', 100], ['C', 100], ['D', 100]];
|
||||
const work = () => {
|
||||
|
|
@ -239,14 +215,12 @@ describe('Scheduler', () => {
|
|||
}
|
||||
}
|
||||
};
|
||||
scheduleCallback(work);
|
||||
scheduleCallback(NormalPriority, work);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A']);
|
||||
|
||||
runWithPriority(UserBlockingPriority, () => {
|
||||
scheduleCallback(() => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('High pri');
|
||||
});
|
||||
scheduleCallback(UserBlockingPriority, () => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('High pri');
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['High pri', 'B', 'C', 'D']);
|
||||
|
|
@ -266,12 +240,10 @@ describe('Scheduler', () => {
|
|||
if (task[0] === 'B') {
|
||||
// Schedule high pri work from inside another callback
|
||||
Scheduler.yieldValue('Schedule high pri');
|
||||
runWithPriority(UserBlockingPriority, () =>
|
||||
scheduleCallback(() => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('High pri');
|
||||
}),
|
||||
);
|
||||
scheduleCallback(UserBlockingPriority, () => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('High pri');
|
||||
});
|
||||
}
|
||||
if (tasks.length > 0 && shouldYield()) {
|
||||
Scheduler.yieldValue('Yield!');
|
||||
|
|
@ -279,7 +251,7 @@ describe('Scheduler', () => {
|
|||
}
|
||||
}
|
||||
};
|
||||
scheduleCallback(work);
|
||||
scheduleCallback(NormalPriority, work);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'A',
|
||||
'B',
|
||||
|
|
@ -295,21 +267,28 @@ describe('Scheduler', () => {
|
|||
},
|
||||
);
|
||||
|
||||
it('immediate callbacks fire at the end of outermost event', () => {
|
||||
runWithPriority(ImmediatePriority, () => {
|
||||
scheduleCallback(() => Scheduler.yieldValue('A'));
|
||||
scheduleCallback(() => Scheduler.yieldValue('B'));
|
||||
// Nested event
|
||||
runWithPriority(ImmediatePriority, () => {
|
||||
scheduleCallback(() => Scheduler.yieldValue('C'));
|
||||
// Nothing should have fired yet
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
});
|
||||
// Nothing should have fired yet
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
it('top-level immediate callbacks fire in a subsequent task', () => {
|
||||
scheduleCallback(ImmediatePriority, () => Scheduler.yieldValue('A'));
|
||||
scheduleCallback(ImmediatePriority, () => Scheduler.yieldValue('B'));
|
||||
scheduleCallback(ImmediatePriority, () => Scheduler.yieldValue('C'));
|
||||
scheduleCallback(ImmediatePriority, () => Scheduler.yieldValue('D'));
|
||||
// Immediate callback hasn't fired, yet.
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
// They all flush immediately within the subsequent task.
|
||||
expect(Scheduler).toFlushExpired(['A', 'B', 'C', 'D']);
|
||||
});
|
||||
|
||||
it('nested immediate callbacks are added to the queue of immediate callbacks', () => {
|
||||
scheduleCallback(ImmediatePriority, () => Scheduler.yieldValue('A'));
|
||||
scheduleCallback(ImmediatePriority, () => {
|
||||
Scheduler.yieldValue('B');
|
||||
// This callback should go to the end of the queue
|
||||
scheduleCallback(ImmediatePriority, () => Scheduler.yieldValue('C'));
|
||||
});
|
||||
// The callbacks were called at the end of the outer event
|
||||
expect(Scheduler).toHaveYielded(['A', 'B', 'C']);
|
||||
scheduleCallback(ImmediatePriority, () => Scheduler.yieldValue('D'));
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
// C should flush at the end
|
||||
expect(Scheduler).toFlushExpired(['A', 'B', 'D', 'C']);
|
||||
});
|
||||
|
||||
it('wrapped callbacks have same signature as original callback', () => {
|
||||
|
|
@ -318,108 +297,83 @@ describe('Scheduler', () => {
|
|||
});
|
||||
|
||||
it('wrapped callbacks inherit the current priority', () => {
|
||||
const wrappedCallback = wrapCallback(() => {
|
||||
scheduleCallback(() => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('Normal');
|
||||
});
|
||||
});
|
||||
const wrappedInteractiveCallback = runWithPriority(
|
||||
const wrappedCallback = runWithPriority(NormalPriority, () =>
|
||||
wrapCallback(() => {
|
||||
Scheduler.yieldValue(getCurrentPriorityLevel());
|
||||
}),
|
||||
);
|
||||
|
||||
const wrappedUserBlockingCallback = runWithPriority(
|
||||
UserBlockingPriority,
|
||||
() =>
|
||||
wrapCallback(() => {
|
||||
scheduleCallback(() => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('User-blocking');
|
||||
});
|
||||
Scheduler.yieldValue(getCurrentPriorityLevel());
|
||||
}),
|
||||
);
|
||||
|
||||
// This should schedule a normal callback
|
||||
wrappedCallback();
|
||||
// This should schedule an user-blocking callback
|
||||
wrappedInteractiveCallback();
|
||||
expect(Scheduler).toHaveYielded([NormalPriority]);
|
||||
|
||||
Scheduler.advanceTime(249);
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
Scheduler.advanceTime(1);
|
||||
expect(Scheduler).toHaveYielded(['User-blocking']);
|
||||
|
||||
Scheduler.advanceTime(10000);
|
||||
expect(Scheduler).toHaveYielded(['Normal']);
|
||||
wrappedUserBlockingCallback();
|
||||
expect(Scheduler).toHaveYielded([UserBlockingPriority]);
|
||||
});
|
||||
|
||||
it('wrapped callbacks inherit the current priority even when nested', () => {
|
||||
const wrappedCallback = wrapCallback(() => {
|
||||
scheduleCallback(() => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('Normal');
|
||||
let wrappedCallback;
|
||||
let wrappedUserBlockingCallback;
|
||||
|
||||
runWithPriority(NormalPriority, () => {
|
||||
wrappedCallback = wrapCallback(() => {
|
||||
Scheduler.yieldValue(getCurrentPriorityLevel());
|
||||
});
|
||||
});
|
||||
const wrappedInteractiveCallback = runWithPriority(
|
||||
UserBlockingPriority,
|
||||
() =>
|
||||
wrappedUserBlockingCallback = runWithPriority(UserBlockingPriority, () =>
|
||||
wrapCallback(() => {
|
||||
scheduleCallback(() => {
|
||||
Scheduler.advanceTime(100);
|
||||
Scheduler.yieldValue('User-blocking');
|
||||
});
|
||||
Scheduler.yieldValue(getCurrentPriorityLevel());
|
||||
}),
|
||||
);
|
||||
|
||||
runWithPriority(UserBlockingPriority, () => {
|
||||
// This should schedule a normal callback
|
||||
wrappedCallback();
|
||||
// This should schedule an user-blocking callback
|
||||
wrappedInteractiveCallback();
|
||||
);
|
||||
});
|
||||
|
||||
Scheduler.advanceTime(249);
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
Scheduler.advanceTime(1);
|
||||
expect(Scheduler).toHaveYielded(['User-blocking']);
|
||||
wrappedCallback();
|
||||
expect(Scheduler).toHaveYielded([NormalPriority]);
|
||||
|
||||
Scheduler.advanceTime(10000);
|
||||
expect(Scheduler).toHaveYielded(['Normal']);
|
||||
});
|
||||
|
||||
it('immediate callbacks fire at the end of callback', () => {
|
||||
const immediateCallback = runWithPriority(ImmediatePriority, () =>
|
||||
wrapCallback(() => {
|
||||
scheduleCallback(() => Scheduler.yieldValue('callback'));
|
||||
}),
|
||||
);
|
||||
immediateCallback();
|
||||
|
||||
// The callback was called at the end of the outer event
|
||||
expect(Scheduler).toHaveYielded(['callback']);
|
||||
wrappedUserBlockingCallback();
|
||||
expect(Scheduler).toHaveYielded([UserBlockingPriority]);
|
||||
});
|
||||
|
||||
it("immediate callbacks fire even if there's an error", () => {
|
||||
expect(() => {
|
||||
runWithPriority(ImmediatePriority, () => {
|
||||
scheduleCallback(() => {
|
||||
Scheduler.yieldValue('A');
|
||||
throw new Error('Oops A');
|
||||
});
|
||||
scheduleCallback(() => {
|
||||
Scheduler.yieldValue('B');
|
||||
});
|
||||
scheduleCallback(() => {
|
||||
Scheduler.yieldValue('C');
|
||||
throw new Error('Oops C');
|
||||
});
|
||||
});
|
||||
}).toThrow('Oops A');
|
||||
scheduleCallback(ImmediatePriority, () => {
|
||||
Scheduler.yieldValue('A');
|
||||
throw new Error('Oops A');
|
||||
});
|
||||
scheduleCallback(ImmediatePriority, () => {
|
||||
Scheduler.yieldValue('B');
|
||||
});
|
||||
scheduleCallback(ImmediatePriority, () => {
|
||||
Scheduler.yieldValue('C');
|
||||
throw new Error('Oops C');
|
||||
});
|
||||
|
||||
expect(() => expect(Scheduler).toFlushExpired()).toThrow('Oops A');
|
||||
expect(Scheduler).toHaveYielded(['A']);
|
||||
|
||||
// B and C flush in a subsequent event. That way, the second error is not
|
||||
// swallowed.
|
||||
expect(() => Scheduler.unstable_flushExpired()).toThrow('Oops C');
|
||||
expect(() => expect(Scheduler).toFlushExpired()).toThrow('Oops C');
|
||||
expect(Scheduler).toHaveYielded(['B', 'C']);
|
||||
});
|
||||
|
||||
it('multiple immediate callbacks can throw and there will be an error for each one', () => {
|
||||
scheduleCallback(ImmediatePriority, () => {
|
||||
throw new Error('First error');
|
||||
});
|
||||
scheduleCallback(ImmediatePriority, () => {
|
||||
throw new Error('Second error');
|
||||
});
|
||||
expect(() => Scheduler.flushAll()).toThrow('First error');
|
||||
// The next error is thrown in the subsequent event
|
||||
expect(() => Scheduler.flushAll()).toThrow('Second error');
|
||||
});
|
||||
|
||||
it('exposes the current priority level', () => {
|
||||
Scheduler.yieldValue(getCurrentPriorityLevel());
|
||||
runWithPriority(ImmediatePriority, () => {
|
||||
|
|
|
|||
|
|
@ -102,20 +102,26 @@ describe('SchedulerDOM', () => {
|
|||
|
||||
describe('scheduleCallback', () => {
|
||||
it('calls the callback within the frame when not blocked', () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const cb = jest.fn();
|
||||
scheduleCallback(cb);
|
||||
scheduleCallback(NormalPriority, cb);
|
||||
advanceOneFrame({timeLeftInFrame: 15});
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('inserts its rAF callback as early into the queue as possible', () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const log = [];
|
||||
const useRAFCallback = () => {
|
||||
log.push('userRAFCallback');
|
||||
};
|
||||
scheduleCallback(() => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
// Call rAF while idle work is being flushed.
|
||||
requestAnimationFrame(useRAFCallback);
|
||||
});
|
||||
|
|
@ -130,15 +136,18 @@ describe('SchedulerDOM', () => {
|
|||
|
||||
describe('with multiple callbacks', () => {
|
||||
it('accepts multiple callbacks and calls within frame when not blocked', () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const callbackLog = [];
|
||||
const callbackA = jest.fn(() => callbackLog.push('A'));
|
||||
const callbackB = jest.fn(() => callbackLog.push('B'));
|
||||
scheduleCallback(callbackA);
|
||||
scheduleCallback(NormalPriority, callbackA);
|
||||
// initially waits to call the callback
|
||||
expect(callbackLog).toEqual([]);
|
||||
// waits while second callback is passed
|
||||
scheduleCallback(callbackB);
|
||||
scheduleCallback(NormalPriority, callbackB);
|
||||
expect(callbackLog).toEqual([]);
|
||||
// after a delay, calls as many callbacks as it has time for
|
||||
advanceOneFrame({timeLeftInFrame: 15});
|
||||
|
|
@ -146,17 +155,20 @@ describe('SchedulerDOM', () => {
|
|||
});
|
||||
|
||||
it("accepts callbacks between animationFrame and postMessage and doesn't stall", () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const callbackLog = [];
|
||||
const callbackA = jest.fn(() => callbackLog.push('A'));
|
||||
const callbackB = jest.fn(() => callbackLog.push('B'));
|
||||
const callbackC = jest.fn(() => callbackLog.push('C'));
|
||||
scheduleCallback(callbackA);
|
||||
scheduleCallback(NormalPriority, callbackA);
|
||||
// initially waits to call the callback
|
||||
expect(callbackLog).toEqual([]);
|
||||
runRAFCallbacks();
|
||||
// this should schedule work *after* the requestAnimationFrame but before the message handler
|
||||
scheduleCallback(callbackB);
|
||||
scheduleCallback(NormalPriority, callbackB);
|
||||
expect(callbackLog).toEqual([]);
|
||||
// now it should drain the message queue and do all scheduled work
|
||||
runPostMessageCallbacks({timeLeftInFrame: 15});
|
||||
|
|
@ -166,7 +178,7 @@ describe('SchedulerDOM', () => {
|
|||
advanceOneFrame({timeLeftInFrame: 15});
|
||||
|
||||
// see if more work can be done now.
|
||||
scheduleCallback(callbackC);
|
||||
scheduleCallback(NormalPriority, callbackC);
|
||||
expect(callbackLog).toEqual(['A', 'B']);
|
||||
advanceOneFrame({timeLeftInFrame: 15});
|
||||
expect(callbackLog).toEqual(['A', 'B', 'C']);
|
||||
|
|
@ -176,11 +188,14 @@ describe('SchedulerDOM', () => {
|
|||
'schedules callbacks in correct order and' +
|
||||
'keeps calling them if there is time',
|
||||
() => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const callbackLog = [];
|
||||
const callbackA = jest.fn(() => {
|
||||
callbackLog.push('A');
|
||||
scheduleCallback(callbackC);
|
||||
scheduleCallback(NormalPriority, callbackC);
|
||||
});
|
||||
const callbackB = jest.fn(() => {
|
||||
callbackLog.push('B');
|
||||
|
|
@ -189,11 +204,11 @@ describe('SchedulerDOM', () => {
|
|||
callbackLog.push('C');
|
||||
});
|
||||
|
||||
scheduleCallback(callbackA);
|
||||
scheduleCallback(NormalPriority, callbackA);
|
||||
// initially waits to call the callback
|
||||
expect(callbackLog).toEqual([]);
|
||||
// continues waiting while B is scheduled
|
||||
scheduleCallback(callbackB);
|
||||
scheduleCallback(NormalPriority, callbackB);
|
||||
expect(callbackLog).toEqual([]);
|
||||
// after a delay, calls the scheduled callbacks,
|
||||
// and also calls new callbacks scheduled by current callbacks
|
||||
|
|
@ -203,17 +218,20 @@ describe('SchedulerDOM', () => {
|
|||
);
|
||||
|
||||
it('schedules callbacks in correct order when callbacks have many nested scheduleCallback calls', () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const callbackLog = [];
|
||||
const callbackA = jest.fn(() => {
|
||||
callbackLog.push('A');
|
||||
scheduleCallback(callbackC);
|
||||
scheduleCallback(callbackD);
|
||||
scheduleCallback(NormalPriority, callbackC);
|
||||
scheduleCallback(NormalPriority, callbackD);
|
||||
});
|
||||
const callbackB = jest.fn(() => {
|
||||
callbackLog.push('B');
|
||||
scheduleCallback(callbackE);
|
||||
scheduleCallback(callbackF);
|
||||
scheduleCallback(NormalPriority, callbackE);
|
||||
scheduleCallback(NormalPriority, callbackF);
|
||||
});
|
||||
const callbackC = jest.fn(() => {
|
||||
callbackLog.push('C');
|
||||
|
|
@ -228,8 +246,8 @@ describe('SchedulerDOM', () => {
|
|||
callbackLog.push('F');
|
||||
});
|
||||
|
||||
scheduleCallback(callbackA);
|
||||
scheduleCallback(callbackB);
|
||||
scheduleCallback(NormalPriority, callbackA);
|
||||
scheduleCallback(NormalPriority, callbackB);
|
||||
// initially waits to call the callback
|
||||
expect(callbackLog).toEqual([]);
|
||||
// while flushing callbacks, calls as many as it has time for
|
||||
|
|
@ -238,22 +256,25 @@ describe('SchedulerDOM', () => {
|
|||
});
|
||||
|
||||
it('schedules callbacks in correct order when they use scheduleCallback to schedule themselves', () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const callbackLog = [];
|
||||
let callbackAIterations = 0;
|
||||
const callbackA = jest.fn(() => {
|
||||
if (callbackAIterations < 1) {
|
||||
scheduleCallback(callbackA);
|
||||
scheduleCallback(NormalPriority, callbackA);
|
||||
}
|
||||
callbackLog.push('A' + callbackAIterations);
|
||||
callbackAIterations++;
|
||||
});
|
||||
const callbackB = jest.fn(() => callbackLog.push('B'));
|
||||
|
||||
scheduleCallback(callbackA);
|
||||
scheduleCallback(NormalPriority, callbackA);
|
||||
// initially waits to call the callback
|
||||
expect(callbackLog).toEqual([]);
|
||||
scheduleCallback(callbackB);
|
||||
scheduleCallback(NormalPriority, callbackB);
|
||||
expect(callbackLog).toEqual([]);
|
||||
// after a delay, calls the latest callback passed
|
||||
advanceOneFrame({timeLeftInFrame: 15});
|
||||
|
|
@ -269,7 +290,10 @@ describe('SchedulerDOM', () => {
|
|||
|
||||
describe('when there is no more time left in the frame', () => {
|
||||
it('calls any callback which has timed out, waits for others', () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
startOfLatestFrame = 1000000000000;
|
||||
currentTime = startOfLatestFrame - 10;
|
||||
const callbackLog = [];
|
||||
|
|
@ -278,9 +302,9 @@ describe('SchedulerDOM', () => {
|
|||
const callbackB = jest.fn(() => callbackLog.push('B'));
|
||||
const callbackC = jest.fn(() => callbackLog.push('C'));
|
||||
|
||||
scheduleCallback(callbackA); // won't time out
|
||||
scheduleCallback(callbackB, {timeout: 100}); // times out later
|
||||
scheduleCallback(callbackC, {timeout: 2}); // will time out fast
|
||||
scheduleCallback(NormalPriority, callbackA); // won't time out
|
||||
scheduleCallback(NormalPriority, callbackB, {timeout: 100}); // times out later
|
||||
scheduleCallback(NormalPriority, callbackC, {timeout: 2}); // will time out fast
|
||||
|
||||
// push time ahead a bit so that we have no idle time
|
||||
advanceOneFrame({timePastFrameDeadline: 16});
|
||||
|
|
@ -304,7 +328,10 @@ describe('SchedulerDOM', () => {
|
|||
|
||||
describe('when there is some time left in the frame', () => {
|
||||
it('calls timed out callbacks and then any more pending callbacks, defers others if time runs out', () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
startOfLatestFrame = 1000000000000;
|
||||
currentTime = startOfLatestFrame - 10;
|
||||
const callbackLog = [];
|
||||
|
|
@ -318,10 +345,10 @@ describe('SchedulerDOM', () => {
|
|||
const callbackC = jest.fn(() => callbackLog.push('C'));
|
||||
const callbackD = jest.fn(() => callbackLog.push('D'));
|
||||
|
||||
scheduleCallback(callbackA, {timeout: 100}); // won't time out
|
||||
scheduleCallback(callbackB, {timeout: 100}); // times out later
|
||||
scheduleCallback(callbackC, {timeout: 2}); // will time out fast
|
||||
scheduleCallback(callbackD, {timeout: 200}); // won't time out
|
||||
scheduleCallback(NormalPriority, callbackA, {timeout: 100}); // won't time out
|
||||
scheduleCallback(NormalPriority, callbackB, {timeout: 100}); // times out later
|
||||
scheduleCallback(NormalPriority, callbackC, {timeout: 2}); // will time out fast
|
||||
scheduleCallback(NormalPriority, callbackD, {timeout: 200}); // won't time out
|
||||
|
||||
advanceOneFrame({timeLeftInFrame: 15}); // runs rAF and postMessage callbacks
|
||||
|
||||
|
|
@ -355,9 +382,10 @@ describe('SchedulerDOM', () => {
|
|||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_cancelCallback: cancelCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const cb = jest.fn();
|
||||
const callbackId = scheduleCallback(cb);
|
||||
const callbackId = scheduleCallback(NormalPriority, cb);
|
||||
expect(cb).toHaveBeenCalledTimes(0);
|
||||
cancelCallback(callbackId);
|
||||
advanceOneFrame({timeLeftInFrame: 15});
|
||||
|
|
@ -369,14 +397,15 @@ describe('SchedulerDOM', () => {
|
|||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_cancelCallback: cancelCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const callbackLog = [];
|
||||
const callbackA = jest.fn(() => callbackLog.push('A'));
|
||||
const callbackB = jest.fn(() => callbackLog.push('B'));
|
||||
const callbackC = jest.fn(() => callbackLog.push('C'));
|
||||
scheduleCallback(callbackA);
|
||||
const callbackId = scheduleCallback(callbackB);
|
||||
scheduleCallback(callbackC);
|
||||
scheduleCallback(NormalPriority, callbackA);
|
||||
const callbackId = scheduleCallback(NormalPriority, callbackB);
|
||||
scheduleCallback(NormalPriority, callbackC);
|
||||
cancelCallback(callbackId);
|
||||
cancelCallback(callbackId);
|
||||
cancelCallback(callbackId);
|
||||
|
|
@ -393,6 +422,7 @@ describe('SchedulerDOM', () => {
|
|||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_cancelCallback: cancelCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const callbackLog = [];
|
||||
let callbackBId;
|
||||
|
|
@ -401,8 +431,8 @@ describe('SchedulerDOM', () => {
|
|||
cancelCallback(callbackBId);
|
||||
});
|
||||
const callbackB = jest.fn(() => callbackLog.push('B'));
|
||||
scheduleCallback(callbackA);
|
||||
callbackBId = scheduleCallback(callbackB);
|
||||
scheduleCallback(NormalPriority, callbackA);
|
||||
callbackBId = scheduleCallback(NormalPriority, callbackB);
|
||||
// Initially doesn't call anything
|
||||
expect(callbackLog).toEqual([]);
|
||||
advanceOneFrame({timeLeftInFrame: 15});
|
||||
|
|
@ -430,7 +460,10 @@ describe('SchedulerDOM', () => {
|
|||
*
|
||||
*/
|
||||
it('still calls all callbacks within same frame', () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const callbackLog = [];
|
||||
const callbackA = jest.fn(() => callbackLog.push('A'));
|
||||
const callbackB = jest.fn(() => {
|
||||
|
|
@ -443,11 +476,11 @@ describe('SchedulerDOM', () => {
|
|||
throw new Error('D error');
|
||||
});
|
||||
const callbackE = jest.fn(() => callbackLog.push('E'));
|
||||
scheduleCallback(callbackA);
|
||||
scheduleCallback(callbackB);
|
||||
scheduleCallback(callbackC);
|
||||
scheduleCallback(callbackD);
|
||||
scheduleCallback(callbackE);
|
||||
scheduleCallback(NormalPriority, callbackA);
|
||||
scheduleCallback(NormalPriority, callbackB);
|
||||
scheduleCallback(NormalPriority, callbackC);
|
||||
scheduleCallback(NormalPriority, callbackD);
|
||||
scheduleCallback(NormalPriority, callbackE);
|
||||
// Initially doesn't call anything
|
||||
expect(callbackLog).toEqual([]);
|
||||
catchPostMessageErrors = true;
|
||||
|
|
@ -476,7 +509,10 @@ describe('SchedulerDOM', () => {
|
|||
*
|
||||
*/
|
||||
it('and with some timed out callbacks, still calls all callbacks within same frame', () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const callbackLog = [];
|
||||
const callbackA = jest.fn(() => {
|
||||
callbackLog.push('A');
|
||||
|
|
@ -489,11 +525,11 @@ describe('SchedulerDOM', () => {
|
|||
throw new Error('D error');
|
||||
});
|
||||
const callbackE = jest.fn(() => callbackLog.push('E'));
|
||||
scheduleCallback(callbackA);
|
||||
scheduleCallback(callbackB);
|
||||
scheduleCallback(callbackC, {timeout: 2}); // times out fast
|
||||
scheduleCallback(callbackD, {timeout: 2}); // times out fast
|
||||
scheduleCallback(callbackE, {timeout: 2}); // times out fast
|
||||
scheduleCallback(NormalPriority, callbackA);
|
||||
scheduleCallback(NormalPriority, callbackB);
|
||||
scheduleCallback(NormalPriority, callbackC, {timeout: 2}); // times out fast
|
||||
scheduleCallback(NormalPriority, callbackD, {timeout: 2}); // times out fast
|
||||
scheduleCallback(NormalPriority, callbackE, {timeout: 2}); // times out fast
|
||||
// Initially doesn't call anything
|
||||
expect(callbackLog).toEqual([]);
|
||||
catchPostMessageErrors = true;
|
||||
|
|
@ -522,7 +558,10 @@ describe('SchedulerDOM', () => {
|
|||
*
|
||||
*/
|
||||
it('still calls all callbacks within same frame', () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const callbackLog = [];
|
||||
const callbackA = jest.fn(() => {
|
||||
callbackLog.push('A');
|
||||
|
|
@ -544,11 +583,11 @@ describe('SchedulerDOM', () => {
|
|||
callbackLog.push('E');
|
||||
throw new Error('E error');
|
||||
});
|
||||
scheduleCallback(callbackA);
|
||||
scheduleCallback(callbackB);
|
||||
scheduleCallback(callbackC);
|
||||
scheduleCallback(callbackD);
|
||||
scheduleCallback(callbackE);
|
||||
scheduleCallback(NormalPriority, callbackA);
|
||||
scheduleCallback(NormalPriority, callbackB);
|
||||
scheduleCallback(NormalPriority, callbackC);
|
||||
scheduleCallback(NormalPriority, callbackD);
|
||||
scheduleCallback(NormalPriority, callbackE);
|
||||
// Initially doesn't call anything
|
||||
expect(callbackLog).toEqual([]);
|
||||
catchPostMessageErrors = true;
|
||||
|
|
@ -583,7 +622,10 @@ describe('SchedulerDOM', () => {
|
|||
*
|
||||
*/
|
||||
it('and with all timed out callbacks, still calls all callbacks within same frame', () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
const callbackLog = [];
|
||||
const callbackA = jest.fn(() => {
|
||||
callbackLog.push('A');
|
||||
|
|
@ -605,11 +647,11 @@ describe('SchedulerDOM', () => {
|
|||
callbackLog.push('E');
|
||||
throw new Error('E error');
|
||||
});
|
||||
scheduleCallback(callbackA, {timeout: 2}); // times out fast
|
||||
scheduleCallback(callbackB, {timeout: 2}); // times out fast
|
||||
scheduleCallback(callbackC, {timeout: 2}); // times out fast
|
||||
scheduleCallback(callbackD, {timeout: 2}); // times out fast
|
||||
scheduleCallback(callbackE, {timeout: 2}); // times out fast
|
||||
scheduleCallback(NormalPriority, callbackA, {timeout: 2}); // times out fast
|
||||
scheduleCallback(NormalPriority, callbackB, {timeout: 2}); // times out fast
|
||||
scheduleCallback(NormalPriority, callbackC, {timeout: 2}); // times out fast
|
||||
scheduleCallback(NormalPriority, callbackD, {timeout: 2}); // times out fast
|
||||
scheduleCallback(NormalPriority, callbackE, {timeout: 2}); // times out fast
|
||||
// Initially doesn't call anything
|
||||
expect(callbackLog).toEqual([]);
|
||||
catchPostMessageErrors = true;
|
||||
|
|
@ -664,7 +706,10 @@ describe('SchedulerDOM', () => {
|
|||
*
|
||||
*/
|
||||
it('still calls all callbacks within same frame', () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
startOfLatestFrame = 1000000000000;
|
||||
currentTime = startOfLatestFrame - 10;
|
||||
catchPostMessageErrors = true;
|
||||
|
|
@ -698,13 +743,13 @@ describe('SchedulerDOM', () => {
|
|||
});
|
||||
const callbackG = jest.fn(() => callbackLog.push('G'));
|
||||
|
||||
scheduleCallback(callbackA);
|
||||
scheduleCallback(callbackB);
|
||||
scheduleCallback(callbackC);
|
||||
scheduleCallback(callbackD);
|
||||
scheduleCallback(callbackE);
|
||||
scheduleCallback(callbackF);
|
||||
scheduleCallback(callbackG);
|
||||
scheduleCallback(NormalPriority, callbackA);
|
||||
scheduleCallback(NormalPriority, callbackB);
|
||||
scheduleCallback(NormalPriority, callbackC);
|
||||
scheduleCallback(NormalPriority, callbackD);
|
||||
scheduleCallback(NormalPriority, callbackE);
|
||||
scheduleCallback(NormalPriority, callbackF);
|
||||
scheduleCallback(NormalPriority, callbackG);
|
||||
|
||||
// does nothing initially
|
||||
expect(callbackLog).toEqual([]);
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
let Scheduler;
|
||||
let scheduleCallback;
|
||||
let runWithPriority;
|
||||
let ImmediatePriority;
|
||||
let UserBlockingPriority;
|
||||
let NormalPriority;
|
||||
|
||||
describe('SchedulerNoDOM', () => {
|
||||
// If Scheduler runs in a non-DOM environment, it falls back to a naive
|
||||
|
|
@ -30,22 +31,22 @@ describe('SchedulerNoDOM', () => {
|
|||
),
|
||||
);
|
||||
|
||||
const Scheduler = require('scheduler');
|
||||
Scheduler = require('scheduler');
|
||||
scheduleCallback = Scheduler.unstable_scheduleCallback;
|
||||
runWithPriority = Scheduler.unstable_runWithPriority;
|
||||
ImmediatePriority = Scheduler.unstable_ImmediatePriority;
|
||||
UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
|
||||
NormalPriority = Scheduler.unstable_NormalPriority;
|
||||
});
|
||||
|
||||
it('runAllTimers flushes all scheduled callbacks', () => {
|
||||
let log = [];
|
||||
scheduleCallback(() => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
log.push('A');
|
||||
});
|
||||
scheduleCallback(() => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
log.push('B');
|
||||
});
|
||||
scheduleCallback(() => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
log.push('C');
|
||||
});
|
||||
expect(log).toEqual([]);
|
||||
|
|
@ -56,19 +57,17 @@ describe('SchedulerNoDOM', () => {
|
|||
it('executes callbacks in order of priority', () => {
|
||||
let log = [];
|
||||
|
||||
scheduleCallback(() => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
log.push('A');
|
||||
});
|
||||
scheduleCallback(() => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
log.push('B');
|
||||
});
|
||||
runWithPriority(UserBlockingPriority, () => {
|
||||
scheduleCallback(() => {
|
||||
log.push('C');
|
||||
});
|
||||
scheduleCallback(() => {
|
||||
log.push('D');
|
||||
});
|
||||
scheduleCallback(UserBlockingPriority, () => {
|
||||
log.push('C');
|
||||
});
|
||||
scheduleCallback(UserBlockingPriority, () => {
|
||||
log.push('D');
|
||||
});
|
||||
|
||||
expect(log).toEqual([]);
|
||||
|
|
@ -76,39 +75,22 @@ describe('SchedulerNoDOM', () => {
|
|||
expect(log).toEqual(['C', 'D', 'A', 'B']);
|
||||
});
|
||||
|
||||
it('calls immediate callbacks immediately', () => {
|
||||
let log = [];
|
||||
|
||||
runWithPriority(ImmediatePriority, () => {
|
||||
scheduleCallback(() => {
|
||||
log.push('A');
|
||||
scheduleCallback(() => {
|
||||
log.push('B');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
expect(log).toEqual(['A', 'B']);
|
||||
});
|
||||
|
||||
it('handles errors', () => {
|
||||
let log = [];
|
||||
|
||||
expect(() => {
|
||||
runWithPriority(ImmediatePriority, () => {
|
||||
scheduleCallback(() => {
|
||||
log.push('A');
|
||||
throw new Error('Oops A');
|
||||
});
|
||||
scheduleCallback(() => {
|
||||
log.push('B');
|
||||
});
|
||||
scheduleCallback(() => {
|
||||
log.push('C');
|
||||
throw new Error('Oops C');
|
||||
});
|
||||
});
|
||||
}).toThrow('Oops A');
|
||||
scheduleCallback(ImmediatePriority, () => {
|
||||
log.push('A');
|
||||
throw new Error('Oops A');
|
||||
});
|
||||
scheduleCallback(ImmediatePriority, () => {
|
||||
log.push('B');
|
||||
});
|
||||
scheduleCallback(ImmediatePriority, () => {
|
||||
log.push('C');
|
||||
throw new Error('Oops C');
|
||||
});
|
||||
|
||||
expect(() => jest.runAllTimers()).toThrow('Oops A');
|
||||
|
||||
expect(log).toEqual(['A']);
|
||||
|
||||
|
|
|
|||
30
packages/shared/forks/ReactFeatureFlags.new-scheduler.js
Normal file
30
packages/shared/forks/ReactFeatureFlags.new-scheduler.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
export const enableUserTimingAPI = __DEV__;
|
||||
export const debugRenderPhaseSideEffects = false;
|
||||
export const debugRenderPhaseSideEffectsForStrictMode = __DEV__;
|
||||
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
|
||||
export const warnAboutDeprecatedLifecycles = true;
|
||||
export const enableProfilerTimer = __PROFILE__;
|
||||
export const enableSchedulerTracing = __PROFILE__;
|
||||
export const enableSuspenseServerRenderer = false; // TODO: __DEV__? Here it might just be false.
|
||||
export const enableSchedulerDebugging = false;
|
||||
export function addUserTimingListener() {
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
export const disableJavaScriptURLs = false;
|
||||
export const disableYielding = false;
|
||||
export const disableInputAttributeSyncing = false;
|
||||
export const enableStableConcurrentModeAPIs = false;
|
||||
export const warnAboutShorthandPropertyCollision = false;
|
||||
export const warnAboutDeprecatedSetNativeProps = false;
|
||||
export const enableEventAPI = false;
|
||||
|
||||
export const enableNewScheduler = true;
|
||||
39
packages/shared/forks/ReactFeatureFlags.www-new-scheduler.js
Normal file
39
packages/shared/forks/ReactFeatureFlags.www-new-scheduler.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
|
||||
import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.www-new-scheduler';
|
||||
|
||||
export {
|
||||
enableUserTimingAPI,
|
||||
debugRenderPhaseSideEffects,
|
||||
debugRenderPhaseSideEffectsForStrictMode,
|
||||
replayFailedUnitOfWorkWithInvokeGuardedCallback,
|
||||
warnAboutDeprecatedLifecycles,
|
||||
enableProfilerTimer,
|
||||
enableSchedulerTracing,
|
||||
enableSuspenseServerRenderer,
|
||||
enableSchedulerDebugging,
|
||||
addUserTimingListener,
|
||||
disableJavaScriptURLs,
|
||||
disableYielding,
|
||||
disableInputAttributeSyncing,
|
||||
enableStableConcurrentModeAPIs,
|
||||
warnAboutShorthandPropertyCollision,
|
||||
warnAboutDeprecatedSetNativeProps,
|
||||
enableEventAPI,
|
||||
} from './ReactFeatureFlags.www';
|
||||
|
||||
export const enableNewScheduler = true;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
type Check<_X, Y: _X, X: Y = _X> = null;
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
(null: Check<FeatureFlagsShimType, FeatureFlagsType>);
|
||||
|
|
@ -11,6 +11,7 @@ if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
|
|||
COMMANDS_TO_RUN+=('node ./scripts/tasks/flow-ci')
|
||||
COMMANDS_TO_RUN+=('node ./scripts/tasks/eslint')
|
||||
COMMANDS_TO_RUN+=('yarn test --maxWorkers=2')
|
||||
COMMANDS_TO_RUN+=('yarn test-new-scheduler --maxWorkers=2')
|
||||
COMMANDS_TO_RUN+=('yarn test-persistent --maxWorkers=2')
|
||||
COMMANDS_TO_RUN+=('./scripts/circleci/check_license.sh')
|
||||
COMMANDS_TO_RUN+=('./scripts/circleci/check_modules.sh')
|
||||
|
|
|
|||
|
|
@ -48,6 +48,15 @@ function toFlushWithoutYielding(Scheduler) {
|
|||
return toFlushAndYield(Scheduler, []);
|
||||
}
|
||||
|
||||
function toFlushExpired(Scheduler, expectedYields) {
|
||||
assertYieldsWereCleared(Scheduler);
|
||||
Scheduler.unstable_flushExpired();
|
||||
const actualYields = Scheduler.unstable_clearYields();
|
||||
return captureAssertion(() => {
|
||||
expect(actualYields).toEqual(expectedYields);
|
||||
});
|
||||
}
|
||||
|
||||
function toHaveYielded(Scheduler, expectedYields) {
|
||||
return captureAssertion(() => {
|
||||
const actualYields = Scheduler.unstable_clearYields();
|
||||
|
|
@ -68,6 +77,7 @@ module.exports = {
|
|||
toFlushAndYield,
|
||||
toFlushAndYieldThrough,
|
||||
toFlushWithoutYielding,
|
||||
toFlushExpired,
|
||||
toHaveYielded,
|
||||
toFlushAndThrow,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -110,6 +110,21 @@ const bundles = [
|
|||
externals: ['react'],
|
||||
},
|
||||
|
||||
/******* React DOM (new scheduler) *******/
|
||||
{
|
||||
bundleTypes: [
|
||||
FB_WWW_DEV,
|
||||
FB_WWW_PROD,
|
||||
FB_WWW_PROFILING,
|
||||
NODE_DEV,
|
||||
NODE_PROD,
|
||||
],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-dom/unstable-new-scheduler',
|
||||
global: 'ReactDOMNewScheduler',
|
||||
externals: ['react'],
|
||||
},
|
||||
|
||||
/******* Test Utils *******/
|
||||
{
|
||||
moduleType: RENDERER_UTILS,
|
||||
|
|
|
|||
|
|
@ -68,6 +68,12 @@ const forks = Object.freeze({
|
|||
// We have a few forks for different environments.
|
||||
'shared/ReactFeatureFlags': (bundleType, entry) => {
|
||||
switch (entry) {
|
||||
case 'react-dom/unstable-new-scheduler': {
|
||||
if (entry === 'react-dom/unstable-new-scheduler') {
|
||||
return 'shared/forks/ReactFeatureFlags.www-new-scheduler.js';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 'react-native-renderer':
|
||||
switch (bundleType) {
|
||||
case RN_FB_DEV:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@
|
|||
module.exports = [
|
||||
{
|
||||
shortName: 'dom',
|
||||
entryPoints: ['react-dom', 'react-dom/unstable-fizz.node'],
|
||||
entryPoints: [
|
||||
'react-dom',
|
||||
'react-dom/unstable-fizz.node',
|
||||
'react-dom/unstable-new-scheduler',
|
||||
],
|
||||
isFlowTyped: true,
|
||||
isFizzSupported: true,
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user