forked from facebook/react
-
Notifications
You must be signed in to change notification settings - Fork 1
/
internalAct.js
152 lines (137 loc) · 4.67 KB
/
internalAct.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
* 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
*/
// 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 'shared/enqueueTask';
let actingUpdatesScopeDepth = 0;
export function act<T>(scope: () => Thenable<T> | T): Thenable<T> {
if (Scheduler.unstable_flushUntilNextPaint === undefined) {
throw Error(
'This version of `act` requires a special mock build of Scheduler.',
);
}
// $FlowFixMe: _isMockFunction doesn't exist on function
if (setTimeout._isMockFunction !== true) {
throw Error(
"This version of `act` requires Jest's timer mocks " +
'(i.e. jest.useFakeTimers({legacyFakeTimers: true})).',
);
}
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) {
if (Scheduler.unstable_hasPendingWork()) {
try {
Scheduler.unstable_flushUntilNextPaint();
} catch (error) {
reject(error);
}
// If Scheduler yields while there's still work, it's so that we can
// unblock the main thread (e.g. for paint or for microtasks). Yield to
// the main thread and continue in a new task.
enqueueTask(() => flushActWork(resolve, reject));
return;
}
// Once the scheduler queue is empty, run all the timers. The purpose of this
// is to force any pending fallbacks to commit. The public version of act does
// this with dev-only React runtime logic, but since our internal act needs to
// work production builds of React, we have to cheat.
// $FlowFixMe: Flow doesn't know about global Jest object
jest.runOnlyPendingTimers();
if (Scheduler.unstable_hasPendingWork()) {
// Committing a fallback scheduled additional work. Continue flushing.
flushActWork(resolve, reject);
return;
}
resolve();
}