Fixed scheduler setTimeout fallback (#14358)
* Fixed scheduler setTimeout fallback * Moved unit-test-specific setTimeout code into a new NPM package, jest-mock-scheduler.
Brian Vaughn committed
Dec 1, 2018 at 13:03 UTC
52bea95cfce4a75bc7c1a09455abaeef50fa0700
11 files changed
+156
-20
packages/jest-mock-scheduler/README.md
new
+5
@@ -0,0 +1,5 @@
1
+# `jest-mock-scheduler`
2
+
3
+Jest matchers and utilities for testing the `scheduler` package.
4
+
5
+This package is experimental. APIs may change between releases.
\ No newline at end of file
packages/jest-mock-scheduler/index.js
new
+8
@@ -0,0 +1,8 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+export * from './src/JestMockScheduler';
packages/jest-mock-scheduler/npm/index.js
new
+7
@@ -0,0 +1,7 @@
1
+'use strict';
2
+
3
+if (process.env.NODE_ENV === 'production') {
4
+ module.exports = require('./cjs/jest-mock-scheduler.production.min.js');
5
+} else {
6
+ module.exports = require('./cjs/jest-mock-scheduler.development.js');
7
+}
packages/jest-mock-scheduler/package.json
new
+28
@@ -0,0 +1,28 @@
1
+{
2
+ "name": "jest-mock-scheduler",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "description": "Jest matchers and utilities for testing the scheduler package.",
6
+ "main": "index.js",
7
+ "repository": "facebook/react",
8
+ "keywords": [
9
+ "jest",
10
+ "scheduler"
11
+ ],
12
+ "license": "MIT",
13
+ "bugs": {
14
+ "url": "https://github.com/facebook/react/issues"
15
+ },
16
+ "homepage": "https://reactjs.org/",
17
+ "peerDependencies": {
18
+ "jest": "^23.0.1",
19
+ "scheduler": "^0.11.0"
20
+ },
21
+ "files": [
22
+ "LICENSE",
23
+ "README.md",
24
+ "build-info.json",
25
+ "index.js",
26
+ "cjs/"
27
+ ]
28
+}
packages/jest-mock-scheduler/src/JestMockScheduler.js
new
+61
@@ -0,0 +1,61 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
9
+// Math.pow(2, 30) - 1
10
+// 0b111111111111111111111111111111
11
+const maxSigned31BitInt = 1073741823;
12
+
13
+export function mockRestore() {
14
+ delete global._schedMock;
15
+}
16
+
17
+let callback = null;
18
+let currentTime = -1;
19
+
20
+function flushCallback(didTimeout, ms) {
21
+ if (callback !== null) {
22
+ let cb = callback;
23
+ callback = null;
24
+ try {
25
+ currentTime = ms;
26
+ cb(didTimeout);
27
+ } finally {
28
+ currentTime = -1;
29
+ }
30
+ }
31
+}
32
+
33
+function requestHostCallback(cb, ms) {
34
+ if (currentTime !== -1) {
35
+ // Protect against re-entrancy.
36
+ setTimeout(requestHostCallback, 0, cb, ms);
37
+ } else {
38
+ callback = cb;
39
+ setTimeout(flushCallback, ms, true, ms);
40
+ setTimeout(flushCallback, maxSigned31BitInt, false, maxSigned31BitInt);
41
+ }
42
+}
43
+
44
+function cancelHostCallback() {
45
+ callback = null;
46
+}
47
+
48
+function shouldYieldToHost() {
49
+ return false;
50
+}
51
+
52
+function getCurrentTime() {
53
+ return currentTime === -1 ? 0 : currentTime;
54
+}
55
+
56
+global._schedMock = [
57
+ requestHostCallback,
58
+ cancelHostCallback,
59
+ shouldYieldToHost,
60
+ getCurrentTime,
61
+];
packages/scheduler/src/Scheduler.js
+20
-20
@@ -447,12 +447,20 @@ var requestHostCallback;
447
var cancelHostCallback;
448
var shouldYieldToHost;
449
450
-if (typeof window !== 'undefined' && window._schedMock) {
450
+var globalValue = null;
451
+if (typeof window !== 'undefined') {
452
+ globalValue = window;
453
+} else if (typeof global !== 'undefined') {
454
+ globalValue = global;
455
+}
456
+
457
+if (globalValue && globalValue._schedMock) {
458
// Dynamic injection, only for testing purposes.
452
- var impl = window._schedMock;
453
- requestHostCallback = impl[0];
454
- cancelHostCallback = impl[1];
455
- shouldYieldToHost = impl[2];
459
+ var globalImpl = globalValue._schedMock;
460
+ requestHostCallback = globalImpl[0];
461
+ cancelHostCallback = globalImpl[1];
462
+ shouldYieldToHost = globalImpl[2];
463
+ getCurrentTime = globalImpl[3];
464
} else if (
465
// If Scheduler runs in a non-DOM environment, it falls back to a naive
466
// implementation using setTimeout.
@@ -460,28 +468,23 @@ if (typeof window !== 'undefined' && window._schedMock) {
468
// Check if MessageChannel is supported, too.
469
typeof MessageChannel !== 'function'
470
) {
471
+ // If this accidentally gets imported in a non-browser environment, e.g. JavaScriptCore,
472
+ // fallback to a naive implementation.
473
var _callback = null;
464
- var _currentTime = -1;
465
- var _flushCallback = function(didTimeout, ms) {
474
+ var _flushCallback = function(didTimeout) {
475
if (_callback !== null) {
476
var cb = _callback;
477
_callback = null;
469
- try {
470
- _currentTime = ms;
471
- cb(didTimeout);
472
- } finally {
473
- _currentTime = -1;
474
- }
478
+ cb(didTimeout);
479
}
480
};
481
requestHostCallback = function(cb, ms) {
478
- if (_currentTime !== -1) {
482
+ if (_callback !== null) {
483
// Protect against re-entrancy.
480
- setTimeout(requestHostCallback, 0, cb, ms);
484
+ setTimeout(requestHostCallback, 0, cb);
485
} else {
486
_callback = cb;
483
- setTimeout(_flushCallback, ms, true, ms);
484
- setTimeout(_flushCallback, maxSigned31BitInt, false, maxSigned31BitInt);
487
+ setTimeout(_flushCallback, 0, false);
488
}
489
};
490
cancelHostCallback = function() {
@@ -490,9 +493,6 @@ if (typeof window !== 'undefined' && window._schedMock) {
493
shouldYieldToHost = function() {
494
return false;
495
};
493
- getCurrentTime = function() {
494
- return _currentTime === -1 ? 0 : _currentTime;
495
- };
496
} else {
497
if (typeof console !== 'undefined') {
498
// TODO: Remove fb.me link
packages/scheduler/src/__tests__/Scheduler-test.js
+8
@@ -30,6 +30,9 @@ describe('Scheduler', () => {
30
jest.useFakeTimers();
31
jest.resetModules();
32
33
+ const JestMockScheduler = require('jest-mock-scheduler');
34
+ JestMockScheduler.mockRestore();
35
+
36
let _flushWork = null;
37
let isFlushing = false;
38
let timeoutID = -1;
@@ -123,16 +126,21 @@ describe('Scheduler', () => {
126
function shouldYieldToHost() {
127
return endOfFrame <= currentTime;
128
}
129
+ function getCurrentTime() {
130
+ return currentTime;
131
+ }
132
133
// Override host implementation
134
delete global.performance;
135
global.Date.now = () => {
136
return currentTime;
137
};
138
+
139
window._schedMock = [
140
requestHostCallback,
141
cancelHostCallback,
142
shouldYieldToHost,
143
+ getCurrentTime,
144
];
145
146
const Schedule = require('scheduler');
packages/scheduler/src/__tests__/SchedulerDOM-test.js
+4
@@ -88,6 +88,10 @@ describe('SchedulerDOM', () => {
88
return currentTime;
89
};
90
jest.resetModules();
91
+
92
+ const JestMockScheduler = require('jest-mock-scheduler');
93
+ JestMockScheduler.mockRestore();
94
+
95
Scheduler = require('scheduler');
96
});
97
packages/shared/__tests__/ReactDOMFrameScheduling-test.js
+4
@@ -22,6 +22,10 @@ describe('ReactDOMFrameScheduling', () => {
22
};
23
};
24
jest.resetModules();
25
+
26
+ const JestMockScheduler = require('jest-mock-scheduler');
27
+ JestMockScheduler.mockRestore();
28
+
29
spyOnDevAndProd(console, 'error');
30
require('react-dom');
31
expect(console.error.calls.count()).toEqual(1);
scripts/jest/setupTests.js
+2
@@ -56,6 +56,8 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
56
toMatchRenderedOutput: JestReact.unstable_toMatchRenderedOutput,
57
});
58
59
+ require('jest-mock-scheduler');
60
+
61
// We have a Babel transform that inserts guards against infinite loops.
62
// If a loop runs for too many iterations, we throw an error and set this
63
// global variable. The global lets us detect an infinite loop even if
scripts/rollup/bundles.js
+9
@@ -462,6 +462,15 @@ const bundles = [
462
externals: ['jest-diff'],
463
},
464
465
+ /******* Jest Scheduler (experimental) *******/
466
+ {
467
+ bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD],
468
+ moduleType: ISOMORPHIC,
469
+ entry: 'jest-mock-scheduler',
470
+ global: 'JestMockScheduler',
471
+ externals: ['jest-diff'],
472
+ },
473
+
474
/******* ESLint Plugin for Hooks (proposal) *******/
475
{
476
// TODO: it's awkward to create a bundle for this