mirror of
https://github.com/facebook/react.git
synced 2026-02-24 20:53:03 +00:00
## Summary This PR bumps Flow all the way to the latest 0.245.2. Most of the suppressions comes from Flow v0.239.0's change to include undefined in the return of `Array.pop`. I also enabled `react.custom_jsx_typing=true` and added custom jsx typing to match the old behavior that `React.createElement` is effectively any typed. This is necessary since various builtin components like `React.Fragment` is actually symbol in the React repo instead of `React.AbstractComponent<...>`. It can be made more accurate by customizing the `React$CustomJSXFactory` type, but I will leave it to the React team to decide. ## How did you test this change? `yarn flow` for all the renderers
284 lines
10 KiB
JavaScript
284 lines
10 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and 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
|
|
*/
|
|
|
|
// This version of `act` is only used by our tests. Unlike the public version
|
|
// of `act`, it's designed to work identically in both production and
|
|
// development. It may have slightly different behavior from the public
|
|
// version, too, since our constraints in our test suite are not the same as
|
|
// those of developers using React — we're testing React itself, as opposed to
|
|
// building an app with React.
|
|
|
|
import type {Thenable} from 'shared/ReactTypes';
|
|
|
|
import * as Scheduler from 'scheduler/unstable_mock';
|
|
|
|
import enqueueTask from './enqueueTask';
|
|
import {assertConsoleLogsCleared} from './consoleMock';
|
|
import {diff} from 'jest-diff';
|
|
|
|
export let actingUpdatesScopeDepth: number = 0;
|
|
|
|
export const thrownErrors: Array<mixed> = [];
|
|
|
|
async function waitForMicrotasks() {
|
|
return new Promise(resolve => {
|
|
enqueueTask(() => resolve());
|
|
});
|
|
}
|
|
|
|
function aggregateErrors(errors: Array<mixed>): mixed {
|
|
if (errors.length > 1 && typeof AggregateError === 'function') {
|
|
// eslint-disable-next-line no-undef
|
|
return new AggregateError(errors);
|
|
}
|
|
return errors[0];
|
|
}
|
|
|
|
export async function act<T>(scope: () => Thenable<T>): Thenable<T> {
|
|
if (Scheduler.unstable_flushUntilNextPaint === undefined) {
|
|
throw Error(
|
|
'This version of `act` requires a special mock build of Scheduler.',
|
|
);
|
|
}
|
|
|
|
const actualYields = Scheduler.unstable_clearLog();
|
|
if (actualYields.length !== 0) {
|
|
const error = Error(
|
|
'Log of yielded values is not empty. Call assertLog first.\n\n' +
|
|
`Received:\n${diff('', actualYields.join('\n'), {
|
|
omitAnnotationLines: true,
|
|
})}`,
|
|
);
|
|
Error.captureStackTrace(error, act);
|
|
throw error;
|
|
}
|
|
|
|
// We require every `act` call to assert console logs
|
|
// with one of the assertion helpers. Fails if not empty.
|
|
assertConsoleLogsCleared();
|
|
|
|
// $FlowFixMe[cannot-resolve-name]: Flow doesn't know about global Jest object
|
|
if (!jest.isMockFunction(setTimeout)) {
|
|
throw Error(
|
|
"This version of `act` requires Jest's timer mocks " +
|
|
'(i.e. jest.useFakeTimers).',
|
|
);
|
|
}
|
|
|
|
const previousIsActEnvironment = global.IS_REACT_ACT_ENVIRONMENT;
|
|
const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
|
|
actingUpdatesScopeDepth++;
|
|
if (actingUpdatesScopeDepth === 1) {
|
|
// Because this is not the "real" `act`, we set this to `false` so React
|
|
// knows not to fire `act` warnings.
|
|
global.IS_REACT_ACT_ENVIRONMENT = false;
|
|
}
|
|
|
|
// Create the error object before doing any async work, to get a better
|
|
// stack trace.
|
|
const error = new Error();
|
|
Error.captureStackTrace(error, act);
|
|
|
|
// Call the provided scope function after an async gap. This is an extra
|
|
// precaution to ensure that our tests do not accidentally rely on the act
|
|
// scope adding work to the queue synchronously. We don't do this in the
|
|
// public version of `act`, though we maybe should in the future.
|
|
await waitForMicrotasks();
|
|
|
|
const errorHandlerDOM = function (event: ErrorEvent) {
|
|
// Prevent logs from reprinting this error.
|
|
event.preventDefault();
|
|
thrownErrors.push(event.error);
|
|
};
|
|
const errorHandlerNode = function (err: mixed) {
|
|
thrownErrors.push(err);
|
|
};
|
|
// We track errors that were logged globally as if they occurred in this scope and then rethrow them.
|
|
if (actingUpdatesScopeDepth === 1) {
|
|
if (
|
|
typeof window === 'object' &&
|
|
typeof window.addEventListener === 'function'
|
|
) {
|
|
// We're in a JS DOM environment.
|
|
window.addEventListener('error', errorHandlerDOM);
|
|
} else if (typeof process === 'object') {
|
|
// Node environment
|
|
process.on('uncaughtException', errorHandlerNode);
|
|
}
|
|
}
|
|
|
|
try {
|
|
const result = await scope();
|
|
|
|
do {
|
|
// Wait until end of current task/microtask.
|
|
await waitForMicrotasks();
|
|
|
|
// $FlowFixMe[cannot-resolve-name]: Flow doesn't know about global Jest object
|
|
if (jest.isEnvironmentTornDown()) {
|
|
error.message =
|
|
'The Jest environment was torn down before `act` completed. This ' +
|
|
'probably means you forgot to `await` an `act` call.';
|
|
throw error;
|
|
}
|
|
|
|
if (!Scheduler.unstable_hasPendingWork()) {
|
|
// $FlowFixMe[cannot-resolve-name]: Flow doesn't know about global Jest object
|
|
const j = jest;
|
|
if (j.getTimerCount() > 0) {
|
|
// There's a pending timer. Flush it now. We only do this in order to
|
|
// force Suspense fallbacks to display; the fact that it's a timer
|
|
// is an implementation detail. If there are other timers scheduled,
|
|
// those will also fire now, too, which is not ideal. (The public
|
|
// version of `act` doesn't do this.) For this reason, we should try
|
|
// to avoid using timers in our internal tests.
|
|
j.runOnlyPendingTimers();
|
|
// If a committing a fallback triggers another update, it might not
|
|
// get scheduled until a microtask. So wait one more time.
|
|
await waitForMicrotasks();
|
|
}
|
|
if (Scheduler.unstable_hasPendingWork()) {
|
|
// Committing a fallback scheduled additional work. Continue flushing.
|
|
} else {
|
|
// There's no pending work, even after both the microtask queue
|
|
// and the timer queue are empty. Stop flushing.
|
|
break;
|
|
}
|
|
}
|
|
// flushUntilNextPaint stops when React yields execution. Allow microtasks
|
|
// queue to flush before continuing.
|
|
Scheduler.unstable_flushUntilNextPaint();
|
|
} while (true);
|
|
|
|
if (thrownErrors.length > 0) {
|
|
// Rethrow any errors logged by the global error handling.
|
|
const thrownError = aggregateErrors(thrownErrors);
|
|
thrownErrors.length = 0;
|
|
throw thrownError;
|
|
}
|
|
|
|
// $FlowFixMe[incompatible-return]
|
|
return result;
|
|
} finally {
|
|
const depth = actingUpdatesScopeDepth;
|
|
if (depth === 1) {
|
|
if (
|
|
typeof window === 'object' &&
|
|
typeof window.addEventListener === 'function'
|
|
) {
|
|
// We're in a JS DOM environment.
|
|
window.removeEventListener('error', errorHandlerDOM);
|
|
} else if (typeof process === 'object') {
|
|
// Node environment
|
|
process.off('uncaughtException', errorHandlerNode);
|
|
}
|
|
global.IS_REACT_ACT_ENVIRONMENT = previousIsActEnvironment;
|
|
}
|
|
actingUpdatesScopeDepth = depth - 1;
|
|
|
|
if (actingUpdatesScopeDepth !== previousActingUpdatesScopeDepth) {
|
|
// if it's _less than_ previousActingUpdatesScopeDepth, then we can
|
|
// assume the 'other' one has warned
|
|
Scheduler.unstable_clearLog();
|
|
error.message =
|
|
'You seem to have overlapping act() calls, this is not supported. ' +
|
|
'Be sure to await previous act() calls before making a new one. ';
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function serverAct<T>(scope: () => Thenable<T>): Thenable<T> {
|
|
// We require every `act` call to assert console logs
|
|
// with one of the assertion helpers. Fails if not empty.
|
|
assertConsoleLogsCleared();
|
|
|
|
// $FlowFixMe[cannot-resolve-name]: Flow doesn't know about global Jest object
|
|
if (!jest.isMockFunction(setTimeout)) {
|
|
throw Error(
|
|
"This version of `act` requires Jest's timer mocks " +
|
|
'(i.e. jest.useFakeTimers).',
|
|
);
|
|
}
|
|
|
|
// Create the error object before doing any async work, to get a better
|
|
// stack trace.
|
|
const error = new Error();
|
|
Error.captureStackTrace(error, act);
|
|
|
|
// Call the provided scope function after an async gap. This is an extra
|
|
// precaution to ensure that our tests do not accidentally rely on the act
|
|
// scope adding work to the queue synchronously. We don't do this in the
|
|
// public version of `act`, though we maybe should in the future.
|
|
await waitForMicrotasks();
|
|
|
|
const errorHandlerNode = function (err: mixed) {
|
|
thrownErrors.push(err);
|
|
};
|
|
// We track errors that were logged globally as if they occurred in this scope and then rethrow them.
|
|
if (typeof process === 'object') {
|
|
// Node environment
|
|
process.on('uncaughtException', errorHandlerNode);
|
|
} else if (
|
|
typeof window === 'object' &&
|
|
typeof window.addEventListener === 'function'
|
|
) {
|
|
throw new Error('serverAct is not supported in JSDOM environments');
|
|
}
|
|
|
|
try {
|
|
const result = await scope();
|
|
|
|
do {
|
|
// Wait until end of current task/microtask.
|
|
await waitForMicrotasks();
|
|
|
|
// $FlowFixMe[cannot-resolve-name]: Flow doesn't know about global Jest object
|
|
if (jest.isEnvironmentTornDown()) {
|
|
error.message =
|
|
'The Jest environment was torn down before `act` completed. This ' +
|
|
'probably means you forgot to `await` an `act` call.';
|
|
throw error;
|
|
}
|
|
|
|
// $FlowFixMe[cannot-resolve-name]: Flow doesn't know about global Jest object
|
|
const j = jest;
|
|
if (j.getTimerCount() > 0) {
|
|
// There's a pending timer. Flush it now. We only do this in order to
|
|
// force Suspense fallbacks to display; the fact that it's a timer
|
|
// is an implementation detail. If there are other timers scheduled,
|
|
// those will also fire now, too, which is not ideal. (The public
|
|
// version of `act` doesn't do this.) For this reason, we should try
|
|
// to avoid using timers in our internal tests.
|
|
j.runOnlyPendingTimers();
|
|
// If a committing a fallback triggers another update, it might not
|
|
// get scheduled until a microtask. So wait one more time.
|
|
await waitForMicrotasks();
|
|
} else {
|
|
break;
|
|
}
|
|
} while (true);
|
|
|
|
if (thrownErrors.length > 0) {
|
|
// Rethrow any errors logged by the global error handling.
|
|
const thrownError = aggregateErrors(thrownErrors);
|
|
thrownErrors.length = 0;
|
|
throw thrownError;
|
|
}
|
|
|
|
// $FlowFixMe[incompatible-return]
|
|
return result;
|
|
} finally {
|
|
if (typeof process === 'object') {
|
|
// Node environment
|
|
process.off('uncaughtException', errorHandlerNode);
|
|
}
|
|
}
|
|
}
|