UNPKG

react-relay

Version:

A framework for building GraphQL-driven React applications.

155 lines (140 loc) 4.53 kB
/** * 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. * * @format * @oncall relay */ /* global jest */ // This file is sync'd from https://github.com/facebook/react/tree/main/packages/jest-react // 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. 'use strict'; const enqueueTask = require('./enqueueTask'); const Scheduler = require('scheduler/unstable_mock'); // The subset of a Promise that React APIs rely on. This resolves a value. // This doesn't require a return value neither from the handler nor the // then function. interface Thenable<+R> { then<U>( onFulfill: (value: R) => void | Thenable<U> | U, onReject: (error: mixed) => void | Thenable<U> | U, ): void | Thenable<U>; } let actingUpdatesScopeDepth = 0; function act<T>(scope: () => Thenable<T> | T): Thenable<T> { if (Scheduler.unstable_flushAllWithoutAsserting === undefined) { throw Error( 'This version of `act` requires a special mock build of Scheduler.', ); } if (setTimeout._isMockFunction !== true) { 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 (__DEV__ && 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; } const unwind = () => { if (__DEV__ && actingUpdatesScopeDepth === 1) { global.IS_REACT_ACT_ENVIRONMENT = previousIsActEnvironment; } actingUpdatesScopeDepth--; 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. ', ); } } }; // TODO: This would be way simpler if 1) we required a promise to be // returned and 2) we could use async/await. Since it's only our used in // our test suite, we should be able to. try { const result = scope(); if ( typeof result === 'object' && result !== null && typeof result.then === 'function' ) { const thenableResult: Thenable<T> = (result: any); return { then(resolve, reject) { thenableResult.then( returnValue => { flushActWork( () => { unwind(); resolve(returnValue); }, error => { unwind(); reject(error); }, ); }, error => { unwind(); reject(error); }, ); }, }; } else { const returnValue: T = (result: any); try { // TODO: Let's not support non-async scopes at all in our tests. Need to // migrate existing tests. let didFlushWork; do { didFlushWork = Scheduler.unstable_flushAllWithoutAsserting(); } while (didFlushWork); return { then(resolve, reject) { resolve(returnValue); }, }; } finally { unwind(); } } } catch (error) { unwind(); throw error; } } function flushActWork(resolve, reject) { // Flush suspended fallbacks // $FlowFixMe: Flow doesn't know about global Jest object jest.runOnlyPendingTimers(); enqueueTask(() => { try { const didFlushWork = Scheduler.unstable_flushAllWithoutAsserting(); if (didFlushWork) { flushActWork(resolve, reject); } else { resolve(); } } catch (error) { reject(error); } }); } exports.act = act;