@samitouri / QOS-React / commits / 52bea95cfc

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