From b99e3e0e339ba89e6d83671cea5b4c0414765ed5 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 29 Nov 2018 15:54:31 -0800 Subject: [PATCH 1/3] Fixed scheduler setTimeout fallback Moved unit-test-specific setTimeout code into a new NPM package, jest-mock-scheduler. --- packages/jest-mock-scheduler/README.md | 3 + packages/jest-mock-scheduler/index.js | 10 +++ packages/jest-mock-scheduler/npm/index.js | 7 +++ packages/jest-mock-scheduler/package.json | 27 ++++++++ .../src/JestMockScheduler.js | 61 +++++++++++++++++++ packages/scheduler/src/Scheduler.js | 38 ++++++------ .../scheduler/src/__tests__/Scheduler-test.js | 8 +++ .../src/__tests__/SchedulerDOM-test.js | 4 ++ .../__tests__/ReactDOMFrameScheduling-test.js | 4 ++ scripts/jest/setupTests.js | 2 + scripts/rollup/bundles.js | 9 +++ 11 files changed, 154 insertions(+), 19 deletions(-) create mode 100644 packages/jest-mock-scheduler/README.md create mode 100644 packages/jest-mock-scheduler/index.js create mode 100644 packages/jest-mock-scheduler/npm/index.js create mode 100644 packages/jest-mock-scheduler/package.json create mode 100644 packages/jest-mock-scheduler/src/JestMockScheduler.js diff --git a/packages/jest-mock-scheduler/README.md b/packages/jest-mock-scheduler/README.md new file mode 100644 index 0000000000000..e31991234ae47 --- /dev/null +++ b/packages/jest-mock-scheduler/README.md @@ -0,0 +1,3 @@ +# `jest-mock-scheduler` + +Jest matchers and utilities for testing the `scheduler` package. \ No newline at end of file diff --git a/packages/jest-mock-scheduler/index.js b/packages/jest-mock-scheduler/index.js new file mode 100644 index 0000000000000..f08bbae43bc40 --- /dev/null +++ b/packages/jest-mock-scheduler/index.js @@ -0,0 +1,10 @@ +/** + * 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. + */ + +'use strict'; + +export * from './src/JestMockScheduler'; diff --git a/packages/jest-mock-scheduler/npm/index.js b/packages/jest-mock-scheduler/npm/index.js new file mode 100644 index 0000000000000..d7a102c971275 --- /dev/null +++ b/packages/jest-mock-scheduler/npm/index.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/jest-mock-scheduler.production.min.js'); +} else { + module.exports = require('./cjs/jest-mock-scheduler.development.js'); +} diff --git a/packages/jest-mock-scheduler/package.json b/packages/jest-mock-scheduler/package.json new file mode 100644 index 0000000000000..bad5de6742e06 --- /dev/null +++ b/packages/jest-mock-scheduler/package.json @@ -0,0 +1,27 @@ +{ + "name": "jest-mock-scheduler", + "version": "0.1.0", + "description": "Jest matchers and utilities for testing the scheduler package.", + "main": "index.js", + "repository": "facebook/react", + "keywords": [ + "jest", + "scheduler" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "homepage": "https://reactjs.org/", + "peerDependencies": { + "jest": "^23.0.1", + "scheduler": "^0.11.0" + }, + "files": [ + "LICENSE", + "README.md", + "build-info.json", + "index.js", + "cjs/" + ] +} diff --git a/packages/jest-mock-scheduler/src/JestMockScheduler.js b/packages/jest-mock-scheduler/src/JestMockScheduler.js new file mode 100644 index 0000000000000..609ece2c21906 --- /dev/null +++ b/packages/jest-mock-scheduler/src/JestMockScheduler.js @@ -0,0 +1,61 @@ +/** + * 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. + */ + +// Max 31 bit integer. The max integer size in V8 for 32-bit systems. +// Math.pow(2, 30) - 1 +// 0b111111111111111111111111111111 +const maxSigned31BitInt = 1073741823; + +export function mockRestore() { + delete global._schedMock; +} + +let callback = null; +let currentTime = -1; + +function flushCallback(didTimeout, ms) { + if (callback !== null) { + let cb = callback; + callback = null; + try { + currentTime = ms; + cb(didTimeout); + } finally { + currentTime = -1; + } + } +} + +function requestHostCallback(cb, ms) { + if (currentTime !== -1) { + // Protect against re-entrancy. + setTimeout(requestHostCallback, 0, cb, ms); + } else { + callback = cb; + setTimeout(flushCallback, ms, true, ms); + setTimeout(flushCallback, maxSigned31BitInt, false, maxSigned31BitInt); + } +} + +function cancelHostCallback() { + callback = null; +} + +function shouldYieldToHost() { + return false; +} + +function getCurrentTime() { + return currentTime === -1 ? 0 : currentTime; +} + +global._schedMock = [ + requestHostCallback, + cancelHostCallback, + shouldYieldToHost, + getCurrentTime, +]; diff --git a/packages/scheduler/src/Scheduler.js b/packages/scheduler/src/Scheduler.js index c815d0071bd41..af4a051c6dbd3 100644 --- a/packages/scheduler/src/Scheduler.js +++ b/packages/scheduler/src/Scheduler.js @@ -449,10 +449,18 @@ var shouldYieldToHost; if (typeof window !== 'undefined' && window._schedMock) { // Dynamic injection, only for testing purposes. - var impl = window._schedMock; - requestHostCallback = impl[0]; - cancelHostCallback = impl[1]; - shouldYieldToHost = impl[2]; + var windowImpl = window._schedMock; + requestHostCallback = windowImpl[0]; + cancelHostCallback = windowImpl[1]; + shouldYieldToHost = windowImpl[2]; + getCurrentTime = windowImpl[3]; +} else if (typeof global !== 'undefined' && global._schedMock) { + // Dynamic injection, only for testing purposes. + var globalImpl = global._schedMock; + requestHostCallback = globalImpl[0]; + cancelHostCallback = globalImpl[1]; + shouldYieldToHost = globalImpl[2]; + getCurrentTime = globalImpl[3]; } else if ( // If Scheduler runs in a non-DOM environment, it falls back to a naive // implementation using setTimeout. @@ -460,28 +468,23 @@ if (typeof window !== 'undefined' && window._schedMock) { // Check if MessageChannel is supported, too. typeof MessageChannel !== 'function' ) { + // If this accidentally gets imported in a non-browser environment, e.g. JavaScriptCore, + // fallback to a naive implementation. var _callback = null; - var _currentTime = -1; - var _flushCallback = function(didTimeout, ms) { + var _flushCallback = function(didTimeout) { if (_callback !== null) { var cb = _callback; _callback = null; - try { - _currentTime = ms; - cb(didTimeout); - } finally { - _currentTime = -1; - } + cb(didTimeout); } }; requestHostCallback = function(cb, ms) { - if (_currentTime !== -1) { + if (_callback !== null) { // Protect against re-entrancy. - setTimeout(requestHostCallback, 0, cb, ms); + setTimeout(requestHostCallback, 0, cb); } else { _callback = cb; - setTimeout(_flushCallback, ms, true, ms); - setTimeout(_flushCallback, maxSigned31BitInt, false, maxSigned31BitInt); + setTimeout(_flushCallback, 0, false); } }; cancelHostCallback = function() { @@ -490,9 +493,6 @@ if (typeof window !== 'undefined' && window._schedMock) { shouldYieldToHost = function() { return false; }; - getCurrentTime = function() { - return _currentTime === -1 ? 0 : _currentTime; - }; } else { if (typeof console !== 'undefined') { // TODO: Remove fb.me link diff --git a/packages/scheduler/src/__tests__/Scheduler-test.js b/packages/scheduler/src/__tests__/Scheduler-test.js index d6db53db8dcef..4674a9b16a5f4 100644 --- a/packages/scheduler/src/__tests__/Scheduler-test.js +++ b/packages/scheduler/src/__tests__/Scheduler-test.js @@ -30,6 +30,9 @@ describe('Scheduler', () => { jest.useFakeTimers(); jest.resetModules(); + const JestMockScheduler = require('jest-mock-scheduler'); + JestMockScheduler.mockRestore(); + let _flushWork = null; let isFlushing = false; let timeoutID = -1; @@ -123,16 +126,21 @@ describe('Scheduler', () => { function shouldYieldToHost() { return endOfFrame <= currentTime; } + function getCurrentTime() { + return currentTime; + } // Override host implementation delete global.performance; global.Date.now = () => { return currentTime; }; + window._schedMock = [ requestHostCallback, cancelHostCallback, shouldYieldToHost, + getCurrentTime, ]; const Schedule = require('scheduler'); diff --git a/packages/scheduler/src/__tests__/SchedulerDOM-test.js b/packages/scheduler/src/__tests__/SchedulerDOM-test.js index 562113f65bbda..9cdec37285afc 100644 --- a/packages/scheduler/src/__tests__/SchedulerDOM-test.js +++ b/packages/scheduler/src/__tests__/SchedulerDOM-test.js @@ -88,6 +88,10 @@ describe('SchedulerDOM', () => { return currentTime; }; jest.resetModules(); + + const JestMockScheduler = require('jest-mock-scheduler'); + JestMockScheduler.mockRestore(); + Scheduler = require('scheduler'); }); diff --git a/packages/shared/__tests__/ReactDOMFrameScheduling-test.js b/packages/shared/__tests__/ReactDOMFrameScheduling-test.js index 7d8a1652f06c0..b23ce3a7fd7db 100644 --- a/packages/shared/__tests__/ReactDOMFrameScheduling-test.js +++ b/packages/shared/__tests__/ReactDOMFrameScheduling-test.js @@ -22,6 +22,10 @@ describe('ReactDOMFrameScheduling', () => { }; }; jest.resetModules(); + + const JestMockScheduler = require('jest-mock-scheduler'); + JestMockScheduler.mockRestore(); + spyOnDevAndProd(console, 'error'); require('react-dom'); expect(console.error.calls.count()).toEqual(1); diff --git a/scripts/jest/setupTests.js b/scripts/jest/setupTests.js index 285f2adc898c1..8a5a522f312e6 100644 --- a/scripts/jest/setupTests.js +++ b/scripts/jest/setupTests.js @@ -56,6 +56,8 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) { toMatchRenderedOutput: JestReact.unstable_toMatchRenderedOutput, }); + require('jest-mock-scheduler'); + // We have a Babel transform that inserts guards against infinite loops. // If a loop runs for too many iterations, we throw an error and set this // global variable. The global lets us detect an infinite loop even if diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index f8ec115438834..958c84f97d768 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -396,6 +396,15 @@ const bundles = [ externals: ['jest-diff'], }, + /******* Jest Scheduler (experimental) *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD], + moduleType: ISOMORPHIC, + entry: 'jest-mock-scheduler', + global: 'JestMockScheduler', + externals: ['jest-diff'], + }, + /******* ESLint Plugin for Hooks (proposal) *******/ { // TODO: it's awkward to create a bundle for this From db14ad8fe4e41df60d10fbd9f124ee44cc65375d Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 30 Nov 2018 14:16:39 -0800 Subject: [PATCH 2/3] Combined window and global check for _schedMock --- packages/scheduler/src/Scheduler.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/scheduler/src/Scheduler.js b/packages/scheduler/src/Scheduler.js index af4a051c6dbd3..90e33a70dceb0 100644 --- a/packages/scheduler/src/Scheduler.js +++ b/packages/scheduler/src/Scheduler.js @@ -447,16 +447,16 @@ var requestHostCallback; var cancelHostCallback; var shouldYieldToHost; -if (typeof window !== 'undefined' && window._schedMock) { - // Dynamic injection, only for testing purposes. - var windowImpl = window._schedMock; - requestHostCallback = windowImpl[0]; - cancelHostCallback = windowImpl[1]; - shouldYieldToHost = windowImpl[2]; - getCurrentTime = windowImpl[3]; -} else if (typeof global !== 'undefined' && global._schedMock) { +var globalValue = null; +if (typeof window !== 'undefined') { + globalValue = window; +} else if (typeof global !== 'undefined') { + globalValue = global; +} + +if (globalValue && globalValue._schedMock) { // Dynamic injection, only for testing purposes. - var globalImpl = global._schedMock; + var globalImpl = globalValue._schedMock; requestHostCallback = globalImpl[0]; cancelHostCallback = globalImpl[1]; shouldYieldToHost = globalImpl[2]; From 25248365eb47510777b3b1fbd19203bbef648782 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 1 Dec 2018 10:45:16 -0800 Subject: [PATCH 3/3] Made Jest scheduler package private --- packages/jest-mock-scheduler/README.md | 4 +++- packages/jest-mock-scheduler/index.js | 2 -- packages/jest-mock-scheduler/package.json | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/jest-mock-scheduler/README.md b/packages/jest-mock-scheduler/README.md index e31991234ae47..387f015246617 100644 --- a/packages/jest-mock-scheduler/README.md +++ b/packages/jest-mock-scheduler/README.md @@ -1,3 +1,5 @@ # `jest-mock-scheduler` -Jest matchers and utilities for testing the `scheduler` package. \ No newline at end of file +Jest matchers and utilities for testing the `scheduler` package. + +This package is experimental. APIs may change between releases. \ No newline at end of file diff --git a/packages/jest-mock-scheduler/index.js b/packages/jest-mock-scheduler/index.js index f08bbae43bc40..c1311b7e234ba 100644 --- a/packages/jest-mock-scheduler/index.js +++ b/packages/jest-mock-scheduler/index.js @@ -5,6 +5,4 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - export * from './src/JestMockScheduler'; diff --git a/packages/jest-mock-scheduler/package.json b/packages/jest-mock-scheduler/package.json index bad5de6742e06..5c1cfacca989e 100644 --- a/packages/jest-mock-scheduler/package.json +++ b/packages/jest-mock-scheduler/package.json @@ -1,5 +1,6 @@ { "name": "jest-mock-scheduler", + "private": true, "version": "0.1.0", "description": "Jest matchers and utilities for testing the scheduler package.", "main": "index.js",