Delete flushSuspenseFallbacksInTests flag (#18596)

* Move renderer `act` to work loop

* Delete `flushSuspenseFallbacksInTests`

This was meant to be a temporary hack to unblock the `act` work, but it
quickly spread throughout our tests.

What it's meant to do is force fallbacks to flush inside `act` even in
Concurrent Mode. It does this by wrapping the `setTimeout` call in a
check to see if it's in an `act` context. If so, it skips the delay and
immediately commits the fallback.

Really this is only meant for our internal React tests that need to
incrementally render. Nobody outside our team (and Relay) needs to do
that, yet. Even if/when we do support that, it may or may not be with
the same `flushAndYield` pattern we use internally.

However, even for our internal purposes, the behavior isn't right
because a really common reason we flush work incrementally is to make
assertions on the "suspended" state, before the fallback has committed.
There's no way to do that from inside `act` with the behavior of this
flag, because it causes the fallback to immediately commit. This has led
us to *not* use `act` in a lot of our tests, or to write code that
doesn't match what would actually happen in a real environment.

What we really want is for the fallbacks to be flushed at the *end` of
the `act` scope. Not within it.

This only affects the noop and test renderer versions of `act`, which
are implemented inside the reconciler. Whereas `ReactTestUtils.act` is
implemented in "userspace" for backwards compatibility. This is fine
because we didn't have any DOM Suspense tests that relied on this flag;
they all use test renderer or noop.

In the future, we'll probably want to move always use the reconciler
implementation of `act`. It will not affect the prod bundle, because we
currently only plan to support `act` in dev. Though we still haven't
completely figured that out. However, regardless of whether we support a
production `act` for users, we'll still need to write internal React
tests in production mode. For that use case, we'll likely add our own
internal version of `act` that assumes a mock Scheduler and might rely
on hacks that don't 100% align up with the public one.
This commit is contained in:
Andrew Clark
2020-04-13 20:02:18 -07:00
committed by GitHub
parent f3f3d77c20
commit b928fc030a
18 changed files with 732 additions and 687 deletions
@@ -14,7 +14,6 @@
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
let React;
let ReactFeatureFlags;
let ReactDOM;
let ReactDOMServer;
let ReactTestUtils;
@@ -39,9 +38,6 @@ function initModules() {
// Reset warning cache.
jest.resetModuleRegistry();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.flushSuspenseFallbacksInTests = false;
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
@@ -1281,13 +1277,26 @@ describe('ReactDOMServerHooks', () => {
// State update should trigger the ID to update, which changes the props
// of ChildWithID. This should cause ChildWithID to hydrate before Children
expect(Scheduler).toFlushAndYieldThrough([
'Child with ID',
'Child with ID',
'Child with ID',
'Child One',
'Child Two',
]);
expect(Scheduler).toFlushAndYieldThrough(
__DEV__
? [
'Child with ID',
// Fallbacks are immdiately committed in TestUtils version
// of act
// 'Child with ID',
// 'Child with ID',
'Child One',
'Child Two',
]
: [
'Child with ID',
'Child with ID',
'Child with ID',
'Child One',
'Child Two',
],
);
expect(child1Ref.current).toBe(null);
expect(childWithIDRef.current).toEqual(
@@ -18,7 +18,7 @@ import type {
} from './ReactFiberHostConfig';
import type {RendererInspectionConfig} from './ReactFiberHostConfig';
import {FundamentalComponent} from './ReactWorkTags';
import type {ReactNodeList, Thenable} from 'shared/ReactTypes';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {ExpirationTime} from './ReactFiberExpirationTime.new';
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
@@ -64,6 +64,7 @@ import {
warnIfNotScopedWithMatchingAct,
warnIfUnmockedScheduler,
IsThisRendererActing,
act,
} from './ReactFiberWorkLoop.new';
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue.new';
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
@@ -85,11 +86,6 @@ import {
findHostInstancesForRefresh,
} from './ReactFiberHotReloading.new';
// used by isTestEnvironment builds
import enqueueTask from 'shared/enqueueTask';
import * as Scheduler from 'scheduler';
// end isTestEnvironment imports
export {createPortal} from './ReactPortal';
type OpaqueRoot = FiberRoot;
@@ -308,6 +304,7 @@ export {
flushSync,
flushPassiveEffects,
IsThisRendererActing,
act,
};
export function getPublicRootInstance(
@@ -547,183 +544,3 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
getCurrentFiber: __DEV__ ? getCurrentFiberForDevTools : null,
});
}
const {IsSomeRendererActing} = ReactSharedInternals;
const isSchedulerMocked =
typeof Scheduler.unstable_flushAllWithoutAsserting === 'function';
const flushWork =
Scheduler.unstable_flushAllWithoutAsserting ||
function() {
let didFlushWork = false;
while (flushPassiveEffects()) {
didFlushWork = true;
}
return didFlushWork;
};
function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
try {
flushWork();
enqueueTask(() => {
if (flushWork()) {
flushWorkAndMicroTasks(onDone);
} else {
onDone();
}
});
} catch (err) {
onDone(err);
}
}
// we track the 'depth' of the act() calls with this counter,
// so we can tell if any async act() calls try to run in parallel.
let actingUpdatesScopeDepth = 0;
let didWarnAboutUsingActInProd = false;
// eslint-disable-next-line no-inner-declarations
export function act(callback: () => Thenable<mixed>): Thenable<void> {
if (!__DEV__) {
if (didWarnAboutUsingActInProd === false) {
didWarnAboutUsingActInProd = true;
// eslint-disable-next-line react-internal/no-production-logging
console.error(
'act(...) is not supported in production builds of React, and might not behave as expected.',
);
}
}
const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
actingUpdatesScopeDepth++;
const previousIsSomeRendererActing = IsSomeRendererActing.current;
const previousIsThisRendererActing = IsThisRendererActing.current;
IsSomeRendererActing.current = true;
IsThisRendererActing.current = true;
function onDone() {
actingUpdatesScopeDepth--;
IsSomeRendererActing.current = previousIsSomeRendererActing;
IsThisRendererActing.current = previousIsThisRendererActing;
if (__DEV__) {
if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
// if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
console.error(
'You seem to have overlapping act() calls, this is not supported. ' +
'Be sure to await previous act() calls before making a new one. ',
);
}
}
}
let result;
try {
result = batchedUpdates(callback);
} catch (error) {
// on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
onDone();
throw error;
}
if (
result !== null &&
typeof result === 'object' &&
typeof result.then === 'function'
) {
// setup a boolean that gets set to true only
// once this act() call is await-ed
let called = false;
if (__DEV__) {
if (typeof Promise !== 'undefined') {
//eslint-disable-next-line no-undef
Promise.resolve()
.then(() => {})
.then(() => {
if (called === false) {
console.error(
'You called act(async () => ...) without await. ' +
'This could lead to unexpected testing behaviour, interleaving multiple act ' +
'calls and mixing their scopes. You should - await act(async () => ...);',
);
}
});
}
}
// in the async case, the returned thenable runs the callback, flushes
// effects and microtasks in a loop until flushPassiveEffects() === false,
// and cleans up
return {
then(resolve, reject) {
called = true;
result.then(
() => {
if (
actingUpdatesScopeDepth > 1 ||
(isSchedulerMocked === true &&
previousIsSomeRendererActing === true)
) {
onDone();
resolve();
return;
}
// we're about to exit the act() scope,
// now's the time to flush tasks/effects
flushWorkAndMicroTasks((err: ?Error) => {
onDone();
if (err) {
reject(err);
} else {
resolve();
}
});
},
err => {
onDone();
reject(err);
},
);
},
};
} else {
if (__DEV__) {
if (result !== undefined) {
console.error(
'The callback passed to act(...) function ' +
'must return undefined, or a Promise. You returned %s',
result,
);
}
}
// flush effects until none remain, and cleanup
try {
if (
actingUpdatesScopeDepth === 1 &&
(isSchedulerMocked === false || previousIsSomeRendererActing === false)
) {
// we're about to exit the act() scope,
// now's the time to flush effects
flushWork();
}
onDone();
} catch (err) {
onDone();
throw err;
}
// in the sync case, the returned thenable only warns *if* await-ed
return {
then(resolve) {
if (__DEV__) {
console.error(
'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
);
}
resolve();
},
};
}
}
@@ -18,7 +18,7 @@ import type {
} from './ReactFiberHostConfig';
import type {RendererInspectionConfig} from './ReactFiberHostConfig';
import {FundamentalComponent} from './ReactWorkTags';
import type {ReactNodeList, Thenable} from 'shared/ReactTypes';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {ExpirationTime} from './ReactFiberExpirationTime.old';
import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
@@ -64,6 +64,7 @@ import {
warnIfNotScopedWithMatchingAct,
warnIfUnmockedScheduler,
IsThisRendererActing,
act,
} from './ReactFiberWorkLoop.old';
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue.old';
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
@@ -85,11 +86,6 @@ import {
findHostInstancesForRefresh,
} from './ReactFiberHotReloading.old';
// used by isTestEnvironment builds
import enqueueTask from 'shared/enqueueTask';
import * as Scheduler from 'scheduler';
// end isTestEnvironment imports
export {createPortal} from './ReactPortal';
type OpaqueRoot = FiberRoot;
@@ -308,6 +304,7 @@ export {
flushSync,
flushPassiveEffects,
IsThisRendererActing,
act,
};
export function getPublicRootInstance(
@@ -547,183 +544,3 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
getCurrentFiber: __DEV__ ? getCurrentFiberForDevTools : null,
});
}
const {IsSomeRendererActing} = ReactSharedInternals;
const isSchedulerMocked =
typeof Scheduler.unstable_flushAllWithoutAsserting === 'function';
const flushWork =
Scheduler.unstable_flushAllWithoutAsserting ||
function() {
let didFlushWork = false;
while (flushPassiveEffects()) {
didFlushWork = true;
}
return didFlushWork;
};
function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
try {
flushWork();
enqueueTask(() => {
if (flushWork()) {
flushWorkAndMicroTasks(onDone);
} else {
onDone();
}
});
} catch (err) {
onDone(err);
}
}
// we track the 'depth' of the act() calls with this counter,
// so we can tell if any async act() calls try to run in parallel.
let actingUpdatesScopeDepth = 0;
let didWarnAboutUsingActInProd = false;
// eslint-disable-next-line no-inner-declarations
export function act(callback: () => Thenable<mixed>): Thenable<void> {
if (!__DEV__) {
if (didWarnAboutUsingActInProd === false) {
didWarnAboutUsingActInProd = true;
// eslint-disable-next-line react-internal/no-production-logging
console.error(
'act(...) is not supported in production builds of React, and might not behave as expected.',
);
}
}
const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
actingUpdatesScopeDepth++;
const previousIsSomeRendererActing = IsSomeRendererActing.current;
const previousIsThisRendererActing = IsThisRendererActing.current;
IsSomeRendererActing.current = true;
IsThisRendererActing.current = true;
function onDone() {
actingUpdatesScopeDepth--;
IsSomeRendererActing.current = previousIsSomeRendererActing;
IsThisRendererActing.current = previousIsThisRendererActing;
if (__DEV__) {
if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
// if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
console.error(
'You seem to have overlapping act() calls, this is not supported. ' +
'Be sure to await previous act() calls before making a new one. ',
);
}
}
}
let result;
try {
result = batchedUpdates(callback);
} catch (error) {
// on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
onDone();
throw error;
}
if (
result !== null &&
typeof result === 'object' &&
typeof result.then === 'function'
) {
// setup a boolean that gets set to true only
// once this act() call is await-ed
let called = false;
if (__DEV__) {
if (typeof Promise !== 'undefined') {
//eslint-disable-next-line no-undef
Promise.resolve()
.then(() => {})
.then(() => {
if (called === false) {
console.error(
'You called act(async () => ...) without await. ' +
'This could lead to unexpected testing behaviour, interleaving multiple act ' +
'calls and mixing their scopes. You should - await act(async () => ...);',
);
}
});
}
}
// in the async case, the returned thenable runs the callback, flushes
// effects and microtasks in a loop until flushPassiveEffects() === false,
// and cleans up
return {
then(resolve, reject) {
called = true;
result.then(
() => {
if (
actingUpdatesScopeDepth > 1 ||
(isSchedulerMocked === true &&
previousIsSomeRendererActing === true)
) {
onDone();
resolve();
return;
}
// we're about to exit the act() scope,
// now's the time to flush tasks/effects
flushWorkAndMicroTasks((err: ?Error) => {
onDone();
if (err) {
reject(err);
} else {
resolve();
}
});
},
err => {
onDone();
reject(err);
},
);
},
};
} else {
if (__DEV__) {
if (result !== undefined) {
console.error(
'The callback passed to act(...) function ' +
'must return undefined, or a Promise. You returned %s',
result,
);
}
}
// flush effects until none remain, and cleanup
try {
if (
actingUpdatesScopeDepth === 1 &&
(isSchedulerMocked === false || previousIsSomeRendererActing === false)
) {
// we're about to exit the act() scope,
// now's the time to flush effects
flushWork();
}
onDone();
} catch (err) {
onDone();
throw err;
}
// in the sync case, the returned thenable only warns *if* await-ed
return {
then(resolve) {
if (__DEV__) {
console.error(
'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
);
}
resolve();
},
};
}
}
@@ -7,7 +7,7 @@
* @flow
*/
import type {Wakeable} from 'shared/ReactTypes';
import type {Thenable, Wakeable} from 'shared/ReactTypes';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {ExpirationTime} from './ReactFiberExpirationTime.new';
import type {ReactPriorityLevel} from './ReactInternalTypes';
@@ -26,7 +26,6 @@ import {
enableProfilerCommitHooks,
enableSchedulerTracing,
warnAboutUnmockedScheduler,
flushSuspenseFallbacksInTests,
disableSchedulerTimeoutBasedOnReactExpirationTime,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -180,6 +179,9 @@ import {
} from 'shared/ReactErrorUtils';
import {onCommitRoot} from './ReactFiberDevToolsHook.new';
// Used by `act`
import enqueueTask from 'shared/enqueueTask';
const ceil = Math.ceil;
const {
@@ -739,11 +741,7 @@ function finishConcurrentRender(
if (
hasNotProcessedNewUpdates &&
// do not delay if we're inside an act() scope
!(
__DEV__ &&
flushSuspenseFallbacksInTests &&
IsThisRendererActing.current
)
!shouldForceFlushFallbacksInDEV()
) {
// If we have not processed any new updates during this pass, then
// this is either a retry of an existing fallback state or a
@@ -802,11 +800,7 @@ function finishConcurrentRender(
if (
// do not delay if we're inside an act() scope
!(
__DEV__ &&
flushSuspenseFallbacksInTests &&
IsThisRendererActing.current
)
!shouldForceFlushFallbacksInDEV()
) {
// We're suspended in a state that should be avoided. We'll try to
// avoid committing it for as long as the timeouts let us.
@@ -878,11 +872,7 @@ function finishConcurrentRender(
// The work completed. Ready to commit.
if (
// do not delay if we're inside an act() scope
!(
__DEV__ &&
flushSuspenseFallbacksInTests &&
IsThisRendererActing.current
) &&
!shouldForceFlushFallbacksInDEV() &&
workInProgressRootLatestProcessedEventTime !== Sync &&
workInProgressRootCanSuspendUsingConfig !== null
) {
@@ -3197,3 +3187,227 @@ function finishPendingInteractions(root, committedExpirationTime) {
);
}
}
// `act` testing API
//
// TODO: This is mostly a copy-paste from the legacy `act`, which does not have
// access to the same internals that we do here. Some trade offs in the
// implementation no longer make sense.
let isFlushingAct = false;
let isInsideThisAct = false;
// TODO: Yes, this is confusing. See above comment. We'll refactor it.
function shouldForceFlushFallbacksInDEV() {
if (!__DEV__) {
// Never force flush in production. This function should get stripped out.
return false;
}
// `IsThisRendererActing.current` is used by ReactTestUtils version of `act`.
if (IsThisRendererActing.current) {
// `isInsideAct` is only used by the reconciler implementation of `act`.
// We don't want to flush suspense fallbacks until the end.
return !isInsideThisAct;
}
// Flush callbacks at the end.
return isFlushingAct;
}
const flushMockScheduler = Scheduler.unstable_flushAllWithoutAsserting;
const isSchedulerMocked = typeof flushMockScheduler === 'function';
// Returns whether additional work was scheduled. Caller should keep flushing
// until there's no work left.
function flushActWork(): boolean {
if (flushMockScheduler !== undefined) {
const prevIsFlushing = isFlushingAct;
isFlushingAct = true;
try {
return flushMockScheduler();
} finally {
isFlushingAct = prevIsFlushing;
}
} else {
// No mock scheduler available. However, the only type of pending work is
// passive effects, which we control. So we can flush that.
const prevIsFlushing = isFlushingAct;
isFlushingAct = true;
try {
let didFlushWork = false;
while (flushPassiveEffects()) {
didFlushWork = true;
}
return didFlushWork;
} finally {
isFlushingAct = prevIsFlushing;
}
}
}
function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
try {
flushActWork();
enqueueTask(() => {
if (flushActWork()) {
flushWorkAndMicroTasks(onDone);
} else {
onDone();
}
});
} catch (err) {
onDone(err);
}
}
// we track the 'depth' of the act() calls with this counter,
// so we can tell if any async act() calls try to run in parallel.
let actingUpdatesScopeDepth = 0;
let didWarnAboutUsingActInProd = false;
export function act(callback: () => Thenable<mixed>): Thenable<void> {
if (!__DEV__) {
if (didWarnAboutUsingActInProd === false) {
didWarnAboutUsingActInProd = true;
// eslint-disable-next-line react-internal/no-production-logging
console.error(
'act(...) is not supported in production builds of React, and might not behave as expected.',
);
}
}
const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
actingUpdatesScopeDepth++;
const previousIsSomeRendererActing = IsSomeRendererActing.current;
const previousIsThisRendererActing = IsThisRendererActing.current;
const previousIsInsideThisAct = isInsideThisAct;
IsSomeRendererActing.current = true;
IsThisRendererActing.current = true;
isInsideThisAct = true;
function onDone() {
actingUpdatesScopeDepth--;
IsSomeRendererActing.current = previousIsSomeRendererActing;
IsThisRendererActing.current = previousIsThisRendererActing;
isInsideThisAct = previousIsInsideThisAct;
if (__DEV__) {
if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
// if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
console.error(
'You seem to have overlapping act() calls, this is not supported. ' +
'Be sure to await previous act() calls before making a new one. ',
);
}
}
}
let result;
try {
result = batchedUpdates(callback);
} catch (error) {
// on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
onDone();
throw error;
}
if (
result !== null &&
typeof result === 'object' &&
typeof result.then === 'function'
) {
// setup a boolean that gets set to true only
// once this act() call is await-ed
let called = false;
if (__DEV__) {
if (typeof Promise !== 'undefined') {
//eslint-disable-next-line no-undef
Promise.resolve()
.then(() => {})
.then(() => {
if (called === false) {
console.error(
'You called act(async () => ...) without await. ' +
'This could lead to unexpected testing behaviour, interleaving multiple act ' +
'calls and mixing their scopes. You should - await act(async () => ...);',
);
}
});
}
}
// in the async case, the returned thenable runs the callback, flushes
// effects and microtasks in a loop until flushPassiveEffects() === false,
// and cleans up
return {
then(resolve, reject) {
called = true;
result.then(
() => {
if (
actingUpdatesScopeDepth > 1 ||
(isSchedulerMocked === true &&
previousIsSomeRendererActing === true)
) {
onDone();
resolve();
return;
}
// we're about to exit the act() scope,
// now's the time to flush tasks/effects
flushWorkAndMicroTasks((err: ?Error) => {
onDone();
if (err) {
reject(err);
} else {
resolve();
}
});
},
err => {
onDone();
reject(err);
},
);
},
};
} else {
if (__DEV__) {
if (result !== undefined) {
console.error(
'The callback passed to act(...) function ' +
'must return undefined, or a Promise. You returned %s',
result,
);
}
}
// flush effects until none remain, and cleanup
try {
if (
actingUpdatesScopeDepth === 1 &&
(isSchedulerMocked === false || previousIsSomeRendererActing === false)
) {
// we're about to exit the act() scope,
// now's the time to flush effects
flushActWork();
}
onDone();
} catch (err) {
onDone();
throw err;
}
// in the sync case, the returned thenable only warns *if* await-ed
return {
then(resolve) {
if (__DEV__) {
console.error(
'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
);
}
resolve();
},
};
}
}
@@ -7,7 +7,7 @@
* @flow
*/
import type {Wakeable} from 'shared/ReactTypes';
import type {Thenable, Wakeable} from 'shared/ReactTypes';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {ExpirationTime} from './ReactFiberExpirationTime.old';
import type {ReactPriorityLevel} from './ReactInternalTypes';
@@ -26,7 +26,6 @@ import {
enableProfilerCommitHooks,
enableSchedulerTracing,
warnAboutUnmockedScheduler,
flushSuspenseFallbacksInTests,
disableSchedulerTimeoutBasedOnReactExpirationTime,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -178,6 +177,9 @@ import {
} from 'shared/ReactErrorUtils';
import {onCommitRoot} from './ReactFiberDevToolsHook.old';
// Used by `act`
import enqueueTask from 'shared/enqueueTask';
const ceil = Math.ceil;
const {
@@ -732,11 +734,7 @@ function finishConcurrentRender(
if (
hasNotProcessedNewUpdates &&
// do not delay if we're inside an act() scope
!(
__DEV__ &&
flushSuspenseFallbacksInTests &&
IsThisRendererActing.current
)
!shouldForceFlushFallbacksInDEV()
) {
// If we have not processed any new updates during this pass, then
// this is either a retry of an existing fallback state or a
@@ -795,11 +793,7 @@ function finishConcurrentRender(
if (
// do not delay if we're inside an act() scope
!(
__DEV__ &&
flushSuspenseFallbacksInTests &&
IsThisRendererActing.current
)
!shouldForceFlushFallbacksInDEV()
) {
// We're suspended in a state that should be avoided. We'll try to
// avoid committing it for as long as the timeouts let us.
@@ -886,11 +880,7 @@ function finishConcurrentRender(
// The work completed. Ready to commit.
if (
// do not delay if we're inside an act() scope
!(
__DEV__ &&
flushSuspenseFallbacksInTests &&
IsThisRendererActing.current
) &&
!shouldForceFlushFallbacksInDEV() &&
workInProgressRootLatestProcessedExpirationTime !== Sync &&
workInProgressRootCanSuspendUsingConfig !== null
) {
@@ -3219,3 +3209,227 @@ function finishPendingInteractions(root, committedExpirationTime) {
);
}
}
// `act` testing API
//
// TODO: This is mostly a copy-paste from the legacy `act`, which does not have
// access to the same internals that we do here. Some trade offs in the
// implementation no longer make sense.
let isFlushingAct = false;
let isInsideThisAct = false;
// TODO: Yes, this is confusing. See above comment. We'll refactor it.
function shouldForceFlushFallbacksInDEV() {
if (!__DEV__) {
// Never force flush in production. This function should get stripped out.
return false;
}
// `IsThisRendererActing.current` is used by ReactTestUtils version of `act`.
if (IsThisRendererActing.current) {
// `isInsideAct` is only used by the reconciler implementation of `act`.
// We don't want to flush suspense fallbacks until the end.
return !isInsideThisAct;
}
// Flush callbacks at the end.
return isFlushingAct;
}
const flushMockScheduler = Scheduler.unstable_flushAllWithoutAsserting;
const isSchedulerMocked = typeof flushMockScheduler === 'function';
// Returns whether additional work was scheduled. Caller should keep flushing
// until there's no work left.
function flushActWork(): boolean {
if (flushMockScheduler !== undefined) {
const prevIsFlushing = isFlushingAct;
isFlushingAct = true;
try {
return flushMockScheduler();
} finally {
isFlushingAct = prevIsFlushing;
}
} else {
// No mock scheduler available. However, the only type of pending work is
// passive effects, which we control. So we can flush that.
const prevIsFlushing = isFlushingAct;
isFlushingAct = true;
try {
let didFlushWork = false;
while (flushPassiveEffects()) {
didFlushWork = true;
}
return didFlushWork;
} finally {
isFlushingAct = prevIsFlushing;
}
}
}
function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
try {
flushActWork();
enqueueTask(() => {
if (flushActWork()) {
flushWorkAndMicroTasks(onDone);
} else {
onDone();
}
});
} catch (err) {
onDone(err);
}
}
// we track the 'depth' of the act() calls with this counter,
// so we can tell if any async act() calls try to run in parallel.
let actingUpdatesScopeDepth = 0;
let didWarnAboutUsingActInProd = false;
export function act(callback: () => Thenable<mixed>): Thenable<void> {
if (!__DEV__) {
if (didWarnAboutUsingActInProd === false) {
didWarnAboutUsingActInProd = true;
// eslint-disable-next-line react-internal/no-production-logging
console.error(
'act(...) is not supported in production builds of React, and might not behave as expected.',
);
}
}
const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
actingUpdatesScopeDepth++;
const previousIsSomeRendererActing = IsSomeRendererActing.current;
const previousIsThisRendererActing = IsThisRendererActing.current;
const previousIsInsideThisAct = isInsideThisAct;
IsSomeRendererActing.current = true;
IsThisRendererActing.current = true;
isInsideThisAct = true;
function onDone() {
actingUpdatesScopeDepth--;
IsSomeRendererActing.current = previousIsSomeRendererActing;
IsThisRendererActing.current = previousIsThisRendererActing;
isInsideThisAct = previousIsInsideThisAct;
if (__DEV__) {
if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
// if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
console.error(
'You seem to have overlapping act() calls, this is not supported. ' +
'Be sure to await previous act() calls before making a new one. ',
);
}
}
}
let result;
try {
result = batchedUpdates(callback);
} catch (error) {
// on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
onDone();
throw error;
}
if (
result !== null &&
typeof result === 'object' &&
typeof result.then === 'function'
) {
// setup a boolean that gets set to true only
// once this act() call is await-ed
let called = false;
if (__DEV__) {
if (typeof Promise !== 'undefined') {
//eslint-disable-next-line no-undef
Promise.resolve()
.then(() => {})
.then(() => {
if (called === false) {
console.error(
'You called act(async () => ...) without await. ' +
'This could lead to unexpected testing behaviour, interleaving multiple act ' +
'calls and mixing their scopes. You should - await act(async () => ...);',
);
}
});
}
}
// in the async case, the returned thenable runs the callback, flushes
// effects and microtasks in a loop until flushPassiveEffects() === false,
// and cleans up
return {
then(resolve, reject) {
called = true;
result.then(
() => {
if (
actingUpdatesScopeDepth > 1 ||
(isSchedulerMocked === true &&
previousIsSomeRendererActing === true)
) {
onDone();
resolve();
return;
}
// we're about to exit the act() scope,
// now's the time to flush tasks/effects
flushWorkAndMicroTasks((err: ?Error) => {
onDone();
if (err) {
reject(err);
} else {
resolve();
}
});
},
err => {
onDone();
reject(err);
},
);
},
};
} else {
if (__DEV__) {
if (result !== undefined) {
console.error(
'The callback passed to act(...) function ' +
'must return undefined, or a Promise. You returned %s',
result,
);
}
}
// flush effects until none remain, and cleanup
try {
if (
actingUpdatesScopeDepth === 1 &&
(isSchedulerMocked === false || previousIsSomeRendererActing === false)
) {
// we're about to exit the act() scope,
// now's the time to flush effects
flushActWork();
}
onDone();
} catch (err) {
onDone();
throw err;
}
// in the sync case, the returned thenable only warns *if* await-ed
return {
then(resolve) {
if (__DEV__) {
console.error(
'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
);
}
resolve();
},
};
}
}
@@ -44,7 +44,6 @@ describe('ReactHooksWithNoopRenderer', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableSchedulerTracing = true;
ReactFeatureFlags.flushSuspenseFallbacksInTests = false;
ReactFeatureFlags.enableProfilerTimer = true;
deferPassiveEffectCleanupDuringUnmount =
ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount;
@@ -659,7 +658,7 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(ReactNoop.getChildren()).toEqual([span(22)]);
});
it('discards render phase updates if something suspends', () => {
it('discards render phase updates if something suspends', async () => {
const thenable = {then() {}};
function Foo({signal}) {
return (
@@ -747,19 +746,20 @@ describe('ReactHooksWithNoopRenderer', () => {
await ReactNoop.act(async () => {
root.render(<Foo signal={false} />);
setLabel('B');
expect(Scheduler).toFlushAndYield(['Suspend!']);
expect(root).toMatchRenderedOutput(<span prop="A:0" />);
// Rendering again should suspend again.
root.render(<Foo signal={false} />);
expect(Scheduler).toFlushAndYield(['Suspend!']);
// Flip the signal back to "cancel" the update. However, the update to
// label should still proceed. It shouldn't have been dropped.
root.render(<Foo signal={true} />);
expect(Scheduler).toFlushAndYield(['B:0']);
expect(root).toMatchRenderedOutput(<span prop="B:0" />);
});
expect(Scheduler).toHaveYielded(['Suspend!']);
expect(root).toMatchRenderedOutput(<span prop="A:0" />);
// Rendering again should suspend again.
root.render(<Foo signal={false} />);
expect(Scheduler).toFlushAndYield(['Suspend!']);
// Flip the signal back to "cancel" the update. However, the update to
// label should still proceed. It shouldn't have been dropped.
root.render(<Foo signal={true} />);
expect(Scheduler).toFlushAndYield(['B:0']);
expect(root).toMatchRenderedOutput(<span prop="B:0" />);
});
it('regression: render phase updates cause lower pri work to be dropped', async () => {
@@ -2755,39 +2755,40 @@ describe('ReactHooksWithNoopRenderer', () => {
span('Before... Pending: false'),
]);
act(() => {
await act(async () => {
Scheduler.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
transition,
);
expect(Scheduler).toFlushAndYield([
'Before... Pending: true',
'Suspend! [After... Pending: false]',
'Loading... Pending: false',
]);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
Scheduler.unstable_advanceTime(500);
await advanceTimers(500);
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
expect(ReactNoop.getChildren()).toEqual([
hiddenSpan('Before... Pending: true'),
span('Loading... Pending: false'),
]);
Scheduler.unstable_advanceTime(500);
await advanceTimers(500);
expect(Scheduler).toHaveYielded([
'Promise resolved [After... Pending: false]',
]);
expect(Scheduler).toFlushAndYield(['After... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('After... Pending: false'),
]);
});
Scheduler.unstable_advanceTime(500);
await advanceTimers(500);
expect(Scheduler).toHaveYielded([
'Before... Pending: true',
'Suspend! [After... Pending: false]',
'Loading... Pending: false',
]);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
expect(ReactNoop.getChildren()).toEqual([
hiddenSpan('Before... Pending: true'),
span('Loading... Pending: false'),
]);
Scheduler.unstable_advanceTime(500);
await advanceTimers(500);
expect(Scheduler).toHaveYielded([
'Promise resolved [After... Pending: false]',
]);
expect(Scheduler).toFlushAndYield(['After... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('After... Pending: false'),
]);
});
// @gate experimental
it('delays showing loading state until after busyDelayMs + busyMinDurationMs', async () => {
@@ -2820,51 +2821,54 @@ describe('ReactHooksWithNoopRenderer', () => {
span('Before... Pending: false'),
]);
act(() => {
await act(async () => {
Scheduler.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
transition,
);
expect(Scheduler).toFlushAndYield([
'Before... Pending: true',
'Suspend! [After... Pending: false]',
'Loading... Pending: false',
]);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
// Resolve the promise. The whole tree has now completed. However,
// because we exceeded the busy threshold, we won't commit the
// result yet.
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
expect(Scheduler).toHaveYielded([
'Promise resolved [After... Pending: false]',
]);
expect(Scheduler).toFlushAndYield(['After... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
// Advance time until just before the `busyMinDuration` threshold.
Scheduler.unstable_advanceTime(999);
await advanceTimers(999);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
// Advance time just a bit more. Now we complete the transition.
Scheduler.unstable_advanceTime(300);
await advanceTimers(300);
expect(ReactNoop.getChildren()).toEqual([
span('After... Pending: false'),
]);
});
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
expect(Scheduler).toHaveYielded([
'Before... Pending: true',
'Suspend! [After... Pending: false]',
'Loading... Pending: false',
]);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
// Resolve the promise. The whole tree has now completed. However,
// because we exceeded the busy threshold, we won't commit the
// result yet.
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
expect(Scheduler).toHaveYielded([
'Promise resolved [After... Pending: false]',
]);
expect(Scheduler).toFlushAndYield(['After... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
// Advance time until just before the `busyMinDuration` threshold.
Scheduler.unstable_advanceTime(999);
await advanceTimers(999);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
// Advance time just a bit more. Now we complete the transition.
Scheduler.unstable_advanceTime(300);
await advanceTimers(300);
expect(ReactNoop.getChildren()).toEqual([
span('After... Pending: false'),
]);
});
});
describe('useDeferredValue', () => {
// @gate experimental
it('defers text value until specified timeout', async () => {
@@ -2902,39 +2906,42 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(Scheduler).toFlushAndYield(['A']);
expect(ReactNoop.getChildren()).toEqual([span('A'), span('A')]);
act(() => {
await act(async () => {
_setText('B');
expect(Scheduler).toFlushAndYield([
'B',
'A',
'B',
'Suspend! [B]',
'Loading',
]);
expect(Scheduler).toFlushAndYield([]);
expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
});
expect(Scheduler).toHaveYielded([
'B',
'A',
'B',
'Suspend! [B]',
'Loading',
]);
expect(Scheduler).toFlushAndYield([]);
await act(async () => {
Scheduler.unstable_advanceTime(250);
await advanceTimers(250);
});
expect(Scheduler).toHaveYielded([]);
expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
Scheduler.unstable_advanceTime(250);
await advanceTimers(250);
expect(Scheduler).toFlushAndYield([]);
expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
Scheduler.unstable_advanceTime(500);
await advanceTimers(500);
await act(async () => {
Scheduler.unstable_advanceTime(500);
await advanceTimers(500);
});
expect(Scheduler).toHaveYielded([]);
expect(ReactNoop.getChildren()).toEqual([
span('B'),
hiddenSpan('A'),
span('Loading'),
]);
Scheduler.unstable_advanceTime(250);
await advanceTimers(250);
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
act(() => {
expect(Scheduler).toFlushAndYield(['B']);
await act(async () => {
Scheduler.unstable_advanceTime(250);
await advanceTimers(250);
});
expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
expect(ReactNoop.getChildren()).toEqual([span('B'), span('B')]);
});
});
@@ -2469,35 +2469,21 @@ describe('ReactSuspenseList', () => {
jest.runAllTimers();
expect(Scheduler).toHaveYielded(
__DEV__
? [
// First attempt at high pri.
'Suspend! [A]',
'Loading A',
// Re-render at forced.
'Suspend! [A]',
'Loading A',
// We auto-commit this on DEV.
// Try again on low-pri.
'Suspend! [A]',
'Loading A',
]
: [
// First attempt at high pri.
'Suspend! [A]',
'Loading A',
// Re-render at forced.
'Suspend! [A]',
'Loading A',
// We didn't commit so retry at low-pri.
'Suspend! [A]',
'Loading A',
// Re-render at forced.
'Suspend! [A]',
'Loading A',
],
);
expect(Scheduler).toHaveYielded([
// First attempt at high pri.
'Suspend! [A]',
'Loading A',
// Re-render at forced.
'Suspend! [A]',
'Loading A',
// We auto-commit this on DEV.
// Try again on low-pri.
'Suspend! [A]',
'Loading A',
// Re-render at forced.
'Suspend! [A]',
'Loading A',
]);
expect(ReactNoop).toMatchRenderedOutput(<span>Loading A</span>);
@@ -17,7 +17,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
ReactFeatureFlags.flushSuspenseFallbacksInTests = false;
React = require('react');
Fragment = React.Fragment;
ReactNoop = require('react-noop-renderer');
@@ -3069,25 +3068,26 @@ describe('ReactSuspenseWithNoopRenderer', () => {
setText('C');
},
);
expect(Scheduler).toFlushAndYield([
// First we attempt the high pri update. It suspends.
'Suspend! [B]',
'Loading...',
]);
// Commit the placeholder to unblock the Idle update.
await advanceTimers(250);
expect(root).toMatchRenderedOutput(
<>
<span hidden={true} prop="A" />
<span prop="Loading..." />
</>,
);
// Now flush the remaining work. The Idle update successfully finishes.
expect(Scheduler).toFlushAndYield(['C']);
expect(root).toMatchRenderedOutput(<span prop="C" />);
});
expect(Scheduler).toHaveYielded([
// First we attempt the high pri update. It suspends.
'Suspend! [B]',
'Loading...',
]);
// Commit the placeholder to unblock the Idle update.
await advanceTimers(250);
expect(root).toMatchRenderedOutput(
<>
<span hidden={true} prop="A" />
<span prop="Loading..." />
</>,
);
// Now flush the remaining work. The Idle update successfully finishes.
expect(Scheduler).toFlushAndYield(['C']);
expect(root).toMatchRenderedOutput(<span prop="C" />);
},
);
@@ -3126,48 +3126,47 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(Scheduler).toHaveYielded(['A']);
expect(root).toMatchRenderedOutput(<span prop="A" />);
// Schedule an update inside the Suspense boundary that suspends.
await ReactNoop.act(async () => {
// Schedule an update inside the Suspense boundary that suspends.
setAppText('B');
});
expect(Scheduler).toHaveYielded(['Suspend! [B]', 'Loading...']);
// Commit the placeholder
await advanceTimers(250);
expect(root).toMatchRenderedOutput(
<>
<span hidden={true} prop="A" />
<span prop="Loading..." />
</>,
);
expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
// Schedule a high pri update on the boundary, and a lower pri update
// on the fallback. We're testing to make sure the fallback can still
// update even though the primary tree is suspended.
await ReactNoop.act(async () => {
// Commit the placeholder
await advanceTimers(250);
expect(root).toMatchRenderedOutput(
<>
<span hidden={true} prop="A" />
<span prop="Loading..." />
</>,
);
// Schedule a high pri update on the boundary, and a lower pri update
// on the fallback. We're testing to make sure the fallback can still
// update even though the primary tree is suspended.{
ReactNoop.discreteUpdates(() => {
setAppText('C');
});
setFallbackText('Still loading...');
expect(Scheduler).toFlushAndYield([
// First try to render the high pri update. We won't try to re-render
// the suspended tree during this pass, because it still has unfinished
// updates at a lower priority.
'Loading...',
// Now try the suspended update again. It's still suspended.
'Suspend! [C]',
// Then complete the update to the fallback.
'Still loading...',
]);
expect(root).toMatchRenderedOutput(
<>
<span hidden={true} prop="A" />
<span prop="Still loading..." />
</>,
);
});
expect(Scheduler).toHaveYielded([
// First try to render the high pri update. We won't try to re-render
// the suspended tree during this pass, because it still has unfinished
// updates at a lower priority.
'Loading...',
// Now try the suspended update again. It's still suspended.
'Suspend! [C]',
// Then complete the update to the fallback.
'Still loading...',
]);
expect(root).toMatchRenderedOutput(
<>
<span hidden={true} prop="A" />
<span prop="Still loading..." />
</>,
);
},
);
@@ -3693,54 +3692,53 @@ describe('ReactSuspenseWithNoopRenderer', () => {
setTextA('A2');
setTextB('B2');
});
expect(Scheduler).toFlushAndYield([
'B',
'Suspend! [A1]',
'Loading...',
'Suspend! [A2]',
'Loading...',
'Suspend! [B2]',
'Loading...',
]);
expect(root).toMatchRenderedOutput(
<>
<span prop="A" />
<span prop="B" />
</>,
);
await resolveText('A1');
expect(Scheduler).toHaveYielded(['Promise resolved [A1]']);
expect(Scheduler).toFlushAndYield([
'A1',
'Suspend! [A2]',
'Loading...',
'Suspend! [B2]',
'Loading...',
]);
expect(root).toMatchRenderedOutput(
<>
<span prop="A1" />
<span prop="B" />
</>,
);
// Commit the placeholder
Scheduler.unstable_advanceTime(20000);
await advanceTimers(20000);
expect(root).toMatchRenderedOutput(
<>
<span hidden={true} prop="A1" />
<span prop="Loading..." />
<span hidden={true} prop="B" />
<span prop="Loading..." />
</>,
);
});
expect(Scheduler).toHaveYielded([
'B',
'Suspend! [A1]',
'Loading...',
'Suspend! [A2]',
'Loading...',
'Suspend! [B2]',
'Loading...',
]);
expect(root).toMatchRenderedOutput(
<>
<span prop="A" />
<span prop="B" />
</>,
);
await ReactNoop.act(async () => {
resolveText('A1');
});
expect(Scheduler).toHaveYielded([
'Promise resolved [A1]',
'A1',
'Suspend! [A2]',
'Loading...',
'Suspend! [B2]',
'Loading...',
]);
expect(root).toMatchRenderedOutput(
<>
<span prop="A1" />
<span prop="B" />
</>,
);
// Commit the placeholder
Scheduler.unstable_advanceTime(20000);
await advanceTimers(20000);
expect(root).toMatchRenderedOutput(
<>
<span hidden={true} prop="A1" />
<span prop="Loading..." />
<span hidden={true} prop="B" />
<span prop="Loading..." />
</>,
);
});
// Regression: https://github.com/facebook/react/issues/18486
@@ -3842,27 +3840,29 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Resolve "a". But "b" is still pending.
await ReactNoop.act(async () => {
await resolveText('a');
});
expect(Scheduler).toHaveYielded([
'Promise resolved [a]',
'Pending...',
'a',
'Suspend! [b]',
'Loading...',
]);
expect(root).toMatchRenderedOutput(
<>
<span prop="Pending..." />
<span prop="a" />
</>,
);
// Resolve "b". This should remove the pending state.
await ReactNoop.act(async () => {
await resolveText('b');
expect(Scheduler).toHaveYielded(['Promise resolved [a]']);
expect(Scheduler).toFlushAndYield([
'Pending...',
'a',
'Suspend! [b]',
'Loading...',
]);
expect(root).toMatchRenderedOutput(
<>
<span prop="Pending..." />
<span prop="a" />
</>,
);
// Resolve "b". This should remove the pending state.
await ReactNoop.act(async () => {
await resolveText('b');
});
expect(Scheduler).toHaveYielded(['Promise resolved [b]']);
expect(Scheduler).toFlushAndYield(['b']);
// The bug was that the pending state got stuck forever.
expect(root).toMatchRenderedOutput(<span prop="b" />);
});
expect(Scheduler).toHaveYielded(['Promise resolved [b]', 'b']);
// The bug was that the pending state got stuck forever.
expect(root).toMatchRenderedOutput(<span prop="b" />);
});
});
@@ -10,7 +10,6 @@
'use strict';
let ReactFeatureFlags;
let React;
let ReactNoop;
let Scheduler;
@@ -22,11 +21,6 @@ let act;
describe('ReactTransition', () => {
beforeEach(() => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableSchedulerTracing = true;
ReactFeatureFlags.flushSuspenseFallbacksInTests = false;
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
@@ -26,7 +26,6 @@ function loadModules() {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableSchedulerTracing = true;
ReactFeatureFlags.flushSuspenseFallbacksInTests = false;
ReactFeatureFlags.enableProfilerTimer = true;
React = require('react');
ReactNoop = require('react-noop-renderer');
-4
View File
@@ -62,10 +62,6 @@ export const enableUseEventAPI = false;
// Till then, we warn about the missing mock, but still fallback to a legacy mode compatible version
export const warnAboutUnmockedScheduler = false;
// For tests, we flush suspense fallbacks in an act scope;
// *except* in some of our own tests, where we test incremental loading states.
export const flushSuspenseFallbacksInTests = true;
// Add a callback property to suspense to notify which promises are currently
// in the update queue. This allows reporting and tracing of what is causing
// the user to see a loading state.
@@ -28,7 +28,6 @@ export const enableFundamentalAPI = false;
export const enableScopeAPI = false;
export const enableUseEventAPI = false;
export const warnAboutUnmockedScheduler = true;
export const flushSuspenseFallbacksInTests = true;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
@@ -27,7 +27,6 @@ export const enableFundamentalAPI = false;
export const enableScopeAPI = false;
export const enableUseEventAPI = false;
export const warnAboutUnmockedScheduler = false;
export const flushSuspenseFallbacksInTests = true;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
@@ -27,7 +27,6 @@ export const enableFundamentalAPI = false;
export const enableScopeAPI = false;
export const enableUseEventAPI = false;
export const warnAboutUnmockedScheduler = false;
export const flushSuspenseFallbacksInTests = true;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
@@ -27,7 +27,6 @@ export const enableFundamentalAPI = false;
export const enableScopeAPI = true;
export const enableUseEventAPI = true;
export const warnAboutUnmockedScheduler = true;
export const flushSuspenseFallbacksInTests = true;
export const enableSuspenseCallback = true;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
@@ -27,7 +27,6 @@ export const enableFundamentalAPI = false;
export const enableScopeAPI = false;
export const enableUseEventAPI = false;
export const warnAboutUnmockedScheduler = false;
export const flushSuspenseFallbacksInTests = true;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
@@ -27,7 +27,6 @@ export const enableFundamentalAPI = false;
export const enableScopeAPI = true;
export const enableUseEventAPI = true;
export const warnAboutUnmockedScheduler = true;
export const flushSuspenseFallbacksInTests = true;
export const enableSuspenseCallback = true;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
@@ -64,8 +64,6 @@ export const warnAboutUnmockedScheduler = true;
export const enableSuspenseCallback = true;
export const flushSuspenseFallbacksInTests = true;
export const disableTextareaChildren = __EXPERIMENTAL__;
export const warnUnstableRenderSubtreeIntoContainer = false;