diff --git a/packages/jest-react/src/JestReact.js b/packages/jest-react/src/JestReact.js index e7fe0eaf0bb73..af228abdbd874 100644 --- a/packages/jest-react/src/JestReact.js +++ b/packages/jest-react/src/JestReact.js @@ -26,7 +26,8 @@ function captureAssertion(fn) { } function assertYieldsWereCleared(root) { - const actualYields = root.unstable_clearYields(); + const Scheduler = root._Scheduler; + const actualYields = Scheduler.unstable_clearYields(); invariant( actualYields.length === 0, 'Log of yielded values is not empty. ' + diff --git a/packages/react-cache/src/__tests__/ReactCache-test.internal.js b/packages/react-cache/src/__tests__/ReactCache-test.internal.js index 8630765c3e7d8..4532602dd5314 100644 --- a/packages/react-cache/src/__tests__/ReactCache-test.internal.js +++ b/packages/react-cache/src/__tests__/ReactCache-test.internal.js @@ -14,55 +14,15 @@ let createResource; let React; let ReactFeatureFlags; let ReactTestRenderer; +let Scheduler; let Suspense; let TextResource; let textResourceShouldFail; -let flushScheduledWork; -let evictLRU; describe('ReactCache', () => { beforeEach(() => { jest.resetModules(); - let currentPriorityLevel = 3; - - jest.mock('scheduler', () => { - let callbacks = []; - return { - unstable_scheduleCallback(callback) { - const callbackIndex = callbacks.length; - callbacks.push(callback); - return {callbackIndex}; - }, - flushScheduledWork() { - while (callbacks.length) { - const callback = callbacks.pop(); - callback(); - } - }, - - unstable_ImmediatePriority: 1, - unstable_UserBlockingPriority: 2, - unstable_NormalPriority: 3, - unstable_LowPriority: 4, - unstable_IdlePriority: 5, - - unstable_runWithPriority(priorityLevel, fn) { - const prevPriorityLevel = currentPriorityLevel; - currentPriorityLevel = priorityLevel; - try { - return fn(); - } finally { - currentPriorityLevel = prevPriorityLevel; - } - }, - - unstable_getCurrentPriorityLevel() { - return currentPriorityLevel; - }, - }; - }); - ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; @@ -71,8 +31,7 @@ describe('ReactCache', () => { ReactCache = require('react-cache'); createResource = ReactCache.unstable_createResource; ReactTestRenderer = require('react-test-renderer'); - flushScheduledWork = require('scheduler').flushScheduledWork; - evictLRU = flushScheduledWork; + Scheduler = require('scheduler'); TextResource = createResource(([text, ms = 0]) => { let listeners = null; @@ -86,16 +45,12 @@ describe('ReactCache', () => { listeners = [{resolve, reject}]; setTimeout(() => { if (textResourceShouldFail) { - ReactTestRenderer.unstable_yield( - `Promise rejected [${text}]`, - ); + Scheduler.yieldValue(`Promise rejected [${text}]`); status = 'rejected'; value = new Error('Failed to load: ' + text); listeners.forEach(listener => listener.reject(value)); } else { - ReactTestRenderer.unstable_yield( - `Promise resolved [${text}]`, - ); + Scheduler.yieldValue(`Promise resolved [${text}]`); status = 'resolved'; value = text; listeners.forEach(listener => listener.resolve(value)); @@ -123,7 +78,7 @@ describe('ReactCache', () => { }); function Text(props) { - ReactTestRenderer.unstable_yield(props.text); + Scheduler.yieldValue(props.text); return props.text; } @@ -131,13 +86,13 @@ describe('ReactCache', () => { const text = props.text; try { TextResource.read([props.text, props.ms]); - ReactTestRenderer.unstable_yield(text); + Scheduler.yieldValue(text); return text; } catch (promise) { if (typeof promise.then === 'function') { - ReactTestRenderer.unstable_yield(`Suspend! [${text}]`); + Scheduler.yieldValue(`Suspend! [${text}]`); } else { - ReactTestRenderer.unstable_yield(`Error! [${text}]`); + Scheduler.yieldValue(`Error! [${text}]`); } throw promise; } @@ -201,7 +156,7 @@ describe('ReactCache', () => { }); function App() { - ReactTestRenderer.unstable_yield('App'); + Scheduler.yieldValue('App'); return BadTextResource.read(['Hi', 100]); } @@ -284,9 +239,7 @@ describe('ReactCache', () => { expect(root).toMatchRenderedOutput('145'); // We've now rendered values 1, 2, 3, 4, 5, over our limit of 3. The least - // recently used values are 2 and 3. They will be evicted during the - // next sweep. - evictLRU(); + // recently used values are 2 and 3. They should have been evicted. root.update( }> @@ -368,13 +321,13 @@ describe('ReactCache', () => { const text = props.text; try { const actualText = BadTextResource.read([props.text, props.ms]); - ReactTestRenderer.unstable_yield(actualText); + Scheduler.yieldValue(actualText); return actualText; } catch (promise) { if (typeof promise.then === 'function') { - ReactTestRenderer.unstable_yield(`Suspend! [${text}]`); + Scheduler.yieldValue(`Suspend! [${text}]`); } else { - ReactTestRenderer.unstable_yield(`Error! [${text}]`); + Scheduler.yieldValue(`Error! [${text}]`); } throw promise; } diff --git a/packages/react-reconciler/src/__tests__/ErrorBoundaryReconciliation-test.internal.js b/packages/react-reconciler/src/__tests__/ErrorBoundaryReconciliation-test.internal.js index 66ddccf894d4d..546494a05ebce 100644 --- a/packages/react-reconciler/src/__tests__/ErrorBoundaryReconciliation-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ErrorBoundaryReconciliation-test.internal.js @@ -5,6 +5,7 @@ describe('ErrorBoundaryReconciliation', () => { let React; let ReactFeatureFlags; let ReactTestRenderer; + let Scheduler; let span; beforeEach(() => { @@ -14,6 +15,7 @@ describe('ErrorBoundaryReconciliation', () => { ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; ReactTestRenderer = require('react-test-renderer'); React = require('react'); + Scheduler = require('scheduler'); DidCatchErrorBoundary = class extends React.Component { state = {error: null}; @@ -56,9 +58,7 @@ describe('ErrorBoundaryReconciliation', () => { , {unstable_isConcurrent: isConcurrent}, ); - if (isConcurrent) { - renderer.unstable_flushAll(); - } + Scheduler.flushAll(); expect(renderer).toMatchRenderedOutput(); expect(() => { @@ -67,9 +67,7 @@ describe('ErrorBoundaryReconciliation', () => { , ); - if (isConcurrent) { - renderer.unstable_flushAll(); - } + Scheduler.flushAll(); }).toWarnDev(isConcurrent ? ['invalid', 'invalid'] : ['invalid']); const Fallback = fallbackTagName; expect(renderer).toMatchRenderedOutput(); diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js index 8a3e7d98a86f7..b88676e77baaf 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js @@ -15,6 +15,7 @@ let React; let ReactFeatureFlags; let ReactTestRenderer; +let Scheduler; let ReactDOMServer; let act; @@ -28,6 +29,7 @@ describe('ReactHooks', () => { ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; React = require('react'); ReactTestRenderer = require('react-test-renderer'); + Scheduler = require('scheduler'); ReactDOMServer = require('react-dom/server'); act = ReactTestRenderer.act; }); @@ -53,7 +55,7 @@ describe('ReactHooks', () => { const {useState, useLayoutEffect} = React; function Child({text}) { - ReactTestRenderer.unstable_yield('Child: ' + text); + Scheduler.yieldValue('Child: ' + text); return text; } @@ -66,9 +68,9 @@ describe('ReactHooks', () => { setCounter2 = _setCounter2; const text = `${counter1}, ${counter2}`; - ReactTestRenderer.unstable_yield(`Parent: ${text}`); + Scheduler.yieldValue(`Parent: ${text}`); useLayoutEffect(() => { - ReactTestRenderer.unstable_yield(`Effect: ${text}`); + Scheduler.yieldValue(`Effect: ${text}`); }); return ; } @@ -161,7 +163,7 @@ describe('ReactHooks', () => { const {useState, memo} = React; function Child({text}) { - ReactTestRenderer.unstable_yield('Child: ' + text); + Scheduler.yieldValue('Child: ' + text); return text; } @@ -174,7 +176,7 @@ describe('ReactHooks', () => { setCounter2 = _setCounter2; const text = `${counter1}, ${counter2} (${theme})`; - ReactTestRenderer.unstable_yield(`Parent: ${text}`); + Scheduler.yieldValue(`Parent: ${text}`); return ; } @@ -243,7 +245,7 @@ describe('ReactHooks', () => { const [counter, _setCounter] = useState(0); setCounter = _setCounter; - ReactTestRenderer.unstable_yield(`Count: ${counter}`); + Scheduler.yieldValue(`Count: ${counter}`); return counter; } @@ -277,7 +279,7 @@ describe('ReactHooks', () => { const [counter, _dispatch] = useReducer((s, a) => a, 0); dispatch = _dispatch; - ReactTestRenderer.unstable_yield(`Count: ${counter}`); + Scheduler.yieldValue(`Count: ${counter}`); return counter; } @@ -311,7 +313,7 @@ describe('ReactHooks', () => { let setTheme; function ThemeProvider({children}) { const [theme, _setTheme] = useState('light'); - ReactTestRenderer.unstable_yield('Theme: ' + theme); + Scheduler.yieldValue('Theme: ' + theme); setTheme = _setTheme; return ( {children} @@ -319,7 +321,7 @@ describe('ReactHooks', () => { } function Child({text}) { - ReactTestRenderer.unstable_yield('Child: ' + text); + Scheduler.yieldValue('Child: ' + text); return text; } @@ -331,9 +333,9 @@ describe('ReactHooks', () => { const theme = useContext(ThemeContext); const text = `${counter} (${theme})`; - ReactTestRenderer.unstable_yield(`Parent: ${text}`); + Scheduler.yieldValue(`Parent: ${text}`); useLayoutEffect(() => { - ReactTestRenderer.unstable_yield(`Effect: ${text}`); + Scheduler.yieldValue(`Effect: ${text}`); }); return ; } @@ -392,7 +394,7 @@ describe('ReactHooks', () => { const {useState, useLayoutEffect} = React; function Child({text}) { - ReactTestRenderer.unstable_yield('Child: ' + text); + Scheduler.yieldValue('Child: ' + text); return text; } @@ -400,9 +402,9 @@ describe('ReactHooks', () => { function Parent() { const [counter, _setCounter] = useState(0); setCounter = _setCounter; - ReactTestRenderer.unstable_yield('Parent: ' + counter); + Scheduler.yieldValue('Parent: ' + counter); useLayoutEffect(() => { - ReactTestRenderer.unstable_yield('Effect: ' + counter); + Scheduler.yieldValue('Effect: ' + counter); }); return ; } @@ -470,7 +472,7 @@ describe('ReactHooks', () => { const {useState} = React; function Child({text}) { - ReactTestRenderer.unstable_yield('Child: ' + text); + Scheduler.yieldValue('Child: ' + text); return text; } @@ -478,7 +480,7 @@ describe('ReactHooks', () => { function Parent() { const [counter, _setCounter] = useState(0); setCounter = _setCounter; - ReactTestRenderer.unstable_yield('Parent: ' + counter); + Scheduler.yieldValue('Parent: ' + counter); return ; } @@ -489,9 +491,7 @@ describe('ReactHooks', () => { const update = value => { setCounter(previous => { - ReactTestRenderer.unstable_yield( - `Compute state (${previous} -> ${value})`, - ); + Scheduler.yieldValue(`Compute state (${previous} -> ${value})`); return value; }); }; @@ -530,7 +530,7 @@ describe('ReactHooks', () => { const {useState} = React; function Child({text}) { - ReactTestRenderer.unstable_yield('Child: ' + text); + Scheduler.yieldValue('Child: ' + text); return text; } @@ -538,7 +538,7 @@ describe('ReactHooks', () => { function Parent() { const [counter, _setCounter] = useState(1); setCounter = _setCounter; - ReactTestRenderer.unstable_yield('Parent: ' + counter); + Scheduler.yieldValue('Parent: ' + counter); return ; } @@ -550,9 +550,7 @@ describe('ReactHooks', () => { const update = compute => { setCounter(previous => { const value = compute(previous); - ReactTestRenderer.unstable_yield( - `Compute state (${previous} -> ${value})`, - ); + Scheduler.yieldValue(`Compute state (${previous} -> ${value})`); return value; }); }; @@ -590,9 +588,7 @@ describe('ReactHooks', () => { const {useLayoutEffect} = React; function App(props) { useLayoutEffect(() => { - ReactTestRenderer.unstable_yield( - 'Did commit: ' + props.dependencies.join(', '), - ); + Scheduler.yieldValue('Did commit: ' + props.dependencies.join(', ')); }, props.dependencies); return props.dependencies; } @@ -613,7 +609,7 @@ describe('ReactHooks', () => { const {useMemo} = React; function App({text, hasDeps}) { const resolvedText = useMemo(() => { - ReactTestRenderer.unstable_yield('Compute'); + Scheduler.yieldValue('Compute'); return text.toUpperCase(); }, hasDeps ? null : [text]); return resolvedText; diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js index 699f8e3a98889..987c0f0e6edf3 100644 --- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js @@ -1,6 +1,7 @@ let PropTypes; let React; let ReactTestRenderer; +let Scheduler; let ReactFeatureFlags; let Suspense; let lazy; @@ -16,10 +17,11 @@ describe('ReactLazy', () => { Suspense = React.Suspense; lazy = React.lazy; ReactTestRenderer = require('react-test-renderer'); + Scheduler = require('scheduler'); }); function Text(props) { - ReactTestRenderer.unstable_yield(props.text); + Scheduler.yieldValue(props.text); return props.text; } @@ -203,10 +205,10 @@ describe('ReactLazy', () => { it('mount and reorder', async () => { class Child extends React.Component { componentDidMount() { - ReactTestRenderer.unstable_yield('Did mount: ' + this.props.label); + Scheduler.yieldValue('Did mount: ' + this.props.label); } componentDidUpdate() { - ReactTestRenderer.unstable_yield('Did update: ' + this.props.label); + Scheduler.yieldValue('Did update: ' + this.props.label); } render() { return ; @@ -287,7 +289,7 @@ describe('ReactLazy', () => { it('resolves defaultProps without breaking memoization', async () => { function LazyImpl(props) { - ReactTestRenderer.unstable_yield('Lazy'); + Scheduler.yieldValue('Lazy'); return ( @@ -337,44 +339,38 @@ describe('ReactLazy', () => { state = {}; static getDerivedStateFromProps(props) { - ReactTestRenderer.unstable_yield( - `getDerivedStateFromProps: ${props.text}`, - ); + Scheduler.yieldValue(`getDerivedStateFromProps: ${props.text}`); return null; } constructor(props) { super(props); - ReactTestRenderer.unstable_yield(`constructor: ${this.props.text}`); + Scheduler.yieldValue(`constructor: ${this.props.text}`); } componentDidMount() { - ReactTestRenderer.unstable_yield( - `componentDidMount: ${this.props.text}`, - ); + Scheduler.yieldValue(`componentDidMount: ${this.props.text}`); } componentDidUpdate(prevProps) { - ReactTestRenderer.unstable_yield( + Scheduler.yieldValue( `componentDidUpdate: ${prevProps.text} -> ${this.props.text}`, ); } componentWillUnmount() { - ReactTestRenderer.unstable_yield( - `componentWillUnmount: ${this.props.text}`, - ); + Scheduler.yieldValue(`componentWillUnmount: ${this.props.text}`); } shouldComponentUpdate(nextProps) { - ReactTestRenderer.unstable_yield( + Scheduler.yieldValue( `shouldComponentUpdate: ${this.props.text} -> ${nextProps.text}`, ); return true; } getSnapshotBeforeUpdate(prevProps) { - ReactTestRenderer.unstable_yield( + Scheduler.yieldValue( `getSnapshotBeforeUpdate: ${prevProps.text} -> ${this.props.text}`, ); return null; @@ -443,19 +439,17 @@ describe('ReactLazy', () => { state = {}; UNSAFE_componentWillMount() { - ReactTestRenderer.unstable_yield( - `UNSAFE_componentWillMount: ${this.props.text}`, - ); + Scheduler.yieldValue(`UNSAFE_componentWillMount: ${this.props.text}`); } UNSAFE_componentWillUpdate(nextProps) { - ReactTestRenderer.unstable_yield( + Scheduler.yieldValue( `UNSAFE_componentWillUpdate: ${this.props.text} -> ${nextProps.text}`, ); } UNSAFE_componentWillReceiveProps(nextProps) { - ReactTestRenderer.unstable_yield( + Scheduler.yieldValue( `UNSAFE_componentWillReceiveProps: ${this.props.text} -> ${ nextProps.text }`, @@ -512,7 +506,7 @@ describe('ReactLazy', () => { it('resolves defaultProps on the outer wrapper but warns', async () => { function T(props) { - ReactTestRenderer.unstable_yield(props.inner + ' ' + props.outer); + Scheduler.yieldValue(props.inner + ' ' + props.outer); return props.inner + ' ' + props.outer; } T.defaultProps = {inner: 'Hi'}; @@ -580,9 +574,7 @@ describe('ReactLazy', () => { , ); - expect(() => { - root.unstable_flushAll(); - }).toThrow( + expect(Scheduler).toFlushAndThrow( 'Element type is invalid. Received a promise that resolves to: 42. ' + 'Lazy element type must resolve to a class or function.', ); @@ -610,9 +602,7 @@ describe('ReactLazy', () => { , ); - expect(() => { - root.unstable_flushAll(); - }).toThrow( + expect(Scheduler).toFlushAndThrow( 'Element type is invalid. Received a promise that resolves to: [object Object]. ' + 'Lazy element type must resolve to a class or function.' + (__DEV__ @@ -659,10 +649,11 @@ describe('ReactLazy', () => { // Mount await Promise.resolve(); expect(() => { - root.unstable_flushAll(); - }).toWarnDev( + expect(Scheduler); + Scheduler.flushAll(); + }).toWarnDev([ 'Invalid prop `inner` of type `string` supplied to `Add`, expected `number`.', - ); + ]); expect(root).toMatchRenderedOutput('22'); // Update @@ -672,7 +663,7 @@ describe('ReactLazy', () => { , ); - root.unstable_flushAll(); + expect(Scheduler).toFlushWithoutYielding(); }).toWarnDev( 'Invalid prop `inner` of type `boolean` supplied to `Add`, expected `number`.', ); @@ -844,7 +835,7 @@ describe('ReactLazy', () => { // Mount await Promise.resolve(); expect(() => { - root.unstable_flushAll(); + expect(Scheduler).toFlushAndYield(['Inner default text']); }).toWarnDev( 'The prop `text` is marked as required in `T`, but its value is `undefined`', ); @@ -857,7 +848,7 @@ describe('ReactLazy', () => { , ); - root.unstable_flushAll(); + expect(Scheduler).toFlushAndYield([null]); }).toWarnDev( 'The prop `text` is marked as required in `T`, but its value is `null`', ); @@ -866,7 +857,7 @@ describe('ReactLazy', () => { it('includes lazy-loaded component in warning stack', async () => { const LazyFoo = lazy(() => { - ReactTestRenderer.unstable_yield('Started loading'); + Scheduler.yieldValue('Started loading'); const Foo = props =>
{[, ]}
; return fakeImport(Foo); }); @@ -909,7 +900,7 @@ describe('ReactLazy', () => { } return fakeImport( React.forwardRef((props, ref) => { - ReactTestRenderer.unstable_yield('forwardRef'); + Scheduler.yieldValue('forwardRef'); return ; }), ); @@ -959,7 +950,7 @@ describe('ReactLazy', () => { // Mount await Promise.resolve(); - root.unstable_flushAll(); + expect(Scheduler).toFlushWithoutYielding(); expect(root).toMatchRenderedOutput('4'); // Update (shallowly equal) @@ -968,7 +959,7 @@ describe('ReactLazy', () => { , ); - root.unstable_flushAll(); + expect(Scheduler).toFlushWithoutYielding(); expect(root).toMatchRenderedOutput('4'); // Update @@ -977,7 +968,7 @@ describe('ReactLazy', () => { , ); - root.unstable_flushAll(); + expect(Scheduler).toFlushWithoutYielding(); expect(root).toMatchRenderedOutput('5'); // Update (shallowly equal) @@ -986,7 +977,7 @@ describe('ReactLazy', () => { , ); - root.unstable_flushAll(); + expect(Scheduler).toFlushWithoutYielding(); expect(root).toMatchRenderedOutput('5'); // Update (explicit props) @@ -995,7 +986,7 @@ describe('ReactLazy', () => { , ); - root.unstable_flushAll(); + expect(Scheduler).toFlushWithoutYielding(); expect(root).toMatchRenderedOutput('2'); // Update (explicit props, shallowly equal) @@ -1004,7 +995,7 @@ describe('ReactLazy', () => { , ); - root.unstable_flushAll(); + expect(Scheduler).toFlushWithoutYielding(); expect(root).toMatchRenderedOutput('2'); // Update @@ -1013,7 +1004,7 @@ describe('ReactLazy', () => { , ); - root.unstable_flushAll(); + expect(Scheduler).toFlushWithoutYielding(); expect(root).toMatchRenderedOutput('3'); }); @@ -1043,7 +1034,7 @@ describe('ReactLazy', () => { // Mount await Promise.resolve(); - root.unstable_flushAll(); + expect(Scheduler).toFlushWithoutYielding(); expect(root).toMatchRenderedOutput('4'); // Update @@ -1052,7 +1043,7 @@ describe('ReactLazy', () => { , ); - root.unstable_flushAll(); + expect(Scheduler).toFlushWithoutYielding(); expect(root).toMatchRenderedOutput('5'); // Update @@ -1061,7 +1052,7 @@ describe('ReactLazy', () => { , ); - root.unstable_flushAll(); + expect(Scheduler).toFlushWithoutYielding(); expect(root).toMatchRenderedOutput('2'); }); diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js index 69e4135cc70ed..814c7f8caa034 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js @@ -1,12 +1,11 @@ let React; let ReactTestRenderer; let ReactFeatureFlags; +let Scheduler; let ReactCache; let Suspense; let act; -// let JestReact; - let TextResource; let textResourceShouldFail; @@ -21,7 +20,7 @@ describe('ReactSuspense', () => { React = require('react'); ReactTestRenderer = require('react-test-renderer'); act = ReactTestRenderer.act; - // JestReact = require('jest-react'); + Scheduler = require('scheduler'); ReactCache = require('react-cache'); Suspense = React.Suspense; @@ -38,16 +37,12 @@ describe('ReactSuspense', () => { listeners = [{resolve, reject}]; setTimeout(() => { if (textResourceShouldFail) { - ReactTestRenderer.unstable_yield( - `Promise rejected [${text}]`, - ); + Scheduler.yieldValue(`Promise rejected [${text}]`); status = 'rejected'; value = new Error('Failed to load: ' + text); listeners.forEach(listener => listener.reject(value)); } else { - ReactTestRenderer.unstable_yield( - `Promise resolved [${text}]`, - ); + Scheduler.yieldValue(`Promise resolved [${text}]`); status = 'resolved'; value = text; listeners.forEach(listener => listener.resolve(value)); @@ -74,7 +69,7 @@ describe('ReactSuspense', () => { }); function Text(props) { - ReactTestRenderer.unstable_yield(props.text); + Scheduler.yieldValue(props.text); return props.text; } @@ -82,13 +77,13 @@ describe('ReactSuspense', () => { const text = props.text; try { TextResource.read([props.text, props.ms]); - ReactTestRenderer.unstable_yield(text); + Scheduler.yieldValue(text); return text; } catch (promise) { if (typeof promise.then === 'function') { - ReactTestRenderer.unstable_yield(`Suspend! [${text}]`); + Scheduler.yieldValue(`Suspend! [${text}]`); } else { - ReactTestRenderer.unstable_yield(`Error! [${text}]`); + Scheduler.yieldValue(`Error! [${text}]`); } throw promise; } @@ -96,12 +91,12 @@ describe('ReactSuspense', () => { it('suspends rendering and continues later', () => { function Bar(props) { - ReactTestRenderer.unstable_yield('Bar'); + Scheduler.yieldValue('Bar'); return props.children; } function Foo() { - ReactTestRenderer.unstable_yield('Foo'); + Scheduler.yieldValue('Foo'); return ( }> @@ -208,10 +203,10 @@ describe('ReactSuspense', () => { function Async() { if (!didResolve) { - ReactTestRenderer.unstable_yield('Suspend!'); + Scheduler.yieldValue('Suspend!'); throw thenable; } - ReactTestRenderer.unstable_yield('Async'); + Scheduler.yieldValue('Async'); return 'Async'; } @@ -243,10 +238,10 @@ describe('ReactSuspense', () => { it('mounts a lazy class component in non-concurrent mode', async () => { class Class extends React.Component { componentDidMount() { - ReactTestRenderer.unstable_yield('Did mount: ' + this.props.label); + Scheduler.yieldValue('Did mount: ' + this.props.label); } componentDidUpdate() { - ReactTestRenderer.unstable_yield('Did update: ' + this.props.label); + Scheduler.yieldValue('Did update: ' + this.props.label); } render() { return ; @@ -304,7 +299,7 @@ describe('ReactSuspense', () => { }); it('throws if tree suspends and none of the Suspense ancestors have a fallback', () => { - const root = ReactTestRenderer.create( + ReactTestRenderer.create( , @@ -313,34 +308,23 @@ describe('ReactSuspense', () => { }, ); - let err; - try { - root.unstable_flushAll(); - } catch (e) { - err = e; - } - expect(err.message.replace(/at .+?:\d+/g, 'at **')).toBe( - 'AsyncText suspended while rendering, but no fallback UI was specified.\n' + - '\n' + - 'Add a component higher in the tree to provide ' + - 'a loading indicator or placeholder to display.\n' + - (__DEV__ ? ' in AsyncText (at **)\n' : ' in AsyncText\n') + - (__DEV__ ? ' in Suspense (at **)' : ' in Suspense'), + expect(Scheduler).toFlushAndThrow( + 'AsyncText suspended while rendering, but no fallback UI was specified.', ); - expect(ReactTestRenderer).toHaveYielded(['Suspend! [Hi]', 'Suspend! [Hi]']); + expect(Scheduler).toHaveYielded(['Suspend! [Hi]', 'Suspend! [Hi]']); }); describe('outside concurrent mode', () => { it('a mounted class component can suspend without losing state', () => { class TextWithLifecycle extends React.Component { componentDidMount() { - ReactTestRenderer.unstable_yield(`Mount [${this.props.text}]`); + Scheduler.yieldValue(`Mount [${this.props.text}]`); } componentDidUpdate() { - ReactTestRenderer.unstable_yield(`Update [${this.props.text}]`); + Scheduler.yieldValue(`Update [${this.props.text}]`); } componentWillUnmount() { - ReactTestRenderer.unstable_yield(`Unmount [${this.props.text}]`); + Scheduler.yieldValue(`Unmount [${this.props.text}]`); } render() { return ; @@ -351,17 +335,15 @@ describe('ReactSuspense', () => { class AsyncTextWithLifecycle extends React.Component { state = {step: 1}; componentDidMount() { - ReactTestRenderer.unstable_yield( - `Mount [${this.props.text}:${this.state.step}]`, - ); + Scheduler.yieldValue(`Mount [${this.props.text}:${this.state.step}]`); } componentDidUpdate() { - ReactTestRenderer.unstable_yield( + Scheduler.yieldValue( `Update [${this.props.text}:${this.state.step}]`, ); } componentWillUnmount() { - ReactTestRenderer.unstable_yield( + Scheduler.yieldValue( `Unmount [${this.props.text}:${this.state.step}]`, ); } @@ -371,13 +353,13 @@ describe('ReactSuspense', () => { const ms = this.props.ms; try { TextResource.read([text, ms]); - ReactTestRenderer.unstable_yield(text); + Scheduler.yieldValue(text); return text; } catch (promise) { if (typeof promise.then === 'function') { - ReactTestRenderer.unstable_yield(`Suspend! [${text}]`); + Scheduler.yieldValue(`Suspend! [${text}]`); } else { - ReactTestRenderer.unstable_yield(`Error! [${text}]`); + Scheduler.yieldValue(`Error! [${text}]`); } throw promise; } @@ -556,20 +538,20 @@ describe('ReactSuspense', () => { it('suspends in a class that has componentWillUnmount and is then deleted', () => { class AsyncTextWithUnmount extends React.Component { componentWillUnmount() { - ReactTestRenderer.unstable_yield('will unmount'); + Scheduler.yieldValue('will unmount'); } render() { const text = this.props.text; const ms = this.props.ms; try { TextResource.read([text, ms]); - ReactTestRenderer.unstable_yield(text); + Scheduler.yieldValue(text); return text; } catch (promise) { if (typeof promise.then === 'function') { - ReactTestRenderer.unstable_yield(`Suspend! [${text}]`); + Scheduler.yieldValue(`Suspend! [${text}]`); } else { - ReactTestRenderer.unstable_yield(`Error! [${text}]`); + Scheduler.yieldValue(`Error! [${text}]`); } throw promise; } @@ -600,20 +582,20 @@ describe('ReactSuspense', () => { useLayoutEffect( () => { - ReactTestRenderer.unstable_yield('Did commit: ' + text); + Scheduler.yieldValue('Did commit: ' + text); }, [text], ); try { TextResource.read([props.text, props.ms]); - ReactTestRenderer.unstable_yield(text); + Scheduler.yieldValue(text); return text; } catch (promise) { if (typeof promise.then === 'function') { - ReactTestRenderer.unstable_yield(`Suspend! [${text}]`); + Scheduler.yieldValue(`Suspend! [${text}]`); } else { - ReactTestRenderer.unstable_yield(`Error! [${text}]`); + Scheduler.yieldValue(`Error! [${text}]`); } throw promise; } @@ -840,13 +822,13 @@ describe('ReactSuspense', () => { const fullText = `${text}:${step}`; try { TextResource.read([fullText, ms]); - ReactTestRenderer.unstable_yield(fullText); + Scheduler.yieldValue(fullText); return fullText; } catch (promise) { if (typeof promise.then === 'function') { - ReactTestRenderer.unstable_yield(`Suspend! [${fullText}]`); + Scheduler.yieldValue(`Suspend! [${fullText}]`); } else { - ReactTestRenderer.unstable_yield(`Error! [${fullText}]`); + Scheduler.yieldValue(`Error! [${fullText}]`); } throw promise; } diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js index 9444bdbf6f019..079f70f3dbf5f 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js @@ -1,6 +1,7 @@ let React; let Suspense; let ReactTestRenderer; +let Scheduler; let ReactFeatureFlags; let Random; @@ -26,6 +27,7 @@ describe('ReactSuspenseFuzz', () => { React = require('react'); Suspense = React.Suspense; ReactTestRenderer = require('react-test-renderer'); + Scheduler = require('scheduler'); Random = require('random-seed'); }); @@ -55,7 +57,7 @@ describe('ReactSuspenseFuzz', () => { }; const timeoutID = setTimeout(() => { pendingTasks.delete(task); - ReactTestRenderer.unstable_yield(task.label); + Scheduler.yieldValue(task.label); setStep(i + 1); }, remountAfter); pendingTasks.add(task); @@ -88,7 +90,7 @@ describe('ReactSuspenseFuzz', () => { }; const timeoutID = setTimeout(() => { pendingTasks.delete(task); - ReactTestRenderer.unstable_yield(task.label); + Scheduler.yieldValue(task.label); setStep([i + 1, suspendFor]); }, beginAfter); pendingTasks.add(task); @@ -120,24 +122,24 @@ describe('ReactSuspenseFuzz', () => { setTimeout(() => { cache.set(fullText, fullText); pendingTasks.delete(task); - ReactTestRenderer.unstable_yield(task.label); + Scheduler.yieldValue(task.label); resolve(); }, delay); }, }; cache.set(fullText, thenable); - ReactTestRenderer.unstable_yield(`Suspended! [${fullText}]`); + Scheduler.yieldValue(`Suspended! [${fullText}]`); throw thenable; } else if (typeof resolvedText.then === 'function') { const thenable = resolvedText; - ReactTestRenderer.unstable_yield(`Suspended! [${fullText}]`); + Scheduler.yieldValue(`Suspended! [${fullText}]`); throw thenable; } } else { resolvedText = fullText; } - ReactTestRenderer.unstable_yield(resolvedText); + Scheduler.yieldValue(resolvedText); return resolvedText; } @@ -151,7 +153,7 @@ describe('ReactSuspenseFuzz', () => { {children} , ); - root.unstable_flushAll(); + Scheduler.unstable_flushWithoutYielding(); let elapsedTime = 0; while (pendingTasks && pendingTasks.size > 0) { @@ -159,7 +161,7 @@ describe('ReactSuspenseFuzz', () => { throw new Error('Something did not resolve properly.'); } ReactTestRenderer.act(() => jest.advanceTimersByTime(1000)); - root.unstable_flushAll(); + Scheduler.unstable_flushWithoutYielding(); } return root.toJSON(); @@ -189,9 +191,7 @@ describe('ReactSuspenseFuzz', () => { const concurrentOutput = renderToRoot(concurrentRoot, children); expect(concurrentOutput).toEqual(expectedOutput); concurrentRoot.unmount(); - concurrentRoot.unstable_flushAll(); - - ReactTestRenderer.unstable_clearYields(); + Scheduler.unstable_flushWithoutYielding(); } function pickRandomWeighted(rand, options) { diff --git a/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js index 50bef0ccfc836..1bfd372f743b0 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js @@ -19,11 +19,10 @@ runPlaceholderTests('ReactSuspensePlaceholder (persistence)', () => ); function runPlaceholderTests(suiteLabel, loadReactNoop) { - let advanceTimeBy; - let mockNow; let Profiler; let React; let ReactTestRenderer; + let Scheduler; let ReactFeatureFlags; let ReactCache; let Suspense; @@ -34,20 +33,13 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) { beforeEach(() => { jest.resetModules(); - let currentTime = 0; - mockNow = jest.fn().mockImplementation(() => currentTime); - global.Date.now = mockNow; - advanceTimeBy = amount => { - currentTime += amount; - }; - ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; ReactFeatureFlags.enableProfilerTimer = true; ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; React = require('react'); ReactTestRenderer = require('react-test-renderer'); - ReactTestRenderer.unstable_setNowImplementation(mockNow); + Scheduler = require('scheduler'); ReactCache = require('react-cache'); Profiler = React.unstable_Profiler; @@ -65,16 +57,12 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) { listeners = [{resolve, reject}]; setTimeout(() => { if (textResourceShouldFail) { - ReactTestRenderer.unstable_yield( - `Promise rejected [${text}]`, - ); + Scheduler.yieldValue(`Promise rejected [${text}]`); status = 'rejected'; value = new Error('Failed to load: ' + text); listeners.forEach(listener => listener.reject(value)); } else { - ReactTestRenderer.unstable_yield( - `Promise resolved [${text}]`, - ); + Scheduler.yieldValue(`Promise resolved [${text}]`); status = 'resolved'; value = text; listeners.forEach(listener => listener.resolve(value)); @@ -101,22 +89,22 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) { }); function Text({fakeRenderDuration = 0, text = 'Text'}) { - advanceTimeBy(fakeRenderDuration); - ReactTestRenderer.unstable_yield(text); + Scheduler.advanceTime(fakeRenderDuration); + Scheduler.yieldValue(text); return text; } function AsyncText({fakeRenderDuration = 0, ms, text}) { - advanceTimeBy(fakeRenderDuration); + Scheduler.advanceTime(fakeRenderDuration); try { TextResource.read([text, ms]); - ReactTestRenderer.unstable_yield(text); + Scheduler.yieldValue(text); return text; } catch (promise) { if (typeof promise.then === 'function') { - ReactTestRenderer.unstable_yield(`Suspend! [${text}]`); + Scheduler.yieldValue(`Suspend! [${text}]`); } else { - ReactTestRenderer.unstable_yield(`Error! [${text}]`); + Scheduler.yieldValue(`Error! [${text}]`); } throw promise; } @@ -126,7 +114,7 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) { class HiddenText extends React.PureComponent { render() { const text = this.props.text; - ReactTestRenderer.unstable_yield(text); + Scheduler.yieldValue(text); return ; } } @@ -243,19 +231,19 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) { onRender = jest.fn(); const Fallback = () => { - ReactTestRenderer.unstable_yield('Fallback'); - advanceTimeBy(10); + Scheduler.yieldValue('Fallback'); + Scheduler.advanceTime(10); return 'Loading...'; }; const Suspending = () => { - ReactTestRenderer.unstable_yield('Suspending'); - advanceTimeBy(2); + Scheduler.yieldValue('Suspending'); + Scheduler.advanceTime(2); return ; }; App = ({shouldSuspend, text = 'Text', textRenderDuration = 5}) => { - ReactTestRenderer.unstable_yield('App'); + Scheduler.yieldValue('App'); return ( }> diff --git a/packages/react-test-renderer/src/ReactTestHostConfig.js b/packages/react-test-renderer/src/ReactTestHostConfig.js index 4006a3cb2257d..f8d98bc0e18b8 100644 --- a/packages/react-test-renderer/src/ReactTestHostConfig.js +++ b/packages/react-test-renderer/src/ReactTestHostConfig.js @@ -7,13 +7,8 @@ * @flow */ +import * as Scheduler from 'scheduler/unstable_mock'; import warning from 'shared/warning'; -import { - nowImplementation as TestRendererSchedulingNowImplementation, - scheduleDeferredCallback as TestRendererSchedulingScheduleDeferredCallback, - cancelDeferredCallback as TestRendererSchedulingCancelDeferredCallback, - shouldYield as TestRendererSchedulingShouldYield, -} from './ReactTestRendererScheduling'; export type Type = string; export type Props = Object; @@ -202,16 +197,16 @@ export function createTextInstance( export const isPrimaryRenderer = false; // This approach enables `now` to be mocked by tests, // Even after the reconciler has initialized and read host config values. -export const now = () => TestRendererSchedulingNowImplementation(); -export const scheduleDeferredCallback = TestRendererSchedulingScheduleDeferredCallback; -export const cancelDeferredCallback = TestRendererSchedulingCancelDeferredCallback; -export const shouldYield = TestRendererSchedulingShouldYield; +export const now = Scheduler.unstable_now; +export const scheduleDeferredCallback = Scheduler.unstable_scheduleCallback; +export const cancelDeferredCallback = Scheduler.unstable_cancelCallback; +export const shouldYield = Scheduler.unstable_shouldYield; export const scheduleTimeout = setTimeout; export const cancelTimeout = clearTimeout; export const noTimeout = -1; -export const schedulePassiveEffects = scheduleDeferredCallback; -export const cancelPassiveEffects = cancelDeferredCallback; +export const schedulePassiveEffects = Scheduler.unstable_scheduleCallback; +export const cancelPassiveEffects = Scheduler.unstable_cancelCallback; // ------------------- // Mutation diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index 39767e5bc77ed..de53adb201935 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -11,6 +11,7 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber'; import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot'; import type {Instance, TextInstance} from './ReactTestHostConfig'; +import * as Scheduler from 'scheduler/unstable_mock'; import { getPublicRootInstance, createContainer, @@ -42,13 +43,6 @@ import ReactVersion from 'shared/ReactVersion'; import warningWithoutStack from 'shared/warningWithoutStack'; import {getPublicInstance} from './ReactTestHostConfig'; -import { - flushAll, - flushNumberOfYields, - clearYields, - setNowImplementation, - yieldValue, -} from './ReactTestRendererScheduling'; type TestRendererOptions = { createNodeMock: (element: React$Element) => any, @@ -430,6 +424,8 @@ function propsMatch(props: Object, filter: Object): boolean { } const ReactTestRendererFiber = { + _Scheduler: Scheduler, + create(element: React$Element, options: TestRendererOptions) { let createNodeMock = defaultTestOptions.createNodeMock; let isConcurrent = false; @@ -455,6 +451,8 @@ const ReactTestRendererFiber = { updateContainer(element, root, null, null); const entry = { + _Scheduler: Scheduler, + root: undefined, // makes flow happy // we define a 'getter' for 'root' below using 'Object.defineProperty' toJSON(): Array | ReactTestRendererNode | null { @@ -518,13 +516,9 @@ const ReactTestRendererFiber = { return getPublicRootInstance(root); }, - unstable_flushAll: flushAll, unstable_flushSync(fn: () => T): T { - clearYields(); return flushSync(fn); }, - unstable_flushNumberOfYields: flushNumberOfYields, - unstable_clearYields: clearYields, }; Object.defineProperty( @@ -555,15 +549,10 @@ const ReactTestRendererFiber = { return entry; }, - unstable_yield: yieldValue, - unstable_clearYields: clearYields, - /* eslint-disable camelcase */ unstable_batchedUpdates: batchedUpdates, /* eslint-enable camelcase */ - unstable_setNowImplementation: setNowImplementation, - act(callback: () => void): Thenable { // note: keep these warning messages in sync with // createNoop.js and ReactTestUtils.js diff --git a/packages/react-test-renderer/src/ReactTestRendererScheduling.js b/packages/react-test-renderer/src/ReactTestRendererScheduling.js deleted file mode 100644 index 973bdc53504a0..0000000000000 --- a/packages/react-test-renderer/src/ReactTestRendererScheduling.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * 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 - */ - -// Current virtual time -export let nowImplementation = () => 0; -export let scheduledCallback: (() => mixed) | null = null; -export let yieldedValues: Array = []; - -let didStop: boolean = false; -let expectedNumberOfYields: number = -1; - -export function scheduleDeferredCallback( - callback: () => mixed, - options?: {timeout: number}, -): number { - scheduledCallback = callback; - const fakeCallbackId = 0; - return fakeCallbackId; -} - -export function cancelDeferredCallback(timeoutID: number): void { - scheduledCallback = null; -} - -export function setNowImplementation(implementation: () => number): void { - nowImplementation = implementation; -} - -export function shouldYield() { - if ( - expectedNumberOfYields !== -1 && - yieldedValues.length >= expectedNumberOfYields - ) { - // We yielded at least as many values as expected. Stop rendering. - didStop = true; - return true; - } - // Keep rendering. - return false; -} - -export function flushAll(): Array { - yieldedValues = []; - while (scheduledCallback !== null) { - const cb = scheduledCallback; - scheduledCallback = null; - cb(); - } - const values = yieldedValues; - yieldedValues = []; - return values; -} - -export function flushNumberOfYields(count: number): Array { - expectedNumberOfYields = count; - didStop = false; - yieldedValues = []; - try { - while (scheduledCallback !== null && !didStop) { - const cb = scheduledCallback; - scheduledCallback = null; - cb(); - } - return yieldedValues; - } finally { - expectedNumberOfYields = -1; - didStop = false; - yieldedValues = []; - } -} - -export function yieldValue(value: mixed): void { - yieldedValues.push(value); -} - -export function clearYields(): Array { - const values = yieldedValues; - yieldedValues = []; - return values; -} diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererAsync-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererAsync-test.js index cf61a0c3759df..469504b1cc7ed 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRendererAsync-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererAsync-test.js @@ -12,12 +12,14 @@ let React; let ReactTestRenderer; +let Scheduler; describe('ReactTestRendererAsync', () => { beforeEach(() => { jest.resetModules(); React = require('react'); ReactTestRenderer = require('react-test-renderer'); + Scheduler = require('scheduler'); }); it('flushAll flushes all work', () => { @@ -46,7 +48,7 @@ describe('ReactTestRendererAsync', () => { it('flushAll returns array of yielded values', () => { function Child(props) { - ReactTestRenderer.unstable_yield(props.children); + Scheduler.yieldValue(props.children); return props.children; } function Parent(props) { @@ -72,7 +74,7 @@ describe('ReactTestRendererAsync', () => { it('flushThrough flushes until the expected values is yielded', () => { function Child(props) { - ReactTestRenderer.unstable_yield(props.children); + Scheduler.yieldValue(props.children); return props.children; } function Parent(props) { @@ -100,7 +102,7 @@ describe('ReactTestRendererAsync', () => { it('supports high priority interruptions', () => { function Child(props) { - ReactTestRenderer.unstable_yield(props.children); + Scheduler.yieldValue(props.children); return props.children; } @@ -141,7 +143,7 @@ describe('ReactTestRendererAsync', () => { describe('Jest matchers', () => { it('toFlushAndYieldThrough', () => { const Yield = ({id}) => { - ReactTestRenderer.unstable_yield(id); + Scheduler.yieldValue(id); return id; }; @@ -163,7 +165,7 @@ describe('ReactTestRendererAsync', () => { it('toFlushAndYield', () => { const Yield = ({id}) => { - ReactTestRenderer.unstable_yield(id); + Scheduler.yieldValue(id); return id; }; @@ -197,7 +199,7 @@ describe('ReactTestRendererAsync', () => { it('toFlushAndThrow', () => { const Yield = ({id}) => { - ReactTestRenderer.unstable_yield(id); + Scheduler.yieldValue(id); return id; }; @@ -254,7 +256,7 @@ describe('ReactTestRendererAsync', () => { it('toHaveYielded', () => { const Yield = ({id}) => { - ReactTestRenderer.unstable_yield(id); + Scheduler.yieldValue(id); return id; }; @@ -278,7 +280,7 @@ describe('ReactTestRendererAsync', () => { const renderer = ReactTestRenderer.create(
, { unstable_isConcurrent: true, }); - ReactTestRenderer.unstable_yield('Something'); + Scheduler.yieldValue('Something'); expect(() => expect(renderer).toFlushWithoutYielding()).toThrow( 'Log of yielded values is not empty.', ); diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index 4be4de71c3456..1413c40edddda 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -16,9 +16,7 @@ let ReactNoop; let Scheduler; let ReactCache; let ReactTestRenderer; -let advanceTimeBy; let SchedulerTracing; -let mockNow; let AdvanceTime; let AsyncText; let Text; @@ -31,12 +29,6 @@ function loadModules({ replayFailedUnitOfWorkWithInvokeGuardedCallback = false, useNoopRenderer = false, } = {}) { - let currentTime = 0; - - mockNow = jest.fn().mockImplementation(() => currentTime); - - global.Date.now = mockNow; - ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.debugRenderPhaseSideEffects = false; ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; @@ -55,13 +47,8 @@ function loadModules({ } else { ReactNoop = null; ReactTestRenderer = require('react-test-renderer'); - ReactTestRenderer.unstable_setNowImplementation(mockNow); } - advanceTimeBy = amount => { - currentTime += amount; - }; - AdvanceTime = class extends React.Component { static defaultProps = { byAmount: 10, @@ -72,25 +59,17 @@ function loadModules({ } render() { // Simulate time passing when this component is rendered - advanceTimeBy(this.props.byAmount); + Scheduler.advanceTime(this.props.byAmount); return this.props.children || null; } }; resourcePromise = null; - function yieldForRenderer(value) { - if (ReactNoop) { - ReactNoop.yield(value); - } else { - ReactTestRenderer.unstable_yield(value); - } - } - TextResource = ReactCache.unstable_createResource(([text, ms = 0]) => { resourcePromise = new Promise((resolve, reject) => setTimeout(() => { - yieldForRenderer(`Promise resolved [${text}]`); + Scheduler.yieldValue(`Promise resolved [${text}]`); resolve(text); }, ms), ); @@ -100,20 +79,20 @@ function loadModules({ AsyncText = ({ms, text}) => { try { TextResource.read([text, ms]); - yieldForRenderer(`AsyncText [${text}]`); + Scheduler.yieldValue(`AsyncText [${text}]`); return text; } catch (promise) { if (typeof promise.then === 'function') { - yieldForRenderer(`Suspend [${text}]`); + Scheduler.yieldValue(`Suspend [${text}]`); } else { - yieldForRenderer(`Error [${text}]`); + Scheduler.yieldValue(`Error [${text}]`); } throw promise; } }; Text = ({text}) => { - yieldForRenderer(`Text [${text}]`); + Scheduler.yieldValue(`Text [${text}]`); return text; }; } @@ -258,7 +237,7 @@ describe('Profiler', () => { const callback = jest.fn(); const Yield = ({value}) => { - ReactTestRenderer.unstable_yield(value); + Scheduler.yieldValue(value); return null; }; @@ -280,6 +259,28 @@ describe('Profiler', () => { }); it('does not record times for components outside of Profiler tree', () => { + // Mock the Scheduler module so we can track how many times the current + // time is read + jest.mock('scheduler/unstable_mock', obj => { + const ActualScheduler = require.requireActual( + 'scheduler/unstable_mock', + ); + return { + ...ActualScheduler, + unstable_now: function mockUnstableNow() { + ActualScheduler.yieldValue('read current time'); + return ActualScheduler.unstable_now(); + }, + }; + }); + + jest.resetModules(); + + loadModules({enableSchedulerTracing}); + + // Clear yields in case the current time is read during initialization. + Scheduler.unstable_clearYields(); + ReactTestRenderer.create(
@@ -294,13 +295,19 @@ describe('Profiler', () => { // 2. To compute the update expiration time // 3. To record the commit time // No additional calls from ProfilerTimer are expected. - expect(mockNow).toHaveBeenCalledTimes(2); + expect(Scheduler).toHaveYielded([ + 'read current time', + 'read current time', + ]); + + // Remove mock + jest.unmock('scheduler/unstable_mock'); }); it('logs render times for both mount and update', () => { const callback = jest.fn(); - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 const renderer = ReactTestRenderer.create( @@ -323,7 +330,7 @@ describe('Profiler', () => { callback.mockReset(); - advanceTimeBy(20); // 15 -> 35 + Scheduler.advanceTime(20); // 15 -> 35 renderer.update( @@ -346,7 +353,7 @@ describe('Profiler', () => { callback.mockReset(); - advanceTimeBy(20); // 45 -> 65 + Scheduler.advanceTime(20); // 45 -> 65 renderer.update( @@ -371,7 +378,7 @@ describe('Profiler', () => { it('includes render times of nested Profilers in their parent times', () => { const callback = jest.fn(); - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 ReactTestRenderer.create( @@ -406,7 +413,7 @@ describe('Profiler', () => { it('traces sibling Profilers separately', () => { const callback = jest.fn(); - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 ReactTestRenderer.create( @@ -439,7 +446,7 @@ describe('Profiler', () => { it('does not include time spent outside of profile root', () => { const callback = jest.fn(); - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 ReactTestRenderer.create( @@ -514,7 +521,7 @@ describe('Profiler', () => { it('decreases actual time but not base time when sCU prevents an update', () => { const callback = jest.fn(); - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 const renderer = ReactTestRenderer.create( @@ -526,7 +533,7 @@ describe('Profiler', () => { expect(callback).toHaveBeenCalledTimes(1); - advanceTimeBy(30); // 28 -> 58 + Scheduler.advanceTime(30); // 28 -> 58 renderer.update( @@ -557,22 +564,22 @@ describe('Profiler', () => { class WithLifecycles extends React.Component { state = {}; static getDerivedStateFromProps() { - advanceTimeBy(3); + Scheduler.advanceTime(3); return null; } shouldComponentUpdate() { - advanceTimeBy(7); + Scheduler.advanceTime(7); return true; } render() { - advanceTimeBy(5); + Scheduler.advanceTime(5); return null; } } const callback = jest.fn(); - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 const renderer = ReactTestRenderer.create( @@ -580,7 +587,7 @@ describe('Profiler', () => { , ); - advanceTimeBy(15); // 13 -> 28 + Scheduler.advanceTime(15); // 13 -> 28 renderer.update( @@ -610,12 +617,12 @@ describe('Profiler', () => { const callback = jest.fn(); const Yield = ({renderTime}) => { - advanceTimeBy(renderTime); - ReactTestRenderer.unstable_yield('Yield:' + renderTime); + Scheduler.advanceTime(renderTime); + Scheduler.yieldValue('Yield:' + renderTime); return null; }; - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 // Render partially, but run out of time before completing. const renderer = ReactTestRenderer.create( @@ -644,12 +651,12 @@ describe('Profiler', () => { const callback = jest.fn(); const Yield = ({renderTime}) => { - advanceTimeBy(renderTime); - ReactTestRenderer.unstable_yield('Yield:' + renderTime); + Scheduler.advanceTime(renderTime); + Scheduler.yieldValue('Yield:' + renderTime); return null; }; - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 // Render partially, but don't finish. // This partial render should take 5ms of simulated time. @@ -667,7 +674,7 @@ describe('Profiler', () => { expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. - advanceTimeBy(50); // 10 -> 60 + Scheduler.advanceTime(50); // 10 -> 60 // Flush the remaining work, // Which should take an additional 10ms of simulated time. @@ -694,12 +701,12 @@ describe('Profiler', () => { const callback = jest.fn(); const Yield = ({renderTime}) => { - advanceTimeBy(renderTime); - ReactTestRenderer.unstable_yield('Yield:' + renderTime); + Scheduler.advanceTime(renderTime); + Scheduler.yieldValue('Yield:' + renderTime); return null; }; - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 // Render a partially update, but don't finish. // This partial render should take 10ms of simulated time. @@ -714,7 +721,7 @@ describe('Profiler', () => { expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. - advanceTimeBy(100); // 15 -> 115 + Scheduler.advanceTime(100); // 15 -> 115 // Interrupt with higher priority work. // The interrupted work simulates an additional 5ms of time. @@ -747,12 +754,12 @@ describe('Profiler', () => { const callback = jest.fn(); const Yield = ({renderTime}) => { - advanceTimeBy(renderTime); - ReactTestRenderer.unstable_yield('Yield:' + renderTime); + Scheduler.advanceTime(renderTime); + Scheduler.yieldValue('Yield:' + renderTime); return null; }; - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 const renderer = ReactTestRenderer.create( @@ -774,7 +781,7 @@ describe('Profiler', () => { callback.mockReset(); - advanceTimeBy(30); // 26 -> 56 + Scheduler.advanceTime(30); // 26 -> 56 // Render a partially update, but don't finish. // This partial render should take 3ms of simulated time. @@ -789,14 +796,14 @@ describe('Profiler', () => { expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. - advanceTimeBy(100); // 59 -> 159 + Scheduler.advanceTime(100); // 59 -> 159 // Render another 5ms of simulated time. expect(renderer).toFlushAndYieldThrough(['Yield:5']); expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. - advanceTimeBy(100); // 164 -> 264 + Scheduler.advanceTime(100); // 164 -> 264 // Interrupt with higher priority work. // The interrupted work simulates an additional 11ms of time. @@ -828,8 +835,8 @@ describe('Profiler', () => { const callback = jest.fn(); const Yield = ({renderTime}) => { - advanceTimeBy(renderTime); - ReactTestRenderer.unstable_yield('Yield:' + renderTime); + Scheduler.advanceTime(renderTime); + Scheduler.yieldValue('Yield:' + renderTime); return null; }; @@ -838,10 +845,8 @@ describe('Profiler', () => { state = {renderTime: 1}; render() { first = this; - advanceTimeBy(this.state.renderTime); - ReactTestRenderer.unstable_yield( - 'FirstComponent:' + this.state.renderTime, - ); + Scheduler.advanceTime(this.state.renderTime); + Scheduler.yieldValue('FirstComponent:' + this.state.renderTime); return ; } } @@ -850,15 +855,13 @@ describe('Profiler', () => { state = {renderTime: 2}; render() { second = this; - advanceTimeBy(this.state.renderTime); - ReactTestRenderer.unstable_yield( - 'SecondComponent:' + this.state.renderTime, - ); + Scheduler.advanceTime(this.state.renderTime); + Scheduler.yieldValue('SecondComponent:' + this.state.renderTime); return ; } } - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 const renderer = ReactTestRenderer.create( @@ -886,7 +889,7 @@ describe('Profiler', () => { callback.mockClear(); - advanceTimeBy(100); // 19 -> 119 + Scheduler.advanceTime(100); // 19 -> 119 // Render a partially update, but don't finish. // This partial render will take 10ms of actual render time. @@ -895,7 +898,7 @@ describe('Profiler', () => { expect(callback).toHaveBeenCalledTimes(0); // Simulate time moving forward while frame is paused. - advanceTimeBy(100); // 129 -> 229 + Scheduler.advanceTime(100); // 129 -> 229 // Interrupt with higher priority work. // This simulates a total of 37ms of actual render time. @@ -919,7 +922,7 @@ describe('Profiler', () => { callback.mockClear(); // Simulate time moving forward while frame is paused. - advanceTimeBy(100); // 266 -> 366 + Scheduler.advanceTime(100); // 266 -> 366 // Resume the original low priority update, with rebased state. // This simulates a total of 14ms of actual render time, @@ -955,7 +958,7 @@ describe('Profiler', () => { const callback = jest.fn(); const ThrowsError = () => { - advanceTimeBy(3); + Scheduler.advanceTime(3); throw Error('expected error'); }; @@ -965,7 +968,7 @@ describe('Profiler', () => { this.setState({error}); } render() { - advanceTimeBy(2); + Scheduler.advanceTime(2); return this.state.error === null ? ( this.props.children ) : ( @@ -974,7 +977,7 @@ describe('Profiler', () => { } } - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 ReactTestRenderer.create( @@ -1034,7 +1037,7 @@ describe('Profiler', () => { const callback = jest.fn(); const ThrowsError = () => { - advanceTimeBy(10); + Scheduler.advanceTime(10); throw Error('expected error'); }; @@ -1044,7 +1047,7 @@ describe('Profiler', () => { return {error}; } render() { - advanceTimeBy(2); + Scheduler.advanceTime(2); return this.state.error === null ? ( this.props.children ) : ( @@ -1053,7 +1056,7 @@ describe('Profiler', () => { } } - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 ReactTestRenderer.create( @@ -1133,7 +1136,7 @@ describe('Profiler', () => { it('reflects the most recently rendered id value', () => { const callback = jest.fn(); - advanceTimeBy(5); // 0 -> 5 + Scheduler.advanceTime(5); // 0 -> 5 const renderer = ReactTestRenderer.create( @@ -1143,7 +1146,7 @@ describe('Profiler', () => { expect(callback).toHaveBeenCalledTimes(1); - advanceTimeBy(20); // 7 -> 27 + Scheduler.advanceTime(20); // 7 -> 27 renderer.update( @@ -1181,11 +1184,11 @@ describe('Profiler', () => { class ClassComponent extends React.Component { componentDidMount() { - advanceTimeBy(5); + Scheduler.advanceTime(5); classComponentMounted = true; } render() { - advanceTimeBy(2); + Scheduler.advanceTime(2); return null; } } @@ -1331,7 +1334,7 @@ describe('Profiler', () => { describe('error handling', () => { it('should cover errors thrown in onWorkScheduled', () => { function Component({children}) { - ReactTestRenderer.unstable_yield('Component:' + children); + Scheduler.yieldValue('Component:' + children); return children; } @@ -1340,11 +1343,15 @@ describe('Profiler', () => { // Errors that happen inside of a subscriber should throw, throwInOnWorkScheduled = true; expect(() => { - SchedulerTracing.unstable_trace('event', mockNow(), () => { - renderer = ReactTestRenderer.create(fail, { - unstable_isConcurrent: true, - }); - }); + SchedulerTracing.unstable_trace( + 'event', + Scheduler.unstable_now(), + () => { + renderer = ReactTestRenderer.create(fail, { + unstable_isConcurrent: true, + }); + }, + ); }).toThrow('Expected error onWorkScheduled'); throwInOnWorkScheduled = false; expect(onWorkScheduled).toHaveBeenCalled(); @@ -1361,16 +1368,20 @@ describe('Profiler', () => { it('should cover errors thrown in onWorkStarted', () => { function Component({children}) { - ReactTestRenderer.unstable_yield('Component:' + children); + Scheduler.yieldValue('Component:' + children); return children; } let renderer; - SchedulerTracing.unstable_trace('event', mockNow(), () => { - renderer = ReactTestRenderer.create(text, { - unstable_isConcurrent: true, - }); - }); + SchedulerTracing.unstable_trace( + 'event', + Scheduler.unstable_now(), + () => { + renderer = ReactTestRenderer.create(text, { + unstable_isConcurrent: true, + }); + }, + ); onWorkStarted.mockClear(); // Errors that happen inside of a subscriber should throw, @@ -1389,22 +1400,26 @@ describe('Profiler', () => { it('should cover errors thrown in onWorkStopped', () => { function Component({children}) { - ReactTestRenderer.unstable_yield('Component:' + children); + Scheduler.yieldValue('Component:' + children); return children; } let renderer; - SchedulerTracing.unstable_trace('event', mockNow(), () => { - renderer = ReactTestRenderer.create(text, { - unstable_isConcurrent: true, - }); - }); + SchedulerTracing.unstable_trace( + 'event', + Scheduler.unstable_now(), + () => { + renderer = ReactTestRenderer.create(text, { + unstable_isConcurrent: true, + }); + }, + ); expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); // Errors that happen in an on-stopped callback, throwInOnWorkStopped = true; expect(() => { - renderer.unstable_flushAll(['Component:text']); + expect(Scheduler).toFlushAndYield(['Component:text']); }).toThrow('Expected error onWorkStopped'); throwInOnWorkStopped = false; expect(onWorkStopped).toHaveBeenCalledTimes(2); @@ -1420,34 +1435,41 @@ describe('Profiler', () => { it('should cover errors thrown in onInteractionScheduledWorkCompleted', () => { function Component({children}) { - ReactTestRenderer.unstable_yield('Component:' + children); + Scheduler.yieldValue('Component:' + children); return children; } const eventOne = { id: 0, name: 'event one', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; const eventTwo = { id: 1, name: 'event two', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; - let renderer; - SchedulerTracing.unstable_trace(eventOne.name, mockNow(), () => { - SchedulerTracing.unstable_trace(eventTwo.name, mockNow(), () => { - renderer = ReactTestRenderer.create(text, { - unstable_isConcurrent: true, - }); - }); - }); + SchedulerTracing.unstable_trace( + eventOne.name, + Scheduler.unstable_now(), + () => { + SchedulerTracing.unstable_trace( + eventTwo.name, + Scheduler.unstable_now(), + () => { + ReactTestRenderer.create(text, { + unstable_isConcurrent: true, + }); + }, + ); + }, + ); expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); throwInOnInteractionScheduledWorkCompleted = true; expect(() => { - renderer.unstable_flushAll(['Component:text']); + expect(Scheduler).toFlushAndYield(['Component:text']); }).toThrow('Expected error onInteractionScheduledWorkCompleted'); // Even though an error is thrown for one completed interaction, @@ -1465,7 +1487,7 @@ describe('Profiler', () => { }); let interaction; - SchedulerTracing.unstable_trace('event', mockNow(), () => { + SchedulerTracing.unstable_trace('event', Scheduler.unstable_now(), () => { const interactions = SchedulerTracing.unstable_getCurrent(); expect(interactions.size).toBe(1); interaction = Array.from(interactions)[0]; @@ -1488,8 +1510,8 @@ describe('Profiler', () => { let instance = null; const Yield = ({duration = 10, value}) => { - advanceTimeBy(duration); - ReactTestRenderer.unstable_yield(value); + Scheduler.advanceTime(duration); + Scheduler.yieldValue(value); return null; }; @@ -1509,19 +1531,19 @@ describe('Profiler', () => { } } - advanceTimeBy(1); + Scheduler.advanceTime(1); const interactionCreation = { id: 0, name: 'creation event', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; const onRender = jest.fn(); let renderer; SchedulerTracing.unstable_trace( interactionCreation.name, - mockNow(), + Scheduler.unstable_now(), () => { renderer = ReactTestRenderer.create( @@ -1555,11 +1577,11 @@ describe('Profiler', () => { expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true); // Mount - renderer.unstable_flushAll(['first', 'last']); + expect(Scheduler).toFlushAndYield(['first', 'last']); expect(onRender).toHaveBeenCalledTimes(1); let call = onRender.mock.calls[0]; expect(call[0]).toEqual('test-profiler'); - expect(call[5]).toEqual(mockNow()); + expect(call[5]).toEqual(Scheduler.unstable_now()); if (ReactFeatureFlags.enableSchedulerTracing) { expect(call[6]).toMatchInteractions([interactionCreation]); } @@ -1583,70 +1605,74 @@ describe('Profiler', () => { onWorkStarted.mockClear(); onWorkStopped.mockClear(); - advanceTimeBy(3); + Scheduler.advanceTime(3); let didRunCallback = false; const interactionOne = { id: 1, name: 'initial event', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; - SchedulerTracing.unstable_trace(interactionOne.name, mockNow(), () => { - instance.setState({count: 1}); - - // Update state again to verify our traced interaction isn't registered twice - instance.setState({count: 2}); - - // The scheduler/tracing package will notify of work started for the default thread, - // But React shouldn't notify until it's been flushed. - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); - - // Work may have been scheduled multiple times. - // We only care that the subscriber was notified at least once. - // As for the thread ID- the actual value isn't important, only that there was one. - expect(onWorkScheduled).toHaveBeenCalled(); - expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([ - interactionOne, - ]); - expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true); - - expect(renderer).toFlushAndYieldThrough(['first']); - expect(onRender).not.toHaveBeenCalled(); - - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interactionOne, - ); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1); - expect(getWorkForReactThreads(onWorkStarted)[0][0]).toMatchInteractions( - [interactionOne], - ); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); + SchedulerTracing.unstable_trace( + interactionOne.name, + Scheduler.unstable_now(), + () => { + instance.setState({count: 1}); + + // Update state again to verify our traced interaction isn't registered twice + instance.setState({count: 2}); + + // The scheduler/tracing package will notify of work started for the default thread, + // But React shouldn't notify until it's been flushed. + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0); + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); + + // Work may have been scheduled multiple times. + // We only care that the subscriber was notified at least once. + // As for the thread ID- the actual value isn't important, only that there was one. + expect(onWorkScheduled).toHaveBeenCalled(); + expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([ + interactionOne, + ]); + expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true); - renderer.unstable_flushAll(['last']); - expect(onRender).toHaveBeenCalledTimes(1); + expect(renderer).toFlushAndYieldThrough(['first']); + expect(onRender).not.toHaveBeenCalled(); - call = onRender.mock.calls[0]; - expect(call[0]).toEqual('test-profiler'); - expect(call[5]).toEqual(mockNow()); - if (ReactFeatureFlags.enableSchedulerTracing) { - expect(call[6]).toMatchInteractions([interactionOne]); - } + expect(onInteractionTraced).toHaveBeenCalledTimes(2); + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( + interactionOne, + ); + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1); + expect( + getWorkForReactThreads(onWorkStarted)[0][0], + ).toMatchInteractions([interactionOne]); + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); + + expect(Scheduler).toFlushAndYield(['last']); + expect(onRender).toHaveBeenCalledTimes(1); + + call = onRender.mock.calls[0]; + expect(call[0]).toEqual('test-profiler'); + expect(call[5]).toEqual(Scheduler.unstable_now()); + if (ReactFeatureFlags.enableSchedulerTracing) { + expect(call[6]).toMatchInteractions([interactionOne]); + } - didRunCallback = true; + didRunCallback = true; - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1); - expect(getWorkForReactThreads(onWorkStarted)[0][0]).toMatchInteractions( - [interactionOne], - ); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1); - expect(getWorkForReactThreads(onWorkStopped)[0][0]).toMatchInteractions( - [interactionOne], - ); - }); + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1); + expect( + getWorkForReactThreads(onWorkStarted)[0][0], + ).toMatchInteractions([interactionOne]); + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1); + expect( + getWorkForReactThreads(onWorkStopped)[0][0], + ).toMatchInteractions([interactionOne]); + }, + ); expect(didRunCallback).toBe(true); @@ -1655,16 +1681,16 @@ describe('Profiler', () => { onWorkStarted.mockClear(); onWorkStopped.mockClear(); - advanceTimeBy(17); + Scheduler.advanceTime(17); // Verify that updating state again does not re-log our interaction. instance.setState({count: 3}); - renderer.unstable_flushAll(['first', 'last']); + expect(Scheduler).toFlushAndYield(['first', 'last']); expect(onRender).toHaveBeenCalledTimes(1); call = onRender.mock.calls[0]; expect(call[0]).toEqual('test-profiler'); - expect(call[5]).toEqual(mockNow()); + expect(call[5]).toEqual(Scheduler.unstable_now()); if (ReactFeatureFlags.enableSchedulerTracing) { expect(call[6]).toMatchInteractions([]); } @@ -1679,21 +1705,25 @@ describe('Profiler', () => { onRender.mockClear(); - advanceTimeBy(3); + Scheduler.advanceTime(3); // Verify that root updates are also associated with traced events. const interactionTwo = { id: 2, name: 'root update event', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; - SchedulerTracing.unstable_trace(interactionTwo.name, mockNow(), () => { - renderer.update( - - - , - ); - }); + SchedulerTracing.unstable_trace( + interactionTwo.name, + Scheduler.unstable_now(), + () => { + renderer.update( + + + , + ); + }, + ); expect(onInteractionTraced).toHaveBeenCalledTimes(3); expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( @@ -1715,12 +1745,12 @@ describe('Profiler', () => { ]); expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true); - renderer.unstable_flushAll(['first', 'last']); + expect(Scheduler).toFlushAndYield(['first', 'last']); expect(onRender).toHaveBeenCalledTimes(1); call = onRender.mock.calls[0]; expect(call[0]).toEqual('test-profiler'); - expect(call[5]).toEqual(mockNow()); + expect(call[5]).toEqual(Scheduler.unstable_now()); if (ReactFeatureFlags.enableSchedulerTracing) { expect(call[6]).toMatchInteractions([interactionTwo]); } @@ -1748,7 +1778,7 @@ describe('Profiler', () => { state = {count: 0}; render() { first = this; - ReactTestRenderer.unstable_yield('FirstComponent'); + Scheduler.yieldValue('FirstComponent'); return null; } } @@ -1757,12 +1787,12 @@ describe('Profiler', () => { state = {count: 0}; render() { second = this; - ReactTestRenderer.unstable_yield('SecondComponent'); + Scheduler.yieldValue('SecondComponent'); return null; } } - advanceTimeBy(5); + Scheduler.advanceTime(5); const renderer = ReactTestRenderer.create( @@ -1773,128 +1803,132 @@ describe('Profiler', () => { ); // Initial mount. - renderer.unstable_flushAll(['FirstComponent', 'SecondComponent']); + expect(Scheduler).toFlushAndYield(['FirstComponent', 'SecondComponent']); expect(onInteractionTraced).not.toHaveBeenCalled(); expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); onRender.mockClear(); - advanceTimeBy(100); + Scheduler.advanceTime(100); const interactionLowPri = { id: 0, name: 'lowPri', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; - SchedulerTracing.unstable_trace(interactionLowPri.name, mockNow(), () => { - // Render a partially update, but don't finish. - first.setState({count: 1}); - - expect(onWorkScheduled).toHaveBeenCalled(); - expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([ - interactionLowPri, - ]); - - expect(renderer).toFlushAndYieldThrough(['FirstComponent']); - expect(onRender).not.toHaveBeenCalled(); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interactionLowPri, - ); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1); - expect(getWorkForReactThreads(onWorkStarted)[0][0]).toMatchInteractions( - [interactionLowPri], - ); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); - - advanceTimeBy(100); - - const interactionHighPri = { - id: 1, - name: 'highPri', - timestamp: mockNow(), - }; + SchedulerTracing.unstable_trace( + interactionLowPri.name, + Scheduler.unstable_now(), + () => { + // Render a partially update, but don't finish. + first.setState({count: 1}); - // Interrupt with higher priority work. - // This simulates a total of 37ms of actual render time. - renderer.unstable_flushSync(() => { - SchedulerTracing.unstable_trace( - interactionHighPri.name, - mockNow(), - () => { - second.setState({count: 1}); + expect(onWorkScheduled).toHaveBeenCalled(); + expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([ + interactionLowPri, + ]); - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interactionHighPri, - ); - expect( - onInteractionScheduledWorkCompleted, - ).not.toHaveBeenCalled(); + expect(renderer).toFlushAndYieldThrough(['FirstComponent']); + expect(onRender).not.toHaveBeenCalled(); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); - }, + expect(onInteractionTraced).toHaveBeenCalledTimes(1); + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( + interactionLowPri, ); - }); - expect(ReactTestRenderer).toHaveYielded(['SecondComponent']); - - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interactionHighPri); + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1); + expect( + getWorkForReactThreads(onWorkStarted)[0][0], + ).toMatchInteractions([interactionLowPri]); + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); + + Scheduler.advanceTime(100); + + const interactionHighPri = { + id: 1, + name: 'highPri', + timestamp: Scheduler.unstable_now(), + }; - // Verify the high priority update was associated with the high priority event. - expect(onRender).toHaveBeenCalledTimes(1); - let call = onRender.mock.calls[0]; - expect(call[0]).toEqual('test'); - expect(call[5]).toEqual(mockNow()); - expect(call[6]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing - ? [interactionLowPri, interactionHighPri] - : [], - ); + // Interrupt with higher priority work. + // This simulates a total of 37ms of actual render time. + renderer.unstable_flushSync(() => { + SchedulerTracing.unstable_trace( + interactionHighPri.name, + Scheduler.unstable_now(), + () => { + second.setState({count: 1}); + + expect(onInteractionTraced).toHaveBeenCalledTimes(2); + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( + interactionHighPri, + ); + expect( + onInteractionScheduledWorkCompleted, + ).not.toHaveBeenCalled(); - onRender.mockClear(); + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1); + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); + }, + ); + }); + expect(ReactTestRenderer).toHaveYielded(['SecondComponent']); + + expect(onInteractionTraced).toHaveBeenCalledTimes(2); + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); + expect( + onInteractionScheduledWorkCompleted, + ).toHaveBeenLastNotifiedOfInteraction(interactionHighPri); + + // Verify the high priority update was associated with the high priority event. + expect(onRender).toHaveBeenCalledTimes(1); + let call = onRender.mock.calls[0]; + expect(call[0]).toEqual('test'); + expect(call[5]).toEqual(Scheduler.unstable_now()); + expect(call[6]).toMatchInteractions( + ReactFeatureFlags.enableSchedulerTracing + ? [interactionLowPri, interactionHighPri] + : [], + ); - advanceTimeBy(100); + onRender.mockClear(); - // Resume the original low priority update, with rebased state. - // Verify the low priority update was retained. - renderer.unstable_flushAll(['FirstComponent']); - expect(onRender).toHaveBeenCalledTimes(1); - call = onRender.mock.calls[0]; - expect(call[0]).toEqual('test'); - expect(call[5]).toEqual(mockNow()); - expect(call[6]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing ? [interactionLowPri] : [], - ); + Scheduler.advanceTime(100); - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); + // Resume the original low priority update, with rebased state. + // Verify the low priority update was retained. + expect(Scheduler).toFlushAndYield(['FirstComponent']); + expect(onRender).toHaveBeenCalledTimes(1); + call = onRender.mock.calls[0]; + expect(call[0]).toEqual('test'); + expect(call[5]).toEqual(Scheduler.unstable_now()); + expect(call[6]).toMatchInteractions( + ReactFeatureFlags.enableSchedulerTracing ? [interactionLowPri] : [], + ); - // Work might be started multiple times before being completed. - // This is okay; it's part of the scheduler/tracing contract. - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(3); - expect(getWorkForReactThreads(onWorkStarted)[1][0]).toMatchInteractions( - [interactionLowPri, interactionHighPri], - ); - expect(getWorkForReactThreads(onWorkStarted)[2][0]).toMatchInteractions( - [interactionLowPri], - ); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2); - expect(getWorkForReactThreads(onWorkStopped)[0][0]).toMatchInteractions( - [interactionLowPri, interactionHighPri], - ); - expect(getWorkForReactThreads(onWorkStopped)[1][0]).toMatchInteractions( - [interactionLowPri], - ); - }); + expect(onInteractionTraced).toHaveBeenCalledTimes(2); + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); + + // Work might be started multiple times before being completed. + // This is okay; it's part of the scheduler/tracing contract. + expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(3); + expect( + getWorkForReactThreads(onWorkStarted)[1][0], + ).toMatchInteractions([interactionLowPri, interactionHighPri]); + expect( + getWorkForReactThreads(onWorkStarted)[2][0], + ).toMatchInteractions([interactionLowPri]); + expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2); + expect( + getWorkForReactThreads(onWorkStopped)[0][0], + ).toMatchInteractions([interactionLowPri, interactionHighPri]); + expect( + getWorkForReactThreads(onWorkStopped)[1][0], + ).toMatchInteractions([interactionLowPri]); + }, + ); expect(onInteractionTraced).toHaveBeenCalledTimes(2); expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2); @@ -1912,18 +1946,18 @@ describe('Profiler', () => { count: 0, }; componentDidMount() { - advanceTimeBy(10); // Advance timer to keep commits separate + Scheduler.advanceTime(10); // Advance timer to keep commits separate this.setState({count: 1}); // Intentional cascading update } componentDidUpdate(prevProps, prevState) { if (this.state.count === 2 && prevState.count === 1) { - advanceTimeBy(10); // Advance timer to keep commits separate + Scheduler.advanceTime(10); // Advance timer to keep commits separate this.setState({count: 3}); // Intentional cascading update } } render() { instance = this; - ReactTestRenderer.unstable_yield('Example:' + this.state.count); + Scheduler.yieldValue('Example:' + this.state.count); return null; } } @@ -1931,21 +1965,24 @@ describe('Profiler', () => { const interactionOne = { id: 0, name: 'componentDidMount test', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; // Initial mount. const onRender = jest.fn(); - let firstCommitTime = mockNow(); - let renderer; - SchedulerTracing.unstable_trace(interactionOne.name, mockNow(), () => { - renderer = ReactTestRenderer.create( - - - , - {unstable_isConcurrent: true}, - ); - }); + let firstCommitTime = Scheduler.unstable_now(); + SchedulerTracing.unstable_trace( + interactionOne.name, + Scheduler.unstable_now(), + () => { + ReactTestRenderer.create( + + + , + {unstable_isConcurrent: true}, + ); + }, + ); expect(onInteractionTraced).toHaveBeenCalledTimes(1); expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( @@ -1955,7 +1992,7 @@ describe('Profiler', () => { expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0); expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); - renderer.unstable_flushAll(['Example:0', 'Example:1']); + expect(Scheduler).toFlushAndYield(['Example:0', 'Example:1']); expect(onInteractionTraced).toHaveBeenCalledTimes(1); expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); @@ -1986,7 +2023,7 @@ describe('Profiler', () => { ); call = onRender.mock.calls[1]; expect(call[0]).toEqual('test'); - expect(call[5]).toEqual(mockNow()); + expect(call[5]).toEqual(Scheduler.unstable_now()); expect(call[6]).toMatchInteractions( ReactFeatureFlags.enableSchedulerTracing ? [interactionOne] : [], ); @@ -1996,13 +2033,17 @@ describe('Profiler', () => { const interactionTwo = { id: 1, name: 'componentDidUpdate test', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; // Cause an traced, async update - SchedulerTracing.unstable_trace(interactionTwo.name, mockNow(), () => { - instance.setState({count: 2}); - }); + SchedulerTracing.unstable_trace( + interactionTwo.name, + Scheduler.unstable_now(), + () => { + instance.setState({count: 2}); + }, + ); expect(onRender).not.toHaveBeenCalled(); expect(onInteractionTraced).toHaveBeenCalledTimes(2); expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( @@ -2012,12 +2053,12 @@ describe('Profiler', () => { expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(2); expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2); - advanceTimeBy(5); + Scheduler.advanceTime(5); // Flush async work (outside of traced scope) // This will cause an intentional cascading update from did-update - firstCommitTime = mockNow(); - renderer.unstable_flushAll(['Example:2', 'Example:3']); + firstCommitTime = Scheduler.unstable_now(); + expect(Scheduler).toFlushAndYield(['Example:2', 'Example:3']); expect(onInteractionTraced).toHaveBeenCalledTimes(2); expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2); @@ -2049,7 +2090,7 @@ describe('Profiler', () => { ); call = onRender.mock.calls[1]; expect(call[0]).toEqual('test'); - expect(call[5]).toEqual(mockNow()); + expect(call[5]).toEqual(Scheduler.unstable_now()); expect(call[6]).toMatchInteractions( ReactFeatureFlags.enableSchedulerTracing ? [interactionTwo] : [], ); @@ -2059,16 +2100,20 @@ describe('Profiler', () => { const interactionThree = { id: 2, name: 'setState callback test', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; // Cause a cascading update from the setState callback function callback() { instance.setState({count: 6}); } - SchedulerTracing.unstable_trace(interactionThree.name, mockNow(), () => { - instance.setState({count: 5}, callback); - }); + SchedulerTracing.unstable_trace( + interactionThree.name, + Scheduler.unstable_now(), + () => { + instance.setState({count: 5}, callback); + }, + ); expect(onRender).not.toHaveBeenCalled(); expect(onInteractionTraced).toHaveBeenCalledTimes(3); @@ -2081,8 +2126,8 @@ describe('Profiler', () => { // Flush async work (outside of traced scope) // This will cause an intentional cascading update from the setState callback - firstCommitTime = mockNow(); - renderer.unstable_flushAll(['Example:5', 'Example:6']); + firstCommitTime = Scheduler.unstable_now(); + expect(Scheduler).toFlushAndYield(['Example:5', 'Example:6']); expect(onInteractionTraced).toHaveBeenCalledTimes(3); expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(3); @@ -2114,7 +2159,7 @@ describe('Profiler', () => { ); call = onRender.mock.calls[1]; expect(call[0]).toEqual('test'); - expect(call[5]).toEqual(mockNow()); + expect(call[5]).toEqual(Scheduler.unstable_now()); expect(call[6]).toMatchInteractions( ReactFeatureFlags.enableSchedulerTracing ? [interactionThree] : [], ); @@ -2126,7 +2171,7 @@ describe('Profiler', () => { class Child extends React.Component { render() { - ReactTestRenderer.unstable_yield('Child:', this.props.count); + Scheduler.yieldValue('Child:' + this.props.count); return null; } } @@ -2145,23 +2190,27 @@ describe('Profiler', () => { } } - advanceTimeBy(1); + Scheduler.advanceTime(1); - const renderer = ReactTestRenderer.create(, { + ReactTestRenderer.create(, { unstable_isConcurrent: true, }); - renderer.unstable_flushAll(['Child:0']); + expect(Scheduler).toFlushAndYield(['Child:0']); onRender.mockClear(); const interaction = { id: 0, name: 'parent interaction', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; - SchedulerTracing.unstable_trace(interaction.name, mockNow(), () => { - parentInstance.setState({count: 1}); - }); + SchedulerTracing.unstable_trace( + interaction.name, + Scheduler.unstable_now(), + () => { + parentInstance.setState({count: 1}); + }, + ); expect(onInteractionTraced).toHaveBeenCalledTimes(1); expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( @@ -2172,7 +2221,7 @@ describe('Profiler', () => { expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); expect(onRender).not.toHaveBeenCalled(); - renderer.unstable_flushAll(['Child:1']); + expect(Scheduler).toFlushAndYield(['Child:1']); expect(onRender).toHaveBeenCalledTimes(1); let call = onRender.mock.calls[0]; expect(call[0]).toEqual('test-profiler'); @@ -2209,7 +2258,7 @@ describe('Profiler', () => { const interaction = { id: 0, name: 'initial render', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; const monkey = React.createRef(); @@ -2221,17 +2270,21 @@ describe('Profiler', () => { } const onRender = jest.fn(); - SchedulerTracing.unstable_trace(interaction.name, mockNow(), () => { - ReactNoop.render( - - }> - - - - - , - ); - }); + SchedulerTracing.unstable_trace( + interaction.name, + Scheduler.unstable_now(), + () => { + ReactNoop.render( + + }> + + + + + , + ); + }, + ); expect(onInteractionTraced).toHaveBeenCalledTimes(1); expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( @@ -2300,7 +2353,7 @@ describe('Profiler', () => { const interaction = { id: 0, name: 'initial render', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; const onRender = jest.fn(); @@ -2354,7 +2407,7 @@ describe('Profiler', () => { const interaction = { id: 0, name: 'initial render', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; const onRender = jest.fn(); @@ -2393,7 +2446,7 @@ describe('Profiler', () => { const interaction = { id: 0, name: 'initial render', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; const onRender = jest.fn(); @@ -2417,7 +2470,7 @@ describe('Profiler', () => { }, ); - advanceTimeBy(1500); + Scheduler.advanceTime(1500); await awaitableAdvanceTimers(1500); expect(renderer).toFlushAndYield([ @@ -2426,7 +2479,7 @@ describe('Profiler', () => { ]); expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - advanceTimeBy(2500); + Scheduler.advanceTime(2500); await awaitableAdvanceTimers(2500); expect(ReactTestRenderer).toHaveYielded(['Promise resolved [loaded]']); @@ -2441,16 +2494,15 @@ describe('Profiler', () => { const interaction = { id: 0, name: 'initial render', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; const onRender = jest.fn(); - let renderer; SchedulerTracing.unstable_trace( interaction.name, interaction.timestamp, () => { - renderer = ReactTestRenderer.create( + ReactTestRenderer.create( { ); }, ); - renderer.unstable_flushAll(); + expect(Scheduler).toFlushAndYield([ + 'Suspend [loaded]', + 'Text [loading]', + ]); expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); jest.advanceTimersByTime(1000); await resourcePromise; - renderer.unstable_flushAll(); + expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']); + expect(Scheduler).toFlushAndYield(['AsyncText [loaded]']); expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); expect( @@ -2480,7 +2536,7 @@ describe('Profiler', () => { const initialRenderInteraction = { id: 0, name: 'initial render', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; const onRender = jest.fn(); @@ -2513,7 +2569,7 @@ describe('Profiler', () => { const highPriUpdateInteraction = { id: 1, name: 'hiPriUpdate', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; const originalPromise = resourcePromise; @@ -2546,7 +2602,7 @@ describe('Profiler', () => { expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - advanceTimeBy(1000); + Scheduler.advanceTime(1000); jest.advanceTimersByTime(1000); await originalPromise; expect(renderer.toJSON()).toEqual(['loaded', 'updated']); @@ -2566,7 +2622,7 @@ describe('Profiler', () => { const initialRenderInteraction = { id: 0, name: 'initial render', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; const onRender = jest.fn(); @@ -2597,13 +2653,13 @@ describe('Profiler', () => { expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); expect(onRender).not.toHaveBeenCalled(); - advanceTimeBy(500); + Scheduler.advanceTime(500); jest.advanceTimersByTime(500); const highPriUpdateInteraction = { id: 1, name: 'hiPriUpdate', - timestamp: mockNow(), + timestamp: Scheduler.unstable_now(), }; const originalPromise = resourcePromise; @@ -2641,7 +2697,7 @@ describe('Profiler', () => { expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(0); - advanceTimeBy(500); + Scheduler.advanceTime(500); jest.advanceTimersByTime(500); await originalPromise; expect(ReactTestRenderer).toHaveYielded(['Promise resolved [loaded]']); diff --git a/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js b/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js index 3e486eefa36aa..e0bc879752840 100644 --- a/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js +++ b/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js @@ -14,11 +14,10 @@ describe('ReactProfiler DevTools integration', () => { let React; let ReactFeatureFlags; let ReactTestRenderer; + let Scheduler; let SchedulerTracing; let AdvanceTime; - let advanceTimeBy; let hook; - let mockNow; beforeEach(() => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook = { @@ -30,24 +29,14 @@ describe('ReactProfiler DevTools integration', () => { jest.resetModules(); - let currentTime = 0; - - mockNow = jest.fn().mockImplementation(() => currentTime); - - global.Date.now = mockNow; - ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.enableProfilerTimer = true; ReactFeatureFlags.enableSchedulerTracing = true; + Scheduler = require('scheduler'); SchedulerTracing = require('scheduler/tracing'); React = require('react'); ReactTestRenderer = require('react-test-renderer'); - ReactTestRenderer.unstable_setNowImplementation(mockNow); - advanceTimeBy = amount => { - currentTime += amount; - }; - AdvanceTime = class extends React.Component { static defaultProps = { byAmount: 10, @@ -58,7 +47,7 @@ describe('ReactProfiler DevTools integration', () => { } render() { // Simulate time passing when this component is rendered - advanceTimeBy(this.props.byAmount); + Scheduler.advanceTime(this.props.byAmount); return this.props.children || null; } }; @@ -66,7 +55,7 @@ describe('ReactProfiler DevTools integration', () => { it('should auto-Profile all fibers if the DevTools hook is detected', () => { const App = ({multiplier}) => { - advanceTimeBy(2); + Scheduler.advanceTime(2); return ( @@ -132,7 +121,7 @@ describe('ReactProfiler DevTools integration', () => { }); it('should reset the fiber stack correctly after an error when profiling host roots', () => { - advanceTimeBy(20); + Scheduler.advanceTime(20); const rendered = ReactTestRenderer.create(
@@ -140,7 +129,7 @@ describe('ReactProfiler DevTools integration', () => {
, ); - advanceTimeBy(20); + Scheduler.advanceTime(20); expect(() => { rendered.update( @@ -150,7 +139,7 @@ describe('ReactProfiler DevTools integration', () => { ); }).toThrow(); - advanceTimeBy(20); + Scheduler.advanceTime(20); // But this should render correctly, if the profiler's fiber stack has been reset. rendered.update( @@ -175,9 +164,9 @@ describe('ReactProfiler DevTools integration', () => { const root = rendered.root._currentFiber().return; expect(root.stateNode.memoizedInteractions).toContainNoInteractions(); - advanceTimeBy(10); + Scheduler.advanceTime(10); - const eventTime = mockNow(); + const eventTime = Scheduler.unstable_now(); // Render with an interaction SchedulerTracing.unstable_trace('some event', eventTime, () => { diff --git a/scripts/jest/matchers/reactTestMatchers.js b/scripts/jest/matchers/reactTestMatchers.js index 4007065e163e4..1e089db169d90 100644 --- a/scripts/jest/matchers/reactTestMatchers.js +++ b/scripts/jest/matchers/reactTestMatchers.js @@ -23,10 +23,7 @@ function resolveScheduler(obj) { if (obj._Scheduler !== undefined) { return obj._Scheduler; } - if (typeof obj.unstable_scheduleCallback === 'function') { - return obj; - } - return null; + return obj; } function assertYieldsWereCleared(Scheduler) { @@ -34,48 +31,33 @@ function assertYieldsWereCleared(Scheduler) { if (actualYields.length !== 0) { throw new Error( 'Log of yielded values is not empty. ' + - 'Call expect(ReactNoop).toHaveYielded(...) first.' + 'Call expect(Scheduler).toHaveYielded(...) first.' ); } } function toFlushAndYield(ReactNoop, expectedYields) { const Scheduler = resolveScheduler(ReactNoop); - if (Scheduler === null) { - return JestReact.unstable_toFlushAndYield(ReactNoop, expectedYields); - } return SchedulerMatchers.toFlushAndYield(Scheduler, expectedYields); } function toFlushAndYieldThrough(ReactNoop, expectedYields) { const Scheduler = resolveScheduler(ReactNoop); - if (Scheduler === null) { - return JestReact.unstable_toFlushAndYieldThrough(ReactNoop, expectedYields); - } return SchedulerMatchers.toFlushAndYieldThrough(Scheduler, expectedYields); } function toFlushWithoutYielding(ReactNoop) { const Scheduler = resolveScheduler(ReactNoop); - if (Scheduler === null) { - return JestReact.unstable_toFlushWithoutYielding(ReactNoop); - } return SchedulerMatchers.toFlushWithoutYielding(Scheduler); } function toHaveYielded(ReactNoop, expectedYields) { const Scheduler = resolveScheduler(ReactNoop); - if (Scheduler === null) { - return JestReact.unstable_toHaveYielded(ReactNoop, expectedYields); - } return SchedulerMatchers.toHaveYielded(Scheduler, expectedYields); } function toFlushAndThrow(ReactNoop, ...rest) { const Scheduler = resolveScheduler(ReactNoop); - if (Scheduler === null) { - return JestReact.unstable_toFlushAndThrow(ReactNoop, ...rest); - } return SchedulerMatchers.toFlushAndThrow(Scheduler, ...rest); } diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js index f86cceb09bae9..9d028262ba425 100644 --- a/scripts/rollup/forks.js +++ b/scripts/rollup/forks.js @@ -172,7 +172,8 @@ const forks = Object.freeze({ if ( entry === 'scheduler/unstable_mock' || entry === 'react-noop-renderer' || - entry === 'react-noop-renderer/persistent' + entry === 'react-noop-renderer/persistent' || + entry === 'react-test-renderer' ) { return 'scheduler/src/forks/SchedulerHostConfig.mock'; }