Skip to content

Commit 52bea95

Browse files
authored
Fixed scheduler setTimeout fallback (#14358)
* Fixed scheduler setTimeout fallback * Moved unit-test-specific setTimeout code into a new NPM package, jest-mock-scheduler.
1 parent 1d25aa5 commit 52bea95

File tree

11 files changed

+156
-20
lines changed

11 files changed

+156
-20
lines changed
+5
Original file line numberDiff line numberDiff line change
@@ -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.

packages/jest-mock-scheduler/index.js

+8
Original file line numberDiff line numberDiff line change
@@ -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';
Original file line numberDiff line numberDiff line change
@@ -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+
}
+28
Original file line numberDiff line numberDiff line change
@@ -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+
}
Original file line numberDiff line numberDiff line change
@@ -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
Original file line numberDiff line numberDiff line change
@@ -447,41 +447,44 @@ var requestHostCallback;
447447
var cancelHostCallback;
448448
var shouldYieldToHost;
449449

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) {
451458
// 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];
456464
} else if (
457465
// If Scheduler runs in a non-DOM environment, it falls back to a naive
458466
// implementation using setTimeout.
459467
typeof window === 'undefined' ||
460468
// Check if MessageChannel is supported, too.
461469
typeof MessageChannel !== 'function'
462470
) {
471+
// If this accidentally gets imported in a non-browser environment, e.g. JavaScriptCore,
472+
// fallback to a naive implementation.
463473
var _callback = null;
464-
var _currentTime = -1;
465-
var _flushCallback = function(didTimeout, ms) {
474+
var _flushCallback = function(didTimeout) {
466475
if (_callback !== null) {
467476
var cb = _callback;
468477
_callback = null;
469-
try {
470-
_currentTime = ms;
471-
cb(didTimeout);
472-
} finally {
473-
_currentTime = -1;
474-
}
478+
cb(didTimeout);
475479
}
476480
};
477481
requestHostCallback = function(cb, ms) {
478-
if (_currentTime !== -1) {
482+
if (_callback !== null) {
479483
// Protect against re-entrancy.
480-
setTimeout(requestHostCallback, 0, cb, ms);
484+
setTimeout(requestHostCallback, 0, cb);
481485
} else {
482486
_callback = cb;
483-
setTimeout(_flushCallback, ms, true, ms);
484-
setTimeout(_flushCallback, maxSigned31BitInt, false, maxSigned31BitInt);
487+
setTimeout(_flushCallback, 0, false);
485488
}
486489
};
487490
cancelHostCallback = function() {
@@ -490,9 +493,6 @@ if (typeof window !== 'undefined' && window._schedMock) {
490493
shouldYieldToHost = function() {
491494
return false;
492495
};
493-
getCurrentTime = function() {
494-
return _currentTime === -1 ? 0 : _currentTime;
495-
};
496496
} else {
497497
if (typeof console !== 'undefined') {
498498
// TODO: Remove fb.me link

packages/scheduler/src/__tests__/Scheduler-test.js

+8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ describe('Scheduler', () => {
3030
jest.useFakeTimers();
3131
jest.resetModules();
3232

33+
const JestMockScheduler = require('jest-mock-scheduler');
34+
JestMockScheduler.mockRestore();
35+
3336
let _flushWork = null;
3437
let isFlushing = false;
3538
let timeoutID = -1;
@@ -123,16 +126,21 @@ describe('Scheduler', () => {
123126
function shouldYieldToHost() {
124127
return endOfFrame <= currentTime;
125128
}
129+
function getCurrentTime() {
130+
return currentTime;
131+
}
126132

127133
// Override host implementation
128134
delete global.performance;
129135
global.Date.now = () => {
130136
return currentTime;
131137
};
138+
132139
window._schedMock = [
133140
requestHostCallback,
134141
cancelHostCallback,
135142
shouldYieldToHost,
143+
getCurrentTime,
136144
];
137145

138146
const Schedule = require('scheduler');

packages/scheduler/src/__tests__/SchedulerDOM-test.js

+4
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ describe('SchedulerDOM', () => {
8888
return currentTime;
8989
};
9090
jest.resetModules();
91+
92+
const JestMockScheduler = require('jest-mock-scheduler');
93+
JestMockScheduler.mockRestore();
94+
9195
Scheduler = require('scheduler');
9296
});
9397

packages/shared/__tests__/ReactDOMFrameScheduling-test.js

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ describe('ReactDOMFrameScheduling', () => {
2222
};
2323
};
2424
jest.resetModules();
25+
26+
const JestMockScheduler = require('jest-mock-scheduler');
27+
JestMockScheduler.mockRestore();
28+
2529
spyOnDevAndProd(console, 'error');
2630
require('react-dom');
2731
expect(console.error.calls.count()).toEqual(1);

scripts/jest/setupTests.js

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
5656
toMatchRenderedOutput: JestReact.unstable_toMatchRenderedOutput,
5757
});
5858

59+
require('jest-mock-scheduler');
60+
5961
// We have a Babel transform that inserts guards against infinite loops.
6062
// If a loop runs for too many iterations, we throw an error and set this
6163
// global variable. The global lets us detect an infinite loop even if

scripts/rollup/bundles.js

+9
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,15 @@ const bundles = [
462462
externals: ['jest-diff'],
463463
},
464464

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+
465474
/******* ESLint Plugin for Hooks (proposal) *******/
466475
{
467476
// TODO: it's awkward to create a bundle for this

0 commit comments

Comments
 (0)