diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index b57dd8fb54387..8fdab3e563d60 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -1,6 +1,6 @@ { "packages": ["packages/react", "packages/react-dom", "packages/scheduler"], - "buildCommand": "build --type=NODE react/index,react-dom/index,react-dom/server,react-dom/test-utils,scheduler/index,scheduler/unstable_no_dom,scheduler/tracing,react/jsx-runtime,react/jsx-dev-runtime", + "buildCommand": "build --type=NODE react/index,react-dom/index,react-dom/server,react-dom/test-utils,scheduler/index,scheduler/unstable_no_dom,react/jsx-runtime,react/jsx-dev-runtime", "publishDirectory": { "react": "build/node_modules/react", "react-dom": "build/node_modules/react-dom", diff --git a/fixtures/tracing/index.html b/fixtures/tracing/index.html deleted file mode 100644 index 61b2af1a43da0..0000000000000 --- a/fixtures/tracing/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - Test tracing UMD - - - -

Test tracing UMD

-

- This fixture tests that the new tracing API is accessible via UMD build using the UMD shim. - It does not exhaustively test API functionality, only that the forwarded methods can be called. -

-

- Before running the tests below, check the console to make sure there are no errors. -

-

- Tests - -

-
    -
  1. - Test scheduler API -
  2. -
  3. - Test tracing API -
  4. -
  5. - Test tracing subscriptions API -
  6. -
  7. - Test end-to-end integration -
  8. -
- - - - - - - - - - - diff --git a/fixtures/tracing/script.js b/fixtures/tracing/script.js deleted file mode 100644 index 72a86c618d588..0000000000000 --- a/fixtures/tracing/script.js +++ /dev/null @@ -1,206 +0,0 @@ -function runTest(listItem, callback) { - try { - callback(); - listItem.className = 'correct'; - listItem.setAttribute('data-value', 'All checks pass'); - } catch (error) { - listItem.className = 'incorrect'; - listItem.setAttribute('data-value', error); - } -} - -function runAllTests() { - try { - checkSchedulerAPI(); - } finally { - try { - checkSchedulerTracingAPI(); - } finally { - try { - checkSchedulerTracingSubscriptionsAPI(); - } finally { - checkEndToEndIntegration(); - } - } - } -} - -function checkSchedulerAPI() { - runTest(document.getElementById('checkSchedulerAPI'), () => { - if ( - typeof Scheduler === 'undefined' || - typeof Scheduler.unstable_now !== 'function' || - typeof Scheduler.unstable_scheduleCallback !== 'function' || - typeof Scheduler.unstable_cancelCallback !== 'function' - ) { - throw 'API is not defined'; - } - - const abs = Math.abs(Scheduler.unstable_now() - performance.now()); - if (typeof abs !== 'number' || Number.isNaN(abs) || abs > 5) { - throw 'API does not work'; - } - - // There is no real way to verify that the two APIs are connected. - }); -} - -function checkSchedulerTracingAPI() { - runTest(document.getElementById('checkSchedulerTracingAPI'), () => { - if ( - typeof SchedulerTracing === 'undefined' || - typeof SchedulerTracing.unstable_clear !== 'function' || - typeof SchedulerTracing.unstable_getCurrent !== 'function' || - typeof SchedulerTracing.unstable_getThreadID !== 'function' || - typeof SchedulerTracing.unstable_trace !== 'function' || - typeof SchedulerTracing.unstable_wrap !== 'function' - ) { - throw 'API is not defined'; - } - - try { - let interactionsSet; - SchedulerTracing.unstable_trace('test', 123, () => { - interactionsSet = SchedulerTracing.unstable_getCurrent(); - }); - if (interactionsSet.size !== 1) { - throw null; - } - const interaction = Array.from(interactionsSet)[0]; - if (interaction.name !== 'test' || interaction.timestamp !== 123) { - throw null; - } - } catch (error) { - throw 'API does not work'; - } - - const ForwardedSchedulerTracing = - React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing; - - if ( - SchedulerTracing.unstable_getThreadID() === - ForwardedSchedulerTracing.unstable_getThreadID() - ) { - throw 'API forwarding is broken'; - } - }); -} - -function checkSchedulerTracingSubscriptionsAPI() { - runTest( - document.getElementById('checkSchedulerTracingSubscriptionsAPI'), - () => { - if ( - typeof SchedulerTracing === 'undefined' || - typeof SchedulerTracing.unstable_subscribe !== 'function' || - typeof SchedulerTracing.unstable_unsubscribe !== 'function' - ) { - throw 'API is not defined'; - } - - const onInteractionScheduledWorkCompletedCalls = []; - const onInteractionTracedCalls = []; - const onWorkCanceledCalls = []; - const onWorkScheduledCalls = []; - const onWorkStartedCalls = []; - const onWorkStoppedCalls = []; - const subscriber = { - onInteractionScheduledWorkCompleted: (...args) => - onInteractionScheduledWorkCompletedCalls.push(args), - onInteractionTraced: (...args) => onInteractionTracedCalls.push(args), - onWorkCanceled: (...args) => onWorkCanceledCalls.push(args), - onWorkScheduled: (...args) => onWorkScheduledCalls.push(args), - onWorkStarted: (...args) => onWorkStartedCalls.push(args), - onWorkStopped: (...args) => onWorkStoppedCalls.push(args), - }; - - try { - SchedulerTracing.unstable_subscribe(subscriber); - SchedulerTracing.unstable_trace('foo', 123, () => {}); - SchedulerTracing.unstable_unsubscribe(subscriber); - if (onInteractionTracedCalls.length !== 1) { - throw null; - } - const interaction = onInteractionTracedCalls[0][0]; - if (interaction.name !== 'foo' || interaction.timestamp !== 123) { - throw null; - } - SchedulerTracing.unstable_trace('bar', 456, () => {}); - if (onInteractionTracedCalls.length !== 1) { - throw null; - } - } catch (error) { - throw 'API does not forward methods'; - } - - const ForwardedSchedulerTracing = - React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED - .SchedulerTracing; - - try { - ForwardedSchedulerTracing.unstable_subscribe(subscriber); - SchedulerTracing.unstable_trace('foo', 123, () => {}); - ForwardedSchedulerTracing.unstable_trace('bar', 456, () => {}); - SchedulerTracing.unstable_unsubscribe(subscriber); - if (onInteractionTracedCalls.length !== 3) { - throw null; - } - const interactionFoo = onInteractionTracedCalls[1][0]; - const interactionBar = onInteractionTracedCalls[2][0]; - if ( - interactionFoo.name !== 'foo' || - interactionFoo.timestamp !== 123 || - interactionBar.name !== 'bar' || - interactionBar.timestamp !== 456 - ) { - throw null; - } - ForwardedSchedulerTracing.unstable_trace('baz', 789, () => {}); - if (onInteractionTracedCalls.length !== 3) { - throw null; - } - } catch (error) { - throw 'API forwarding is broken'; - } - } - ); -} - -function checkEndToEndIntegration() { - runTest(document.getElementById('checkEndToEndIntegration'), () => { - try { - const onRenderCalls = []; - const onRender = (...args) => onRenderCalls.push(args); - const container = document.createElement('div'); - - SchedulerTracing.unstable_trace('render', 123, () => { - ReactDOM.render( - React.createElement( - React.Profiler, - {id: 'profiler', onRender}, - React.createElement('div', null, 'hi') - ), - container - ); - }); - - if (container.textContent !== 'hi') { - throw null; - } - - if (onRenderCalls.length !== 1) { - throw null; - } - const call = onRenderCalls[0]; - if (call.length !== 7) { - throw null; - } - const interaction = Array.from(call[6])[0]; - if (interaction.name !== 'render' || interaction.timestamp !== 123) { - throw null; - } - } catch (error) { - throw 'End to end integration is broken'; - } - }); -} diff --git a/fixtures/tracing/test.html b/fixtures/tracing/test.html deleted file mode 100644 index df09e7b62d017..0000000000000 --- a/fixtures/tracing/test.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Test tracing UMD - - - -
- - - - - - - - diff --git a/fixtures/tracing/test.js b/fixtures/tracing/test.js deleted file mode 100644 index 18753a819300b..0000000000000 --- a/fixtures/tracing/test.js +++ /dev/null @@ -1,101 +0,0 @@ -const {createElement, Component, Suspense} = React; -const {createRoot} = ReactDOM; -const { - unstable_subscribe: subscribe, - unstable_trace: trace, - unstable_wrap: wrap, -} = SchedulerTracing; - -const createLogger = (backgroundColor, color, enabled) => ( - message, - ...args -) => { - if (enabled === false) return; - console.groupCollapsed( - `%c${message}`, - `background-color: ${backgroundColor}; color: ${color}; padding: 2px 4px;`, - ...args - ); - console.log( - new Error('stack').stack - .split('\n') - .slice(2) - .join('\n') - ); - console.groupEnd(); -}; - -window.log = { - app: createLogger('#37474f', '#fff'), - interaction: createLogger('#6a1b9a', '#fff'), - react: createLogger('#ff5722', '#fff'), - tracing: createLogger('#2962ff', '#fff'), - work: createLogger('#e1bee7', '#000'), -}; - -// Fake suspense -const resolvedValues = {}; -const read = key => { - if (!resolvedValues[key]) { - log.app(`Suspending for "${key}" ...`); - throw new Promise( - wrap(resolve => { - setTimeout( - wrap(() => { - log.app(`Loaded "${key}" ...`); - resolvedValues[key] = true; - resolve(key); - }), - 1000 - ); - }) - ); - } - return key; -}; - -const TestApp = () => - createElement( - Suspense, - {fallback: createElement(PlaceholderText)}, - createElement(SuspendingChild, {text: 'foo'}), - createElement(SuspendingChild, {text: 'bar'}), - createElement(SuspendingChild, {text: 'baz'}) - ); - -const PlaceholderText = () => 'Loading ...'; - -const SuspendingChild = ({text}) => { - const resolvedValue = read(text); - return resolvedValue; -}; - -subscribe({ - onInteractionScheduledWorkCompleted: interaction => - log.interaction( - 'onInteractionScheduledWorkCompleted', - JSON.stringify(interaction) - ), - onInteractionTraced: interaction => - log.interaction('onInteractionTraced', JSON.stringify(interaction)), - onWorkCanceled: interactions => - log.work('onWorkCanceled', JSON.stringify(Array.from(interactions))), - onWorkScheduled: interactions => - log.work('onWorkScheduled', JSON.stringify(Array.from(interactions))), - onWorkStarted: interactions => - log.work('onWorkStarted', JSON.stringify(Array.from(interactions))), - onWorkStopped: interactions => - log.work('onWorkStopped', JSON.stringify(Array.from(interactions))), -}); - -const element = document.getElementById('root'); -trace('initial_render', performance.now(), () => { - const root = createRoot(element); - log.app('render()'); - root.render( - createElement(TestApp), - wrap(() => { - log.app('committed'); - }) - ); -}); diff --git a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap index 9b94c71e6ed12..ad1d487ea790c 100644 --- a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap +++ b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap @@ -39,7 +39,6 @@ Object { 3 => 1, 5 => 1, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 16, @@ -87,7 +86,6 @@ Object { 3 => 3, 4 => 2, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 15, @@ -124,7 +122,6 @@ Object { 5 => 3, 3 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 18, @@ -188,7 +185,6 @@ Object { 4 => 1, 5 => 1, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 12, @@ -254,7 +250,6 @@ Object { 2 => 10, 1 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 25, @@ -302,7 +297,6 @@ Object { 2 => 10, 1 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 35, @@ -341,7 +335,6 @@ Object { 2 => 10, 1 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 45, @@ -451,7 +444,6 @@ Object { 1, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 12, @@ -556,7 +548,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 25, @@ -625,7 +616,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 35, @@ -676,7 +666,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 45, @@ -693,8 +682,6 @@ Object { ], "displayName": "Parent", "initialTreeBaseDurations": Array [], - "interactionCommits": Array [], - "interactions": Array [], "operations": Array [ Array [ 1, @@ -950,7 +937,6 @@ Object { 1, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 11, @@ -1037,7 +1023,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 22, @@ -1142,7 +1127,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 35, @@ -1159,8 +1143,6 @@ Object { ], "displayName": "Parent", "initialTreeBaseDurations": Array [], - "interactionCommits": Array [], - "interactions": Array [], "operations": Array [ Array [ 1, @@ -1352,7 +1334,6 @@ Object { 2 => 10, 1 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 13, @@ -1397,7 +1378,6 @@ Object { 2 => 10, 1 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 34, @@ -1433,7 +1413,6 @@ Object { 2 => 10, 1 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 44, @@ -1456,8 +1435,6 @@ Object { 4 => 1, 5 => 1, }, - "interactionCommits": Map {}, - "interactions": Map {}, "operations": Array [ Array [ 1, @@ -1619,7 +1596,6 @@ Object { 13 => 0, 14 => 1, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 24, @@ -1636,8 +1612,6 @@ Object { ], "displayName": "Parent", "initialTreeBaseDurations": Map {}, - "interactionCommits": Map {}, - "interactions": Map {}, "operations": Array [ Array [ 1, @@ -1712,7 +1686,6 @@ Object { "effectDuration": null, "fiberActualDurations": Map {}, "fiberSelfDurations": Map {}, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 34, @@ -1734,8 +1707,6 @@ Object { 8 => 0, 9 => 1, }, - "interactionCommits": Map {}, - "interactions": Map {}, "operations": Array [ Array [ 1, @@ -1890,7 +1861,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 13, @@ -1959,7 +1929,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 34, @@ -2010,7 +1979,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 44, @@ -2048,8 +2016,6 @@ Object { 1, ], ], - "interactionCommits": Array [], - "interactions": Array [], "operations": Array [ Array [ 1, @@ -2256,7 +2222,6 @@ Object { 1, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 24, @@ -2273,8 +2238,6 @@ Object { ], "displayName": "Parent", "initialTreeBaseDurations": Array [], - "interactionCommits": Array [], - "interactions": Array [], "operations": Array [ Array [ 1, @@ -2346,7 +2309,6 @@ Object { "effectDuration": null, "fiberActualDurations": Array [], "fiberSelfDurations": Array [], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 34, @@ -2380,8 +2342,6 @@ Object { 1, ], ], - "interactionCommits": Array [], - "interactions": Array [], "operations": Array [ Array [ 1, @@ -2470,7 +2430,6 @@ Object { 1 => 0, 2 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 0, @@ -2487,8 +2446,6 @@ Object { ], "displayName": "Suspense", "initialTreeBaseDurations": Map {}, - "interactionCommits": Map {}, - "interactions": Map {}, "operations": Array [ Array [ 1, @@ -2548,7 +2505,6 @@ Object { 2 => 0, 3 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 0, @@ -2589,7 +2545,6 @@ Object { 2 => 0, 1 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -2624,7 +2579,6 @@ Object { "fiberSelfDurations": Map { 3 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -2659,7 +2613,6 @@ Object { "fiberSelfDurations": Map { 3 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -2698,7 +2651,6 @@ Object { 2 => 0, 1 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -2762,7 +2714,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 0, @@ -2821,7 +2772,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -2862,7 +2812,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -2903,7 +2852,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -2960,7 +2908,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -2977,8 +2924,6 @@ Object { ], "displayName": "Component", "initialTreeBaseDurations": Array [], - "interactionCommits": Array [], - "interactions": Array [], "operations": Array [ Array [ 1, @@ -3125,7 +3070,6 @@ Object { 6 => 0, 7 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 0, @@ -3206,7 +3150,6 @@ Object { 3 => 0, 2 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -3283,7 +3226,6 @@ Object { 2 => 0, 1 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -3361,7 +3303,6 @@ Object { 2 => 0, 1 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -3438,7 +3379,6 @@ Object { 2 => 0, 1 => 0, }, - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -3574,7 +3514,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 0, @@ -3703,7 +3642,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -3834,7 +3772,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -3966,7 +3903,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -4097,7 +4033,6 @@ Object { 0, ], ], - "interactionIDs": Array [], "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, @@ -4114,8 +4049,6 @@ Object { ], "displayName": "LegacyContextProvider", "initialTreeBaseDurations": Array [], - "interactionCommits": Array [], - "interactions": Array [], "operations": Array [ Array [ 1, @@ -4325,341 +4258,3 @@ Object { "version": 5, } `; - -exports[`ProfilingCache should report every traced interaction: Interactions 1`] = ` -Array [ - Object { - "__count": 1, - "id": 0, - "name": "mount: one child", - "timestamp": 0, - }, - Object { - "__count": 0, - "id": 1, - "name": "update: two children", - "timestamp": 11, - }, -] -`; - -exports[`ProfilingCache should report every traced interaction: imported data 1`] = ` -Object { - "dataForRoots": Array [ - Object { - "commitData": Array [ - Object { - "changeDescriptions": Array [ - Array [ - 2, - Object { - "context": null, - "didHooksChange": false, - "isFirstMount": true, - "props": null, - "state": null, - }, - ], - Array [ - 3, - Object { - "context": null, - "didHooksChange": false, - "isFirstMount": true, - "props": null, - "state": null, - }, - ], - Array [ - 4, - Object { - "context": null, - "didHooksChange": false, - "isFirstMount": true, - "props": null, - "state": null, - }, - ], - ], - "duration": 11, - "effectDuration": null, - "fiberActualDurations": Array [ - Array [ - 1, - 11, - ], - Array [ - 2, - 11, - ], - Array [ - 3, - 0, - ], - Array [ - 4, - 1, - ], - ], - "fiberSelfDurations": Array [ - Array [ - 1, - 0, - ], - Array [ - 2, - 10, - ], - Array [ - 3, - 0, - ], - Array [ - 4, - 1, - ], - ], - "interactionIDs": Array [ - 0, - ], - "passiveEffectDuration": null, - "priorityLevel": "Normal", - "timestamp": 11, - "updaters": Array [ - Object { - "displayName": "Anonymous", - "hocDisplayNames": null, - "id": 1, - "key": null, - "type": 11, - }, - ], - }, - Object { - "changeDescriptions": Array [ - Array [ - 3, - Object { - "context": null, - "didHooksChange": false, - "isFirstMount": false, - "props": Array [], - "state": null, - }, - ], - Array [ - 5, - Object { - "context": null, - "didHooksChange": false, - "isFirstMount": true, - "props": null, - "state": null, - }, - ], - Array [ - 2, - Object { - "context": null, - "didHooksChange": false, - "isFirstMount": false, - "props": Array [ - "count", - ], - "state": null, - }, - ], - ], - "duration": 11, - "effectDuration": null, - "fiberActualDurations": Array [ - Array [ - 3, - 0, - ], - Array [ - 5, - 1, - ], - Array [ - 2, - 11, - ], - Array [ - 1, - 11, - ], - ], - "fiberSelfDurations": Array [ - Array [ - 3, - 0, - ], - Array [ - 5, - 1, - ], - Array [ - 2, - 10, - ], - Array [ - 1, - 0, - ], - ], - "interactionIDs": Array [ - 1, - ], - "passiveEffectDuration": null, - "priorityLevel": "Immediate", - "timestamp": 22, - "updaters": Array [ - Object { - "displayName": "Anonymous", - "hocDisplayNames": null, - "id": 1, - "key": null, - "type": 11, - }, - ], - }, - ], - "displayName": "Parent", - "initialTreeBaseDurations": Array [], - "interactionCommits": Array [ - Array [ - 0, - Array [ - 0, - ], - ], - Array [ - 1, - Array [ - 1, - ], - ], - ], - "interactions": Array [ - Array [ - 0, - Object { - "__count": 1, - "id": 0, - "name": "mount: one child", - "timestamp": 0, - }, - ], - Array [ - 1, - Object { - "__count": 0, - "id": 1, - "name": "update: two children", - "timestamp": 11, - }, - ], - ], - "operations": Array [ - Array [ - 1, - 1, - 15, - 6, - 80, - 97, - 114, - 101, - 110, - 116, - 5, - 67, - 104, - 105, - 108, - 100, - 1, - 48, - 1, - 1, - 11, - 1, - 1, - 4, - 1, - 11000, - 1, - 2, - 5, - 1, - 0, - 1, - 0, - 4, - 2, - 11000, - 1, - 3, - 5, - 2, - 2, - 2, - 3, - 4, - 3, - 0, - 1, - 4, - 8, - 2, - 2, - 2, - 0, - 4, - 4, - 1000, - ], - Array [ - 1, - 1, - 8, - 5, - 67, - 104, - 105, - 108, - 100, - 1, - 49, - 1, - 5, - 5, - 2, - 2, - 1, - 2, - 4, - 5, - 1000, - 4, - 2, - 12000, - 3, - 2, - 3, - 3, - 5, - 4, - 4, - 1, - 12000, - ], - ], - "rootID": 1, - "snapshots": Array [], - }, - ], - "version": 5, -} -`; diff --git a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCharts-test.js.snap b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCharts-test.js.snap index 5a497bafb6e9f..e118ef9da325d 100644 --- a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCharts-test.js.snap +++ b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCharts-test.js.snap @@ -255,48 +255,6 @@ Object { } `; -exports[`profiling charts interactions should contain valid data: Interactions 1`] = ` -Object { - "interactions": Array [ - Object { - "__count": 1, - "id": 0, - "name": "mount", - "timestamp": 0, - }, - Object { - "__count": 0, - "id": 1, - "name": "update", - "timestamp": 15, - }, - ], - "lastInteractionTime": 25, - "maxCommitDuration": 15, -} -`; - -exports[`profiling charts interactions should contain valid data: Interactions 2`] = ` -Object { - "interactions": Array [ - Object { - "__count": 1, - "id": 0, - "name": "mount", - "timestamp": 0, - }, - Object { - "__count": 0, - "id": 1, - "name": "update", - "timestamp": 15, - }, - ], - "lastInteractionTime": 25, - "maxCommitDuration": 15, -} -`; - exports[`profiling charts ranked chart should contain valid data: 0: CommitTree 1`] = ` Object { "nodes": Map { diff --git a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js index 84311c0d87975..0b7273054c32e 100644 --- a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js @@ -16,7 +16,6 @@ describe('ProfilingCache', () => { let React; let ReactDOM; let Scheduler; - let SchedulerTracing; let TestRenderer: ReactTestRenderer; let bridge: FrontendBridge; let store: Store; @@ -35,7 +34,6 @@ describe('ProfilingCache', () => { React = require('react'); ReactDOM = require('react-dom'); Scheduler = require('scheduler'); - SchedulerTracing = require('scheduler/tracing'); TestRenderer = utils.requireTestRenderer(); }); @@ -622,79 +620,6 @@ describe('ProfilingCache', () => { } }); - it('should report every traced interaction', () => { - const Parent = ({count}) => { - Scheduler.unstable_advanceTime(10); - const children = new Array(count) - .fill(true) - .map((_, index) => ); - return ( - - {children} - - - ); - }; - const Child = ({duration}) => { - Scheduler.unstable_advanceTime(duration); - return null; - }; - const MemoizedChild = React.memo(Child); - - const container = document.createElement('div'); - - utils.act(() => store.profilerStore.startProfiling()); - utils.act(() => - SchedulerTracing.unstable_trace( - 'mount: one child', - Scheduler.unstable_now(), - () => ReactDOM.render(, container), - ), - ); - utils.act(() => - SchedulerTracing.unstable_trace( - 'update: two children', - Scheduler.unstable_now(), - () => ReactDOM.render(, container), - ), - ); - utils.act(() => store.profilerStore.stopProfiling()); - - let interactions = null; - - function Validator({previousInteractions, rootID}) { - interactions = store.profilerStore.profilingCache.getInteractionsChartData( - { - rootID, - }, - ).interactions; - if (previousInteractions != null) { - expect(interactions).toEqual(previousInteractions); - } else { - expect(interactions).toMatchSnapshot('Interactions'); - } - return null; - } - - const rootID = store.roots[0]; - - utils.act(() => - TestRenderer.create( - , - ), - ); - - expect(interactions).not.toBeNull(); - - utils.exportImportHelper(bridge, store); - - utils.act(() => - TestRenderer.create( - , - ), - ); - }); - it('should handle unexpectedly shallow suspense trees', () => { const container = document.createElement('div'); diff --git a/packages/react-devtools-shared/src/__tests__/profilingCharts-test.js b/packages/react-devtools-shared/src/__tests__/profilingCharts-test.js index 802a7421575e6..b16e38f88c833 100644 --- a/packages/react-devtools-shared/src/__tests__/profilingCharts-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilingCharts-test.js @@ -14,7 +14,6 @@ describe('profiling charts', () => { let React; let ReactDOM; let Scheduler; - let SchedulerTracing; let TestRenderer: TestRendererType; let store: Store; let utils; @@ -30,7 +29,6 @@ describe('profiling charts', () => { React = require('react'); ReactDOM = require('react-dom'); Scheduler = require('scheduler'); - SchedulerTracing = require('scheduler/tracing'); TestRenderer = utils.requireTestRenderer(); }); @@ -56,18 +54,8 @@ describe('profiling charts', () => { const container = document.createElement('div'); utils.act(() => store.profilerStore.startProfiling()); - utils.act(() => - SchedulerTracing.unstable_trace('mount', Scheduler.unstable_now(), () => - ReactDOM.render(, container), - ), - ); - utils.act(() => - SchedulerTracing.unstable_trace( - 'update', - Scheduler.unstable_now(), - () => ReactDOM.render(, container), - ), - ); + utils.act(() => ReactDOM.render(, container)); + utils.act(() => ReactDOM.render(, container)); utils.act(() => store.profilerStore.stopProfiling()); let renderFinished = false; @@ -132,18 +120,8 @@ describe('profiling charts', () => { const container = document.createElement('div'); utils.act(() => store.profilerStore.startProfiling()); - utils.act(() => - SchedulerTracing.unstable_trace('mount', Scheduler.unstable_now(), () => - ReactDOM.render(, container), - ), - ); - utils.act(() => - SchedulerTracing.unstable_trace( - 'update', - Scheduler.unstable_now(), - () => ReactDOM.render(, container), - ), - ); + utils.act(() => ReactDOM.render(, container)); + utils.act(() => ReactDOM.render(, container)); utils.act(() => store.profilerStore.stopProfiling()); let renderFinished = false; @@ -181,69 +159,4 @@ describe('profiling charts', () => { } }); }); - - describe('interactions', () => { - it('should contain valid data', () => { - const Parent = (_: {||}) => { - Scheduler.unstable_advanceTime(10); - return ( - - - - - - ); - }; - - // Memoize children to verify that chart doesn't include in the update. - const Child = React.memo(function Child({duration}) { - Scheduler.unstable_advanceTime(duration); - return null; - }); - - const container = document.createElement('div'); - - utils.act(() => store.profilerStore.startProfiling()); - utils.act(() => - SchedulerTracing.unstable_trace('mount', Scheduler.unstable_now(), () => - ReactDOM.render(, container), - ), - ); - utils.act(() => - SchedulerTracing.unstable_trace( - 'update', - Scheduler.unstable_now(), - () => ReactDOM.render(, container), - ), - ); - utils.act(() => store.profilerStore.stopProfiling()); - - let renderFinished = false; - - function Validator({commitIndex, rootID}) { - const chartData = store.profilerStore.profilingCache.getInteractionsChartData( - { - rootID, - }, - ); - expect(chartData).toMatchSnapshot('Interactions'); - renderFinished = true; - return null; - } - - const rootID = store.roots[0]; - - for (let commitIndex = 0; commitIndex < 2; commitIndex++) { - renderFinished = false; - - utils.act(() => { - TestRenderer.create( - , - ); - }); - - expect(renderFinished).toBe(true); - } - }); - }); }); diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 807bbde0c7997..c1172bfbe1267 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -102,7 +102,6 @@ import type { SerializedElement, WorkTagMap, } from './types'; -import type {Interaction} from 'react-devtools-shared/src/devtools/views/Profiler/types'; import type { ComponentFilter, ElementType, @@ -2136,6 +2135,22 @@ export function attach( // We don't patch any methods so there is no cleanup. } + function rootSupportsProfiling(root) { + if (root.memoizedInteractions != null) { + // v16 builds include this field for the scheduler/tracing API. + return true; + } else if ( + root.current != null && + root.current.hasOwnProperty('treeBaseDuration') + ) { + // The scheduler/tracing API was removed in v17 though + // so we need to check a non-root Fiber. + return true; + } else { + return false; + } + } + function flushInitialOperations() { const localPendingOperationsQueue = pendingOperationsQueue; @@ -2161,21 +2176,14 @@ export function attach( currentRootID = getFiberID(getPrimaryFiber(root.current)); setRootPseudoKey(currentRootID, root.current); - // Checking root.memoizedInteractions handles multi-renderer edge-case- - // where some v16 renderers support profiling and others don't. - if (isProfiling && root.memoizedInteractions != null) { - // If profiling is active, store commit time and duration, and the current interactions. + // Handle multi-renderer edge-case where only some v16 renderers support profiling. + if (isProfiling && rootSupportsProfiling(root)) { + // If profiling is active, store commit time and duration. // The frontend may request this information after profiling has stopped. currentCommitProfilingMetadata = { changeDescriptions: recordChangeDescriptions ? new Map() : null, durations: [], commitTime: getCurrentTime() - profilingStartTime, - interactions: Array.from(root.memoizedInteractions).map( - (interaction: Interaction) => ({ - ...interaction, - timestamp: interaction.timestamp - profilingStartTime, - }), - ), maxActualDuration: 0, priorityLevel: null, updaters: getUpdatersList(root), @@ -2205,8 +2213,7 @@ export function attach( } function handlePostCommitFiberRoot(root) { - const isProfilingSupported = root.memoizedInteractions != null; - if (isProfiling && isProfilingSupported) { + if (isProfiling && rootSupportsProfiling(root)) { if (currentCommitProfilingMetadata !== null) { const {effectDuration, passiveEffectDuration} = getEffectDurations( root, @@ -2233,23 +2240,16 @@ export function attach( traceUpdatesForNodes.clear(); } - // Checking root.memoizedInteractions handles multi-renderer edge-case- - // where some v16 renderers support profiling and others don't. - const isProfilingSupported = root.memoizedInteractions != null; + // Handle multi-renderer edge-case where only some v16 renderers support profiling. + const isProfilingSupported = rootSupportsProfiling(root); if (isProfiling && isProfilingSupported) { - // If profiling is active, store commit time and duration, and the current interactions. + // If profiling is active, store commit time and duration. // The frontend may request this information after profiling has stopped. currentCommitProfilingMetadata = { changeDescriptions: recordChangeDescriptions ? new Map() : null, durations: [], commitTime: getCurrentTime() - profilingStartTime, - interactions: Array.from(root.memoizedInteractions).map( - (interaction: Interaction) => ({ - ...interaction, - timestamp: interaction.timestamp - profilingStartTime, - }), - ), maxActualDuration: 0, priorityLevel: priorityLevel == null ? null : formatPriorityLevel(priorityLevel), @@ -3386,7 +3386,6 @@ export function attach( commitTime: number, durations: Array, effectDuration: number | null, - interactions: Array, maxActualDuration: number, passiveEffectDuration: number | null, priorityLevel: string | null, @@ -3419,8 +3418,6 @@ export function attach( (commitProfilingMetadata, rootID) => { const commitData: Array = []; const initialTreeBaseDurations: Array<[number, number]> = []; - const allInteractions: Map = new Map(); - const interactionCommits: Map> = new Map(); const displayName = (displayNamesByRootID !== null && displayNamesByRootID.get(rootID)) || @@ -3444,7 +3441,6 @@ export function attach( changeDescriptions, durations, effectDuration, - interactions, maxActualDuration, passiveEffectDuration, priorityLevel, @@ -3452,23 +3448,6 @@ export function attach( updaters, } = commitProfilingData; - const interactionIDs: Array = []; - - interactions.forEach(interaction => { - if (!allInteractions.has(interaction.id)) { - allInteractions.set(interaction.id, interaction); - } - - interactionIDs.push(interaction.id); - - const commitIndices = interactionCommits.get(interaction.id); - if (commitIndices != null) { - commitIndices.push(commitIndex); - } else { - interactionCommits.set(interaction.id, [commitIndex]); - } - }); - const fiberActualDurations: Array<[number, number]> = []; const fiberSelfDurations: Array<[number, number]> = []; for (let i = 0; i < durations.length; i += 3) { @@ -3486,7 +3465,6 @@ export function attach( effectDuration, fiberActualDurations, fiberSelfDurations, - interactionIDs, passiveEffectDuration, priorityLevel, timestamp: commitTime, @@ -3498,8 +3476,6 @@ export function attach( commitData, displayName, initialTreeBaseDurations, - interactionCommits: Array.from(interactionCommits.entries()), - interactions: Array.from(allInteractions.entries()), rootID, }); }, diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 466a5b7fa2ddb..342931c250eab 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -14,7 +14,6 @@ import type { ComponentFilter, ElementType, } from 'react-devtools-shared/src/types'; -import type {Interaction} from 'react-devtools-shared/src/devtools/views/Profiler/types'; import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor'; type BundleType = @@ -165,7 +164,6 @@ export type CommitDataBackend = {| fiberActualDurations: Array<[number, number]>, // Tuple of fiber ID and computed "self" duration fiberSelfDurations: Array<[number, number]>, - interactionIDs: Array, // Only available in certain (newer) React builds, passiveEffectDuration: number | null, priorityLevel: string | null, @@ -178,9 +176,6 @@ export type ProfilingDataForRootBackend = {| displayName: string, // Tuple of Fiber ID and base duration initialTreeBaseDurations: Array<[number, number]>, - // Tuple of Interaction ID and commit indices - interactionCommits: Array<[number, Array]>, - interactions: Array<[number, Interaction]>, rootID: number, |}; diff --git a/packages/react-devtools-shared/src/devtools/ProfilingCache.js b/packages/react-devtools-shared/src/devtools/ProfilingCache.js index 03e156cdf0f9d..96e236b143d6c 100644 --- a/packages/react-devtools-shared/src/devtools/ProfilingCache.js +++ b/packages/react-devtools-shared/src/devtools/ProfilingCache.js @@ -16,10 +16,6 @@ import { getChartData as getFlamegraphChartData, invalidateChartData as invalidateFlamegraphChartData, } from 'react-devtools-shared/src/devtools/views/Profiler/FlamegraphChartBuilder'; -import { - getChartData as getInteractionsChartData, - invalidateChartData as invalidateInteractionsChartData, -} from 'react-devtools-shared/src/devtools/views/Profiler/InteractionsChartBuilder'; import { getChartData as getRankedChartData, invalidateChartData as invalidateRankedChartData, @@ -27,7 +23,6 @@ import { import type {CommitTree} from 'react-devtools-shared/src/devtools/views/Profiler/types'; import type {ChartData as FlamegraphChartData} from 'react-devtools-shared/src/devtools/views/Profiler/FlamegraphChartBuilder'; -import type {ChartData as InteractionsChartData} from 'react-devtools-shared/src/devtools/views/Profiler/InteractionsChartBuilder'; import type {ChartData as RankedChartData} from 'react-devtools-shared/src/devtools/views/Profiler/RankedChartBuilder'; export default class ProfilingCache { @@ -92,16 +87,6 @@ export default class ProfilingCache { rootID, }); - getInteractionsChartData = ({ - rootID, - }: {| - rootID: number, - |}): InteractionsChartData => - getInteractionsChartData({ - profilerStore: this._profilerStore, - rootID, - }); - getRankedChartData = ({ commitIndex, commitTree, @@ -123,7 +108,6 @@ export default class ProfilingCache { invalidateCommitTrees(); invalidateFlamegraphChartData(); - invalidateInteractionsChartData(); invalidateRankedChartData(); } } diff --git a/packages/react-devtools-shared/src/devtools/views/Icon.js b/packages/react-devtools-shared/src/devtools/views/Icon.js index e879acdbfa614..ffa297610bdf5 100644 --- a/packages/react-devtools-shared/src/devtools/views/Icon.js +++ b/packages/react-devtools-shared/src/devtools/views/Icon.js @@ -19,7 +19,6 @@ export type IconType = | 'error' | 'facebook' | 'flame-chart' - | 'interactions' | 'profiler' | 'ranked-chart' | 'search' @@ -59,9 +58,6 @@ export default function Icon({className = '', type}: Props) { case 'flame-chart': pathData = PATH_FLAME_CHART; break; - case 'interactions': - pathData = PATH_INTERACTIONS; - break; case 'profiler': pathData = PATH_PROFILER; break; @@ -138,14 +134,6 @@ const PATH_FLAME_CHART = ` 13.3541667,19.4702042 C13.3541667,20.1226027 12.7851952,20.6514763 12.0833333,20.6514763 Z `; -const PATH_INTERACTIONS = ` - M23 8c0 1.1-.9 2-2 2-.18 0-.35-.02-.51-.07l-3.56 3.55c.05.16.07.34.07.52 0 1.1-.9 2-2 - 2s-2-.9-2-2c0-.18.02-.36.07-.52l-2.55-2.55c-.16.05-.34.07-.52.07s-.36-.02-.52-.07l-4.55 - 4.56c.05.16.07.33.07.51 0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.18 0 .35.02.51.07l4.56-4.55C8.02 - 9.36 8 9.18 8 9c0-1.1.9-2 2-2s2 .9 2 2c0 .18-.02.36-.07.52l2.55 - 2.55c.16-.05.34-.07.52-.07s.36.02.52.07l3.55-3.56C19.02 8.35 19 8.18 19 8c0-1.1.9-2 2-2s2 .9 2 2z -`; - const PATH_PROFILER = 'M5 9.2h3V19H5zM10.6 5h2.8v14h-2.8zm5.6 8H19v6h-2.8z'; const PATH_SEARCH = ` diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/InteractionListItem.css b/packages/react-devtools-shared/src/devtools/views/Profiler/InteractionListItem.css deleted file mode 100644 index 62784d3333dc9..0000000000000 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/InteractionListItem.css +++ /dev/null @@ -1,43 +0,0 @@ -.Interaction, -.SelectedInteraction { - display: flex; - align-items: center; - padding: 0 0.25rem; - border-bottom: 1px solid var(--color-border); -} - -.Interaction:hover { - background-color: var(--color-background-hover); -} - -.SelectedInteraction { - background-color: var(--color-background-hover); -} - -.Name { - white-space: nowrap; - overflow-x: hidden; - text-overflow: ellipsis; -} - -.Timeline { - position: relative; - height: 100%; -} - -.InteractionLine { - position: absolute; - height: 3px; - background-color: var(--color-commit-did-not-render-fill); - color: var(--color-commit-did-not-render-fill-text); - border-radius: 0.125rem; -} - -.CommitBox { - position: absolute; - width: var(--interaction-commit-size); - height: var(--interaction-commit-size); - background-color: var(--color-commit-did-not-render-fill); - color: var(--color-commit-did-not-render-fill-text); - cursor: pointer; -} diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/InteractionListItem.js b/packages/react-devtools-shared/src/devtools/views/Profiler/InteractionListItem.js deleted file mode 100644 index b720ca3e0d98e..0000000000000 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/InteractionListItem.js +++ /dev/null @@ -1,105 +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 - */ - -import * as React from 'react'; -import {memo, useCallback} from 'react'; -import {areEqual} from 'react-window'; -import {getGradientColor} from './utils'; - -import styles from './InteractionListItem.css'; - -import type {ItemData} from './Interactions'; - -type Props = { - data: ItemData, - index: number, - style: Object, - ... -}; - -function InteractionListItem({data: itemData, index, style}: Props) { - const { - chartData, - dataForRoot, - labelWidth, - scaleX, - selectedInteractionID, - selectCommitIndex, - selectInteraction, - selectTab, - } = itemData; - - const {commitData, interactionCommits} = dataForRoot; - const {interactions, lastInteractionTime, maxCommitDuration} = chartData; - - const interaction = interactions[index]; - if (interaction == null) { - throw Error(`Could not find interaction #${index}`); - } - - const handleClick = useCallback(() => { - selectInteraction(interaction.id); - }, [interaction, selectInteraction]); - - const commits = interactionCommits.get(interaction.id) || []; - - const startTime = interaction.timestamp; - const stopTime = lastInteractionTime; - - const viewCommit = (commitIndex: number) => { - selectTab('flame-chart'); - selectCommitIndex(commitIndex); - }; - - return ( -
-
- {interaction.name} -
-
- {commits.map(commitIndex => ( -
viewCommit(commitIndex)} - style={{ - backgroundColor: getGradientColor( - Math.min( - 1, - Math.max( - 0, - commitData[commitIndex].duration / maxCommitDuration, - ), - ) || 0, - ), - left: labelWidth + scaleX(commitData[commitIndex].timestamp, 0), - }} - /> - ))} -
- ); -} - -export default memo(InteractionListItem, areEqual); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Interactions.css b/packages/react-devtools-shared/src/devtools/views/Profiler/Interactions.css deleted file mode 100644 index 81fd9da918e15..0000000000000 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/Interactions.css +++ /dev/null @@ -1,9 +0,0 @@ -.Container { - width: 100%; - flex: 1; - padding: 0.5rem; -} - -.FocusTarget:focus { - outline: none; -} diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Interactions.js b/packages/react-devtools-shared/src/devtools/views/Profiler/Interactions.js deleted file mode 100644 index b39d973053b88..0000000000000 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/Interactions.js +++ /dev/null @@ -1,146 +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 - */ - -import * as React from 'react'; -import {useCallback, useContext, useMemo} from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import {FixedSizeList} from 'react-window'; -import {ProfilerContext} from './ProfilerContext'; -import InteractionListItem from './InteractionListItem'; -import NoInteractions from './NoInteractions'; -import {StoreContext} from '../context'; -import {scale} from './utils'; - -import styles from './Interactions.css'; - -import type {ProfilingDataForRootFrontend} from './types'; -import type {ChartData} from './InteractionsChartBuilder'; -import type {TabID} from './ProfilerContext'; - -export type ItemData = {| - chartData: ChartData, - dataForRoot: ProfilingDataForRootFrontend, - labelWidth: number, - scaleX: (value: number, fallbackValue: number) => number, - selectedInteractionID: number | null, - selectCommitIndex: (id: number | null) => void, - selectInteraction: (id: number | null) => void, - selectTab: (id: TabID) => void, -|}; - -export default function InteractionsAutoSizer(_: {||}) { - return ( -
- - {({height, width}) => } - -
- ); -} - -function Interactions({height, width}: {|height: number, width: number|}) { - const { - rootID, - selectedInteractionID, - selectInteraction, - selectCommitIndex, - selectTab, - } = useContext(ProfilerContext); - const {profilerStore} = useContext(StoreContext); - const {profilingCache} = profilerStore; - - const dataForRoot = profilerStore.getDataForRoot(((rootID: any): number)); - - const chartData = profilingCache.getInteractionsChartData({ - rootID: ((rootID: any): number), - }); - - const {interactions} = chartData; - - const handleKeyDown = useCallback( - event => { - let index; - switch (event.key) { - case 'ArrowDown': - index = interactions.findIndex( - interaction => interaction.id === selectedInteractionID, - ); - selectInteraction(Math.min(interactions.length - 1, index + 1)); - event.stopPropagation(); - break; - case 'ArrowUp': - index = interactions.findIndex( - interaction => interaction.id === selectedInteractionID, - ); - selectInteraction(Math.max(0, index - 1)); - event.stopPropagation(); - break; - default: - break; - } - }, - [interactions, selectedInteractionID, selectInteraction], - ); - - const itemData = useMemo(() => { - const interactionCommitSize = parseInt( - getComputedStyle((document.body: any)).getPropertyValue( - '--interaction-commit-size', - ), - 10, - ); - const interactionLabelWidth = parseInt( - getComputedStyle((document.body: any)).getPropertyValue( - '--interaction-label-width', - ), - 10, - ); - - const labelWidth = Math.min(interactionLabelWidth, width / 5); - const timelineWidth = width - labelWidth - interactionCommitSize; - - return { - chartData, - dataForRoot, - labelWidth, - scaleX: scale(0, chartData.lastInteractionTime, 0, timelineWidth), - selectedInteractionID, - selectCommitIndex, - selectInteraction, - selectTab, - }; - }, [ - chartData, - dataForRoot, - selectedInteractionID, - selectCommitIndex, - selectInteraction, - selectTab, - width, - ]); - - // If a commit contains no fibers with an actualDuration > 0, - // Display a fallback message. - if (interactions.length === 0) { - return ; - } - - return ( -
- - {InteractionListItem} - -
- ); -} diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/InteractionsChartBuilder.js b/packages/react-devtools-shared/src/devtools/views/Profiler/InteractionsChartBuilder.js deleted file mode 100644 index 90ebfade4cd9f..0000000000000 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/InteractionsChartBuilder.js +++ /dev/null @@ -1,62 +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 - */ - -import ProfilerStore from 'react-devtools-shared/src/devtools/ProfilerStore'; - -import type {Interaction} from './types'; - -export type ChartData = {| - interactions: Array, - lastInteractionTime: number, - maxCommitDuration: number, -|}; - -const cachedChartData: Map = new Map(); - -export function getChartData({ - profilerStore, - rootID, -}: {| - profilerStore: ProfilerStore, - rootID: number, -|}): ChartData { - if (cachedChartData.has(rootID)) { - return ((cachedChartData.get(rootID): any): ChartData); - } - - const dataForRoot = profilerStore.getDataForRoot(rootID); - if (dataForRoot == null) { - throw Error(`Could not find profiling data for root "${rootID}"`); - } - - const {commitData, interactions} = dataForRoot; - - const lastInteractionTime = - commitData.length > 0 ? commitData[commitData.length - 1].timestamp : 0; - - let maxCommitDuration = 0; - - commitData.forEach(commitDatum => { - maxCommitDuration = Math.max(maxCommitDuration, commitDatum.duration); - }); - - const chartData = { - interactions: Array.from(interactions.values()), - lastInteractionTime, - maxCommitDuration, - }; - - cachedChartData.set(rootID, chartData); - - return chartData; -} - -export function invalidateChartData(): void { - cachedChartData.clear(); -} diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/NoInteractions.css b/packages/react-devtools-shared/src/devtools/views/Profiler/NoInteractions.css deleted file mode 100644 index 6c6872058ac7b..0000000000000 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/NoInteractions.css +++ /dev/null @@ -1,16 +0,0 @@ -.NoInteractions { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} - -.Header { - font-size: var(--font-size-sans-large); -} - -.Link { - color: var(--color-button); -} diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/NoInteractions.js b/packages/react-devtools-shared/src/devtools/views/Profiler/NoInteractions.js deleted file mode 100644 index c2f1f65480d1f..0000000000000 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/NoInteractions.js +++ /dev/null @@ -1,35 +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 - */ - -import * as React from 'react'; - -import styles from './NoInteractions.css'; - -export default function NoInteractions({ - height, - width, -}: {| - height: number, - width: number, -|}) { - return ( -
-

No interactions were recorded.

-

- - Learn more about the interaction tracing API here. - -

-
- ); -} diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js index 2b4e117d66d96..58008334bc8f2 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js @@ -15,14 +15,12 @@ import TabBar from '../TabBar'; import ClearProfilingDataButton from './ClearProfilingDataButton'; import CommitFlamegraph from './CommitFlamegraph'; import CommitRanked from './CommitRanked'; -import Interactions from './Interactions'; import RootSelector from './RootSelector'; import RecordToggle from './RecordToggle'; import ReloadAndProfileButton from './ReloadAndProfileButton'; import ProfilingImportExportButtons from './ProfilingImportExportButtons'; import SnapshotSelector from './SnapshotSelector'; import SidebarCommitInfo from './SidebarCommitInfo'; -import SidebarInteractions from './SidebarInteractions'; import SidebarSelectedFiberInfo from './SidebarSelectedFiberInfo'; import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal'; import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle'; @@ -53,9 +51,6 @@ function Profiler(_: {||}) { case 'ranked-chart': view = ; break; - case 'interactions': - view = ; - break; default: break; } @@ -72,9 +67,6 @@ function Profiler(_: {||}) { let sidebar = null; if (!isProfiling && !isProcessingData && didRecordCommits) { switch (selectedTabID) { - case 'interactions': - sidebar = ; - break; case 'flame-chart': case 'ranked-chart': // TRICKY @@ -148,12 +140,6 @@ const tabs = [ label: 'Ranked', title: 'Ranked chart', }, - { - id: 'interactions', - icon: 'interactions', - label: 'Interactions', - title: 'Profiled interactions', - }, ]; const NoProfilingData = () => ( diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js index ac3b58f187982..48f9aa11eefdb 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js @@ -19,7 +19,7 @@ import {StoreContext} from '../context'; import type {ProfilingDataFrontend} from './types'; -export type TabID = 'flame-chart' | 'ranked-chart' | 'interactions'; +export type TabID = 'flame-chart' | 'ranked-chart'; export type Context = {| // Which tab is selected in the Profiler UI? @@ -64,10 +64,6 @@ export type Context = {| selectedFiberID: number | null, selectedFiberName: string | null, selectFiber: (id: number | null, name: string | null) => void, - - // Which interaction is currently selected in the Interactions graph? - selectedInteractionID: number | null, - selectInteraction: (id: number | null) => void, |}; const ProfilerContext = createContext(((null: any): Context)); @@ -216,9 +212,6 @@ function ProfilerContextController({children}: Props) { null, ); const [selectedTabID, selectTab] = useState('flame-chart'); - const [selectedInteractionID, selectInteraction] = useState( - null, - ); if (isProfiling) { batchedUpdates(() => { @@ -229,9 +222,6 @@ function ProfilerContextController({children}: Props) { selectFiberID(null); selectFiberName(null); } - if (selectedInteractionID !== null) { - selectInteraction(null); - } }); } @@ -262,9 +252,6 @@ function ProfilerContextController({children}: Props) { selectedFiberID, selectedFiberName, selectFiber, - - selectedInteractionID, - selectInteraction, }), [ selectedTabID, @@ -293,9 +280,6 @@ function ProfilerContextController({children}: Props) { selectedFiberID, selectedFiberName, selectFiber, - - selectedInteractionID, - selectInteraction, ], ); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.css b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.css index d82f10b3d73a5..1fe2d298e9a14 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.css +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.css @@ -23,33 +23,6 @@ margin: 0 0 0.5rem; } -.NoInteractions { - color: var(--color-dim); -} - -.Interactions { - margin: 0 0 0.5rem; -} -.NoInteractions, -.Interaction { - display: block; - width: 100%; - text-align: left; - background: none; - border: none; - padding: 0.25rem 0.5rem; - color: var(--color-text); -} -.Interaction:focus, -.Interaction:hover { - outline: none; - background-color: var(--color-background-hover); -} - -.NoInteractions { - color: var(--color-dim); -} - .Label { overflow: hidden; text-overflow: ellipsis; diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.js index dcfaf7d68b3be..6619fb7c78397 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.js @@ -20,12 +20,7 @@ import styles from './SidebarCommitInfo.css'; export type Props = {||}; export default function SidebarCommitInfo(_: Props) { - const { - selectedCommitIndex, - rootID, - selectInteraction, - selectTab, - } = useContext(ProfilerContext); + const {selectedCommitIndex, rootID} = useContext(ProfilerContext); const {profilerStore} = useContext(StoreContext); @@ -33,22 +28,15 @@ export default function SidebarCommitInfo(_: Props) { return
Nothing selected
; } - const {interactions} = profilerStore.getDataForRoot(rootID); const { duration, effectDuration, - interactionIDs, passiveEffectDuration, priorityLevel, timestamp, updaters, } = profilerStore.getCommitData(rootID, selectedCommitIndex); - const viewInteraction = interactionID => { - selectTab('interactions'); - selectInteraction(interactionID); - }; - const hasCommitPhaseDurations = effectDuration !== null || passiveEffectDuration !== null; @@ -120,29 +108,6 @@ export default function SidebarCommitInfo(_: Props) { )} - -
  • - : -
    - {interactionIDs.length === 0 ? ( -
    (none)
    - ) : null} - {interactionIDs.map(interactionID => { - const interaction = interactions.get(interactionID); - if (interaction == null) { - throw Error(`Invalid interaction "${interactionID}"`); - } - return ( - - ); - })} -
    -
  • diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarInteractions.css b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarInteractions.css deleted file mode 100644 index c5643dfcd79f1..0000000000000 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarInteractions.css +++ /dev/null @@ -1,55 +0,0 @@ -.Toolbar { - height: 2.25rem; - padding: 0 0.5rem; - flex: 0 0 auto; - display: flex; - align-items: center; -} - -.Content { - padding: 0.5rem; - user-select: none; - border-top: 1px solid var(--color-border); - overflow: auto; -} - -.Name { - font-size: var(--font-size-sans-large); - white-space: nowrap; - overflow-x: hidden; - text-overflow: ellipsis; -} - -.NothingSelected { - display: flex; - align-items: center; - justify-content: center; - height: 100%; - color: var(--color-dim); -} - -.Commits { - font-weight: bold; -} - -.List { - list-style: none; - margin: 0; - padding: 0; -} - -.ListItem { - display: flex; - flex-direction: row; - align-items: center; - padding: 0.25rem 0.5rem; -} -.ListItem:hover { - background-color: var(--color-background-hover); -} - -.CommitBox { - width: 20px; - height: 20px; - margin-right: 0.5rem; -} diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarInteractions.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarInteractions.js deleted file mode 100644 index 186ec24250eac..0000000000000 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarInteractions.js +++ /dev/null @@ -1,100 +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 - */ - -import * as React from 'react'; -import {Fragment, useContext} from 'react'; -import {ProfilerContext} from './ProfilerContext'; -import {formatDuration, formatTime} from './utils'; -import {StoreContext} from '../context'; -import {getGradientColor} from './utils'; - -import styles from './SidebarInteractions.css'; - -export type Props = {||}; - -export default function SidebarInteractions(_: Props) { - const { - selectedInteractionID, - rootID, - selectCommitIndex, - selectTab, - } = useContext(ProfilerContext); - - const {profilerStore} = useContext(StoreContext); - const {profilingCache} = profilerStore; - - if (selectedInteractionID === null) { - return
    Nothing selected
    ; - } - - const {interactionCommits, interactions} = profilerStore.getDataForRoot( - ((rootID: any): number), - ); - const interaction = interactions.get(selectedInteractionID); - if (interaction == null) { - throw Error( - `Could not find interaction by selected interaction id "${selectedInteractionID}"`, - ); - } - - const {maxCommitDuration} = profilingCache.getInteractionsChartData({ - rootID: ((rootID: any): number), - }); - - const viewCommit = (commitIndex: number) => { - selectTab('flame-chart'); - selectCommitIndex(commitIndex); - }; - - const listItems: Array = []; - const commitIndices = interactionCommits.get(selectedInteractionID); - if (commitIndices != null) { - commitIndices.forEach(commitIndex => { - const {duration, timestamp} = profilerStore.getCommitData( - ((rootID: any): number), - commitIndex, - ); - - listItems.push( -
  • viewCommit(commitIndex)}> -
    -
    - timestamp: {formatTime(timestamp)}s -
    - duration: {formatDuration(duration)}ms -
    -
  • , - ); - }); - } - - return ( - -
    -
    - {interaction.name} -
    -
    -
    -
    Commits:
    -
      {listItems}
    -
    -
    - ); -} diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/constants.js b/packages/react-devtools-shared/src/devtools/views/Profiler/constants.js index fd6b4365f23ac..547642620afe3 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/constants.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/constants.js @@ -8,7 +8,5 @@ */ export const barWidthThreshold = 2; -export const interactionCommitSize = 10; -export const interactionLabelWidth = 200; export const maxBarWidth = 30; export const minBarWidth = 5; diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/types.js b/packages/react-devtools-shared/src/devtools/views/Profiler/types.js index 90c56b3529be4..c3889d47c2b32 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/types.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/types.js @@ -26,12 +26,6 @@ export type CommitTree = {| rootID: number, |}; -export type Interaction = {| - id: number, - name: string, - timestamp: number, -|}; - export type SnapshotNode = {| id: number, children: Array, @@ -69,9 +63,6 @@ export type CommitDataFrontend = {| // Fibers that did not render will not have entries in this Map. fiberSelfDurations: Map, - // Which interactions (IDs) were associated with this commit. - interactionIDs: Array, - // How long was the passive commit phase? // Note that not all builds of React expose this property. passiveEffectDuration: number | null, @@ -98,12 +89,6 @@ export type ProfilingDataForRootFrontend = {| // This info can be used along with commitOperations to reconstruct the tree for any commit. initialTreeBaseDurations: Map, - // All interactions recorded (for this root) during the current session. - interactionCommits: Map>, - - // All interactions recorded (for this root) during the current session. - interactions: Map, - // List of tree mutation that occur during profiling. // These mutations can be used along with initial snapshots to reconstruct the tree for any commit. operations: Array>, @@ -131,7 +116,6 @@ export type CommitDataExport = {| fiberActualDurations: Array<[number, number]>, // Tuple of fiber ID and computed "self" duration fiberSelfDurations: Array<[number, number]>, - interactionIDs: Array, passiveEffectDuration: number | null, priorityLevel: string | null, timestamp: number, @@ -143,9 +127,6 @@ export type ProfilingDataForRootExport = {| displayName: string, // Tuple of Fiber ID and base duration initialTreeBaseDurations: Array<[number, number]>, - // Tuple of Interaction ID and commit indices - interactionCommits: Array<[number, Array]>, - interactions: Array<[number, Interaction]>, operations: Array>, rootID: number, snapshots: Array<[number, SnapshotNode]>, diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js b/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js index cace5aaf2ddb5..2594f255afa2e 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js @@ -43,14 +43,7 @@ export function prepareProfilingDataFrontendFromBackendAndStore( dataBackends.forEach(dataBackend => { dataBackend.dataForRoots.forEach( - ({ - commitData, - displayName, - initialTreeBaseDurations, - interactionCommits, - interactions, - rootID, - }) => { + ({commitData, displayName, initialTreeBaseDurations, rootID}) => { const operations = operationsByRootID.get(rootID); if (operations == null) { throw Error( @@ -66,12 +59,7 @@ export function prepareProfilingDataFrontendFromBackendAndStore( } // Do not filter empty commits from the profiler data! - // We used to do this, but it was error prone (see #18798). - // A commit may appear to be empty (no actual durations) because of component filters, - // but filtering these empty commits causes interaction commit indices to be off by N. - // This not only corrupts the resulting data, but also potentially causes runtime errors. - // - // For that matter, hiding "empty" commits might cause confusion too. + // Hiding "empty" commits might cause confusion too. // A commit *did happen* even if none of the components the Profiler is showing were involved. const convertedCommitData = commitData.map( (commitDataBackend, commitIndex) => ({ @@ -85,7 +73,6 @@ export function prepareProfilingDataFrontendFromBackendAndStore( commitDataBackend.fiberActualDurations, ), fiberSelfDurations: new Map(commitDataBackend.fiberSelfDurations), - interactionIDs: commitDataBackend.interactionIDs, passiveEffectDuration: commitDataBackend.passiveEffectDuration, priorityLevel: commitDataBackend.priorityLevel, timestamp: commitDataBackend.timestamp, @@ -113,8 +100,6 @@ export function prepareProfilingDataFrontendFromBackendAndStore( commitData: convertedCommitData, displayName, initialTreeBaseDurations: new Map(initialTreeBaseDurations), - interactionCommits: new Map(interactionCommits), - interactions: new Map(interactions), operations, rootID, snapshots, @@ -144,8 +129,6 @@ export function prepareProfilingDataFrontendFromExport( commitData, displayName, initialTreeBaseDurations, - interactionCommits, - interactions, operations, rootID, snapshots, @@ -158,7 +141,6 @@ export function prepareProfilingDataFrontendFromExport( effectDuration, fiberActualDurations, fiberSelfDurations, - interactionIDs, passiveEffectDuration, priorityLevel, timestamp, @@ -170,7 +152,6 @@ export function prepareProfilingDataFrontendFromExport( effectDuration, fiberActualDurations: new Map(fiberActualDurations), fiberSelfDurations: new Map(fiberSelfDurations), - interactionIDs, passiveEffectDuration, priorityLevel, timestamp, @@ -179,8 +160,6 @@ export function prepareProfilingDataFrontendFromExport( ), displayName, initialTreeBaseDurations: new Map(initialTreeBaseDurations), - interactionCommits: new Map(interactionCommits), - interactions: new Map(interactions), operations, rootID, snapshots: new Map(snapshots), @@ -201,8 +180,6 @@ export function prepareProfilingDataExport( commitData, displayName, initialTreeBaseDurations, - interactionCommits, - interactions, operations, rootID, snapshots, @@ -215,7 +192,6 @@ export function prepareProfilingDataExport( effectDuration, fiberActualDurations, fiberSelfDurations, - interactionIDs, passiveEffectDuration, priorityLevel, timestamp, @@ -229,7 +205,6 @@ export function prepareProfilingDataExport( effectDuration, fiberActualDurations: Array.from(fiberActualDurations.entries()), fiberSelfDurations: Array.from(fiberSelfDurations.entries()), - interactionIDs, passiveEffectDuration, priorityLevel, timestamp, @@ -240,8 +215,6 @@ export function prepareProfilingDataExport( initialTreeBaseDurations: Array.from( initialTreeBaseDurations.entries(), ), - interactionCommits: Array.from(interactionCommits.entries()), - interactions: Array.from(interactions.entries()), operations, rootID, snapshots: Array.from(snapshots.entries()), diff --git a/packages/react-devtools-shared/src/devtools/views/root.css b/packages/react-devtools-shared/src/devtools/views/root.css index e703e011843e5..6d03a9519a23c 100644 --- a/packages/react-devtools-shared/src/devtools/views/root.css +++ b/packages/react-devtools-shared/src/devtools/views/root.css @@ -211,8 +211,4 @@ Courier, monospace; --font-family-sans: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; - - /* Constant values shared between JS and CSS */ - --interaction-commit-size: 10px; - --interaction-label-width: 200px; } diff --git a/packages/react-devtools-shell/src/app/InteractionTracing/index.js b/packages/react-devtools-shell/src/app/InteractionTracing/index.js deleted file mode 100644 index aab8e77e90910..0000000000000 --- a/packages/react-devtools-shell/src/app/InteractionTracing/index.js +++ /dev/null @@ -1,103 +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 - */ - -import * as React from 'react'; -import { - Fragment, - useCallback, - useLayoutEffect, - useEffect, - useState, -} from 'react'; -import {unstable_batchedUpdates as batchedUpdates} from 'react-dom'; -import { - unstable_trace as trace, - unstable_wrap as wrap, -} from 'scheduler/tracing'; - -function sleep(ms) { - const start = performance.now(); - let now; - do { - now = performance.now(); - } while (now - ms < start); -} - -export default function InteractionTracing() { - const [count, setCount] = useState(0); - const [shouldCascade, setShouldCascade] = useState(false); - - const handleUpdate = useCallback(() => { - trace('count', performance.now(), () => { - setTimeout( - wrap(() => { - setCount(count + 1); - }), - count * 100, - ); - }); - }, [count]); - - const handleCascadingUpdate = useCallback(() => { - trace('cascade', performance.now(), () => { - setTimeout( - wrap(() => { - batchedUpdates(() => { - setCount(count + 1); - setShouldCascade(true); - }); - }), - count * 100, - ); - }); - }, [count]); - - const handleMultiple = useCallback(() => { - trace('first', performance.now(), () => { - trace('second', performance.now(), () => { - setTimeout( - wrap(() => { - setCount(count + 1); - }), - count * 100, - ); - }); - }); - }, [count]); - - useEffect(() => { - if (shouldCascade) { - setTimeout( - wrap(() => { - setShouldCascade(false); - }), - count * 100, - ); - } - }, [count, shouldCascade]); - - useLayoutEffect(() => { - sleep(150); - }); - - useEffect(() => { - sleep(300); - }); - - return ( - -

    Interaction Tracing

    - - - -
    - ); -} diff --git a/packages/react-devtools-shell/src/app/index.js b/packages/react-devtools-shell/src/app/index.js index f5683b69a82f0..51f00c75a5e78 100644 --- a/packages/react-devtools-shell/src/app/index.js +++ b/packages/react-devtools-shell/src/app/index.js @@ -14,7 +14,6 @@ import ElementTypes from './ElementTypes'; import Hydration from './Hydration'; import InlineWarnings from './InlineWarnings'; import InspectableElements from './InspectableElements'; -import InteractionTracing from './InteractionTracing'; import ReactNativeWeb from './ReactNativeWeb'; import ToDoList from './ToDoList'; import Toggle from './Toggle'; @@ -48,7 +47,6 @@ function mountHelper(App) { function mountTestApp() { mountHelper(ToDoList); - mountHelper(InteractionTracing); mountHelper(InspectableElements); mountHelper(Hydration); mountHelper(ElementTypes); diff --git a/packages/react-devtools/OVERVIEW.md b/packages/react-devtools/OVERVIEW.md index ee73da31c64ab..0c8d47da26ebe 100644 --- a/packages/react-devtools/OVERVIEW.md +++ b/packages/react-devtools/OVERVIEW.md @@ -286,7 +286,6 @@ When profiling begins, the frontend takes a snapshot/copy of each root. This sna When profiling begins, the backend records the base durations of each fiber currently in the tree. While profiling is in progress, the backend also stores some information about each commit, including: * Commit time and duration * Which elements were rendered during that commit -* Which interactions (if any) were part of the commit * Which props and state changed (if enabled in profiler settings) This information will eventually be required by the frontend in order to render its profiling graphs, but it will not be sent across the bridge until profiling has completed (to minimize the performance impact of profiling). diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js index 242743d2f3732..6ce9981fde92f 100644 --- a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js +++ b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js @@ -10,7 +10,6 @@ let React; let ReactDOM; let ReactTestUtils; -let SchedulerTracing; let Scheduler; let act; let container; @@ -119,7 +118,6 @@ function runActTests(label, render, unmount, rerender) { React = require('react'); ReactDOM = require('react-dom'); ReactTestUtils = require('react-dom/test-utils'); - SchedulerTracing = require('scheduler/tracing'); Scheduler = require('scheduler'); act = ReactTestUtils.act; container = document.createElement('div'); @@ -497,87 +495,6 @@ function runActTests(label, render, unmount, rerender) { }); }); - describe('interaction tracing', () => { - if (__DEV__) { - it('should correctly trace interactions for sync roots', () => { - let expectedInteraction; - - const Component = jest.fn(() => { - expect(expectedInteraction).toBeDefined(); - - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - expect(interactions).toContain(expectedInteraction); - - return null; - }); - - act(() => { - SchedulerTracing.unstable_trace( - 'mount traced inside act', - performance.now(), - () => { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - expectedInteraction = Array.from(interactions)[0]; - - render(, container); - }, - ); - }); - - act(() => { - SchedulerTracing.unstable_trace( - 'update traced inside act', - performance.now(), - () => { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - expectedInteraction = Array.from(interactions)[0]; - - rerender(); - }, - ); - }); - - const secondContainer = document.createElement('div'); - - SchedulerTracing.unstable_trace( - 'mount traced outside act', - performance.now(), - () => { - act(() => { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - expectedInteraction = Array.from(interactions)[0]; - - render(, secondContainer); - }); - }, - ); - - SchedulerTracing.unstable_trace( - 'update traced outside act', - performance.now(), - () => { - act(() => { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - expectedInteraction = Array.from(interactions)[0]; - - rerender(); - }); - }, - ); - - expect(Component).toHaveBeenCalledTimes( - label === 'legacy mode' ? 4 : 8, - ); - unmount(secondContainer); - }); - } - }); - describe('error propagation', () => { it('propagates errors - sync', () => { let err; diff --git a/packages/react-dom/src/__tests__/ReactUpdates-test.js b/packages/react-dom/src/__tests__/ReactUpdates-test.js index f876158b6718e..28dd03158b7e4 100644 --- a/packages/react-dom/src/__tests__/ReactUpdates-test.js +++ b/packages/react-dom/src/__tests__/ReactUpdates-test.js @@ -1684,84 +1684,4 @@ describe('ReactUpdates', () => { expect(container.textContent).toBe('1000'); }); } - - if (__DEV__) { - it('should properly trace interactions within batched updates', () => { - const SchedulerTracing = require('scheduler/tracing'); - - let expectedInteraction; - - const container = document.createElement('div'); - - const Component = jest.fn(() => { - expect(expectedInteraction).toBeDefined(); - - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - expect(interactions).toContain(expectedInteraction); - - return null; - }); - - ReactDOM.unstable_batchedUpdates(() => { - SchedulerTracing.unstable_trace( - 'mount traced inside a batched update', - 1, - () => { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - expectedInteraction = Array.from(interactions)[0]; - - ReactDOM.render(, container); - }, - ); - }); - - ReactDOM.unstable_batchedUpdates(() => { - SchedulerTracing.unstable_trace( - 'update traced inside a batched update', - 2, - () => { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - expectedInteraction = Array.from(interactions)[0]; - - ReactDOM.render(, container); - }, - ); - }); - - const secondContainer = document.createElement('div'); - - SchedulerTracing.unstable_trace( - 'mount traced outside a batched update', - 3, - () => { - ReactDOM.unstable_batchedUpdates(() => { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - expectedInteraction = Array.from(interactions)[0]; - - ReactDOM.render(, secondContainer); - }); - }, - ); - - SchedulerTracing.unstable_trace( - 'update traced outside a batched update', - 4, - () => { - ReactDOM.unstable_batchedUpdates(() => { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - expectedInteraction = Array.from(interactions)[0]; - - ReactDOM.render(, container); - }); - }, - ); - - expect(Component).toHaveBeenCalledTimes(4); - }); - } }); diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index 325fbac1f28d5..2dfc6766577f9 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -78,7 +78,6 @@ import { disableModulePatternComponents, enableProfilerCommitHooks, enableProfilerTimer, - enableSchedulerTracing, enableSuspenseServerRenderer, warnAboutDefaultPropsOnFunctionComponents, enableScopeAPI, @@ -200,7 +199,6 @@ import { isSimpleFunctionComponent, } from './ReactFiber.new'; import { - markSpawnedWork, retryDehydratedSuspenseBoundary, scheduleUpdateOnFiber, renderDidSuspendDelayIfPossible, @@ -211,7 +209,6 @@ import { RetryAfterError, NoContext, } from './ReactFiberWorkLoop.new'; -import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing'; import {setWorkInProgressVersion} from './ReactMutableSource.new'; import { requestCacheFromPool, @@ -635,9 +632,6 @@ function updateOffscreenComponent( } // Schedule this fiber to re-render at offscreen priority. Then bailout. - if (enableSchedulerTracing) { - markSpawnedWork((OffscreenLane: Lane)); - } workInProgress.lanes = workInProgress.childLanes = laneToLanes( OffscreenLane, ); @@ -1939,9 +1933,6 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) { // RetryLane even if it's the one currently rendering since we're leaving // it behind on this node. workInProgress.lanes = SomeRetryLane; - if (enableSchedulerTracing) { - markSpawnedWork(SomeRetryLane); - } return fallbackFragment; } else { return mountSuspensePrimaryChildren( @@ -2397,17 +2388,11 @@ function mountDehydratedSuspenseComponent( // time. This will mean that Suspense timeouts are slightly shifted to later than // they should be. // Schedule a normal pri update to render this content. - if (enableSchedulerTracing) { - markSpawnedWork(DefaultHydrationLane); - } workInProgress.lanes = laneToLanes(DefaultHydrationLane); } else { // We'll continue hydrating the rest at offscreen priority since we'll already // be showing the right content coming from the server, it is no rush. workInProgress.lanes = laneToLanes(OffscreenLane); - if (enableSchedulerTracing) { - markSpawnedWork(OffscreenLane); - } } return null; } @@ -2520,10 +2505,7 @@ function updateDehydratedSuspenseComponent( // Leave the child in place. I.e. the dehydrated fragment. workInProgress.child = current.child; // Register a callback to retry this boundary once the server has sent the result. - let retry = retryDehydratedSuspenseBoundary.bind(null, current); - if (enableSchedulerTracing) { - retry = Schedule_tracing_wrap(retry); - } + const retry = retryDehydratedSuspenseBoundary.bind(null, current); registerSuspenseInstanceRetry(suspenseInstance, retry); return null; } else { diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 9cac49a046bd6..7c9df820b98c5 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -78,7 +78,6 @@ import { disableModulePatternComponents, enableProfilerCommitHooks, enableProfilerTimer, - enableSchedulerTracing, enableSuspenseServerRenderer, warnAboutDefaultPropsOnFunctionComponents, enableScopeAPI, @@ -200,7 +199,6 @@ import { isSimpleFunctionComponent, } from './ReactFiber.old'; import { - markSpawnedWork, retryDehydratedSuspenseBoundary, scheduleUpdateOnFiber, renderDidSuspendDelayIfPossible, @@ -211,7 +209,6 @@ import { RetryAfterError, NoContext, } from './ReactFiberWorkLoop.old'; -import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing'; import {setWorkInProgressVersion} from './ReactMutableSource.old'; import { requestCacheFromPool, @@ -635,9 +632,6 @@ function updateOffscreenComponent( } // Schedule this fiber to re-render at offscreen priority. Then bailout. - if (enableSchedulerTracing) { - markSpawnedWork((OffscreenLane: Lane)); - } workInProgress.lanes = workInProgress.childLanes = laneToLanes( OffscreenLane, ); @@ -1939,9 +1933,6 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) { // RetryLane even if it's the one currently rendering since we're leaving // it behind on this node. workInProgress.lanes = SomeRetryLane; - if (enableSchedulerTracing) { - markSpawnedWork(SomeRetryLane); - } return fallbackFragment; } else { return mountSuspensePrimaryChildren( @@ -2397,17 +2388,11 @@ function mountDehydratedSuspenseComponent( // time. This will mean that Suspense timeouts are slightly shifted to later than // they should be. // Schedule a normal pri update to render this content. - if (enableSchedulerTracing) { - markSpawnedWork(DefaultHydrationLane); - } workInProgress.lanes = laneToLanes(DefaultHydrationLane); } else { // We'll continue hydrating the rest at offscreen priority since we'll already // be showing the right content coming from the server, it is no rush. workInProgress.lanes = laneToLanes(OffscreenLane); - if (enableSchedulerTracing) { - markSpawnedWork(OffscreenLane); - } } return null; } @@ -2520,10 +2505,7 @@ function updateDehydratedSuspenseComponent( // Leave the child in place. I.e. the dehydrated fragment. workInProgress.child = current.child; // Register a callback to retry this boundary once the server has sent the result. - let retry = retryDehydratedSuspenseBoundary.bind(null, current); - if (enableSchedulerTracing) { - retry = Schedule_tracing_wrap(retry); - } + const retry = retryDehydratedSuspenseBoundary.bind(null, current); registerSuspenseInstanceRetry(suspenseInstance, retry); return null; } else { diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 1e1a3f0b98452..46f19294562c1 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -25,9 +25,7 @@ import type {Wakeable} from 'shared/ReactTypes'; import type {OffscreenState} from './ReactFiberOffscreenComponent'; import type {HookFlags} from './ReactHookEffectTags'; -import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing'; import { - enableSchedulerTracing, enableProfilerTimer, enableProfilerCommitHooks, enableProfilerNestedUpdatePhase, @@ -641,17 +639,7 @@ export function commitPassiveEffectDurations( } if (typeof onPostCommit === 'function') { - if (enableSchedulerTracing) { - onPostCommit( - id, - phase, - passiveEffectDuration, - commitTime, - finishedRoot.memoizedInteractions, - ); - } else { - onPostCommit(id, phase, passiveEffectDuration, commitTime); - } + onPostCommit(id, phase, passiveEffectDuration, commitTime); } // Bubble times to the next nearest ancestor Profiler. @@ -919,46 +907,24 @@ function commitLayoutEffectOnFiber( } if (typeof onRender === 'function') { - if (enableSchedulerTracing) { - onRender( - finishedWork.memoizedProps.id, - phase, - finishedWork.actualDuration, - finishedWork.treeBaseDuration, - finishedWork.actualStartTime, - commitTime, - finishedRoot.memoizedInteractions, - ); - } else { - onRender( - finishedWork.memoizedProps.id, - phase, - finishedWork.actualDuration, - finishedWork.treeBaseDuration, - finishedWork.actualStartTime, - commitTime, - ); - } + onRender( + finishedWork.memoizedProps.id, + phase, + finishedWork.actualDuration, + finishedWork.treeBaseDuration, + finishedWork.actualStartTime, + commitTime, + ); } if (enableProfilerCommitHooks) { if (typeof onCommit === 'function') { - if (enableSchedulerTracing) { - onCommit( - finishedWork.memoizedProps.id, - phase, - effectDuration, - commitTime, - finishedRoot.memoizedInteractions, - ); - } else { - onCommit( - finishedWork.memoizedProps.id, - phase, - effectDuration, - commitTime, - ); - } + onCommit( + finishedWork.memoizedProps.id, + phase, + effectDuration, + commitTime, + ); } // Schedule a passive effect for this Profiler to call onPostCommit hooks. @@ -2105,13 +2071,8 @@ function attachSuspenseRetryListeners(finishedWork: Fiber) { } wakeables.forEach(wakeable => { // Memoize using the boundary fiber to prevent redundant listeners. - let retry = resolveRetryWakeable.bind(null, finishedWork, wakeable); + const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable); if (!retryCache.has(wakeable)) { - if (enableSchedulerTracing) { - if (wakeable.__reactDoNotTraceInteractions !== true) { - retry = Schedule_tracing_wrap(retry); - } - } retryCache.add(wakeable); if (enableUpdaterTracking) { diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index f631c0131aff2..f45a44083e236 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -25,9 +25,7 @@ import type {Wakeable} from 'shared/ReactTypes'; import type {OffscreenState} from './ReactFiberOffscreenComponent'; import type {HookFlags} from './ReactHookEffectTags'; -import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing'; import { - enableSchedulerTracing, enableProfilerTimer, enableProfilerCommitHooks, enableProfilerNestedUpdatePhase, @@ -641,17 +639,7 @@ export function commitPassiveEffectDurations( } if (typeof onPostCommit === 'function') { - if (enableSchedulerTracing) { - onPostCommit( - id, - phase, - passiveEffectDuration, - commitTime, - finishedRoot.memoizedInteractions, - ); - } else { - onPostCommit(id, phase, passiveEffectDuration, commitTime); - } + onPostCommit(id, phase, passiveEffectDuration, commitTime); } // Bubble times to the next nearest ancestor Profiler. @@ -919,46 +907,24 @@ function commitLayoutEffectOnFiber( } if (typeof onRender === 'function') { - if (enableSchedulerTracing) { - onRender( - finishedWork.memoizedProps.id, - phase, - finishedWork.actualDuration, - finishedWork.treeBaseDuration, - finishedWork.actualStartTime, - commitTime, - finishedRoot.memoizedInteractions, - ); - } else { - onRender( - finishedWork.memoizedProps.id, - phase, - finishedWork.actualDuration, - finishedWork.treeBaseDuration, - finishedWork.actualStartTime, - commitTime, - ); - } + onRender( + finishedWork.memoizedProps.id, + phase, + finishedWork.actualDuration, + finishedWork.treeBaseDuration, + finishedWork.actualStartTime, + commitTime, + ); } if (enableProfilerCommitHooks) { if (typeof onCommit === 'function') { - if (enableSchedulerTracing) { - onCommit( - finishedWork.memoizedProps.id, - phase, - effectDuration, - commitTime, - finishedRoot.memoizedInteractions, - ); - } else { - onCommit( - finishedWork.memoizedProps.id, - phase, - effectDuration, - commitTime, - ); - } + onCommit( + finishedWork.memoizedProps.id, + phase, + effectDuration, + commitTime, + ); } // Schedule a passive effect for this Profiler to call onPostCommit hooks. @@ -2105,13 +2071,8 @@ function attachSuspenseRetryListeners(finishedWork: Fiber) { } wakeables.forEach(wakeable => { // Memoize using the boundary fiber to prevent redundant listeners. - let retry = resolveRetryWakeable.bind(null, finishedWork, wakeable); + const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable); if (!retryCache.has(wakeable)) { - if (enableSchedulerTracing) { - if (wakeable.__reactDoNotTraceInteractions !== true) { - retry = Schedule_tracing_wrap(retry); - } - } retryCache.add(wakeable); if (enableUpdaterTracking) { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index f366ccdf8839c..e11e15bac8a62 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -118,7 +118,6 @@ import { getIsHydrating, } from './ReactFiberHydrationContext.new'; import { - enableSchedulerTracing, enableSuspenseCallback, enableSuspenseServerRenderer, enableScopeAPI, @@ -127,7 +126,6 @@ import { enableSuspenseLayoutEffectSemantics, } from 'shared/ReactFeatureFlags'; import { - markSpawnedWork, renderDidSuspend, renderDidSuspendDelayIfPossible, renderHasNotSuspendedYet, @@ -981,9 +979,6 @@ function completeWork( 'This is probably a bug in React.', ); prepareToHydrateHostSuspenseInstance(workInProgress); - if (enableSchedulerTracing) { - markSpawnedWork(OffscreenLane); - } bubbleProperties(workInProgress); if (enableProfilerTimer) { if ((workInProgress.mode & ProfileMode) !== NoMode) { @@ -1259,9 +1254,6 @@ function completeWork( // We can use any RetryLane even if it's the one currently rendering // since we're leaving it behind on this node. workInProgress.lanes = SomeRetryLane; - if (enableSchedulerTracing) { - markSpawnedWork(SomeRetryLane); - } } } else { cutOffTailIfNeeded(renderState, false); @@ -1320,9 +1312,6 @@ function completeWork( // We can use any RetryLane even if it's the one currently rendering // since we're leaving it behind on this node. workInProgress.lanes = SomeRetryLane; - if (enableSchedulerTracing) { - markSpawnedWork(SomeRetryLane); - } } } if (renderState.isBackwards) { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 8bfd7d3fe999c..ba6200d364a34 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -118,7 +118,6 @@ import { getIsHydrating, } from './ReactFiberHydrationContext.old'; import { - enableSchedulerTracing, enableSuspenseCallback, enableSuspenseServerRenderer, enableScopeAPI, @@ -127,7 +126,6 @@ import { enableSuspenseLayoutEffectSemantics, } from 'shared/ReactFeatureFlags'; import { - markSpawnedWork, renderDidSuspend, renderDidSuspendDelayIfPossible, renderHasNotSuspendedYet, @@ -981,9 +979,6 @@ function completeWork( 'This is probably a bug in React.', ); prepareToHydrateHostSuspenseInstance(workInProgress); - if (enableSchedulerTracing) { - markSpawnedWork(OffscreenLane); - } bubbleProperties(workInProgress); if (enableProfilerTimer) { if ((workInProgress.mode & ProfileMode) !== NoMode) { @@ -1259,9 +1254,6 @@ function completeWork( // We can use any RetryLane even if it's the one currently rendering // since we're leaving it behind on this node. workInProgress.lanes = SomeRetryLane; - if (enableSchedulerTracing) { - markSpawnedWork(SomeRetryLane); - } } } else { cutOffTailIfNeeded(renderState, false); @@ -1320,9 +1312,6 @@ function completeWork( // We can use any RetryLane even if it's the one currently rendering // since we're leaving it behind on this node. workInProgress.lanes = SomeRetryLane; - if (enableSchedulerTracing) { - markSpawnedWork(SomeRetryLane); - } } } if (renderState.isBackwards) { diff --git a/packages/react-reconciler/src/ReactFiberRoot.new.js b/packages/react-reconciler/src/ReactFiberRoot.new.js index 728769261cbe9..e82262b5737e7 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.new.js +++ b/packages/react-reconciler/src/ReactFiberRoot.new.js @@ -20,14 +20,12 @@ import { createLaneMap, } from './ReactFiberLane.new'; import { - enableSchedulerTracing, enableSuspenseCallback, enableCache, enableProfilerCommitHooks, enableProfilerTimer, enableUpdaterTracking, } from 'shared/ReactFeatureFlags'; -import {unstable_getThreadID} from 'scheduler/tracing'; import {initializeUpdateQueue} from './ReactUpdateQueue.new'; import {LegacyRoot, ConcurrentRoot} from './ReactRootTags'; @@ -66,11 +64,6 @@ function FiberRootNode(containerInfo, tag, hydrate) { this.mutableSourceEagerHydrationData = null; } - if (enableSchedulerTracing) { - this.interactionThreadID = unstable_getThreadID(); - this.memoizedInteractions = new Set(); - this.pendingInteractionMap = new Map(); - } if (enableSuspenseCallback) { this.hydrationCallbacks = null; } diff --git a/packages/react-reconciler/src/ReactFiberRoot.old.js b/packages/react-reconciler/src/ReactFiberRoot.old.js index 7f338a4dbb21a..20c3cdaa6cbff 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.old.js +++ b/packages/react-reconciler/src/ReactFiberRoot.old.js @@ -20,14 +20,12 @@ import { createLaneMap, } from './ReactFiberLane.old'; import { - enableSchedulerTracing, enableSuspenseCallback, enableCache, enableProfilerCommitHooks, enableProfilerTimer, enableUpdaterTracking, } from 'shared/ReactFeatureFlags'; -import {unstable_getThreadID} from 'scheduler/tracing'; import {initializeUpdateQueue} from './ReactUpdateQueue.old'; import {LegacyRoot, ConcurrentRoot} from './ReactRootTags'; @@ -66,11 +64,6 @@ function FiberRootNode(containerInfo, tag, hydrate) { this.mutableSourceEagerHydrationData = null; } - if (enableSchedulerTracing) { - this.interactionThreadID = unstable_getThreadID(); - this.memoizedInteractions = new Set(); - this.pendingInteractionMap = new Map(); - } if (enableSuspenseCallback) { this.hydrationCallbacks = null; } diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index ddf72f428dfd4..0b15fb13a2351 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -10,7 +10,6 @@ import type {Thenable, Wakeable} from 'shared/ReactTypes'; import type {Fiber, FiberRoot} from './ReactInternalTypes'; import type {Lanes, Lane} from './ReactFiberLane.new'; -import type {Interaction} from 'scheduler/src/Tracing'; import type {SuspenseState} from './ReactFiberSuspenseComponent.new'; import type {StackCursor} from './ReactFiberStack.new'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new'; @@ -24,7 +23,6 @@ import { enableProfilerCommitHooks, enableProfilerNestedUpdatePhase, enableProfilerNestedUpdateScheduledHook, - enableSchedulerTracing, warnAboutUnmockedScheduler, deferRenderPhaseUpdateToNextBatch, enableDebugTracing, @@ -83,8 +81,6 @@ import { // The scheduler is imported here *only* to detect whether it's been mocked import * as Scheduler from 'scheduler'; -import {__interactionsRef, __subscriberRef} from 'scheduler/tracing'; - import { resetAfterCommit, scheduleTimeout, @@ -349,13 +345,6 @@ let rootWithNestedUpdates: FiberRoot | null = null; const NESTED_PASSIVE_UPDATE_LIMIT = 50; let nestedPassiveUpdateCount: number = 0; -// Marks the need to reschedule pending interactions at these lanes -// during the commit phase. This enables them to be traced across components -// that spawn new work during render. E.g. hidden boundaries, suspended SSR -// hydration or SuspenseList. -// TODO: Can use a bitmask instead of an array -let spawnedWorkDuringRender: null | Array = null; - // If two updates are scheduled within the same event, we should treat their // event times as simultaneous, even if the actual clock time has advanced // between the first and second call. @@ -496,11 +485,7 @@ export function scheduleUpdateOnFiber( if (current.tag === Profiler) { const {id, onNestedUpdateScheduled} = current.memoizedProps; if (typeof onNestedUpdateScheduled === 'function') { - if (enableSchedulerTracing) { - onNestedUpdateScheduled(id, root.memoizedInteractions); - } else { - onNestedUpdateScheduled(id); - } + onNestedUpdateScheduled(id); } } current = current.return; @@ -543,16 +528,12 @@ export function scheduleUpdateOnFiber( // Check if we're not already rendering (executionContext & (RenderContext | CommitContext)) === NoContext ) { - // Register pending interactions on the root to avoid losing traced interaction data. - schedulePendingInteractions(root, lane); - // This is a legacy edge case. The initial mount of a ReactDOM.render-ed // root inside of batchedUpdates should be synchronous, but layout updates // should be deferred until the end of the batch. performSyncWorkOnRoot(root); } else { ensureRootIsScheduled(root, eventTime); - schedulePendingInteractions(root, lane); if ( executionContext === NoContext && (fiber.mode & ConcurrentMode) === NoMode @@ -569,7 +550,6 @@ export function scheduleUpdateOnFiber( } else { // Schedule other updates after in case the callback is sync. ensureRootIsScheduled(root, eventTime); - schedulePendingInteractions(root, lane); } return root; @@ -1269,10 +1249,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) { enqueueInterleavedUpdates(); - if (enableSchedulerTracing) { - spawnedWorkDuringRender = null; - } - if (__DEV__) { ReactStrictModeWarnings.discardPendingWarnings(); } @@ -1357,21 +1333,6 @@ function popDispatcher(prevDispatcher) { ReactCurrentDispatcher.current = prevDispatcher; } -function pushInteractions(root) { - if (enableSchedulerTracing) { - const prevInteractions: Set | null = __interactionsRef.current; - __interactionsRef.current = root.memoizedInteractions; - return prevInteractions; - } - return null; -} - -function popInteractions(prevInteractions) { - if (enableSchedulerTracing) { - __interactionsRef.current = prevInteractions; - } -} - export function markCommitTimeOfFallback() { globalMostRecentFallbackTime = now(); } @@ -1454,11 +1415,8 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) { } prepareFreshStack(root, lanes); - startWorkOnPendingInteractions(root, lanes); } - const prevInteractions = pushInteractions(root); - if (__DEV__) { if (enableDebugTracing) { logRenderStarted(lanes); @@ -1478,9 +1436,6 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) { } } while (true); resetContextDependencies(); - if (enableSchedulerTracing) { - popInteractions(((prevInteractions: any): Set)); - } executionContext = prevExecutionContext; popDispatcher(prevDispatcher); @@ -1546,11 +1501,8 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { resetRenderTimer(); prepareFreshStack(root, lanes); - startWorkOnPendingInteractions(root, lanes); } - const prevInteractions = pushInteractions(root); - if (__DEV__) { if (enableDebugTracing) { logRenderStarted(lanes); @@ -1570,9 +1522,6 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { } } while (true); resetContextDependencies(); - if (enableSchedulerTracing) { - popInteractions(((prevInteractions: any): Set)); - } popDispatcher(prevDispatcher); executionContext = prevExecutionContext; @@ -1874,7 +1823,6 @@ function commitRootImpl(root, renderPriorityLevel) { const prevExecutionContext = executionContext; executionContext |= CommitContext; - const prevInteractions = pushInteractions(root); // Reset this to null before calling lifecycles ReactCurrentOwner.current = null; @@ -1947,9 +1895,6 @@ function commitRootImpl(root, renderPriorityLevel) { // opportunity to paint. requestPaint(); - if (enableSchedulerTracing) { - popInteractions(((prevInteractions: any): Set)); - } executionContext = prevExecutionContext; // Reset the priority to the previous non-sync value. @@ -1980,22 +1925,7 @@ function commitRootImpl(root, renderPriorityLevel) { remainingLanes = root.pendingLanes; // Check if there's remaining work on this root - if (remainingLanes !== NoLanes) { - if (enableSchedulerTracing) { - if (spawnedWorkDuringRender !== null) { - const expirationTimes = spawnedWorkDuringRender; - spawnedWorkDuringRender = null; - for (let i = 0; i < expirationTimes.length; i++) { - scheduleInteractions( - root, - expirationTimes[i], - root.memoizedInteractions, - ); - } - } - schedulePendingInteractions(root, remainingLanes); - } - } else { + if (remainingLanes === NoLanes) { // If there's no remaining work, we can clear the set of already failed // error boundaries. legacyErrorBoundariesThatAlreadyFailed = null; @@ -2007,16 +1937,6 @@ function commitRootImpl(root, renderPriorityLevel) { } } - if (enableSchedulerTracing) { - if (!rootDidHavePassiveEffects) { - // If there are no passive effects, then we can complete the pending interactions. - // Otherwise, we'll wait until after the passive effects are flushed. - // Wait to do this until after remaining work has been scheduled, - // so that we don't prematurely signal complete for interactions when there's e.g. hidden work. - finishPendingInteractions(root, lanes); - } - } - if (includesSomeLane(remainingLanes, (SyncLane: Lane))) { if (enableProfilerTimer && enableProfilerNestedUpdatePhase) { markNestedUpdateScheduled(); @@ -2177,7 +2097,6 @@ function flushPassiveEffectsImpl() { const prevExecutionContext = executionContext; executionContext |= CommitContext; - const prevInteractions = pushInteractions(root); commitPassiveUnmountEffects(root.current); commitPassiveMountEffects(root, root.current); @@ -2192,11 +2111,6 @@ function flushPassiveEffectsImpl() { } } - if (enableSchedulerTracing) { - popInteractions(((prevInteractions: any): Set)); - finishPendingInteractions(root, lanes); - } - if (__DEV__) { isFlushingPassiveEffects = false; } @@ -2271,7 +2185,6 @@ function captureCommitPhaseErrorOnRoot( if (root !== null) { markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); - schedulePendingInteractions(root, SyncLane); } } @@ -2318,7 +2231,6 @@ export function captureCommitPhaseError( if (root !== null) { markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); - schedulePendingInteractions(root, SyncLane); } return; } @@ -2390,7 +2302,6 @@ export function pingSuspendedRoot( } ensureRootIsScheduled(root, eventTime); - schedulePendingInteractions(root, pingedLanes); } function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) { @@ -2409,7 +2320,6 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) { if (root !== null) { markRootUpdated(root, retryLane, eventTime); ensureRootIsScheduled(root, eventTime); - schedulePendingInteractions(root, retryLane); } } @@ -3008,167 +2918,6 @@ export function warnIfUnmockedScheduler(fiber: Fiber) { } } -function computeThreadID(root: FiberRoot, lane: Lane | Lanes) { - // Interaction threads are unique per root and expiration time. - // NOTE: Intentionally unsound cast. All that matters is that it's a number - // and it represents a batch of work. Could make a helper function instead, - // but meh this is fine for now. - return (lane: any) * 1000 + root.interactionThreadID; -} - -export function markSpawnedWork(lane: Lane | Lanes) { - if (!enableSchedulerTracing) { - return; - } - if (spawnedWorkDuringRender === null) { - spawnedWorkDuringRender = [lane]; - } else { - spawnedWorkDuringRender.push(lane); - } -} - -function scheduleInteractions( - root: FiberRoot, - lane: Lane | Lanes, - interactions: Set, -) { - if (!enableSchedulerTracing) { - return; - } - - if (interactions.size > 0) { - const pendingInteractionMap = root.pendingInteractionMap; - const pendingInteractions = pendingInteractionMap.get(lane); - if (pendingInteractions != null) { - interactions.forEach(interaction => { - if (!pendingInteractions.has(interaction)) { - // Update the pending async work count for previously unscheduled interaction. - interaction.__count++; - } - - pendingInteractions.add(interaction); - }); - } else { - pendingInteractionMap.set(lane, new Set(interactions)); - - // Update the pending async work count for the current interactions. - interactions.forEach(interaction => { - interaction.__count++; - }); - } - - const subscriber = __subscriberRef.current; - if (subscriber !== null) { - const threadID = computeThreadID(root, lane); - subscriber.onWorkScheduled(interactions, threadID); - } - } -} - -function schedulePendingInteractions(root: FiberRoot, lane: Lane | Lanes) { - // This is called when work is scheduled on a root. - // It associates the current interactions with the newly-scheduled expiration. - // They will be restored when that expiration is later committed. - if (!enableSchedulerTracing) { - return; - } - - scheduleInteractions(root, lane, __interactionsRef.current); -} - -function startWorkOnPendingInteractions(root: FiberRoot, lanes: Lanes) { - // This is called when new work is started on a root. - if (!enableSchedulerTracing) { - return; - } - - // Determine which interactions this batch of work currently includes, So that - // we can accurately attribute time spent working on it, And so that cascading - // work triggered during the render phase will be associated with it. - const interactions: Set = new Set(); - root.pendingInteractionMap.forEach((scheduledInteractions, scheduledLane) => { - if (includesSomeLane(lanes, scheduledLane)) { - scheduledInteractions.forEach(interaction => - interactions.add(interaction), - ); - } - }); - - // Store the current set of interactions on the FiberRoot for a few reasons: - // We can re-use it in hot functions like performConcurrentWorkOnRoot() - // without having to recalculate it. We will also use it in commitWork() to - // pass to any Profiler onRender() hooks. This also provides DevTools with a - // way to access it when the onCommitRoot() hook is called. - root.memoizedInteractions = interactions; - - if (interactions.size > 0) { - const subscriber = __subscriberRef.current; - if (subscriber !== null) { - const threadID = computeThreadID(root, lanes); - try { - subscriber.onWorkStarted(interactions, threadID); - } catch (error) { - // If the subscriber throws, rethrow it in a separate task - scheduleCallback(ImmediateSchedulerPriority, () => { - throw error; - }); - } - } - } -} - -function finishPendingInteractions(root, committedLanes) { - if (!enableSchedulerTracing) { - return; - } - - const remainingLanesAfterCommit = root.pendingLanes; - - let subscriber; - - try { - subscriber = __subscriberRef.current; - if (subscriber !== null && root.memoizedInteractions.size > 0) { - // FIXME: More than one lane can finish in a single commit. - const threadID = computeThreadID(root, committedLanes); - subscriber.onWorkStopped(root.memoizedInteractions, threadID); - } - } catch (error) { - // If the subscriber throws, rethrow it in a separate task - scheduleCallback(ImmediateSchedulerPriority, () => { - throw error; - }); - } finally { - // Clear completed interactions from the pending Map. - // Unless the render was suspended or cascading work was scheduled, - // In which case– leave pending interactions until the subsequent render. - const pendingInteractionMap = root.pendingInteractionMap; - pendingInteractionMap.forEach((scheduledInteractions, lane) => { - // Only decrement the pending interaction count if we're done. - // If there's still work at the current priority, - // That indicates that we are waiting for suspense data. - if (!includesSomeLane(remainingLanesAfterCommit, lane)) { - pendingInteractionMap.delete(lane); - - scheduledInteractions.forEach(interaction => { - interaction.__count--; - - if (subscriber !== null && interaction.__count === 0) { - try { - subscriber.onInteractionScheduledWorkCompleted(interaction); - } catch (error) { - // If the subscriber throws, rethrow it in a separate task - scheduleCallback(ImmediateSchedulerPriority, () => { - throw error; - }); - } - } - }); - } - }); - } -} - // `act` testing API // // TODO: This is mostly a copy-paste from the legacy `act`, which does not have diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 3b0b196e112cd..7b03e4cc743df 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -10,7 +10,6 @@ import type {Thenable, Wakeable} from 'shared/ReactTypes'; import type {Fiber, FiberRoot} from './ReactInternalTypes'; import type {Lanes, Lane} from './ReactFiberLane.old'; -import type {Interaction} from 'scheduler/src/Tracing'; import type {SuspenseState} from './ReactFiberSuspenseComponent.old'; import type {StackCursor} from './ReactFiberStack.old'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old'; @@ -24,7 +23,6 @@ import { enableProfilerCommitHooks, enableProfilerNestedUpdatePhase, enableProfilerNestedUpdateScheduledHook, - enableSchedulerTracing, warnAboutUnmockedScheduler, deferRenderPhaseUpdateToNextBatch, enableDebugTracing, @@ -83,8 +81,6 @@ import { // The scheduler is imported here *only* to detect whether it's been mocked import * as Scheduler from 'scheduler'; -import {__interactionsRef, __subscriberRef} from 'scheduler/tracing'; - import { resetAfterCommit, scheduleTimeout, @@ -349,13 +345,6 @@ let rootWithNestedUpdates: FiberRoot | null = null; const NESTED_PASSIVE_UPDATE_LIMIT = 50; let nestedPassiveUpdateCount: number = 0; -// Marks the need to reschedule pending interactions at these lanes -// during the commit phase. This enables them to be traced across components -// that spawn new work during render. E.g. hidden boundaries, suspended SSR -// hydration or SuspenseList. -// TODO: Can use a bitmask instead of an array -let spawnedWorkDuringRender: null | Array = null; - // If two updates are scheduled within the same event, we should treat their // event times as simultaneous, even if the actual clock time has advanced // between the first and second call. @@ -496,11 +485,7 @@ export function scheduleUpdateOnFiber( if (current.tag === Profiler) { const {id, onNestedUpdateScheduled} = current.memoizedProps; if (typeof onNestedUpdateScheduled === 'function') { - if (enableSchedulerTracing) { - onNestedUpdateScheduled(id, root.memoizedInteractions); - } else { - onNestedUpdateScheduled(id); - } + onNestedUpdateScheduled(id); } } current = current.return; @@ -543,16 +528,12 @@ export function scheduleUpdateOnFiber( // Check if we're not already rendering (executionContext & (RenderContext | CommitContext)) === NoContext ) { - // Register pending interactions on the root to avoid losing traced interaction data. - schedulePendingInteractions(root, lane); - // This is a legacy edge case. The initial mount of a ReactDOM.render-ed // root inside of batchedUpdates should be synchronous, but layout updates // should be deferred until the end of the batch. performSyncWorkOnRoot(root); } else { ensureRootIsScheduled(root, eventTime); - schedulePendingInteractions(root, lane); if ( executionContext === NoContext && (fiber.mode & ConcurrentMode) === NoMode @@ -569,7 +550,6 @@ export function scheduleUpdateOnFiber( } else { // Schedule other updates after in case the callback is sync. ensureRootIsScheduled(root, eventTime); - schedulePendingInteractions(root, lane); } return root; @@ -1269,10 +1249,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) { enqueueInterleavedUpdates(); - if (enableSchedulerTracing) { - spawnedWorkDuringRender = null; - } - if (__DEV__) { ReactStrictModeWarnings.discardPendingWarnings(); } @@ -1357,21 +1333,6 @@ function popDispatcher(prevDispatcher) { ReactCurrentDispatcher.current = prevDispatcher; } -function pushInteractions(root) { - if (enableSchedulerTracing) { - const prevInteractions: Set | null = __interactionsRef.current; - __interactionsRef.current = root.memoizedInteractions; - return prevInteractions; - } - return null; -} - -function popInteractions(prevInteractions) { - if (enableSchedulerTracing) { - __interactionsRef.current = prevInteractions; - } -} - export function markCommitTimeOfFallback() { globalMostRecentFallbackTime = now(); } @@ -1454,11 +1415,8 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) { } prepareFreshStack(root, lanes); - startWorkOnPendingInteractions(root, lanes); } - const prevInteractions = pushInteractions(root); - if (__DEV__) { if (enableDebugTracing) { logRenderStarted(lanes); @@ -1478,9 +1436,6 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) { } } while (true); resetContextDependencies(); - if (enableSchedulerTracing) { - popInteractions(((prevInteractions: any): Set)); - } executionContext = prevExecutionContext; popDispatcher(prevDispatcher); @@ -1546,11 +1501,8 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { resetRenderTimer(); prepareFreshStack(root, lanes); - startWorkOnPendingInteractions(root, lanes); } - const prevInteractions = pushInteractions(root); - if (__DEV__) { if (enableDebugTracing) { logRenderStarted(lanes); @@ -1570,9 +1522,6 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { } } while (true); resetContextDependencies(); - if (enableSchedulerTracing) { - popInteractions(((prevInteractions: any): Set)); - } popDispatcher(prevDispatcher); executionContext = prevExecutionContext; @@ -1874,7 +1823,6 @@ function commitRootImpl(root, renderPriorityLevel) { const prevExecutionContext = executionContext; executionContext |= CommitContext; - const prevInteractions = pushInteractions(root); // Reset this to null before calling lifecycles ReactCurrentOwner.current = null; @@ -1947,9 +1895,6 @@ function commitRootImpl(root, renderPriorityLevel) { // opportunity to paint. requestPaint(); - if (enableSchedulerTracing) { - popInteractions(((prevInteractions: any): Set)); - } executionContext = prevExecutionContext; // Reset the priority to the previous non-sync value. @@ -1980,22 +1925,7 @@ function commitRootImpl(root, renderPriorityLevel) { remainingLanes = root.pendingLanes; // Check if there's remaining work on this root - if (remainingLanes !== NoLanes) { - if (enableSchedulerTracing) { - if (spawnedWorkDuringRender !== null) { - const expirationTimes = spawnedWorkDuringRender; - spawnedWorkDuringRender = null; - for (let i = 0; i < expirationTimes.length; i++) { - scheduleInteractions( - root, - expirationTimes[i], - root.memoizedInteractions, - ); - } - } - schedulePendingInteractions(root, remainingLanes); - } - } else { + if (remainingLanes === NoLanes) { // If there's no remaining work, we can clear the set of already failed // error boundaries. legacyErrorBoundariesThatAlreadyFailed = null; @@ -2007,16 +1937,6 @@ function commitRootImpl(root, renderPriorityLevel) { } } - if (enableSchedulerTracing) { - if (!rootDidHavePassiveEffects) { - // If there are no passive effects, then we can complete the pending interactions. - // Otherwise, we'll wait until after the passive effects are flushed. - // Wait to do this until after remaining work has been scheduled, - // so that we don't prematurely signal complete for interactions when there's e.g. hidden work. - finishPendingInteractions(root, lanes); - } - } - if (includesSomeLane(remainingLanes, (SyncLane: Lane))) { if (enableProfilerTimer && enableProfilerNestedUpdatePhase) { markNestedUpdateScheduled(); @@ -2177,7 +2097,6 @@ function flushPassiveEffectsImpl() { const prevExecutionContext = executionContext; executionContext |= CommitContext; - const prevInteractions = pushInteractions(root); commitPassiveUnmountEffects(root.current); commitPassiveMountEffects(root, root.current); @@ -2192,11 +2111,6 @@ function flushPassiveEffectsImpl() { } } - if (enableSchedulerTracing) { - popInteractions(((prevInteractions: any): Set)); - finishPendingInteractions(root, lanes); - } - if (__DEV__) { isFlushingPassiveEffects = false; } @@ -2271,7 +2185,6 @@ function captureCommitPhaseErrorOnRoot( if (root !== null) { markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); - schedulePendingInteractions(root, SyncLane); } } @@ -2318,7 +2231,6 @@ export function captureCommitPhaseError( if (root !== null) { markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); - schedulePendingInteractions(root, SyncLane); } return; } @@ -2390,7 +2302,6 @@ export function pingSuspendedRoot( } ensureRootIsScheduled(root, eventTime); - schedulePendingInteractions(root, pingedLanes); } function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) { @@ -2409,7 +2320,6 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) { if (root !== null) { markRootUpdated(root, retryLane, eventTime); ensureRootIsScheduled(root, eventTime); - schedulePendingInteractions(root, retryLane); } } @@ -3008,167 +2918,6 @@ export function warnIfUnmockedScheduler(fiber: Fiber) { } } -function computeThreadID(root: FiberRoot, lane: Lane | Lanes) { - // Interaction threads are unique per root and expiration time. - // NOTE: Intentionally unsound cast. All that matters is that it's a number - // and it represents a batch of work. Could make a helper function instead, - // but meh this is fine for now. - return (lane: any) * 1000 + root.interactionThreadID; -} - -export function markSpawnedWork(lane: Lane | Lanes) { - if (!enableSchedulerTracing) { - return; - } - if (spawnedWorkDuringRender === null) { - spawnedWorkDuringRender = [lane]; - } else { - spawnedWorkDuringRender.push(lane); - } -} - -function scheduleInteractions( - root: FiberRoot, - lane: Lane | Lanes, - interactions: Set, -) { - if (!enableSchedulerTracing) { - return; - } - - if (interactions.size > 0) { - const pendingInteractionMap = root.pendingInteractionMap; - const pendingInteractions = pendingInteractionMap.get(lane); - if (pendingInteractions != null) { - interactions.forEach(interaction => { - if (!pendingInteractions.has(interaction)) { - // Update the pending async work count for previously unscheduled interaction. - interaction.__count++; - } - - pendingInteractions.add(interaction); - }); - } else { - pendingInteractionMap.set(lane, new Set(interactions)); - - // Update the pending async work count for the current interactions. - interactions.forEach(interaction => { - interaction.__count++; - }); - } - - const subscriber = __subscriberRef.current; - if (subscriber !== null) { - const threadID = computeThreadID(root, lane); - subscriber.onWorkScheduled(interactions, threadID); - } - } -} - -function schedulePendingInteractions(root: FiberRoot, lane: Lane | Lanes) { - // This is called when work is scheduled on a root. - // It associates the current interactions with the newly-scheduled expiration. - // They will be restored when that expiration is later committed. - if (!enableSchedulerTracing) { - return; - } - - scheduleInteractions(root, lane, __interactionsRef.current); -} - -function startWorkOnPendingInteractions(root: FiberRoot, lanes: Lanes) { - // This is called when new work is started on a root. - if (!enableSchedulerTracing) { - return; - } - - // Determine which interactions this batch of work currently includes, So that - // we can accurately attribute time spent working on it, And so that cascading - // work triggered during the render phase will be associated with it. - const interactions: Set = new Set(); - root.pendingInteractionMap.forEach((scheduledInteractions, scheduledLane) => { - if (includesSomeLane(lanes, scheduledLane)) { - scheduledInteractions.forEach(interaction => - interactions.add(interaction), - ); - } - }); - - // Store the current set of interactions on the FiberRoot for a few reasons: - // We can re-use it in hot functions like performConcurrentWorkOnRoot() - // without having to recalculate it. We will also use it in commitWork() to - // pass to any Profiler onRender() hooks. This also provides DevTools with a - // way to access it when the onCommitRoot() hook is called. - root.memoizedInteractions = interactions; - - if (interactions.size > 0) { - const subscriber = __subscriberRef.current; - if (subscriber !== null) { - const threadID = computeThreadID(root, lanes); - try { - subscriber.onWorkStarted(interactions, threadID); - } catch (error) { - // If the subscriber throws, rethrow it in a separate task - scheduleCallback(ImmediateSchedulerPriority, () => { - throw error; - }); - } - } - } -} - -function finishPendingInteractions(root, committedLanes) { - if (!enableSchedulerTracing) { - return; - } - - const remainingLanesAfterCommit = root.pendingLanes; - - let subscriber; - - try { - subscriber = __subscriberRef.current; - if (subscriber !== null && root.memoizedInteractions.size > 0) { - // FIXME: More than one lane can finish in a single commit. - const threadID = computeThreadID(root, committedLanes); - subscriber.onWorkStopped(root.memoizedInteractions, threadID); - } - } catch (error) { - // If the subscriber throws, rethrow it in a separate task - scheduleCallback(ImmediateSchedulerPriority, () => { - throw error; - }); - } finally { - // Clear completed interactions from the pending Map. - // Unless the render was suspended or cascading work was scheduled, - // In which case– leave pending interactions until the subsequent render. - const pendingInteractionMap = root.pendingInteractionMap; - pendingInteractionMap.forEach((scheduledInteractions, lane) => { - // Only decrement the pending interaction count if we're done. - // If there's still work at the current priority, - // That indicates that we are waiting for suspense data. - if (!includesSomeLane(remainingLanesAfterCommit, lane)) { - pendingInteractionMap.delete(lane); - - scheduledInteractions.forEach(interaction => { - interaction.__count--; - - if (subscriber !== null && interaction.__count === 0) { - try { - subscriber.onInteractionScheduledWorkCompleted(interaction); - } catch (error) { - // If the subscriber throws, rethrow it in a separate task - scheduleCallback(ImmediateSchedulerPriority, () => { - throw error; - }); - } - } - }); - } - }); - } -} - // `act` testing API // // TODO: This is mostly a copy-paste from the legacy `act`, which does not have diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index 6d7d38232e347..104a9ef728e4b 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -24,7 +24,6 @@ import type {Lane, Lanes, LaneMap} from './ReactFiberLane.old'; import type {RootTag} from './ReactRootTags'; import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig'; import type {Wakeable} from 'shared/ReactTypes'; -import type {Interaction} from 'scheduler/src/Tracing'; import type {Cache} from './ReactFiberCacheComponent.old'; // Unwind Circular: moved from ReactFiberHooks.old @@ -240,16 +239,6 @@ type BaseFiberRootProperties = {| pooledCacheLanes: Lanes, |}; -// The following attributes are only used by interaction tracing builds. -// They enable interactions to be associated with their async work, -// And expose interaction metadata to the React DevTools Profiler plugin. -// Note that these attributes are only defined when the enableSchedulerTracing flag is enabled. -type ProfilingOnlyFiberRootProperties = {| - interactionThreadID: number, - memoizedInteractions: Set, - pendingInteractionMap: Map>, -|}; - // The following attributes are only used by DevTools and are only present in DEV builds. // They enable DevTools Profiler UI to show which Fiber(s) scheduled a given commit. type UpdaterTrackingOnlyFiberRootProperties = {| @@ -270,12 +259,9 @@ type SuspenseCallbackOnlyFiberRootProperties = {| // Exported FiberRoot type includes all properties, // To avoid requiring potentially error-prone :any casts throughout the project. -// Profiling properties are only safe to access in profiling builds (when enableSchedulerTracing is true). // The types are defined separately within this file to ensure they stay in sync. -// (We don't have to use an inline :any cast when enableSchedulerTracing is disabled.) export type FiberRoot = { ...BaseFiberRootProperties, - ...ProfilingOnlyFiberRootProperties, ...SuspenseCallbackOnlyFiberRootProperties, ...UpdaterTrackingOnlyFiberRootProperties, ... diff --git a/packages/react-reconciler/src/Scheduler.js b/packages/react-reconciler/src/Scheduler.js index f590f284a9ed4..dabb5baaf64be 100644 --- a/packages/react-reconciler/src/Scheduler.js +++ b/packages/react-reconciler/src/Scheduler.js @@ -12,9 +12,6 @@ // because Rollup would use dynamic dispatch for CommonJS interop named imports. // When we switch to ESM, we can delete this module. import * as Scheduler from 'scheduler'; -import {__interactionsRef} from 'scheduler/tracing'; -import {enableSchedulerTracing} from 'shared/ReactFeatureFlags'; -import invariant from 'shared/invariant'; export const scheduleCallback = Scheduler.unstable_scheduleCallback; export const cancelCallback = Scheduler.unstable_cancelCallback; @@ -28,19 +25,4 @@ export const UserBlockingPriority = Scheduler.unstable_UserBlockingPriority; export const NormalPriority = Scheduler.unstable_NormalPriority; export const LowPriority = Scheduler.unstable_LowPriority; export const IdlePriority = Scheduler.unstable_IdlePriority; - -if (enableSchedulerTracing) { - // Provide explicit error message when production+profiling bundle of e.g. - // react-dom is used with production (non-profiling) bundle of - // scheduler/tracing - invariant( - __interactionsRef != null && __interactionsRef.current != null, - 'It is not supported to run the profiling version of a renderer (for ' + - 'example, `react-dom/profiling`) without also replacing the ' + - '`scheduler/tracing` module with `scheduler/tracing-profiling`. Your ' + - 'bundler might have a setting for aliasing both modules. Learn more at ' + - 'https://reactjs.org/link/profiling', - ); -} - export type SchedulerCallback = (isSync: boolean) => SchedulerCallback | null; diff --git a/packages/scheduler/tracing.js b/packages/react-reconciler/src/__mocks__/scheduler/tracing.js similarity index 59% rename from packages/scheduler/tracing.js rename to packages/react-reconciler/src/__mocks__/scheduler/tracing.js index e3001a8ecffd1..c9f74c6bb2a51 100644 --- a/packages/scheduler/tracing.js +++ b/packages/react-reconciler/src/__mocks__/scheduler/tracing.js @@ -7,7 +7,5 @@ * @flow */ -'use strict'; - -export * from './src/Tracing'; -export * from './src/TracingSubscriptions'; +// This placeholder is to support a legacy/regression test in ReactFresh-test. +// Without this mock, jest.mock('scheduler/tracing') throws. diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js index 07d3f7ff526aa..27d09697fc213 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js @@ -18,7 +18,6 @@ let readText; let resolveText; let ReactNoop; let Scheduler; -let SchedulerTracing; let Suspense; let useState; let useReducer; @@ -43,7 +42,6 @@ describe('ReactHooksWithNoopRenderer', () => { React = require('react'); ReactNoop = require('react-noop-renderer'); Scheduler = require('scheduler'); - SchedulerTracing = require('scheduler/tracing'); useState = React.useState; useReducer = React.useReducer; useEffect = React.useEffect; @@ -1797,71 +1795,6 @@ describe('ReactHooksWithNoopRenderer', () => { expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); }); - // @gate enableSchedulerTracing - it('does not flush non-discrete passive effects when flushing sync (with tracing)', () => { - const onInteractionScheduledWorkCompleted = jest.fn(); - const onWorkCanceled = jest.fn(); - SchedulerTracing.unstable_subscribe({ - onInteractionScheduledWorkCompleted, - onInteractionTraced: jest.fn(), - onWorkCanceled, - onWorkScheduled: jest.fn(), - onWorkStarted: jest.fn(), - onWorkStopped: jest.fn(), - }); - - let _updateCount; - function Counter(props) { - const [count, updateCount] = useState(0); - _updateCount = updateCount; - useEffect(() => { - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - tracingEvent, - ]); - Scheduler.unstable_yieldValue(`Will set count to 1`); - updateCount(1); - }, []); - return ; - } - - const tracingEvent = {id: 0, name: 'hello', timestamp: 0}; - // we explicitly wait for missing act() warnings here since - // it's a lot harder to simulate this condition inside an act scope - expect(() => { - SchedulerTracing.unstable_trace( - tracingEvent.name, - tracingEvent.timestamp, - () => { - ReactNoop.render(, () => - Scheduler.unstable_yieldValue('Sync effect'), - ); - }, - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - }).toErrorDev(['An update to Counter ran an effect']); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(0); - - // A flush sync doesn't cause the passive effects to fire. - act(() => { - ReactNoop.flushSync(() => { - _updateCount(2); - }); - }); - - expect(Scheduler).toHaveYielded([ - 'Will set count to 1', - 'Count: 2', - 'Count: 1', - ]); - - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect(onWorkCanceled).toHaveBeenCalledTimes(0); - }); - it( 'in legacy mode, useEffect is deferred and updates finish synchronously ' + '(in a single batch)', diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js index ae3f286f542b3..e0fcab7a77cc0 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js @@ -2,7 +2,6 @@ let React; let ReactTestRenderer; let ReactFeatureFlags; let Scheduler; -let SchedulerTracing; let ReactCache; let Suspense; let act; @@ -18,12 +17,10 @@ describe('ReactSuspense', () => { ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; - ReactFeatureFlags.enableSchedulerTracing = true; React = require('react'); ReactTestRenderer = require('react-test-renderer'); act = ReactTestRenderer.unstable_concurrentAct; Scheduler = require('scheduler'); - SchedulerTracing = require('scheduler/tracing'); ReactCache = require('react-cache'); Suspense = React.Suspense; @@ -1271,76 +1268,6 @@ describe('ReactSuspense', () => { ]); }); - it('should call onInteractionScheduledWorkCompleted after suspending', () => { - const subscriber = { - onInteractionScheduledWorkCompleted: jest.fn(), - onInteractionTraced: jest.fn(), - onWorkCanceled: jest.fn(), - onWorkScheduled: jest.fn(), - onWorkStarted: jest.fn(), - onWorkStopped: jest.fn(), - }; - SchedulerTracing.unstable_subscribe(subscriber); - SchedulerTracing.unstable_trace('test', performance.now(), () => { - function App() { - return ( - }> - - - - - ); - } - - const root = ReactTestRenderer.create(null); - root.update(); - - expect(Scheduler).toHaveYielded([ - 'Suspend! [A]', - 'Suspend! [B]', - 'Suspend! [C]', - 'Loading...', - ]); - - // Resolve A - jest.advanceTimersByTime(1000); - - expect(Scheduler).toHaveYielded(['Promise resolved [A]']); - expect(Scheduler).toFlushUntilNextPaint([ - 'A', - // The promises for B and C have now been thrown twice - 'Suspend! [B]', - 'Suspend! [C]', - ]); - - // Resolve B - jest.advanceTimersByTime(1000); - - expect(Scheduler).toHaveYielded(['Promise resolved [B]']); - expect(Scheduler).toFlushUntilNextPaint([ - // Even though the promise for B was thrown twice, we should only - // re-render once. - 'B', - // The promise for C has now been thrown three times - 'Suspend! [C]', - ]); - - // Resolve C - jest.advanceTimersByTime(1000); - - expect(Scheduler).toHaveYielded(['Promise resolved [C]']); - expect(Scheduler).toFlushAndYield([ - // Even though the promise for C was thrown three times, we should only - // re-render once. - 'C', - ]); - }); - - expect( - subscriber.onInteractionScheduledWorkCompleted, - ).toHaveBeenCalledTimes(1); - }); - it('#14162', () => { const {lazy} = React; diff --git a/packages/react-reconciler/src/__tests__/ReactTracing-test.internal.js b/packages/react-reconciler/src/__tests__/ReactTracing-test.internal.js deleted file mode 100644 index 3815ec848794c..0000000000000 --- a/packages/react-reconciler/src/__tests__/ReactTracing-test.internal.js +++ /dev/null @@ -1,28 +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. - * - * @emails react-core - * @jest-environment node - */ - -'use strict'; - -describe('ReactTracing', () => { - it('should error if profiling renderer and non-profiling scheduler/tracing bundles are combined', () => { - jest.resetModules(); - - const ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.enableSchedulerTracing = false; - - require('scheduler/tracing'); - - ReactFeatureFlags.enableSchedulerTracing = true; - - expect(() => require('react-dom')).toThrow( - 'Learn more at https://reactjs.org/link/profiling', - ); - }); -}); diff --git a/packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js b/packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js index 8d762f87b8fef..248223e349464 100644 --- a/packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js @@ -292,91 +292,6 @@ describe('updaters', () => { done(); }); - // @gate experimental - it('traces interaction through hidden subtree', async () => { - const { - FunctionComponent, - HostRoot, - } = require('react-reconciler/src/ReactWorkTags'); - - // Note: This is based on a similar component we use in www. We can delete once - // the extra div wrapper is no longer necessary. - function LegacyHiddenDiv({children, mode}) { - return ( - - ); - } - - const Child = () => { - const [didMount, setDidMount] = React.useState(false); - Scheduler.unstable_yieldValue('Child'); - React.useEffect(() => { - if (didMount) { - Scheduler.unstable_yieldValue('Child:update'); - } else { - Scheduler.unstable_yieldValue('Child:mount'); - setDidMount(true); - } - }, [didMount]); - return
    ; - }; - - const App = () => { - Scheduler.unstable_yieldValue('App'); - React.useEffect(() => { - Scheduler.unstable_yieldValue('App:mount'); - }, []); - return ( - - - - ); - }; - - const container = document.createElement('div'); - const root = ReactDOM.createRoot(container); - await ReactTestUtils.act(async () => { - root.render(); - }); - - // TODO: There are 4 commits here instead of 3 - // because this update was scheduled at idle priority, - // and idle updates are slightly higher priority than offscreen work. - // So it takes two render passes to finish it. - // The onCommit hook is called even after the no-op bailout update. - expect(Scheduler).toHaveYielded([ - 'App', - 'onCommitRoot', - 'App:mount', - - 'Child', - 'onCommitRoot', - 'Child:mount', - - 'onCommitRoot', - - 'Child', - 'onCommitRoot', - 'Child:update', - ]); - expect(allSchedulerTypes).toEqual([ - // Initial render - [null], - // Offscreen update - [], - // Child passive effect - [Child], - // Offscreen update - [], - ]); - expect(allSchedulerTags).toEqual([[HostRoot], [], [FunctionComponent], []]); - }); - // @gate experimental it('should cover error handling', async () => { let triggerError = null; diff --git a/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js b/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js index b8c47e93d6bfe..fbd81d9576494 100644 --- a/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js +++ b/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js @@ -25,7 +25,6 @@ function loadModules() { jest.useFakeTimers(); ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.enableSchedulerTracing = true; ReactFeatureFlags.enableProfilerTimer = true; React = require('react'); diff --git a/packages/react-refresh/package.json b/packages/react-refresh/package.json index 590bb19dea996..4f5f3c31c5e0d 100644 --- a/packages/react-refresh/package.json +++ b/packages/react-refresh/package.json @@ -28,6 +28,7 @@ }, "devDependencies": { "react-16-8": "npm:react@16.8.0", - "react-dom-16-8": "npm:react-dom@16.8.0" + "react-dom-16-8": "npm:react-dom@16.8.0", + "scheduler-0-13": "npm:scheduler@0.13.0" } } \ No newline at end of file diff --git a/packages/react-refresh/src/__tests__/ReactFresh-test.js b/packages/react-refresh/src/__tests__/ReactFresh-test.js index 3fa7a61442d7d..1f83a84d83dc2 100644 --- a/packages/react-refresh/src/__tests__/ReactFresh-test.js +++ b/packages/react-refresh/src/__tests__/ReactFresh-test.js @@ -3844,6 +3844,10 @@ describe('ReactFresh', () => { // Redirect all React/ReactDOM requires to v16.8.0 // This version predates Fast Refresh support. + jest.mock('scheduler', () => jest.requireActual('scheduler-0-13')); + jest.mock('scheduler/tracing', () => + jest.requireActual('scheduler-0-13/tracing'), + ); jest.mock('react', () => jest.requireActual('react-16-8')); jest.mock('react-dom', () => jest.requireActual('react-dom-16-8')); diff --git a/packages/react/index.js b/packages/react/index.js index 5319bb80be756..094d0fcd23797 100644 --- a/packages/react/index.js +++ b/packages/react/index.js @@ -28,11 +28,6 @@ export type ElementConfig = React$ElementConfig; export type ElementRef = React$ElementRef; export type Config = React$Config; export type ChildrenArray<+T> = $ReadOnlyArray> | T; -export type Interaction = { - name: string, - timestamp: number, - ... -}; // Export all exports so that they're available in tests. // We can't use export * from in Flow for some reason. diff --git a/packages/react/src/__tests__/ReactDOMTracing-test.internal.js b/packages/react/src/__tests__/ReactDOMTracing-test.internal.js deleted file mode 100644 index 1a026d732497c..0000000000000 --- a/packages/react/src/__tests__/ReactDOMTracing-test.internal.js +++ /dev/null @@ -1,922 +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. - * - * @emails react-core - */ - -'use strict'; - -let React; -let ReactDOM; -let ReactDOMServer; -let ReactFeatureFlags; -let Scheduler; -let SchedulerTracing; -let TestUtils; -let act; -let onInteractionScheduledWorkCompleted; -let onInteractionTraced; -let onWorkCanceled; -let onWorkScheduled; -let onWorkStarted; -let onWorkStopped; -let IdleEventPriority; -let ContinuousEventPriority; - -function loadModules() { - ReactFeatureFlags = require('shared/ReactFeatureFlags'); - - ReactFeatureFlags.enableProfilerTimer = true; - ReactFeatureFlags.enableSchedulerTracing = true; - ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; - - React = require('react'); - ReactDOM = require('react-dom'); - ReactDOMServer = require('react-dom/server'); - Scheduler = require('scheduler'); - SchedulerTracing = require('scheduler/tracing'); - TestUtils = require('react-dom/test-utils'); - - IdleEventPriority = require('react-reconciler/constants').IdleEventPriority; - ContinuousEventPriority = require('react-reconciler/constants') - .ContinuousEventPriority; - - act = TestUtils.unstable_concurrentAct; - - onInteractionScheduledWorkCompleted = jest.fn(); - onInteractionTraced = jest.fn(); - onWorkCanceled = jest.fn(); - onWorkScheduled = jest.fn(); - onWorkStarted = jest.fn(); - onWorkStopped = jest.fn(); - - // Verify interaction subscriber methods are called as expected. - SchedulerTracing.unstable_subscribe({ - onInteractionScheduledWorkCompleted, - onInteractionTraced, - onWorkCanceled, - onWorkScheduled, - onWorkStarted, - onWorkStopped, - }); -} - -// Note: This is based on a similar component we use in www. We can delete once -// the extra div wrapper is no longer necessary. -function LegacyHiddenDiv({children, mode}) { - return ( - - ); -} - -describe('ReactDOMTracing', () => { - beforeEach(() => { - jest.resetModules(); - - loadModules(); - }); - - describe('interaction tracing', () => { - describe('hidden', () => { - // @gate experimental - it('traces interaction through hidden subtree', () => { - const Child = () => { - const [didMount, setDidMount] = React.useState(false); - Scheduler.unstable_yieldValue('Child'); - React.useEffect(() => { - if (didMount) { - Scheduler.unstable_yieldValue('Child:update'); - } else { - Scheduler.unstable_yieldValue('Child:mount'); - setDidMount(true); - } - }, [didMount]); - return
    ; - }; - - const App = () => { - Scheduler.unstable_yieldValue('App'); - React.useEffect(() => { - Scheduler.unstable_yieldValue('App:mount'); - }, []); - return ( - - - - ); - }; - - let interaction; - - const onRender = jest.fn(); - - const container = document.createElement('div'); - const root = ReactDOM.createRoot(container); - SchedulerTracing.unstable_trace('initialization', 0, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - act(() => { - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - root.render( - - - , - ); - }); - } else { - root.render( - - - , - ); - } - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - expect(Scheduler).toFlushAndYieldThrough(['App', 'App:mount']); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(1); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - expect(Scheduler).toFlushAndYieldThrough(['Child', 'Child:mount']); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - expect(onRender).toHaveBeenCalledTimes(2); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - - expect(Scheduler).toFlushAndYield(['Child', 'Child:update']); - }); - }); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - - if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) { - expect(onRender).toHaveBeenCalledTimes(3); - } else { - // TODO: This is 4 instead of 3 because this update was scheduled at - // idle priority, and idle updates are slightly higher priority than - // offscreen work. So it takes two render passes to finish it. Profiler - // calls `onRender` for the first render even though everything - // bails out. - expect(onRender).toHaveBeenCalledTimes(4); - } - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - }); - - // @gate experimental - it('traces interaction through hidden subtree when there is other pending traced work', () => { - const Child = () => { - Scheduler.unstable_yieldValue('Child'); - return
    ; - }; - - let wrapped = null; - - const App = () => { - Scheduler.unstable_yieldValue('App'); - React.useEffect(() => { - wrapped = SchedulerTracing.unstable_wrap(() => {}); - Scheduler.unstable_yieldValue('App:mount'); - }, []); - return ( - - - - ); - }; - - let interaction; - - const onRender = jest.fn(); - - const container = document.createElement('div'); - const root = ReactDOM.createRoot(container); - SchedulerTracing.unstable_trace('initialization', 0, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - - act(() => { - root.render( - - - , - ); - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - - expect(Scheduler).toFlushAndYieldThrough(['App', 'App:mount']); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(1); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - - expect(wrapped).not.toBeNull(); - - expect(Scheduler).toFlushAndYield(['Child']); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(2); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - }); - }); - - wrapped(); - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - }); - - // @gate experimental - it('traces interaction through hidden subtree that schedules more idle/never work', () => { - const Child = () => { - const [didMount, setDidMount] = React.useState(false); - Scheduler.unstable_yieldValue('Child'); - React.useLayoutEffect(() => { - if (didMount) { - Scheduler.unstable_yieldValue('Child:update'); - } else { - Scheduler.unstable_yieldValue('Child:mount'); - ReactDOM.unstable_runWithPriority(IdleEventPriority, () => - setDidMount(true), - ); - } - }, [didMount]); - return
    ; - }; - - const App = () => { - Scheduler.unstable_yieldValue('App'); - React.useEffect(() => { - Scheduler.unstable_yieldValue('App:mount'); - }, []); - return ( - - - - ); - }; - - let interaction; - - const onRender = jest.fn(); - - const container = document.createElement('div'); - const root = ReactDOM.createRoot(container); - SchedulerTracing.unstable_trace('initialization', 0, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - act(() => { - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - root.render( - - - , - ); - }); - } else { - root.render( - - - , - ); - } - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - - expect(Scheduler).toFlushAndYieldThrough(['App', 'App:mount']); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(1); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - - expect(Scheduler).toFlushAndYieldThrough(['Child', 'Child:mount']); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(2); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - - expect(Scheduler).toFlushAndYield(['Child', 'Child:update']); - }); - }); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - - // TODO: This is 4 instead of 3 because this update was scheduled at - // idle priority, and idle updates are slightly higher priority than - // offscreen work. So it takes two render passes to finish it. Profiler - // calls `onRender` for the first render even though everything - // bails out. - expect(onRender).toHaveBeenCalledTimes(4); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - }); - - // @gate experimental - it('does not continue interactions across pre-existing idle work', () => { - const Child = () => { - Scheduler.unstable_yieldValue('Child'); - return
    ; - }; - - let update = null; - - const WithHiddenWork = () => { - Scheduler.unstable_yieldValue('WithHiddenWork'); - return ( - - - - ); - }; - - const Updater = () => { - Scheduler.unstable_yieldValue('Updater'); - React.useEffect(() => { - Scheduler.unstable_yieldValue('Updater:effect'); - }); - - const setCount = React.useState(0)[1]; - update = () => { - setCount(current => current + 1); - }; - - return
    ; - }; - - const App = () => { - Scheduler.unstable_yieldValue('App'); - React.useEffect(() => { - Scheduler.unstable_yieldValue('App:effect'); - }); - - return ( - <> - - - - ); - }; - - const onRender = jest.fn(); - const container = document.createElement('div'); - const root = ReactDOM.createRoot(container); - - // Schedule some idle work without any interactions. - act(() => { - root.render( - - - , - ); - expect(Scheduler).toFlushAndYieldThrough([ - 'App', - 'WithHiddenWork', - 'Updater', - 'Updater:effect', - 'App:effect', - ]); - expect(update).not.toBeNull(); - - // Trace a higher-priority update. - let interaction = null; - SchedulerTracing.unstable_trace('update', 0, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - update(); - }); - expect(interaction).not.toBeNull(); - expect(onRender).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - - // Ensure the traced interaction completes without being attributed to the pre-existing idle work. - expect(Scheduler).toFlushAndYieldThrough([ - 'Updater', - 'Updater:effect', - ]); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - expect(onRender).toHaveBeenCalledTimes(2); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - - // Complete low-priority work and ensure no lingering interaction. - expect(Scheduler).toFlushAndYield(['Child']); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect(onRender).toHaveBeenCalledTimes(3); - expect(onRender).toHaveLastRenderedWithInteractions(new Set([])); - }); - }); - - // @gate experimental - it('should properly trace interactions when there is work of interleaved priorities', () => { - const Child = () => { - Scheduler.unstable_yieldValue('Child'); - return
    ; - }; - - let scheduleUpdate = null; - let scheduleUpdateWithHidden = null; - - const MaybeHiddenWork = () => { - const [flag, setFlag] = React.useState(false); - scheduleUpdateWithHidden = () => setFlag(true); - Scheduler.unstable_yieldValue('MaybeHiddenWork'); - React.useEffect(() => { - Scheduler.unstable_yieldValue('MaybeHiddenWork:effect'); - }); - return flag ? ( - - - - ) : null; - }; - - const Updater = () => { - Scheduler.unstable_yieldValue('Updater'); - React.useEffect(() => { - Scheduler.unstable_yieldValue('Updater:effect'); - }); - - const setCount = React.useState(0)[1]; - scheduleUpdate = () => setCount(current => current + 1); - - return
    ; - }; - - const App = () => { - Scheduler.unstable_yieldValue('App'); - React.useEffect(() => { - Scheduler.unstable_yieldValue('App:effect'); - }); - - return ( - <> - - - - ); - }; - - const onRender = jest.fn(); - const container = document.createElement('div'); - const root = ReactDOM.createRoot(container); - - act(() => { - root.render( - - - , - ); - expect(Scheduler).toFlushAndYield([ - 'App', - 'MaybeHiddenWork', - 'Updater', - 'MaybeHiddenWork:effect', - 'Updater:effect', - 'App:effect', - ]); - expect(scheduleUpdate).not.toBeNull(); - expect(scheduleUpdateWithHidden).not.toBeNull(); - expect(onRender).toHaveBeenCalledTimes(1); - - // schedule traced high-pri update and a (non-traced) low-pri update. - let interaction = null; - SchedulerTracing.unstable_trace('update', 0, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - ReactDOM.unstable_runWithPriority(ContinuousEventPriority, () => - scheduleUpdateWithHidden(), - ); - }); - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - scheduleUpdate(); - }); - } else { - scheduleUpdate(); - } - expect(interaction).not.toBeNull(); - expect(onRender).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - - // high-pri update should leave behind idle work and should not complete the interaction - expect(Scheduler).toFlushAndYieldThrough([ - 'MaybeHiddenWork', - 'MaybeHiddenWork:effect', - ]); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(2); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - - // low-pri update should not have the interaction - expect(Scheduler).toFlushAndYieldThrough([ - 'Updater', - 'Updater:effect', - ]); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(3); - expect(onRender).toHaveLastRenderedWithInteractions(new Set([])); - - // idle work should complete the interaction - expect(Scheduler).toFlushAndYield(['Child']); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - expect(onRender).toHaveBeenCalledTimes(4); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - }); - }); - - // @gate experimental - it('should properly trace interactions through a multi-pass SuspenseList render', () => { - const SuspenseList = React.SuspenseList; - const Suspense = React.Suspense; - function Text({text}) { - Scheduler.unstable_yieldValue(text); - React.useEffect(() => { - Scheduler.unstable_yieldValue('Commit ' + text); - }); - return {text}; - } - function App() { - return ( - - }> - - - }> - - - }> - - - - ); - } - - const container = document.createElement('div'); - const root = ReactDOM.createRoot(container); - - let interaction; - - act(() => { - SchedulerTracing.unstable_trace('initialization', 0, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - // This render is only CPU bound. Nothing suspends. - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - root.render(); - }); - } else { - root.render(); - } - }); - - expect(Scheduler).toFlushAndYieldThrough(['A']); - - Scheduler.unstable_advanceTime(200); - jest.advanceTimersByTime(200); - - expect(Scheduler).toFlushAndYieldThrough(['B']); - - Scheduler.unstable_advanceTime(300); - jest.advanceTimersByTime(300); - - // Time has now elapsed for so long that we're just going to give up - // rendering the rest of the content. So that we can at least show - // something. - expect(Scheduler).toFlushAndYieldThrough([ - 'Loading C', - 'Commit A', - 'Commit B', - 'Commit Loading C', - ]); - - // Schedule an unrelated low priority update that shouldn't be included - // in the previous interaction. This is meant to ensure that we don't - // rely on the whole tree completing to cover up bugs. - ReactDOM.unstable_runWithPriority(IdleEventPriority, () => { - root.render(); - }); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - // Then we do a second pass to commit the last item. - expect(Scheduler).toFlushAndYieldThrough(['C', 'Commit C']); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - }); - }); - }); - - describe('hydration', () => { - // @gate experimental - it('traces interaction across hydration', () => { - const ref = React.createRef(); - - function Child() { - return 'Hello'; - } - - function App() { - return ( -
    - - - -
    - ); - } - - // Render the final HTML. - const finalHTML = ReactDOMServer.renderToString(); - - const container = document.createElement('div'); - container.innerHTML = finalHTML; - - let interaction; - - const root = ReactDOM.createRoot(container, {hydrate: true}); - - // Hydrate it. - SchedulerTracing.unstable_trace('initialization', 0, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - - root.render(); - }); - Scheduler.unstable_flushAll(); - jest.runAllTimers(); - - expect(ref.current).not.toBe(null); - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - }); - - // @gate experimental - it('traces interaction across suspended hydration', async () => { - let suspend = false; - let resolve; - const promise = new Promise( - resolvePromise => (resolve = resolvePromise), - ); - const ref = React.createRef(); - - function Child() { - if (suspend) { - throw promise; - } else { - return 'Hello'; - } - } - - function App() { - return ( -
    - - - - - -
    - ); - } - - // Render the final HTML. - // Don't suspend on the server. - const finalHTML = ReactDOMServer.renderToString(); - - const container = document.createElement('div'); - container.innerHTML = finalHTML; - - let interaction; - - const root = ReactDOM.createRoot(container, {hydrate: true}); - - // Start hydrating but simulate blocking for suspense data. - suspend = true; - SchedulerTracing.unstable_trace('initialization', 0, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - - root.render(); - }); - Scheduler.unstable_flushAll(); - jest.runAllTimers(); - - expect(ref.current).toBe(null); - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - // Resolving the promise should continue hydration - suspend = false; - resolve(); - await promise; - Scheduler.unstable_flushAll(); - jest.runAllTimers(); - - expect(ref.current).not.toBe(null); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - }); - - // @gate experimental - it('traces interaction across suspended hydration from server', async () => { - // Copied from ReactDOMHostConfig.js - const SUSPENSE_START_DATA = '$'; - const SUSPENSE_PENDING_START_DATA = '$?'; - - const ref = React.createRef(); - - function App() { - return ( - - Hello - - ); - } - - const container = document.createElement('div'); - - // Render the final HTML. - const finalHTML = ReactDOMServer.renderToString(); - - // Replace the marker with a pending state. - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping - const escapedMarker = SUSPENSE_START_DATA.replace( - /[.*+\-?^${}()|[\]\\]/g, - '\\$&', - ); - container.innerHTML = finalHTML.replace( - new RegExp(escapedMarker, 'g'), - SUSPENSE_PENDING_START_DATA, - ); - - let interaction; - - const root = ReactDOM.createRoot(container, {hydrate: true}); - - // Start hydrating but simulate blocking for suspense data from the server. - SchedulerTracing.unstable_trace('initialization', 0, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - - root.render(); - }); - Scheduler.unstable_flushAll(); - jest.runAllTimers(); - - expect(ref.current).toBe(null); - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - // Unblock rendering, pretend the content is injected by the server. - const startNode = container.childNodes[0]; - expect(startNode).not.toBe(null); - expect(startNode.nodeType).toBe(Node.COMMENT_NODE); - - startNode.textContent = SUSPENSE_START_DATA; - startNode._reactRetry(); - - Scheduler.unstable_flushAll(); - jest.runAllTimers(); - - expect(ref.current).not.toBe(null); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - }); - - // @gate experimental - it('traces interaction across client-rendered hydration', () => { - let suspend = false; - const promise = new Promise(() => {}); - const ref = React.createRef(); - - function Child() { - if (suspend) { - throw promise; - } else { - return 'Hello'; - } - } - - function App() { - return ( -
    - - - - - -
    - ); - } - - // Render the final HTML. - suspend = true; - const finalHTML = ReactDOMServer.renderToString(); - - const container = document.createElement('div'); - container.innerHTML = finalHTML; - - let interaction; - - const root = ReactDOM.createRoot(container, {hydrate: true}); - - // Hydrate without suspending to fill in the client-rendered content. - suspend = false; - SchedulerTracing.unstable_trace('initialization', 0, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - - root.render(); - }); - - expect(onWorkStopped).toHaveBeenCalledTimes(1); - - // Advance time a bit so that we get into a new expiration bucket. - Scheduler.unstable_advanceTime(300); - jest.advanceTimersByTime(300); - - Scheduler.unstable_flushAll(); - jest.runAllTimers(); - - expect(ref.current).not.toBe(null); - - // We should've had two commits that was traced. - // First one that hydrates the parent, and then one that hydrates - // the boundary at higher than Never priority. - expect(onWorkStopped).toHaveBeenCalledTimes(3); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - }); - }); - }); -}); diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index 02c7541e38374..bac44d736e35f 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -14,23 +14,15 @@ let React; let ReactFeatureFlags; let ReactNoop; let Scheduler; -let ReactCache; let ReactTestRenderer; let ReactTestRendererAct; -let SchedulerTracing; let AdvanceTime; -let AsyncText; -let ComponentWithPassiveEffect; -let Text; -let TextResource; -let resourcePromise; function loadModules({ enableProfilerTimer = true, enableProfilerCommitHooks = true, enableProfilerNestedUpdatePhase = true, enableProfilerNestedUpdateScheduledHook = false, - enableSchedulerTracing = true, replayFailedUnitOfWorkWithInvokeGuardedCallback = false, useNoopRenderer = false, } = {}) { @@ -40,13 +32,10 @@ function loadModules({ ReactFeatureFlags.enableProfilerCommitHooks = enableProfilerCommitHooks; ReactFeatureFlags.enableProfilerNestedUpdatePhase = enableProfilerNestedUpdatePhase; ReactFeatureFlags.enableProfilerNestedUpdateScheduledHook = enableProfilerNestedUpdateScheduledHook; - ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing; ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = replayFailedUnitOfWorkWithInvokeGuardedCallback; React = require('react'); Scheduler = require('scheduler'); - SchedulerTracing = require('scheduler/tracing'); - ReactCache = require('react-cache'); if (useNoopRenderer) { ReactNoop = require('react-noop-renderer'); @@ -72,4960 +61,2774 @@ function loadModules({ return this.props.children || null; } }; - - resourcePromise = null; - - TextResource = ReactCache.unstable_createResource( - ([text, ms = 0]) => { - resourcePromise = new Promise((resolve, reject) => - setTimeout(() => { - Scheduler.unstable_yieldValue(`Promise resolved [${text}]`); - resolve(text); - }, ms), - ); - return resourcePromise; - }, - ([text, ms]) => text, - ); - - AsyncText = ({ms, text}) => { - try { - TextResource.read([text, ms]); - Scheduler.unstable_yieldValue(`AsyncText [${text}]`); - return text; - } catch (promise) { - if (typeof promise.then === 'function') { - Scheduler.unstable_yieldValue(`Suspend [${text}]`); - } else { - Scheduler.unstable_yieldValue(`Error [${text}]`); - } - throw promise; - } - }; - - Text = ({text}) => { - Scheduler.unstable_yieldValue(`Text [${text}]`); - return text; - }; - - ComponentWithPassiveEffect = () => { - // Intentionally schedule a passive effect so the onPostCommit hook will be called. - React.useEffect(() => {}); - return null; - }; } describe('Profiler', () => { describe('works in profiling and non-profiling bundles', () => { - [true, false].forEach(enableSchedulerTracing => { - [true, false].forEach(enableProfilerTimer => { - describe(`enableSchedulerTracing:${ - enableSchedulerTracing ? 'enabled' : 'disabled' - } enableProfilerTimer:${ - enableProfilerTimer ? 'enabled' : 'disabled' - }`, () => { - beforeEach(() => { - jest.resetModules(); - - loadModules({enableSchedulerTracing, enableProfilerTimer}); - }); - - // This will throw in production too, - // But the test is only interested in verifying the DEV error message. - if (__DEV__ && enableProfilerTimer) { - it('should warn if required params are missing', () => { - expect(() => { - ReactTestRenderer.create(); - }).toErrorDev( - 'Profiler must specify an "id" of type `string` as a prop. Received the type `undefined` instead.', - { - withoutStack: true, - }, - ); - }); - } - - it('should support an empty Profiler (with no children)', () => { - // As root - expect( - ReactTestRenderer.create( - , - ).toJSON(), - ).toMatchSnapshot(); - - // As non-root - expect( - ReactTestRenderer.create( -
    - -
    , - ).toJSON(), - ).toMatchSnapshot(); - }); - - it('should render children', () => { - const FunctionComponent = ({label}) => {label}; - const renderer = ReactTestRenderer.create( -
    - outside span - - inside span - - -
    , - ); - expect(renderer.toJSON()).toMatchSnapshot(); - }); + [true, false].forEach(enableProfilerTimer => { + describe(`enableProfilerTimer:${ + enableProfilerTimer ? 'enabled' : 'disabled' + }`, () => { + beforeEach(() => { + jest.resetModules(); + + loadModules({enableProfilerTimer}); + }); - it('should support nested Profilers', () => { - const FunctionComponent = ({label}) =>
    {label}
    ; - class ClassComponent extends React.Component { - render() { - return {this.props.label}; - } - } - const renderer = ReactTestRenderer.create( - - - - - inner span - - , + // This will throw in production too, + // But the test is only interested in verifying the DEV error message. + if (__DEV__ && enableProfilerTimer) { + it('should warn if required params are missing', () => { + expect(() => { + ReactTestRenderer.create(); + }).toErrorDev( + 'Profiler must specify an "id" of type `string` as a prop. Received the type `undefined` instead.', + { + withoutStack: true, + }, ); - expect(renderer.toJSON()).toMatchSnapshot(); }); - }); - }); - }); - }); + } - [true, false].forEach(enableSchedulerTracing => { - describe(`onRender enableSchedulerTracing:${ - enableSchedulerTracing ? 'enabled' : 'disabled' - }`, () => { - beforeEach(() => { - jest.resetModules(); + it('should support an empty Profiler (with no children)', () => { + // As root + expect( + ReactTestRenderer.create( + , + ).toJSON(), + ).toMatchSnapshot(); - loadModules({ - enableSchedulerTracing, + // As non-root + expect( + ReactTestRenderer.create( +
    + +
    , + ).toJSON(), + ).toMatchSnapshot(); }); - }); - it('should handle errors thrown', () => { - const callback = jest.fn(id => { - if (id === 'throw') { - throw Error('expected'); - } + it('should render children', () => { + const FunctionComponent = ({label}) => {label}; + const renderer = ReactTestRenderer.create( +
    + outside span + + inside span + + +
    , + ); + expect(renderer.toJSON()).toMatchSnapshot(); }); - let didMount = false; - class ClassComponent extends React.Component { - componentDidMount() { - didMount = true; - } - render() { - return this.props.children; + it('should support nested Profilers', () => { + const FunctionComponent = ({label}) =>
    {label}
    ; + class ClassComponent extends React.Component { + render() { + return {this.props.label}; + } } - } - - // Errors thrown from onRender should not break the commit phase, - // Or prevent other lifecycles from being called. - expect(() => - ReactTestRenderer.create( - - - -
    - + const renderer = ReactTestRenderer.create( + + + + + inner span - , - ), - ).toThrow('expected'); - expect(didMount).toBe(true); - expect(callback).toHaveBeenCalledTimes(2); - }); - - // @gate experimental || !enableSyncDefaultUpdates - it('is not invoked until the commit phase', () => { - const callback = jest.fn(); - - const Yield = ({value}) => { - Scheduler.unstable_yieldValue(value); - return null; - }; - - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - ReactTestRenderer.create( - - - - , - { - unstable_isConcurrent: true, - }, - ); - }); - } else { - ReactTestRenderer.create( - - - , - { - unstable_isConcurrent: true, - }, ); - } - - // Times are logged until a render is committed. - expect(Scheduler).toFlushAndYieldThrough(['first']); - expect(callback).toHaveBeenCalledTimes(0); - expect(Scheduler).toFlushAndYield(['last']); - expect(callback).toHaveBeenCalledTimes(1); + expect(renderer.toJSON()).toMatchSnapshot(); + }); }); + }); + }); +}); - 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', obj => { - const ActualScheduler = require.requireActual( - 'scheduler/unstable_mock', - ); - return { - ...ActualScheduler, - unstable_now: function mockUnstableNow() { - ActualScheduler.unstable_yieldValue('read current time'); - return ActualScheduler.unstable_now(); - }, - }; - }); +describe(`onRender`, () => { + beforeEach(() => { + jest.resetModules(); - jest.resetModules(); + loadModules(); + }); - loadModules({enableSchedulerTracing}); + it('should handle errors thrown', () => { + const callback = jest.fn(id => { + if (id === 'throw') { + throw Error('expected'); + } + }); - // Clear yields in case the current time is read during initialization. - Scheduler.unstable_clearYields(); + let didMount = false; + class ClassComponent extends React.Component { + componentDidMount() { + didMount = true; + } + render() { + return this.props.children; + } + } - ReactTestRenderer.create( -
    - - - - - -
    , - ); + // Errors thrown from onRender should not break the commit phase, + // Or prevent other lifecycles from being called. + expect(() => + ReactTestRenderer.create( + + + +
    + + + , + ), + ).toThrow('expected'); + expect(didMount).toBe(true); + expect(callback).toHaveBeenCalledTimes(2); + }); - // TODO: unstable_now is called by more places than just the profiler. - // Rewrite this test so it's less fragile. - expect(Scheduler).toHaveYielded([ - 'read current time', - 'read current time', - 'read current time', - 'read current time', - 'read current time', - ]); - - // Restore original mock - jest.mock('scheduler', () => - require.requireActual('scheduler/unstable_mock'), - ); - }); + // @gate experimental || !enableSyncDefaultUpdates + it('is not invoked until the commit phase', () => { + const callback = jest.fn(); - it('does not report work done on a sibling', () => { - const callback = jest.fn(); + const Yield = ({value}) => { + Scheduler.unstable_yieldValue(value); + return null; + }; - const DoesNotUpdate = React.memo( - function DoesNotUpdateInner() { - Scheduler.unstable_advanceTime(10); - return null; + if (gate(flags => flags.enableSyncDefaultUpdates)) { + React.unstable_startTransition(() => { + ReactTestRenderer.create( + + + + , + { + unstable_isConcurrent: true, }, - () => true, ); + }); + } else { + ReactTestRenderer.create( + + + + , + { + unstable_isConcurrent: true, + }, + ); + } - let updateProfilerSibling; + // Times are logged until a render is committed. + expect(Scheduler).toFlushAndYieldThrough(['first']); + expect(callback).toHaveBeenCalledTimes(0); + expect(Scheduler).toFlushAndYield(['last']); + expect(callback).toHaveBeenCalledTimes(1); + }); - function ProfilerSibling() { - const [count, setCount] = React.useState(0); - updateProfilerSibling = () => setCount(count + 1); - return null; - } + 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', obj => { + const ActualScheduler = require.requireActual('scheduler/unstable_mock'); + return { + ...ActualScheduler, + unstable_now: function mockUnstableNow() { + ActualScheduler.unstable_yieldValue('read current time'); + return ActualScheduler.unstable_now(); + }, + }; + }); - function App() { - return ( - - - - - - - ); - } + jest.resetModules(); + + loadModules(); + + // Clear yields in case the current time is read during initialization. + Scheduler.unstable_clearYields(); + + ReactTestRenderer.create( +
    + + + + + +
    , + ); + + // TODO: unstable_now is called by more places than just the profiler. + // Rewrite this test so it's less fragile. + expect(Scheduler).toHaveYielded([ + 'read current time', + 'read current time', + 'read current time', + 'read current time', + 'read current time', + ]); + + // Restore original mock + jest.mock('scheduler', () => + require.requireActual('scheduler/unstable_mock'), + ); + }); - const renderer = ReactTestRenderer.create(); + it('does not report work done on a sibling', () => { + const callback = jest.fn(); - expect(callback).toHaveBeenCalledTimes(1); + const DoesNotUpdate = React.memo( + function DoesNotUpdateInner() { + Scheduler.unstable_advanceTime(10); + return null; + }, + () => true, + ); - let call = callback.mock.calls[0]; + let updateProfilerSibling; - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6); - expect(call[0]).toBe('test'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(10); // actual time - expect(call[3]).toBe(10); // base time - expect(call[4]).toBe(0); // start time - expect(call[5]).toBe(10); // commit time - expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + function ProfilerSibling() { + const [count, setCount] = React.useState(0); + updateProfilerSibling = () => setCount(count + 1); + return null; + } - callback.mockReset(); + function App() { + return ( + + + + + + + ); + } - Scheduler.unstable_advanceTime(20); // 10 -> 30 + const renderer = ReactTestRenderer.create(); - renderer.update(); + expect(callback).toHaveBeenCalledTimes(1); - if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) { - // None of the Profiler's subtree was rendered because App bailed out before the Profiler. - // So we expect onRender not to be called. - expect(callback).not.toHaveBeenCalled(); - } else { - // Updating a parent reports a re-render, - // since React technically did a little bit of work between the Profiler and the bailed out subtree. - // This is not optimal but it's how the old reconciler fork works. - expect(callback).toHaveBeenCalledTimes(1); + let call = callback.mock.calls[0]; - call = callback.mock.calls[0]; + expect(call).toHaveLength(6); + expect(call[0]).toBe('test'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(10); // actual time + expect(call[3]).toBe(10); // base time + expect(call[4]).toBe(0); // start time + expect(call[5]).toBe(10); // commit time - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6); - expect(call[0]).toBe('test'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(0); // actual time - expect(call[3]).toBe(10); // base time - expect(call[4]).toBe(30); // start time - expect(call[5]).toBe(30); // commit time - expect(call[6]).toEqual( - enableSchedulerTracing ? new Set() : undefined, - ); // interaction events + callback.mockReset(); - callback.mockReset(); - } + Scheduler.unstable_advanceTime(20); // 10 -> 30 - Scheduler.unstable_advanceTime(20); // 30 -> 50 + renderer.update(); - // Updating a sibling should not report a re-render. - ReactTestRendererAct(updateProfilerSibling); + if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) { + // None of the Profiler's subtree was rendered because App bailed out before the Profiler. + // So we expect onRender not to be called. + expect(callback).not.toHaveBeenCalled(); + } else { + // Updating a parent reports a re-render, + // since React technically did a little bit of work between the Profiler and the bailed out subtree. + // This is not optimal but it's how the old reconciler fork works. + expect(callback).toHaveBeenCalledTimes(1); - expect(callback).not.toHaveBeenCalled(); - }); + call = callback.mock.calls[0]; - it('logs render times for both mount and update', () => { - const callback = jest.fn(); + expect(call).toHaveLength(6); + expect(call[0]).toBe('test'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(0); // actual time + expect(call[3]).toBe(10); // base time + expect(call[4]).toBe(30); // start time + expect(call[5]).toBe(30); // commit time - Scheduler.unstable_advanceTime(5); // 0 -> 5 + callback.mockReset(); + } - const renderer = ReactTestRenderer.create( - - - , - ); + Scheduler.unstable_advanceTime(20); // 30 -> 50 - expect(callback).toHaveBeenCalledTimes(1); + // Updating a sibling should not report a re-render. + ReactTestRendererAct(updateProfilerSibling); - let [call] = callback.mock.calls; + expect(callback).not.toHaveBeenCalled(); + }); - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6); - expect(call[0]).toBe('test'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(10); // actual time - expect(call[3]).toBe(10); // base time - expect(call[4]).toBe(5); // start time - expect(call[5]).toBe(15); // commit time - expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + it('logs render times for both mount and update', () => { + const callback = jest.fn(); - callback.mockReset(); + Scheduler.unstable_advanceTime(5); // 0 -> 5 - Scheduler.unstable_advanceTime(20); // 15 -> 35 + const renderer = ReactTestRenderer.create( + + + , + ); - renderer.update( - - - , - ); + expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledTimes(1); + let [call] = callback.mock.calls; - [call] = callback.mock.calls; + expect(call).toHaveLength(6); + expect(call[0]).toBe('test'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(10); // actual time + expect(call[3]).toBe(10); // base time + expect(call[4]).toBe(5); // start time + expect(call[5]).toBe(15); // commit time - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6); - expect(call[0]).toBe('test'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(10); // actual time - expect(call[3]).toBe(10); // base time - expect(call[4]).toBe(35); // start time - expect(call[5]).toBe(45); // commit time - expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + callback.mockReset(); - callback.mockReset(); + Scheduler.unstable_advanceTime(20); // 15 -> 35 - Scheduler.unstable_advanceTime(20); // 45 -> 65 + renderer.update( + + + , + ); - renderer.update( - - - , - ); + expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledTimes(1); + [call] = callback.mock.calls; - [call] = callback.mock.calls; + expect(call).toHaveLength(6); + expect(call[0]).toBe('test'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(10); // actual time + expect(call[3]).toBe(10); // base time + expect(call[4]).toBe(35); // start time + expect(call[5]).toBe(45); // commit time - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6); - expect(call[0]).toBe('test'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(4); // actual time - expect(call[3]).toBe(4); // base time - expect(call[4]).toBe(65); // start time - expect(call[5]).toBe(69); // commit time - expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - }); + callback.mockReset(); - it('includes render times of nested Profilers in their parent times', () => { - const callback = jest.fn(); + Scheduler.unstable_advanceTime(20); // 45 -> 65 - Scheduler.unstable_advanceTime(5); // 0 -> 5 + renderer.update( + + + , + ); - ReactTestRenderer.create( - - - - - - - - - , - ); + expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledTimes(2); - - // Callbacks bubble (reverse order). - const [childCall, parentCall] = callback.mock.calls; - expect(childCall[0]).toBe('child'); - expect(parentCall[0]).toBe('parent'); - - // Parent times should include child times - expect(childCall[2]).toBe(20); // actual time - expect(childCall[3]).toBe(20); // base time - expect(childCall[4]).toBe(15); // start time - expect(childCall[5]).toBe(35); // commit time - expect(parentCall[2]).toBe(30); // actual time - expect(parentCall[3]).toBe(30); // base time - expect(parentCall[4]).toBe(5); // start time - expect(parentCall[5]).toBe(35); // commit time - }); + [call] = callback.mock.calls; - it('traces sibling Profilers separately', () => { - const callback = jest.fn(); + expect(call).toHaveLength(6); + expect(call[0]).toBe('test'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(4); // actual time + expect(call[3]).toBe(4); // base time + expect(call[4]).toBe(65); // start time + expect(call[5]).toBe(69); // commit time + }); - Scheduler.unstable_advanceTime(5); // 0 -> 5 + it('includes render times of nested Profilers in their parent times', () => { + const callback = jest.fn(); - ReactTestRenderer.create( - - + Scheduler.unstable_advanceTime(5); // 0 -> 5 + + ReactTestRenderer.create( + + + + - - - - , - ); + + + , + ); + + expect(callback).toHaveBeenCalledTimes(2); + + // Callbacks bubble (reverse order). + const [childCall, parentCall] = callback.mock.calls; + expect(childCall[0]).toBe('child'); + expect(parentCall[0]).toBe('parent'); + + // Parent times should include child times + expect(childCall[2]).toBe(20); // actual time + expect(childCall[3]).toBe(20); // base time + expect(childCall[4]).toBe(15); // start time + expect(childCall[5]).toBe(35); // commit time + expect(parentCall[2]).toBe(30); // actual time + expect(parentCall[3]).toBe(30); // base time + expect(parentCall[4]).toBe(5); // start time + expect(parentCall[5]).toBe(35); // commit time + }); - expect(callback).toHaveBeenCalledTimes(2); - - const [firstCall, secondCall] = callback.mock.calls; - expect(firstCall[0]).toBe('first'); - expect(secondCall[0]).toBe('second'); - - // Parent times should include child times - expect(firstCall[2]).toBe(20); // actual time - expect(firstCall[3]).toBe(20); // base time - expect(firstCall[4]).toBe(5); // start time - expect(firstCall[5]).toBe(30); // commit time - expect(secondCall[2]).toBe(5); // actual time - expect(secondCall[3]).toBe(5); // base time - expect(secondCall[4]).toBe(25); // start time - expect(secondCall[5]).toBe(30); // commit time - }); + it('traces sibling Profilers separately', () => { + const callback = jest.fn(); + + Scheduler.unstable_advanceTime(5); // 0 -> 5 + + ReactTestRenderer.create( + + + + + + + + , + ); + + expect(callback).toHaveBeenCalledTimes(2); + + const [firstCall, secondCall] = callback.mock.calls; + expect(firstCall[0]).toBe('first'); + expect(secondCall[0]).toBe('second'); + + // Parent times should include child times + expect(firstCall[2]).toBe(20); // actual time + expect(firstCall[3]).toBe(20); // base time + expect(firstCall[4]).toBe(5); // start time + expect(firstCall[5]).toBe(30); // commit time + expect(secondCall[2]).toBe(5); // actual time + expect(secondCall[3]).toBe(5); // base time + expect(secondCall[4]).toBe(25); // start time + expect(secondCall[5]).toBe(30); // commit time + }); - it('does not include time spent outside of profile root', () => { - const callback = jest.fn(); + it('does not include time spent outside of profile root', () => { + const callback = jest.fn(); + + Scheduler.unstable_advanceTime(5); // 0 -> 5 + + ReactTestRenderer.create( + + + + + + + , + ); + + expect(callback).toHaveBeenCalledTimes(1); + + const [call] = callback.mock.calls; + expect(call[0]).toBe('test'); + expect(call[2]).toBe(5); // actual time + expect(call[3]).toBe(5); // base time + expect(call[4]).toBe(25); // start time + expect(call[5]).toBe(50); // commit time + }); - Scheduler.unstable_advanceTime(5); // 0 -> 5 + it('is not called when blocked by sCU false', () => { + const callback = jest.fn(); - ReactTestRenderer.create( - - - - - - - , - ); + let instance; + class Updater extends React.Component { + state = {}; + render() { + instance = this; + return this.props.children; + } + } - expect(callback).toHaveBeenCalledTimes(1); + const renderer = ReactTestRenderer.create( + + + +
    + + + , + ); - const [call] = callback.mock.calls; - expect(call[0]).toBe('test'); - expect(call[2]).toBe(5); // actual time - expect(call[3]).toBe(5); // base time - expect(call[4]).toBe(25); // start time - expect(call[5]).toBe(50); // commit time - }); + // All profile callbacks are called for initial render + expect(callback).toHaveBeenCalledTimes(2); - it('is not called when blocked by sCU false', () => { - const callback = jest.fn(); + callback.mockReset(); - let instance; - class Updater extends React.Component { - state = {}; - render() { - instance = this; - return this.props.children; - } - } + renderer.unstable_flushSync(() => { + instance.setState({ + count: 1, + }); + }); - const renderer = ReactTestRenderer.create( - - - -
    - - - , - ); + // Only call onRender for paths that have re-rendered. + // Since the Updater's props didn't change, + // React does not re-render its children. + expect(callback).toHaveBeenCalledTimes(1); + expect(callback.mock.calls[0][0]).toBe('outer'); + }); - // All profile callbacks are called for initial render - expect(callback).toHaveBeenCalledTimes(2); + it('decreases actual time but not base time when sCU prevents an update', () => { + const callback = jest.fn(); - callback.mockReset(); + Scheduler.unstable_advanceTime(5); // 0 -> 5 - renderer.unstable_flushSync(() => { - instance.setState({ - count: 1, - }); - }); + const renderer = ReactTestRenderer.create( + + + + + , + ); - // Only call onRender for paths that have re-rendered. - // Since the Updater's props didn't change, - // React does not re-render its children. - expect(callback).toHaveBeenCalledTimes(1); - expect(callback.mock.calls[0][0]).toBe('outer'); - }); + expect(callback).toHaveBeenCalledTimes(1); - it('decreases actual time but not base time when sCU prevents an update', () => { - const callback = jest.fn(); + Scheduler.unstable_advanceTime(30); // 28 -> 58 - Scheduler.unstable_advanceTime(5); // 0 -> 5 + renderer.update( + + + + + , + ); - const renderer = ReactTestRenderer.create( - - - - - , - ); + expect(callback).toHaveBeenCalledTimes(2); - expect(callback).toHaveBeenCalledTimes(1); + const [mountCall, updateCall] = callback.mock.calls; - Scheduler.unstable_advanceTime(30); // 28 -> 58 + expect(mountCall[1]).toBe('mount'); + expect(mountCall[2]).toBe(23); // actual time + expect(mountCall[3]).toBe(23); // base time + expect(mountCall[4]).toBe(5); // start time + expect(mountCall[5]).toBe(28); // commit time - renderer.update( - - - - - , - ); + expect(updateCall[1]).toBe('update'); + expect(updateCall[2]).toBe(4); // actual time + expect(updateCall[3]).toBe(17); // base time + expect(updateCall[4]).toBe(58); // start time + expect(updateCall[5]).toBe(62); // commit time + }); - expect(callback).toHaveBeenCalledTimes(2); + it('includes time spent in render phase lifecycles', () => { + class WithLifecycles extends React.Component { + state = {}; + static getDerivedStateFromProps() { + Scheduler.unstable_advanceTime(3); + return null; + } + shouldComponentUpdate() { + Scheduler.unstable_advanceTime(7); + return true; + } + render() { + Scheduler.unstable_advanceTime(5); + return null; + } + } - const [mountCall, updateCall] = callback.mock.calls; + const callback = jest.fn(); - expect(mountCall[1]).toBe('mount'); - expect(mountCall[2]).toBe(23); // actual time - expect(mountCall[3]).toBe(23); // base time - expect(mountCall[4]).toBe(5); // start time - expect(mountCall[5]).toBe(28); // commit time + Scheduler.unstable_advanceTime(5); // 0 -> 5 - expect(updateCall[1]).toBe('update'); - expect(updateCall[2]).toBe(4); // actual time - expect(updateCall[3]).toBe(17); // base time - expect(updateCall[4]).toBe(58); // start time - expect(updateCall[5]).toBe(62); // commit time - }); + const renderer = ReactTestRenderer.create( + + + , + ); - it('includes time spent in render phase lifecycles', () => { - class WithLifecycles extends React.Component { - state = {}; - static getDerivedStateFromProps() { - Scheduler.unstable_advanceTime(3); - return null; - } - shouldComponentUpdate() { - Scheduler.unstable_advanceTime(7); - return true; - } - render() { - Scheduler.unstable_advanceTime(5); - return null; - } - } + Scheduler.unstable_advanceTime(15); // 13 -> 28 - const callback = jest.fn(); + renderer.update( + + + , + ); - Scheduler.unstable_advanceTime(5); // 0 -> 5 + expect(callback).toHaveBeenCalledTimes(2); - const renderer = ReactTestRenderer.create( - - - , - ); + const [mountCall, updateCall] = callback.mock.calls; - Scheduler.unstable_advanceTime(15); // 13 -> 28 + expect(mountCall[1]).toBe('mount'); + expect(mountCall[2]).toBe(8); // actual time + expect(mountCall[3]).toBe(8); // base time + expect(mountCall[4]).toBe(5); // start time + expect(mountCall[5]).toBe(13); // commit time - renderer.update( - - - , - ); + expect(updateCall[1]).toBe('update'); + expect(updateCall[2]).toBe(15); // actual time + expect(updateCall[3]).toBe(15); // base time + expect(updateCall[4]).toBe(28); // start time + expect(updateCall[5]).toBe(43); // commit time + }); - expect(callback).toHaveBeenCalledTimes(2); + it('should clear nested-update flag when multiple cascading renders are scheduled', () => { + loadModules({ + useNoopRenderer: true, + }); - const [mountCall, updateCall] = callback.mock.calls; + function Component() { + const [didMount, setDidMount] = React.useState(false); + const [didMountAndUpdate, setDidMountAndUpdate] = React.useState(false); - expect(mountCall[1]).toBe('mount'); - expect(mountCall[2]).toBe(8); // actual time - expect(mountCall[3]).toBe(8); // base time - expect(mountCall[4]).toBe(5); // start time - expect(mountCall[5]).toBe(13); // commit time + React.useLayoutEffect(() => { + setDidMount(true); + }, []); - expect(updateCall[1]).toBe('update'); - expect(updateCall[2]).toBe(15); // actual time - expect(updateCall[3]).toBe(15); // base time - expect(updateCall[4]).toBe(28); // start time - expect(updateCall[5]).toBe(43); // commit time - }); + React.useEffect(() => { + if (didMount && !didMountAndUpdate) { + setDidMountAndUpdate(true); + } + }, [didMount, didMountAndUpdate]); - it('should clear nested-update flag when multiple cascading renders are scheduled', () => { - loadModules({ - enableSchedulerTracing, - useNoopRenderer: true, - }); + Scheduler.unstable_yieldValue(`${didMount}:${didMountAndUpdate}`); - function Component() { - const [didMount, setDidMount] = React.useState(false); - const [didMountAndUpdate, setDidMountAndUpdate] = React.useState( - false, - ); + return null; + } - React.useLayoutEffect(() => { - setDidMount(true); - }, []); + const onRender = jest.fn(); - React.useEffect(() => { - if (didMount && !didMountAndUpdate) { - setDidMountAndUpdate(true); - } - }, [didMount, didMountAndUpdate]); + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + expect(Scheduler).toHaveYielded(['false:false', 'true:false', 'true:true']); - Scheduler.unstable_yieldValue(`${didMount}:${didMountAndUpdate}`); + expect(onRender).toHaveBeenCalledTimes(3); + expect(onRender.mock.calls[0][1]).toBe('mount'); + expect(onRender.mock.calls[1][1]).toBe('nested-update'); + expect(onRender.mock.calls[2][1]).toBe('update'); + }); - return null; - } + // @gate experimental + it('is properly distinguish updates and nested-updates when there is more than sync remaining work', () => { + loadModules({ + useNoopRenderer: true, + }); - const onRender = jest.fn(); + function Component() { + const [didMount, setDidMount] = React.useState(false); - ReactNoop.act(() => { - ReactNoop.render( - - - , - ); - }); - expect(Scheduler).toHaveYielded([ - 'false:false', - 'true:false', - 'true:true', - ]); - - expect(onRender).toHaveBeenCalledTimes(3); - expect(onRender.mock.calls[0][1]).toBe('mount'); - expect(onRender.mock.calls[1][1]).toBe('nested-update'); - expect(onRender.mock.calls[2][1]).toBe('update'); - }); + React.useLayoutEffect(() => { + setDidMount(true); + }, []); + Scheduler.unstable_yieldValue(didMount); + return didMount; + } - // @gate experimental - it('is properly distinguish updates and nested-updates when there is more than sync remaining work', () => { - loadModules({ - enableSchedulerTracing, - useNoopRenderer: true, - }); + const onRender = jest.fn(); + + // Schedule low-priority work. + React.unstable_startTransition(() => + ReactNoop.render( + + + , + ), + ); + + // Flush sync work with a nested update + ReactNoop.flushSync(() => { + ReactNoop.render( + + + , + ); + }); + expect(Scheduler).toHaveYielded([false, true]); - function Component() { - const [didMount, setDidMount] = React.useState(false); + // Verify that the nested update inside of the sync work is appropriately tagged. + expect(onRender).toHaveBeenCalledTimes(2); + expect(onRender.mock.calls[0][1]).toBe('mount'); + expect(onRender.mock.calls[1][1]).toBe('nested-update'); + }); - React.useLayoutEffect(() => { - setDidMount(true); - }, []); - Scheduler.unstable_yieldValue(didMount); - return didMount; - } + describe('with regard to interruptions', () => { + // @gate experimental || !enableSyncDefaultUpdates + it('should accumulate actual time after a scheduling interruptions', () => { + const callback = jest.fn(); - const onRender = jest.fn(); + const Yield = ({renderTime}) => { + Scheduler.unstable_advanceTime(renderTime); + Scheduler.unstable_yieldValue('Yield:' + renderTime); + return null; + }; - // Schedule low-priority work. - React.unstable_startTransition(() => - ReactNoop.render( - - - , - ), - ); + Scheduler.unstable_advanceTime(5); // 0 -> 5 - // Flush sync work with a nested update - ReactNoop.flushSync(() => { - ReactNoop.render( - - + // Render partially, but run out of time before completing. + if (gate(flags => flags.enableSyncDefaultUpdates)) { + React.unstable_startTransition(() => { + ReactTestRenderer.create( + + + , + {unstable_isConcurrent: true}, ); }); - expect(Scheduler).toHaveYielded([false, true]); - - // Verify that the nested update inside of the sync work is appropriately tagged. - expect(onRender).toHaveBeenCalledTimes(2); - expect(onRender.mock.calls[0][1]).toBe('mount'); - expect(onRender.mock.calls[1][1]).toBe('nested-update'); - }); + } else { + ReactTestRenderer.create( + + + + , + {unstable_isConcurrent: true}, + ); + } + expect(Scheduler).toFlushAndYieldThrough(['Yield:2']); + expect(callback).toHaveBeenCalledTimes(0); - describe('with regard to interruptions', () => { - // @gate experimental || !enableSyncDefaultUpdates - it('should accumulate actual time after a scheduling interruptions', () => { - const callback = jest.fn(); + // Resume render for remaining children. + expect(Scheduler).toFlushAndYield(['Yield:3']); - const Yield = ({renderTime}) => { - Scheduler.unstable_advanceTime(renderTime); - Scheduler.unstable_yieldValue('Yield:' + renderTime); - return null; - }; + // Verify that logged times include both durations above. + expect(callback).toHaveBeenCalledTimes(1); + const [call] = callback.mock.calls; + expect(call[2]).toBe(5); // actual time + expect(call[3]).toBe(5); // base time + expect(call[4]).toBe(5); // start time + expect(call[5]).toBe(10); // commit time + }); - Scheduler.unstable_advanceTime(5); // 0 -> 5 + // @gate experimental || !enableSyncDefaultUpdates + it('should not include time between frames', () => { + const callback = jest.fn(); - // Render partially, but run out of time before completing. - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - ReactTestRenderer.create( - - - - , - {unstable_isConcurrent: true}, - ); - }); - } else { - ReactTestRenderer.create( - - - - , - {unstable_isConcurrent: true}, - ); - } - expect(Scheduler).toFlushAndYieldThrough(['Yield:2']); - expect(callback).toHaveBeenCalledTimes(0); + const Yield = ({renderTime}) => { + Scheduler.unstable_advanceTime(renderTime); + Scheduler.unstable_yieldValue('Yield:' + renderTime); + return null; + }; - // Resume render for remaining children. - expect(Scheduler).toFlushAndYield(['Yield:3']); + Scheduler.unstable_advanceTime(5); // 0 -> 5 - // Verify that logged times include both durations above. - expect(callback).toHaveBeenCalledTimes(1); - const [call] = callback.mock.calls; - expect(call[2]).toBe(5); // actual time - expect(call[3]).toBe(5); // base time - expect(call[4]).toBe(5); // start time - expect(call[5]).toBe(10); // commit time + // Render partially, but don't finish. + // This partial render should take 5ms of simulated time. + if (gate(flags => flags.enableSyncDefaultUpdates)) { + React.unstable_startTransition(() => { + ReactTestRenderer.create( + + + + + + + , + {unstable_isConcurrent: true}, + ); }); + } else { + ReactTestRenderer.create( + + + + + + + , + {unstable_isConcurrent: true}, + ); + } + expect(Scheduler).toFlushAndYieldThrough(['Yield:5']); + expect(callback).toHaveBeenCalledTimes(0); + + // Simulate time moving forward while frame is paused. + Scheduler.unstable_advanceTime(50); // 10 -> 60 + + // Flush the remaining work, + // Which should take an additional 10ms of simulated time. + expect(Scheduler).toFlushAndYield(['Yield:10', 'Yield:17']); + expect(callback).toHaveBeenCalledTimes(2); + + const [innerCall, outerCall] = callback.mock.calls; + + // Verify that the actual time includes all work times, + // But not the time that elapsed between frames. + expect(innerCall[0]).toBe('inner'); + expect(innerCall[2]).toBe(17); // actual time + expect(innerCall[3]).toBe(17); // base time + expect(innerCall[4]).toBe(70); // start time + expect(innerCall[5]).toBe(87); // commit time + expect(outerCall[0]).toBe('outer'); + expect(outerCall[2]).toBe(32); // actual time + expect(outerCall[3]).toBe(32); // base time + expect(outerCall[4]).toBe(5); // start time + expect(outerCall[5]).toBe(87); // commit time + }); - // @gate experimental || !enableSyncDefaultUpdates - it('should not include time between frames', () => { - const callback = jest.fn(); - - const Yield = ({renderTime}) => { - Scheduler.unstable_advanceTime(renderTime); - Scheduler.unstable_yieldValue('Yield:' + renderTime); - return null; - }; + // @gate experimental || !enableSyncDefaultUpdates + it('should report the expected times when a high-pri update replaces a mount in-progress', () => { + const callback = jest.fn(); - Scheduler.unstable_advanceTime(5); // 0 -> 5 + const Yield = ({renderTime}) => { + Scheduler.unstable_advanceTime(renderTime); + Scheduler.unstable_yieldValue('Yield:' + renderTime); + return null; + }; - // Render partially, but don't finish. - // This partial render should take 5ms of simulated time. - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - ReactTestRenderer.create( - - - - - - - , - {unstable_isConcurrent: true}, - ); - }); - } else { - ReactTestRenderer.create( - - - - - - - , - {unstable_isConcurrent: true}, - ); - } - expect(Scheduler).toFlushAndYieldThrough(['Yield:5']); - expect(callback).toHaveBeenCalledTimes(0); + Scheduler.unstable_advanceTime(5); // 0 -> 5 + + // Render a partially update, but don't finish. + // This partial render should take 10ms of simulated time. + let renderer; + if (gate(flags => flags.enableSyncDefaultUpdates)) { + React.unstable_startTransition(() => { + renderer = ReactTestRenderer.create( + + + + , + {unstable_isConcurrent: true}, + ); + }); + } else { + renderer = ReactTestRenderer.create( + + + + , + {unstable_isConcurrent: true}, + ); + } + expect(Scheduler).toFlushAndYieldThrough(['Yield:10']); + expect(callback).toHaveBeenCalledTimes(0); - // Simulate time moving forward while frame is paused. - Scheduler.unstable_advanceTime(50); // 10 -> 60 + // Simulate time moving forward while frame is paused. + Scheduler.unstable_advanceTime(100); // 15 -> 115 - // Flush the remaining work, - // Which should take an additional 10ms of simulated time. - expect(Scheduler).toFlushAndYield(['Yield:10', 'Yield:17']); - expect(callback).toHaveBeenCalledTimes(2); + // Interrupt with higher priority work. + // The interrupted work simulates an additional 5ms of time. + renderer.unstable_flushSync(() => { + renderer.update( + + + , + ); + }); + expect(Scheduler).toHaveYielded(['Yield:5']); + + // The initial work was thrown away in this case, + // So the actual and base times should only include the final rendered tree times. + expect(callback).toHaveBeenCalledTimes(1); + const call = callback.mock.calls[0]; + expect(call[2]).toBe(5); // actual time + expect(call[3]).toBe(5); // base time + expect(call[4]).toBe(115); // start time + expect(call[5]).toBe(120); // commit time + + callback.mockReset(); + + // Verify no more unexpected callbacks from low priority work + expect(Scheduler).toFlushWithoutYielding(); + expect(callback).toHaveBeenCalledTimes(0); + }); + + // @gate experimental || !enableSyncDefaultUpdates + it('should report the expected times when a high-priority update replaces a low-priority update', () => { + const callback = jest.fn(); + + const Yield = ({renderTime}) => { + Scheduler.unstable_advanceTime(renderTime); + Scheduler.unstable_yieldValue('Yield:' + renderTime); + return null; + }; + + Scheduler.unstable_advanceTime(5); // 0 -> 5 + + const renderer = ReactTestRenderer.create( + + + + , + {unstable_isConcurrent: true}, + ); + + // Render everything initially. + // This should take 21 seconds of actual and base time. + expect(Scheduler).toFlushAndYield(['Yield:6', 'Yield:15']); + expect(callback).toHaveBeenCalledTimes(1); + let call = callback.mock.calls[0]; + expect(call[2]).toBe(21); // actual time + expect(call[3]).toBe(21); // base time + expect(call[4]).toBe(5); // start time + expect(call[5]).toBe(26); // commit time + + callback.mockReset(); - const [innerCall, outerCall] = callback.mock.calls; - - // Verify that the actual time includes all work times, - // But not the time that elapsed between frames. - expect(innerCall[0]).toBe('inner'); - expect(innerCall[2]).toBe(17); // actual time - expect(innerCall[3]).toBe(17); // base time - expect(innerCall[4]).toBe(70); // start time - expect(innerCall[5]).toBe(87); // commit time - expect(outerCall[0]).toBe('outer'); - expect(outerCall[2]).toBe(32); // actual time - expect(outerCall[3]).toBe(32); // base time - expect(outerCall[4]).toBe(5); // start time - expect(outerCall[5]).toBe(87); // commit time + Scheduler.unstable_advanceTime(30); // 26 -> 56 + + // Render a partially update, but don't finish. + // This partial render should take 3ms of simulated time. + if (gate(flags => flags.enableSyncDefaultUpdates)) { + React.unstable_startTransition(() => { + renderer.update( + + + + + , + ); }); + } else { + renderer.update( + + + + + , + ); + } + expect(Scheduler).toFlushAndYieldThrough(['Yield:3']); + expect(callback).toHaveBeenCalledTimes(0); - // @gate experimental || !enableSyncDefaultUpdates - it('should report the expected times when a high-pri update replaces a mount in-progress', () => { - const callback = jest.fn(); + // Simulate time moving forward while frame is paused. + Scheduler.unstable_advanceTime(100); // 59 -> 159 - const Yield = ({renderTime}) => { - Scheduler.unstable_advanceTime(renderTime); - Scheduler.unstable_yieldValue('Yield:' + renderTime); - return null; - }; + // Render another 5ms of simulated time. + expect(Scheduler).toFlushAndYieldThrough(['Yield:5']); + expect(callback).toHaveBeenCalledTimes(0); - Scheduler.unstable_advanceTime(5); // 0 -> 5 + // Simulate time moving forward while frame is paused. + Scheduler.unstable_advanceTime(100); // 164 -> 264 - // Render a partially update, but don't finish. - // This partial render should take 10ms of simulated time. - let renderer; - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - renderer = ReactTestRenderer.create( - - - - , - {unstable_isConcurrent: true}, - ); - }); - } else { - renderer = ReactTestRenderer.create( - - - - , - {unstable_isConcurrent: true}, - ); - } - expect(Scheduler).toFlushAndYieldThrough(['Yield:10']); - expect(callback).toHaveBeenCalledTimes(0); - - // Simulate time moving forward while frame is paused. - Scheduler.unstable_advanceTime(100); // 15 -> 115 - - // Interrupt with higher priority work. - // The interrupted work simulates an additional 5ms of time. - renderer.unstable_flushSync(() => { - renderer.update( - - - , - ); - }); - expect(Scheduler).toHaveYielded(['Yield:5']); + // Interrupt with higher priority work. + // The interrupted work simulates an additional 11ms of time. + renderer.unstable_flushSync(() => { + renderer.update( + + + , + ); + }); + expect(Scheduler).toHaveYielded(['Yield:11']); - // The initial work was thrown away in this case, - // So the actual and base times should only include the final rendered tree times. - expect(callback).toHaveBeenCalledTimes(1); - const call = callback.mock.calls[0]; - expect(call[2]).toBe(5); // actual time - expect(call[3]).toBe(5); // base time - expect(call[4]).toBe(115); // start time - expect(call[5]).toBe(120); // commit time + // The actual time should include only the most recent render, + // Because this lets us avoid a lot of commit phase reset complexity. + // The base time includes only the final rendered tree times. + expect(callback).toHaveBeenCalledTimes(1); + call = callback.mock.calls[0]; + expect(call[2]).toBe(11); // actual time + expect(call[3]).toBe(11); // base time + expect(call[4]).toBe(264); // start time + expect(call[5]).toBe(275); // commit time + + // Verify no more unexpected callbacks from low priority work + expect(Scheduler).toFlushAndYield([]); + expect(callback).toHaveBeenCalledTimes(1); + }); - callback.mockReset(); + // @gate experimental || !enableSyncDefaultUpdates + it('should report the expected times when a high-priority update interrupts a low-priority update', () => { + const callback = jest.fn(); - // Verify no more unexpected callbacks from low priority work - expect(Scheduler).toFlushWithoutYielding(); - expect(callback).toHaveBeenCalledTimes(0); + const Yield = ({renderTime}) => { + Scheduler.unstable_advanceTime(renderTime); + Scheduler.unstable_yieldValue('Yield:' + renderTime); + return null; + }; + + let first; + class FirstComponent extends React.Component { + state = {renderTime: 1}; + render() { + first = this; + Scheduler.unstable_advanceTime(this.state.renderTime); + Scheduler.unstable_yieldValue( + 'FirstComponent:' + this.state.renderTime, + ); + return ; + } + } + let second; + class SecondComponent extends React.Component { + state = {renderTime: 2}; + render() { + second = this; + Scheduler.unstable_advanceTime(this.state.renderTime); + Scheduler.unstable_yieldValue( + 'SecondComponent:' + this.state.renderTime, + ); + return ; + } + } + + Scheduler.unstable_advanceTime(5); // 0 -> 5 + + const renderer = ReactTestRenderer.create( + + + + , + {unstable_isConcurrent: true}, + ); + + // Render everything initially. + // This simulates a total of 14ms of actual render time. + // The base render time is also 14ms for the initial render. + expect(Scheduler).toFlushAndYield([ + 'FirstComponent:1', + 'Yield:4', + 'SecondComponent:2', + 'Yield:7', + ]); + expect(callback).toHaveBeenCalledTimes(1); + let call = callback.mock.calls[0]; + expect(call[2]).toBe(14); // actual time + expect(call[3]).toBe(14); // base time + expect(call[4]).toBe(5); // start time + expect(call[5]).toBe(19); // commit time + + callback.mockClear(); + + Scheduler.unstable_advanceTime(100); // 19 -> 119 + + // Render a partially update, but don't finish. + // This partial render will take 10ms of actual render time. + if (gate(flags => flags.enableSyncDefaultUpdates)) { + React.unstable_startTransition(() => { + first.setState({renderTime: 10}); + }); + } else { + first.setState({renderTime: 10}); + } + expect(Scheduler).toFlushAndYieldThrough(['FirstComponent:10']); + expect(callback).toHaveBeenCalledTimes(0); + + // Simulate time moving forward while frame is paused. + Scheduler.unstable_advanceTime(100); // 129 -> 229 + + // Interrupt with higher priority work. + // This simulates a total of 37ms of actual render time. + renderer.unstable_flushSync(() => second.setState({renderTime: 30})); + expect(Scheduler).toHaveYielded(['SecondComponent:30', 'Yield:7']); + + // The actual time should include only the most recent render (37ms), + // Because this greatly simplifies the commit phase logic. + // The base time should include the more recent times for the SecondComponent subtree, + // As well as the original times for the FirstComponent subtree. + expect(callback).toHaveBeenCalledTimes(1); + call = callback.mock.calls[0]; + expect(call[2]).toBe(37); // actual time + expect(call[3]).toBe(42); // base time + expect(call[4]).toBe(229); // start time + expect(call[5]).toBe(266); // commit time + + callback.mockClear(); + + // Simulate time moving forward while frame is paused. + Scheduler.unstable_advanceTime(100); // 266 -> 366 + + // Resume the original low priority update, with rebased state. + // This simulates a total of 14ms of actual render time, + // And does not include the original (interrupted) 10ms. + // The tree contains 42ms of base render time at this point, + // Reflecting the most recent (longer) render durations. + // TODO: This actual time should decrease by 10ms once the scheduler supports resuming. + expect(Scheduler).toFlushAndYield(['FirstComponent:10', 'Yield:4']); + expect(callback).toHaveBeenCalledTimes(1); + call = callback.mock.calls[0]; + expect(call[2]).toBe(14); // actual time + expect(call[3]).toBe(51); // base time + expect(call[4]).toBe(366); // start time + expect(call[5]).toBe(380); // commit time + }); + + [true, false].forEach(replayFailedUnitOfWorkWithInvokeGuardedCallback => { + describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${ + replayFailedUnitOfWorkWithInvokeGuardedCallback ? 'enabled' : 'disabled' + }`, () => { + beforeEach(() => { + jest.resetModules(); + + loadModules({ + replayFailedUnitOfWorkWithInvokeGuardedCallback, + }); }); - // @gate experimental || !enableSyncDefaultUpdates - it('should report the expected times when a high-priority update replaces a low-priority update', () => { + it('should accumulate actual time after an error handled by componentDidCatch()', () => { const callback = jest.fn(); - const Yield = ({renderTime}) => { - Scheduler.unstable_advanceTime(renderTime); - Scheduler.unstable_yieldValue('Yield:' + renderTime); - return null; + const ThrowsError = ({unused}) => { + Scheduler.unstable_advanceTime(3); + throw Error('expected error'); }; + class ErrorBoundary extends React.Component { + state = {error: null}; + componentDidCatch(error) { + this.setState({error}); + } + render() { + Scheduler.unstable_advanceTime(2); + return this.state.error === null ? ( + this.props.children + ) : ( + + ); + } + } + Scheduler.unstable_advanceTime(5); // 0 -> 5 - const renderer = ReactTestRenderer.create( + ReactTestRenderer.create( - - + + + + , - {unstable_isConcurrent: true}, ); - // Render everything initially. - // This should take 21 seconds of actual and base time. - expect(Scheduler).toFlushAndYield(['Yield:6', 'Yield:15']); - expect(callback).toHaveBeenCalledTimes(1); - let call = callback.mock.calls[0]; - expect(call[2]).toBe(21); // actual time - expect(call[3]).toBe(21); // base time - expect(call[4]).toBe(5); // start time - expect(call[5]).toBe(26); // commit time - - callback.mockReset(); - - Scheduler.unstable_advanceTime(30); // 26 -> 56 - - // Render a partially update, but don't finish. - // This partial render should take 3ms of simulated time. - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - renderer.update( - - - - - , - ); - }); - } else { - renderer.update( - - - - - , - ); - } - expect(Scheduler).toFlushAndYieldThrough(['Yield:3']); - expect(callback).toHaveBeenCalledTimes(0); - - // Simulate time moving forward while frame is paused. - Scheduler.unstable_advanceTime(100); // 59 -> 159 - - // Render another 5ms of simulated time. - expect(Scheduler).toFlushAndYieldThrough(['Yield:5']); - expect(callback).toHaveBeenCalledTimes(0); - - // Simulate time moving forward while frame is paused. - Scheduler.unstable_advanceTime(100); // 164 -> 264 - - // Interrupt with higher priority work. - // The interrupted work simulates an additional 11ms of time. - renderer.unstable_flushSync(() => { - renderer.update( - - - , - ); - }); - expect(Scheduler).toHaveYielded(['Yield:11']); + expect(callback).toHaveBeenCalledTimes(2); - // The actual time should include only the most recent render, - // Because this lets us avoid a lot of commit phase reset complexity. - // The base time includes only the final rendered tree times. - expect(callback).toHaveBeenCalledTimes(1); - call = callback.mock.calls[0]; - expect(call[2]).toBe(11); // actual time - expect(call[3]).toBe(11); // base time - expect(call[4]).toBe(264); // start time - expect(call[5]).toBe(275); // commit time - - // Verify no more unexpected callbacks from low priority work - expect(Scheduler).toFlushAndYield([]); - expect(callback).toHaveBeenCalledTimes(1); + // Callbacks bubble (reverse order). + const [mountCall, updateCall] = callback.mock.calls; + + // The initial mount only includes the ErrorBoundary (which takes 2) + // But it spends time rendering all of the failed subtree also. + expect(mountCall[1]).toBe('mount'); + // actual time includes: 2 (ErrorBoundary) + 9 (AdvanceTime) + 3 (ThrowsError) + // We don't count the time spent in replaying the failed unit of work (ThrowsError) + expect(mountCall[2]).toBe(14); + // base time includes: 2 (ErrorBoundary) + // Since the tree is empty for the initial commit + expect(mountCall[3]).toBe(2); + // start time + expect(mountCall[4]).toBe(5); + // commit time: 5 initially + 14 of work + // Add an additional 3 (ThrowsError) if we replayed the failed work + expect(mountCall[5]).toBe( + __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback + ? 22 + : 19, + ); + + // The update includes the ErrorBoundary and its fallback child + expect(updateCall[1]).toBe('nested-update'); + // actual time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) + expect(updateCall[2]).toBe(22); + // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) + expect(updateCall[3]).toBe(22); + // start time + expect(updateCall[4]).toBe( + __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback + ? 22 + : 19, + ); + // commit time: 19 (startTime) + 2 (ErrorBoundary) + 20 (AdvanceTime) + // Add an additional 3 (ThrowsError) if we replayed the failed work + expect(updateCall[5]).toBe( + __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback + ? 44 + : 41, + ); }); - // @gate experimental || !enableSyncDefaultUpdates - it('should report the expected times when a high-priority update interrupts a low-priority update', () => { + it('should accumulate actual time after an error handled by getDerivedStateFromError()', () => { const callback = jest.fn(); - const Yield = ({renderTime}) => { - Scheduler.unstable_advanceTime(renderTime); - Scheduler.unstable_yieldValue('Yield:' + renderTime); - return null; + const ThrowsError = ({unused}) => { + Scheduler.unstable_advanceTime(10); + throw Error('expected error'); }; - let first; - class FirstComponent extends React.Component { - state = {renderTime: 1}; - render() { - first = this; - Scheduler.unstable_advanceTime(this.state.renderTime); - Scheduler.unstable_yieldValue( - 'FirstComponent:' + this.state.renderTime, - ); - return ; + class ErrorBoundary extends React.Component { + state = {error: null}; + static getDerivedStateFromError(error) { + return {error}; } - } - let second; - class SecondComponent extends React.Component { - state = {renderTime: 2}; render() { - second = this; - Scheduler.unstable_advanceTime(this.state.renderTime); - Scheduler.unstable_yieldValue( - 'SecondComponent:' + this.state.renderTime, + Scheduler.unstable_advanceTime(2); + return this.state.error === null ? ( + this.props.children + ) : ( + ); - return ; } } Scheduler.unstable_advanceTime(5); // 0 -> 5 - const renderer = ReactTestRenderer.create( + ReactTestRenderer.create( - - + + + + , - {unstable_isConcurrent: true}, ); - // Render everything initially. - // This simulates a total of 14ms of actual render time. - // The base render time is also 14ms for the initial render. - expect(Scheduler).toFlushAndYield([ - 'FirstComponent:1', - 'Yield:4', - 'SecondComponent:2', - 'Yield:7', - ]); expect(callback).toHaveBeenCalledTimes(1); - let call = callback.mock.calls[0]; - expect(call[2]).toBe(14); // actual time - expect(call[3]).toBe(14); // base time - expect(call[4]).toBe(5); // start time - expect(call[5]).toBe(19); // commit time - - callback.mockClear(); - - Scheduler.unstable_advanceTime(100); // 19 -> 119 - - // Render a partially update, but don't finish. - // This partial render will take 10ms of actual render time. - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - first.setState({renderTime: 10}); - }); - } else { - first.setState({renderTime: 10}); - } - expect(Scheduler).toFlushAndYieldThrough(['FirstComponent:10']); - expect(callback).toHaveBeenCalledTimes(0); - // Simulate time moving forward while frame is paused. - Scheduler.unstable_advanceTime(100); // 129 -> 229 + // Callbacks bubble (reverse order). + const [mountCall] = callback.mock.calls; + + // The initial mount includes the ErrorBoundary's error state, + // But it also spends actual time rendering UI that fails and isn't included. + expect(mountCall[1]).toBe('mount'); + // actual time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError) + // Then the re-render: 2 (ErrorBoundary) + 20 (AdvanceTime) + // We don't count the time spent in replaying the failed unit of work (ThrowsError) + expect(mountCall[2]).toBe(39); + // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) + expect(mountCall[3]).toBe(22); + // start time + expect(mountCall[4]).toBe(5); + // commit time + expect(mountCall[5]).toBe( + __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback + ? 54 + : 44, + ); + }); - // Interrupt with higher priority work. - // This simulates a total of 37ms of actual render time. - renderer.unstable_flushSync(() => second.setState({renderTime: 30})); - expect(Scheduler).toHaveYielded(['SecondComponent:30', 'Yield:7']); + it('should reset the fiber stack correct after a "complete" phase error', () => { + jest.resetModules(); - // The actual time should include only the most recent render (37ms), - // Because this greatly simplifies the commit phase logic. - // The base time should include the more recent times for the SecondComponent subtree, - // As well as the original times for the FirstComponent subtree. - expect(callback).toHaveBeenCalledTimes(1); - call = callback.mock.calls[0]; - expect(call[2]).toBe(37); // actual time - expect(call[3]).toBe(42); // base time - expect(call[4]).toBe(229); // start time - expect(call[5]).toBe(266); // commit time - - callback.mockClear(); - - // Simulate time moving forward while frame is paused. - Scheduler.unstable_advanceTime(100); // 266 -> 366 - - // Resume the original low priority update, with rebased state. - // This simulates a total of 14ms of actual render time, - // And does not include the original (interrupted) 10ms. - // The tree contains 42ms of base render time at this point, - // Reflecting the most recent (longer) render durations. - // TODO: This actual time should decrease by 10ms once the scheduler supports resuming. - expect(Scheduler).toFlushAndYield(['FirstComponent:10', 'Yield:4']); - expect(callback).toHaveBeenCalledTimes(1); - call = callback.mock.calls[0]; - expect(call[2]).toBe(14); // actual time - expect(call[3]).toBe(51); // base time - expect(call[4]).toBe(366); // start time - expect(call[5]).toBe(380); // commit time - }); + loadModules({ + useNoopRenderer: true, + replayFailedUnitOfWorkWithInvokeGuardedCallback, + }); - [true, false].forEach( - replayFailedUnitOfWorkWithInvokeGuardedCallback => { - describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${ - replayFailedUnitOfWorkWithInvokeGuardedCallback - ? 'enabled' - : 'disabled' - }`, () => { - beforeEach(() => { - jest.resetModules(); - - loadModules({ - replayFailedUnitOfWorkWithInvokeGuardedCallback, - }); - }); - - it('should accumulate actual time after an error handled by componentDidCatch()', () => { - const callback = jest.fn(); - - const ThrowsError = ({unused}) => { - Scheduler.unstable_advanceTime(3); - throw Error('expected error'); - }; - - class ErrorBoundary extends React.Component { - state = {error: null}; - componentDidCatch(error) { - this.setState({error}); - } - render() { - Scheduler.unstable_advanceTime(2); - return this.state.error === null ? ( - this.props.children - ) : ( - - ); - } - } - - Scheduler.unstable_advanceTime(5); // 0 -> 5 - - ReactTestRenderer.create( - - - - - - , - ); - - expect(callback).toHaveBeenCalledTimes(2); - - // Callbacks bubble (reverse order). - const [mountCall, updateCall] = callback.mock.calls; - - // The initial mount only includes the ErrorBoundary (which takes 2) - // But it spends time rendering all of the failed subtree also. - expect(mountCall[1]).toBe('mount'); - // actual time includes: 2 (ErrorBoundary) + 9 (AdvanceTime) + 3 (ThrowsError) - // We don't count the time spent in replaying the failed unit of work (ThrowsError) - expect(mountCall[2]).toBe(14); - // base time includes: 2 (ErrorBoundary) - // Since the tree is empty for the initial commit - expect(mountCall[3]).toBe(2); - // start time - expect(mountCall[4]).toBe(5); - // commit time: 5 initially + 14 of work - // Add an additional 3 (ThrowsError) if we replayed the failed work - expect(mountCall[5]).toBe( - __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback - ? 22 - : 19, - ); - - // The update includes the ErrorBoundary and its fallback child - expect(updateCall[1]).toBe('nested-update'); - // actual time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) - expect(updateCall[2]).toBe(22); - // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) - expect(updateCall[3]).toBe(22); - // start time - expect(updateCall[4]).toBe( - __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback - ? 22 - : 19, - ); - // commit time: 19 (startTime) + 2 (ErrorBoundary) + 20 (AdvanceTime) - // Add an additional 3 (ThrowsError) if we replayed the failed work - expect(updateCall[5]).toBe( - __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback - ? 44 - : 41, - ); - }); - - it('should accumulate actual time after an error handled by getDerivedStateFromError()', () => { - const callback = jest.fn(); - - const ThrowsError = ({unused}) => { - Scheduler.unstable_advanceTime(10); - throw Error('expected error'); - }; - - class ErrorBoundary extends React.Component { - state = {error: null}; - static getDerivedStateFromError(error) { - return {error}; - } - render() { - Scheduler.unstable_advanceTime(2); - return this.state.error === null ? ( - this.props.children - ) : ( - - ); - } - } - - Scheduler.unstable_advanceTime(5); // 0 -> 5 - - ReactTestRenderer.create( - - - - - - , - ); - - expect(callback).toHaveBeenCalledTimes(1); - - // Callbacks bubble (reverse order). - const [mountCall] = callback.mock.calls; - - // The initial mount includes the ErrorBoundary's error state, - // But it also spends actual time rendering UI that fails and isn't included. - expect(mountCall[1]).toBe('mount'); - // actual time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError) - // Then the re-render: 2 (ErrorBoundary) + 20 (AdvanceTime) - // We don't count the time spent in replaying the failed unit of work (ThrowsError) - expect(mountCall[2]).toBe(39); - // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime) - expect(mountCall[3]).toBe(22); - // start time - expect(mountCall[4]).toBe(5); - // commit time - expect(mountCall[5]).toBe( - __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback - ? 54 - : 44, - ); - }); - - it('should reset the fiber stack correct after a "complete" phase error', () => { - jest.resetModules(); - - loadModules({ - useNoopRenderer: true, - replayFailedUnitOfWorkWithInvokeGuardedCallback, - }); - - // Simulate a renderer error during the "complete" phase. - // This mimics behavior like React Native's View/Text nesting validation. - ReactNoop.render( - - hi - , - ); - expect(Scheduler).toFlushAndThrow('Error in host config.'); - - // A similar case we've seen caused by an invariant in ReactDOM. - // It didn't reproduce without a host component inside. - ReactNoop.render( - - - hi - - , - ); - expect(Scheduler).toFlushAndThrow('Error in host config.'); - - // So long as the profiler timer's fiber stack is reset correctly, - // Subsequent renders should not error. - ReactNoop.render( - - hi - , - ); - expect(Scheduler).toFlushWithoutYielding(); - }); - }); - }, - ); - }); + // Simulate a renderer error during the "complete" phase. + // This mimics behavior like React Native's View/Text nesting validation. + ReactNoop.render( + + hi + , + ); + expect(Scheduler).toFlushAndThrow('Error in host config.'); - it('reflects the most recently rendered id value', () => { - const callback = jest.fn(); + // A similar case we've seen caused by an invariant in ReactDOM. + // It didn't reproduce without a host component inside. + ReactNoop.render( + + + hi + + , + ); + expect(Scheduler).toFlushAndThrow('Error in host config.'); - Scheduler.unstable_advanceTime(5); // 0 -> 5 + // So long as the profiler timer's fiber stack is reset correctly, + // Subsequent renders should not error. + ReactNoop.render( + + hi + , + ); + expect(Scheduler).toFlushWithoutYielding(); + }); + }); + }); + }); - const renderer = ReactTestRenderer.create( - - - , - ); + it('reflects the most recently rendered id value', () => { + const callback = jest.fn(); - expect(callback).toHaveBeenCalledTimes(1); + Scheduler.unstable_advanceTime(5); // 0 -> 5 - Scheduler.unstable_advanceTime(20); // 7 -> 27 + const renderer = ReactTestRenderer.create( + + + , + ); - renderer.update( - - - , - ); + expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledTimes(2); + Scheduler.unstable_advanceTime(20); // 7 -> 27 - const [mountCall, updateCall] = callback.mock.calls; + renderer.update( + + + , + ); - expect(mountCall[0]).toBe('one'); - expect(mountCall[1]).toBe('mount'); - expect(mountCall[2]).toBe(2); // actual time - expect(mountCall[3]).toBe(2); // base time - expect(mountCall[4]).toBe(5); // start time + expect(callback).toHaveBeenCalledTimes(2); - expect(updateCall[0]).toBe('two'); - expect(updateCall[1]).toBe('update'); - expect(updateCall[2]).toBe(1); // actual time - expect(updateCall[3]).toBe(1); // base time - expect(updateCall[4]).toBe(27); // start time - }); + const [mountCall, updateCall] = callback.mock.calls; - it('should not be called until after mutations', () => { - let classComponentMounted = false; - const callback = jest.fn( - (id, phase, actualDuration, baseDuration, startTime, commitTime) => { - // Don't call this hook until after mutations - expect(classComponentMounted).toBe(true); - // But the commit time should reflect pre-mutation - expect(commitTime).toBe(2); - }, - ); + expect(mountCall[0]).toBe('one'); + expect(mountCall[1]).toBe('mount'); + expect(mountCall[2]).toBe(2); // actual time + expect(mountCall[3]).toBe(2); // base time + expect(mountCall[4]).toBe(5); // start time - class ClassComponent extends React.Component { - componentDidMount() { - Scheduler.unstable_advanceTime(5); - classComponentMounted = true; - } - render() { - Scheduler.unstable_advanceTime(2); - return null; - } - } + expect(updateCall[0]).toBe('two'); + expect(updateCall[1]).toBe('update'); + expect(updateCall[2]).toBe(1); // actual time + expect(updateCall[3]).toBe(1); // base time + expect(updateCall[4]).toBe(27); // start time + }); - ReactTestRenderer.create( - - - , - ); + it('should not be called until after mutations', () => { + let classComponentMounted = false; + const callback = jest.fn( + (id, phase, actualDuration, baseDuration, startTime, commitTime) => { + // Don't call this hook until after mutations + expect(classComponentMounted).toBe(true); + // But the commit time should reflect pre-mutation + expect(commitTime).toBe(2); + }, + ); + + class ClassComponent extends React.Component { + componentDidMount() { + Scheduler.unstable_advanceTime(5); + classComponentMounted = true; + } + render() { + Scheduler.unstable_advanceTime(2); + return null; + } + } - expect(callback).toHaveBeenCalledTimes(1); - }); - }); + ReactTestRenderer.create( + + + , + ); + + expect(callback).toHaveBeenCalledTimes(1); + }); +}); - describe(`onCommit enableSchedulerTracing:${ - enableSchedulerTracing ? 'enabled' : 'disabled' - }`, () => { - beforeEach(() => { - jest.resetModules(); +describe(`onCommit`, () => { + beforeEach(() => { + jest.resetModules(); - loadModules({ - enableSchedulerTracing, - }); - }); + loadModules(); + }); - it('should report time spent in layout effects and commit lifecycles', () => { - const callback = jest.fn(); + it('should report time spent in layout effects and commit lifecycles', () => { + const callback = jest.fn(); - const ComponentWithEffects = () => { - React.useLayoutEffect(() => { - Scheduler.unstable_advanceTime(10); - return () => { - Scheduler.unstable_advanceTime(100); - }; - }, []); - React.useLayoutEffect(() => { - Scheduler.unstable_advanceTime(1000); - return () => { - Scheduler.unstable_advanceTime(10000); - }; - }); - React.useEffect(() => { - // This passive effect is here to verify that its time isn't reported. - Scheduler.unstable_advanceTime(5); - return () => { - Scheduler.unstable_advanceTime(7); - }; - }); - return null; + const ComponentWithEffects = () => { + React.useLayoutEffect(() => { + Scheduler.unstable_advanceTime(10); + return () => { + Scheduler.unstable_advanceTime(100); + }; + }, []); + React.useLayoutEffect(() => { + Scheduler.unstable_advanceTime(1000); + return () => { + Scheduler.unstable_advanceTime(10000); }; + }); + React.useEffect(() => { + // This passive effect is here to verify that its time isn't reported. + Scheduler.unstable_advanceTime(5); + return () => { + Scheduler.unstable_advanceTime(7); + }; + }); + return null; + }; - class ComponentWithCommitHooks extends React.Component { - componentDidMount() { - Scheduler.unstable_advanceTime(100000); - } - componentDidUpdate() { - Scheduler.unstable_advanceTime(1000000); - } - render() { - return null; - } - } + class ComponentWithCommitHooks extends React.Component { + componentDidMount() { + Scheduler.unstable_advanceTime(100000); + } + componentDidUpdate() { + Scheduler.unstable_advanceTime(1000000); + } + render() { + return null; + } + } - Scheduler.unstable_advanceTime(1); + Scheduler.unstable_advanceTime(1); - const renderer = ReactTestRenderer.create( - - - - , - ); + const renderer = ReactTestRenderer.create( + + + + , + ); - expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledTimes(1); - let call = callback.mock.calls[0]; + let call = callback.mock.calls[0]; - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('mount-test'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(101010); // durations - expect(call[3]).toBe(1); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + expect(call).toHaveLength(4); + expect(call[0]).toBe('mount-test'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(101010); // durations + expect(call[3]).toBe(1); // commit start time (before mutations or effects) - Scheduler.unstable_advanceTime(1); + Scheduler.unstable_advanceTime(1); - renderer.update( - - - - , - ); + renderer.update( + + + + , + ); - expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenCalledTimes(2); - call = callback.mock.calls[1]; + call = callback.mock.calls[1]; - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('update-test'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(1011000); // durations - expect(call[3]).toBe(101017); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + expect(call).toHaveLength(4); + expect(call[0]).toBe('update-test'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(1011000); // durations + expect(call[3]).toBe(101017); // commit start time (before mutations or effects) - Scheduler.unstable_advanceTime(1); + Scheduler.unstable_advanceTime(1); - renderer.update( - , - ); + renderer.update(); - expect(callback).toHaveBeenCalledTimes(3); + expect(callback).toHaveBeenCalledTimes(3); - call = callback.mock.calls[2]; + call = callback.mock.calls[2]; - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('unmount-test'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(10100); // durations - expect(call[3]).toBe(1112030); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - }); + expect(call).toHaveLength(4); + expect(call[0]).toBe('unmount-test'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(10100); // durations + expect(call[3]).toBe(1112030); // commit start time (before mutations or effects) + }); - it('should report time spent in layout effects and commit lifecycles with cascading renders', () => { - const callback = jest.fn(); + it('should report time spent in layout effects and commit lifecycles with cascading renders', () => { + const callback = jest.fn(); - const ComponentWithEffects = ({shouldCascade}) => { - const [didCascade, setDidCascade] = React.useState(false); - Scheduler.unstable_advanceTime(100000000); - React.useLayoutEffect(() => { - if (shouldCascade && !didCascade) { - setDidCascade(true); - } - Scheduler.unstable_advanceTime(didCascade ? 30 : 10); - return () => { - Scheduler.unstable_advanceTime(100); - }; - }, [didCascade, shouldCascade]); - return null; + const ComponentWithEffects = ({shouldCascade}) => { + const [didCascade, setDidCascade] = React.useState(false); + Scheduler.unstable_advanceTime(100000000); + React.useLayoutEffect(() => { + if (shouldCascade && !didCascade) { + setDidCascade(true); + } + Scheduler.unstable_advanceTime(didCascade ? 30 : 10); + return () => { + Scheduler.unstable_advanceTime(100); }; + }, [didCascade, shouldCascade]); + return null; + }; - class ComponentWithCommitHooks extends React.Component { - state = { - didCascade: false, - }; - componentDidMount() { - Scheduler.unstable_advanceTime(1000); - } - componentDidUpdate() { - Scheduler.unstable_advanceTime(10000); - if (this.props.shouldCascade && !this.state.didCascade) { - this.setState({didCascade: true}); - } - } - render() { - Scheduler.unstable_advanceTime(1000000000); - return null; - } + class ComponentWithCommitHooks extends React.Component { + state = { + didCascade: false, + }; + componentDidMount() { + Scheduler.unstable_advanceTime(1000); + } + componentDidUpdate() { + Scheduler.unstable_advanceTime(10000); + if (this.props.shouldCascade && !this.state.didCascade) { + this.setState({didCascade: true}); } + } + render() { + Scheduler.unstable_advanceTime(1000000000); + return null; + } + } - Scheduler.unstable_advanceTime(1); + Scheduler.unstable_advanceTime(1); - const renderer = ReactTestRenderer.create( - - - - , - ); + const renderer = ReactTestRenderer.create( + + + + , + ); - expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenCalledTimes(2); - let call = callback.mock.calls[0]; + let call = callback.mock.calls[0]; - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('mount-test'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(1010); // durations - expect(call[3]).toBe(1100000001); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + expect(call).toHaveLength(4); + expect(call[0]).toBe('mount-test'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(1010); // durations + expect(call[3]).toBe(1100000001); // commit start time (before mutations or effects) - call = callback.mock.calls[1]; + call = callback.mock.calls[1]; - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('mount-test'); - expect(call[1]).toBe('nested-update'); - expect(call[2]).toBe(130); // durations - expect(call[3]).toBe(1200001011); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + expect(call).toHaveLength(4); + expect(call[0]).toBe('mount-test'); + expect(call[1]).toBe('nested-update'); + expect(call[2]).toBe(130); // durations + expect(call[3]).toBe(1200001011); // commit start time (before mutations or effects) - Scheduler.unstable_advanceTime(1); + Scheduler.unstable_advanceTime(1); - renderer.update( - - - - , - ); + renderer.update( + + + + , + ); - expect(callback).toHaveBeenCalledTimes(4); + expect(callback).toHaveBeenCalledTimes(4); - call = callback.mock.calls[2]; + call = callback.mock.calls[2]; - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('update-test'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(10130); // durations - expect(call[3]).toBe(2300001142); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + expect(call).toHaveLength(4); + expect(call[0]).toBe('update-test'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(10130); // durations + expect(call[3]).toBe(2300001142); // commit start time (before mutations or effects) - call = callback.mock.calls[3]; + call = callback.mock.calls[3]; - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('update-test'); - expect(call[1]).toBe('nested-update'); - expect(call[2]).toBe(10000); // durations - expect(call[3]).toBe(3300011272); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - }); + expect(call).toHaveLength(4); + expect(call[0]).toBe('update-test'); + expect(call[1]).toBe('nested-update'); + expect(call[2]).toBe(10000); // durations + expect(call[3]).toBe(3300011272); // commit start time (before mutations or effects) + }); - it('should include time spent in ref callbacks', () => { - const callback = jest.fn(); + it('should include time spent in ref callbacks', () => { + const callback = jest.fn(); - const refSetter = ref => { - if (ref !== null) { - Scheduler.unstable_advanceTime(10); - } else { - Scheduler.unstable_advanceTime(100); - } - }; + const refSetter = ref => { + if (ref !== null) { + Scheduler.unstable_advanceTime(10); + } else { + Scheduler.unstable_advanceTime(100); + } + }; - class ClassComponent extends React.Component { - render() { - return null; - } - } + class ClassComponent extends React.Component { + render() { + return null; + } + } - const Component = () => { - Scheduler.unstable_advanceTime(1000); - return ; - }; + const Component = () => { + Scheduler.unstable_advanceTime(1000); + return ; + }; - Scheduler.unstable_advanceTime(1); + Scheduler.unstable_advanceTime(1); - const renderer = ReactTestRenderer.create( - - - , - ); + const renderer = ReactTestRenderer.create( + + + , + ); - expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledTimes(1); - let call = callback.mock.calls[0]; + let call = callback.mock.calls[0]; - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(10); // durations - expect(call[3]).toBe(1001); // commit start time (before mutations or effects) + expect(call).toHaveLength(4); + expect(call[0]).toBe('root'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(10); // durations + expect(call[3]).toBe(1001); // commit start time (before mutations or effects) - callback.mockClear(); + callback.mockClear(); - renderer.update(); + renderer.update(); - expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledTimes(1); - call = callback.mock.calls[0]; + call = callback.mock.calls[0]; - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(100); // durations - expect(call[3]).toBe(1011); // commit start time (before mutations or effects) - }); + expect(call).toHaveLength(4); + expect(call[0]).toBe('root'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(100); // durations + expect(call[3]).toBe(1011); // commit start time (before mutations or effects) + }); - it('should bubble time spent in layout effects to higher profilers', () => { - const callback = jest.fn(); - - const ComponentWithEffects = ({ - cleanupDuration, - duration, - setCountRef, - }) => { - const setCount = React.useState(0)[1]; - if (setCountRef != null) { - setCountRef.current = setCount; - } - React.useLayoutEffect(() => { - Scheduler.unstable_advanceTime(duration); - return () => { - Scheduler.unstable_advanceTime(cleanupDuration); - }; - }); - Scheduler.unstable_advanceTime(1); - return null; + it('should bubble time spent in layout effects to higher profilers', () => { + const callback = jest.fn(); + + const ComponentWithEffects = ({cleanupDuration, duration, setCountRef}) => { + const setCount = React.useState(0)[1]; + if (setCountRef != null) { + setCountRef.current = setCount; + } + React.useLayoutEffect(() => { + Scheduler.unstable_advanceTime(duration); + return () => { + Scheduler.unstable_advanceTime(cleanupDuration); }; + }); + Scheduler.unstable_advanceTime(1); + return null; + }; - const setCountRef = React.createRef(null); + const setCountRef = React.createRef(null); + + let renderer = null; + ReactTestRendererAct(() => { + renderer = ReactTestRenderer.create( + + + + + + + + , + ); + }); - let renderer = null; - ReactTestRendererAct(() => { - renderer = ReactTestRenderer.create( - - - - - - - - , - ); - }); + expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledTimes(1); + let call = callback.mock.calls[0]; - let call = callback.mock.calls[0]; + expect(call).toHaveLength(4); + expect(call[0]).toBe('root-mount'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(1010); // durations + expect(call[3]).toBe(2); // commit start time (before mutations or effects) - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root-mount'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(1010); // durations - expect(call[3]).toBe(2); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + ReactTestRendererAct(() => setCountRef.current(count => count + 1)); - ReactTestRendererAct(() => setCountRef.current(count => count + 1)); + expect(callback).toHaveBeenCalledTimes(2); - expect(callback).toHaveBeenCalledTimes(2); + call = callback.mock.calls[1]; - call = callback.mock.calls[1]; + expect(call).toHaveLength(4); + expect(call[0]).toBe('root-mount'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(110); // durations + expect(call[3]).toBe(1013); // commit start time (before mutations or effects) - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root-mount'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(110); // durations - expect(call[3]).toBe(1013); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + ReactTestRendererAct(() => { + renderer.update( + + + + + , + ); + }); - ReactTestRendererAct(() => { - renderer.update( - - - - - , - ); - }); + expect(callback).toHaveBeenCalledTimes(3); - expect(callback).toHaveBeenCalledTimes(3); + call = callback.mock.calls[2]; - call = callback.mock.calls[2]; + expect(call).toHaveLength(4); + expect(call[0]).toBe('root-update'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(1100); // durations + expect(call[3]).toBe(1124); // commit start time (before mutations or effects) + }); - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root-update'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(1100); // durations - expect(call[3]).toBe(1124); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - }); + it('should properly report time in layout effects even when there are errors', () => { + const callback = jest.fn(); - it('should properly report time in layout effects even when there are errors', () => { - const callback = jest.fn(); + class ErrorBoundary extends React.Component { + state = {error: null}; + static getDerivedStateFromError(error) { + return {error}; + } + render() { + return this.state.error === null + ? this.props.children + : this.props.fallback; + } + } - class ErrorBoundary extends React.Component { - state = {error: null}; - static getDerivedStateFromError(error) { - return {error}; - } - render() { - return this.state.error === null - ? this.props.children - : this.props.fallback; - } + const ComponentWithEffects = ({ + cleanupDuration, + duration, + effectDuration, + shouldThrow, + }) => { + React.useLayoutEffect(() => { + Scheduler.unstable_advanceTime(effectDuration); + if (shouldThrow) { + throw Error('expected'); } - - const ComponentWithEffects = ({ - cleanupDuration, - duration, - effectDuration, - shouldThrow, - }) => { - React.useLayoutEffect(() => { - Scheduler.unstable_advanceTime(effectDuration); - if (shouldThrow) { - throw Error('expected'); - } - return () => { - Scheduler.unstable_advanceTime(cleanupDuration); - }; - }); - Scheduler.unstable_advanceTime(duration); - return null; + return () => { + Scheduler.unstable_advanceTime(cleanupDuration); }; + }); + Scheduler.unstable_advanceTime(duration); + return null; + }; - Scheduler.unstable_advanceTime(1); + Scheduler.unstable_advanceTime(1); - // Test an error that happens during an effect + // Test an error that happens during an effect - ReactTestRendererAct(() => { - ReactTestRenderer.create( - - - }> - - + ReactTestRendererAct(() => { + ReactTestRenderer.create( + + - , - ); - }); + }> + + + + , + ); + }); - expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenCalledTimes(2); - let call = callback.mock.calls[0]; + let call = callback.mock.calls[0]; - // Initial render (with error) - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(100100); // durations - expect(call[3]).toBe(10011); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + // Initial render (with error) + expect(call).toHaveLength(4); + expect(call[0]).toBe('root'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(100100); // durations + expect(call[3]).toBe(10011); // commit start time (before mutations or effects) - call = callback.mock.calls[1]; + call = callback.mock.calls[1]; - // Cleanup render from error boundary - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('nested-update'); - expect(call[2]).toBe(100000000); // durations - expect(call[3]).toBe(10110111); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - }); + // Cleanup render from error boundary + expect(call).toHaveLength(4); + expect(call[0]).toBe('root'); + expect(call[1]).toBe('nested-update'); + expect(call[2]).toBe(100000000); // durations + expect(call[3]).toBe(10110111); // commit start time (before mutations or effects) + }); - it('should properly report time in layout effect cleanup functions even when there are errors', () => { - const callback = jest.fn(); + it('should properly report time in layout effect cleanup functions even when there are errors', () => { + const callback = jest.fn(); - class ErrorBoundary extends React.Component { - state = {error: null}; - static getDerivedStateFromError(error) { - return {error}; - } - render() { - return this.state.error === null - ? this.props.children - : this.props.fallback; - } - } + class ErrorBoundary extends React.Component { + state = {error: null}; + static getDerivedStateFromError(error) { + return {error}; + } + render() { + return this.state.error === null + ? this.props.children + : this.props.fallback; + } + } - const ComponentWithEffects = ({ - cleanupDuration, - duration, - effectDuration, - shouldThrow = false, - }) => { - React.useLayoutEffect(() => { - Scheduler.unstable_advanceTime(effectDuration); - return () => { - Scheduler.unstable_advanceTime(cleanupDuration); - if (shouldThrow) { - throw Error('expected'); - } - }; - }); - Scheduler.unstable_advanceTime(duration); - return null; + const ComponentWithEffects = ({ + cleanupDuration, + duration, + effectDuration, + shouldThrow = false, + }) => { + React.useLayoutEffect(() => { + Scheduler.unstable_advanceTime(effectDuration); + return () => { + Scheduler.unstable_advanceTime(cleanupDuration); + if (shouldThrow) { + throw Error('expected'); + } }; + }); + Scheduler.unstable_advanceTime(duration); + return null; + }; - Scheduler.unstable_advanceTime(1); + Scheduler.unstable_advanceTime(1); - let renderer = null; + let renderer = null; - ReactTestRendererAct(() => { - renderer = ReactTestRenderer.create( - - - }> - - + ReactTestRendererAct(() => { + renderer = ReactTestRenderer.create( + + - , - ); - }); + }> + + + + , + ); + }); - expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledTimes(1); - let call = callback.mock.calls[0]; + let call = callback.mock.calls[0]; - // Initial render - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(100100); // durations - expect(call[3]).toBe(10011); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + // Initial render + expect(call).toHaveLength(4); + expect(call[0]).toBe('root'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(100100); // durations + expect(call[3]).toBe(10011); // commit start time (before mutations or effects) - callback.mockClear(); + callback.mockClear(); - // Test an error that happens during an cleanup function + // Test an error that happens during an cleanup function - ReactTestRendererAct(() => { - renderer.update( - - - }> - - + ReactTestRendererAct(() => { + renderer.update( + + - , - ); - }); - - expect(callback).toHaveBeenCalledTimes(2); - - call = callback.mock.calls[0]; - - // Update (that throws) - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(1101100); // durations - expect(call[3]).toBe(120121); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - - call = callback.mock.calls[1]; + }> + + + + , + ); + }); - // Cleanup render from error boundary - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('nested-update'); - expect(call[2]).toBe(100001000); // durations - expect(call[3]).toBe(11221221); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - }); + expect(callback).toHaveBeenCalledTimes(2); - if (enableSchedulerTracing) { - it('should report interactions that were active', () => { - const callback = jest.fn(); + call = callback.mock.calls[0]; - const ComponentWithEffects = () => { - const [didMount, setDidMount] = React.useState(false); - React.useLayoutEffect(() => { - Scheduler.unstable_advanceTime(didMount ? 1000 : 100); - if (!didMount) { - setDidMount(true); - } - return () => { - Scheduler.unstable_advanceTime(10000); - }; - }, [didMount]); - Scheduler.unstable_advanceTime(10); - return null; - }; + // Update (that throws) + expect(call).toHaveLength(4); + expect(call[0]).toBe('root'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(1101100); // durations + expect(call[3]).toBe(120121); // commit start time (before mutations or effects) - const interaction = { - id: 0, - name: 'mount', - timestamp: Scheduler.unstable_now(), - }; + call = callback.mock.calls[1]; - Scheduler.unstable_advanceTime(1); + // Cleanup render from error boundary + expect(call).toHaveLength(4); + expect(call[0]).toBe('root'); + expect(call[1]).toBe('nested-update'); + expect(call[2]).toBe(100001000); // durations + expect(call[3]).toBe(11221221); // commit start time (before mutations or effects) + }); +}); - SchedulerTracing.unstable_trace( - interaction.name, - interaction.timestamp, - () => { - ReactTestRenderer.create( - - - , - ); - }, - ); +describe(`onPostCommit`, () => { + beforeEach(() => { + jest.resetModules(); - expect(callback).toHaveBeenCalledTimes(2); + loadModules(); + }); - let call = callback.mock.calls[0]; + it('should report time spent in passive effects', () => { + const callback = jest.fn(); - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('mount'); - expect(call[4]).toMatchInteractions([interaction]); + const ComponentWithEffects = () => { + React.useLayoutEffect(() => { + // This layout effect is here to verify that its time isn't reported. + Scheduler.unstable_advanceTime(5); + return () => { + Scheduler.unstable_advanceTime(7); + }; + }); + React.useEffect(() => { + Scheduler.unstable_advanceTime(10); + return () => { + Scheduler.unstable_advanceTime(100); + }; + }, []); + React.useEffect(() => { + Scheduler.unstable_advanceTime(1000); + return () => { + Scheduler.unstable_advanceTime(10000); + }; + }); + return null; + }; - call = callback.mock.calls[1]; + Scheduler.unstable_advanceTime(1); - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('nested-update'); - expect(call[4]).toMatchInteractions([interaction]); - }); - } + let renderer; + ReactTestRendererAct(() => { + renderer = ReactTestRenderer.create( + + + , + ); }); + Scheduler.unstable_flushAll(); - describe(`onPostCommit enableSchedulerTracing:${ - enableSchedulerTracing ? 'enabled' : 'disabled' - }`, () => { - beforeEach(() => { - jest.resetModules(); - - loadModules({ - enableSchedulerTracing, - }); - }); - - it('should report time spent in passive effects', () => { - const callback = jest.fn(); + expect(callback).toHaveBeenCalledTimes(1); - const ComponentWithEffects = () => { - React.useLayoutEffect(() => { - // This layout effect is here to verify that its time isn't reported. - Scheduler.unstable_advanceTime(5); - return () => { - Scheduler.unstable_advanceTime(7); - }; - }); - React.useEffect(() => { - Scheduler.unstable_advanceTime(10); - return () => { - Scheduler.unstable_advanceTime(100); - }; - }, []); - React.useEffect(() => { - Scheduler.unstable_advanceTime(1000); - return () => { - Scheduler.unstable_advanceTime(10000); - }; - }); - return null; - }; + let call = callback.mock.calls[0]; - Scheduler.unstable_advanceTime(1); - - let renderer; - ReactTestRendererAct(() => { - renderer = ReactTestRenderer.create( - - - , - ); - }); - Scheduler.unstable_flushAll(); + expect(call).toHaveLength(4); + expect(call[0]).toBe('mount-test'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(1010); // durations + expect(call[3]).toBe(1); // commit start time (before mutations or effects) - expect(callback).toHaveBeenCalledTimes(1); + Scheduler.unstable_advanceTime(1); - let call = callback.mock.calls[0]; + ReactTestRendererAct(() => { + renderer.update( + + + , + ); + }); + Scheduler.unstable_flushAll(); - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('mount-test'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(1010); // durations - expect(call[3]).toBe(1); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + expect(callback).toHaveBeenCalledTimes(2); - Scheduler.unstable_advanceTime(1); + call = callback.mock.calls[1]; - ReactTestRendererAct(() => { - renderer.update( - - - , - ); - }); - Scheduler.unstable_flushAll(); + expect(call).toHaveLength(4); + expect(call[0]).toBe('update-test'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(11000); // durations + expect(call[3]).toBe(1017); // commit start time (before mutations or effects) - expect(callback).toHaveBeenCalledTimes(2); + Scheduler.unstable_advanceTime(1); - call = callback.mock.calls[1]; + ReactTestRendererAct(() => { + renderer.update( + , + ); + }); + Scheduler.unstable_flushAll(); - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('update-test'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(11000); // durations - expect(call[3]).toBe(1017); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + expect(callback).toHaveBeenCalledTimes(3); - Scheduler.unstable_advanceTime(1); + call = callback.mock.calls[2]; - ReactTestRendererAct(() => { - renderer.update( - , - ); - }); - Scheduler.unstable_flushAll(); - - expect(callback).toHaveBeenCalledTimes(3); - - call = callback.mock.calls[2]; - - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('unmount-test'); - expect(call[1]).toBe('update'); - // TODO (bvaughn) The duration reported below should be 10100, but is 0 - // by the time the passive effect is flushed its parent Fiber pointer is gone. - // If we refactor to preserve the unmounted Fiber tree we could fix this. - // The current implementation would require too much extra overhead to track this. - expect(call[2]).toBe(0); // durations - expect(call[3]).toBe(12030); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - }); + expect(call).toHaveLength(4); + expect(call[0]).toBe('unmount-test'); + expect(call[1]).toBe('update'); + // TODO (bvaughn) The duration reported below should be 10100, but is 0 + // by the time the passive effect is flushed its parent Fiber pointer is gone. + // If we refactor to preserve the unmounted Fiber tree we could fix this. + // The current implementation would require too much extra overhead to track this. + expect(call[2]).toBe(0); // durations + expect(call[3]).toBe(12030); // commit start time (before mutations or effects) + }); - it('should report time spent in passive effects with cascading renders', () => { - const callback = jest.fn(); + it('should report time spent in passive effects with cascading renders', () => { + const callback = jest.fn(); - const ComponentWithEffects = () => { - const [didMount, setDidMount] = React.useState(false); - Scheduler.unstable_advanceTime(1000); - React.useEffect(() => { - if (!didMount) { - setDidMount(true); - } - Scheduler.unstable_advanceTime(didMount ? 30 : 10); - return () => { - Scheduler.unstable_advanceTime(100); - }; - }, [didMount]); - return null; + const ComponentWithEffects = () => { + const [didMount, setDidMount] = React.useState(false); + Scheduler.unstable_advanceTime(1000); + React.useEffect(() => { + if (!didMount) { + setDidMount(true); + } + Scheduler.unstable_advanceTime(didMount ? 30 : 10); + return () => { + Scheduler.unstable_advanceTime(100); }; + }, [didMount]); + return null; + }; - Scheduler.unstable_advanceTime(1); + Scheduler.unstable_advanceTime(1); - ReactTestRendererAct(() => { - ReactTestRenderer.create( - - - , - ); - }); + ReactTestRendererAct(() => { + ReactTestRenderer.create( + + + , + ); + }); - expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenCalledTimes(2); - let call = callback.mock.calls[0]; + let call = callback.mock.calls[0]; - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('mount-test'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(10); // durations - expect(call[3]).toBe(1001); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + expect(call).toHaveLength(4); + expect(call[0]).toBe('mount-test'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(10); // durations + expect(call[3]).toBe(1001); // commit start time (before mutations or effects) - call = callback.mock.calls[1]; + call = callback.mock.calls[1]; - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('mount-test'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(130); // durations - expect(call[3]).toBe(2011); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - }); + expect(call).toHaveLength(4); + expect(call[0]).toBe('mount-test'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(130); // durations + expect(call[3]).toBe(2011); // commit start time (before mutations or effects) + }); - it('should bubble time spent in effects to higher profilers', () => { - const callback = jest.fn(); - - const ComponentWithEffects = ({ - cleanupDuration, - duration, - setCountRef, - }) => { - const setCount = React.useState(0)[1]; - if (setCountRef != null) { - setCountRef.current = setCount; - } - React.useEffect(() => { - Scheduler.unstable_advanceTime(duration); - return () => { - Scheduler.unstable_advanceTime(cleanupDuration); - }; - }); - Scheduler.unstable_advanceTime(1); - return null; + it('should bubble time spent in effects to higher profilers', () => { + const callback = jest.fn(); + + const ComponentWithEffects = ({cleanupDuration, duration, setCountRef}) => { + const setCount = React.useState(0)[1]; + if (setCountRef != null) { + setCountRef.current = setCount; + } + React.useEffect(() => { + Scheduler.unstable_advanceTime(duration); + return () => { + Scheduler.unstable_advanceTime(cleanupDuration); }; + }); + Scheduler.unstable_advanceTime(1); + return null; + }; - const setCountRef = React.createRef(null); + const setCountRef = React.createRef(null); + + let renderer = null; + ReactTestRendererAct(() => { + renderer = ReactTestRenderer.create( + + + + + + + + , + ); + }); - let renderer = null; - ReactTestRendererAct(() => { - renderer = ReactTestRenderer.create( - - - - - - - - , - ); - }); + expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledTimes(1); + let call = callback.mock.calls[0]; - let call = callback.mock.calls[0]; + expect(call).toHaveLength(4); + expect(call[0]).toBe('root-mount'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(1010); // durations + expect(call[3]).toBe(2); // commit start time (before mutations or effects) - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root-mount'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(1010); // durations - expect(call[3]).toBe(2); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + ReactTestRendererAct(() => setCountRef.current(count => count + 1)); - ReactTestRendererAct(() => setCountRef.current(count => count + 1)); + expect(callback).toHaveBeenCalledTimes(2); - expect(callback).toHaveBeenCalledTimes(2); + call = callback.mock.calls[1]; - call = callback.mock.calls[1]; + expect(call).toHaveLength(4); + expect(call[0]).toBe('root-mount'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(110); // durations + expect(call[3]).toBe(1013); // commit start time (before mutations or effects) - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root-mount'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(110); // durations - expect(call[3]).toBe(1013); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + ReactTestRendererAct(() => { + renderer.update( + + + + + , + ); + }); - ReactTestRendererAct(() => { - renderer.update( - - - - - , - ); - }); + expect(callback).toHaveBeenCalledTimes(3); - expect(callback).toHaveBeenCalledTimes(3); + call = callback.mock.calls[2]; - call = callback.mock.calls[2]; + expect(call).toHaveLength(4); + expect(call[0]).toBe('root-update'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(1100); // durations + expect(call[3]).toBe(1124); // commit start time (before mutations or effects) + }); - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root-update'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(1100); // durations - expect(call[3]).toBe(1124); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - }); + it('should properly report time in passive effects even when there are errors', () => { + const callback = jest.fn(); - it('should properly report time in passive effects even when there are errors', () => { - const callback = jest.fn(); + class ErrorBoundary extends React.Component { + state = {error: null}; + static getDerivedStateFromError(error) { + return {error}; + } + render() { + return this.state.error === null + ? this.props.children + : this.props.fallback; + } + } - class ErrorBoundary extends React.Component { - state = {error: null}; - static getDerivedStateFromError(error) { - return {error}; - } - render() { - return this.state.error === null - ? this.props.children - : this.props.fallback; - } + const ComponentWithEffects = ({ + cleanupDuration, + duration, + effectDuration, + shouldThrow, + }) => { + React.useEffect(() => { + Scheduler.unstable_advanceTime(effectDuration); + if (shouldThrow) { + throw Error('expected'); } - - const ComponentWithEffects = ({ - cleanupDuration, - duration, - effectDuration, - shouldThrow, - }) => { - React.useEffect(() => { - Scheduler.unstable_advanceTime(effectDuration); - if (shouldThrow) { - throw Error('expected'); - } - return () => { - Scheduler.unstable_advanceTime(cleanupDuration); - }; - }); - Scheduler.unstable_advanceTime(duration); - return null; + return () => { + Scheduler.unstable_advanceTime(cleanupDuration); }; + }); + Scheduler.unstable_advanceTime(duration); + return null; + }; - Scheduler.unstable_advanceTime(1); + Scheduler.unstable_advanceTime(1); - // Test an error that happens during an effect + // Test an error that happens during an effect - ReactTestRendererAct(() => { - ReactTestRenderer.create( - - - }> - - + ReactTestRendererAct(() => { + ReactTestRenderer.create( + + - , - ); - }); + }> + + + + , + ); + }); - expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenCalledTimes(2); - let call = callback.mock.calls[0]; + let call = callback.mock.calls[0]; - // Initial render (with error) - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(100100); // durations - expect(call[3]).toBe(10011); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + // Initial render (with error) + expect(call).toHaveLength(4); + expect(call[0]).toBe('root'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(100100); // durations + expect(call[3]).toBe(10011); // commit start time (before mutations or effects) - call = callback.mock.calls[1]; + call = callback.mock.calls[1]; - // Cleanup render from error boundary - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(100000000); // durations - expect(call[3]).toBe(10110111); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - }); + // Cleanup render from error boundary + expect(call).toHaveLength(4); + expect(call[0]).toBe('root'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(100000000); // durations + expect(call[3]).toBe(10110111); // commit start time (before mutations or effects) + }); - it('should properly report time in passive effect cleanup functions even when there are errors', () => { - const callback = jest.fn(); + it('should properly report time in passive effect cleanup functions even when there are errors', () => { + const callback = jest.fn(); - class ErrorBoundary extends React.Component { - state = {error: null}; - static getDerivedStateFromError(error) { - return {error}; - } - render() { - return this.state.error === null - ? this.props.children - : this.props.fallback; - } - } + class ErrorBoundary extends React.Component { + state = {error: null}; + static getDerivedStateFromError(error) { + return {error}; + } + render() { + return this.state.error === null + ? this.props.children + : this.props.fallback; + } + } - const ComponentWithEffects = ({ - cleanupDuration, - duration, - effectDuration, - shouldThrow = false, - id, - }) => { - React.useEffect(() => { - Scheduler.unstable_advanceTime(effectDuration); - return () => { - Scheduler.unstable_advanceTime(cleanupDuration); - if (shouldThrow) { - throw Error('expected'); - } - }; - }); - Scheduler.unstable_advanceTime(duration); - return null; + const ComponentWithEffects = ({ + cleanupDuration, + duration, + effectDuration, + shouldThrow = false, + id, + }) => { + React.useEffect(() => { + Scheduler.unstable_advanceTime(effectDuration); + return () => { + Scheduler.unstable_advanceTime(cleanupDuration); + if (shouldThrow) { + throw Error('expected'); + } }; + }); + Scheduler.unstable_advanceTime(duration); + return null; + }; - Scheduler.unstable_advanceTime(1); + Scheduler.unstable_advanceTime(1); - let renderer = null; + let renderer = null; - ReactTestRendererAct(() => { - renderer = ReactTestRenderer.create( - - - }> - - + ReactTestRendererAct(() => { + renderer = ReactTestRenderer.create( + + - , - ); - }); + }> + + + + , + ); + }); - expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledTimes(1); - let call = callback.mock.calls[0]; + let call = callback.mock.calls[0]; - // Initial render - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('mount'); - expect(call[2]).toBe(100100); // durations - expect(call[3]).toBe(10011); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events + // Initial render + expect(call).toHaveLength(4); + expect(call[0]).toBe('root'); + expect(call[1]).toBe('mount'); + expect(call[2]).toBe(100100); // durations + expect(call[3]).toBe(10011); // commit start time (before mutations or effects) - callback.mockClear(); + callback.mockClear(); - // Test an error that happens during an cleanup function + // Test an error that happens during an cleanup function - ReactTestRendererAct(() => { - renderer.update( - - - }> - - + ReactTestRendererAct(() => { + renderer.update( + + - , - ); - }); + }> + + + + , + ); + }); - expect(callback).toHaveBeenCalledTimes(2); - - call = callback.mock.calls[0]; - - // Update (that throws) - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('update'); - // We continue flushing pending effects even if one throws. - expect(call[2]).toBe(1101100); // durations - expect(call[3]).toBe(120121); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - - call = callback.mock.calls[1]; - - // Cleanup render from error boundary - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('update'); - expect(call[2]).toBe(100000000); // durations - // The commit time varies because the above duration time varies - expect(call[3]).toBe(11221221); // commit start time (before mutations or effects) - expect(call[4]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events - }); + expect(callback).toHaveBeenCalledTimes(2); - if (enableSchedulerTracing) { - it('should report interactions that were active', () => { - const callback = jest.fn(); + call = callback.mock.calls[0]; - const ComponentWithEffects = () => { - const [didMount, setDidMount] = React.useState(false); - React.useEffect(() => { - Scheduler.unstable_advanceTime(didMount ? 1000 : 100); - if (!didMount) { - setDidMount(true); - } - return () => { - Scheduler.unstable_advanceTime(10000); - }; - }, [didMount]); - Scheduler.unstable_advanceTime(10); - return null; - }; + // Update (that throws) + expect(call).toHaveLength(4); + expect(call[0]).toBe('root'); + expect(call[1]).toBe('update'); + // We continue flushing pending effects even if one throws. + expect(call[2]).toBe(1101100); // durations + expect(call[3]).toBe(120121); // commit start time (before mutations or effects) - const interaction = { - id: 0, - name: 'mount', - timestamp: Scheduler.unstable_now(), - }; + call = callback.mock.calls[1]; - Scheduler.unstable_advanceTime(1); - - ReactTestRendererAct(() => { - SchedulerTracing.unstable_trace( - interaction.name, - interaction.timestamp, - () => { - ReactTestRenderer.create( - - - , - ); - }, - ); - }); + // Cleanup render from error boundary + expect(call).toHaveLength(4); + expect(call[0]).toBe('root'); + expect(call[1]).toBe('update'); + expect(call[2]).toBe(100000000); // durations + // The commit time varies because the above duration time varies + expect(call[3]).toBe(11221221); // commit start time (before mutations or effects) + }); +}); - expect(callback).toHaveBeenCalledTimes(2); +describe(`onNestedUpdateScheduled`, () => { + beforeEach(() => { + jest.resetModules(); - let call = callback.mock.calls[0]; + loadModules({ + enableProfilerNestedUpdateScheduledHook: true, + useNoopRenderer: true, + }); + }); - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('mount'); - expect(call[4]).toMatchInteractions([interaction]); + it('is not called when the legacy render API is used to schedule an update', () => { + const onNestedUpdateScheduled = jest.fn(); + + ReactNoop.renderLegacySyncRoot( + +
    initial
    +
    , + ); + + ReactNoop.renderLegacySyncRoot( + +
    update
    +
    , + ); + + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); - call = callback.mock.calls[1]; + it('is not called when the root API is used to schedule an update', () => { + const onNestedUpdateScheduled = jest.fn(); + + ReactNoop.render( + +
    initial
    +
    , + ); + + ReactNoop.render( + +
    update
    +
    , + ); + + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); - expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4); - expect(call[0]).toBe('root'); - expect(call[1]).toBe('update'); - expect(call[4]).toMatchInteractions([interaction]); - }); - } + it('is called when a function component schedules an update during a layout effect', () => { + function Component() { + const [didMount, setDidMount] = React.useState(false); + React.useLayoutEffect(() => { + setDidMount(true); + }, []); + Scheduler.unstable_yieldValue(`Component:${didMount}`); + return didMount; + } + + const onNestedUpdateScheduled = jest.fn(); + + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); }); - describe(`onNestedUpdateScheduled enableSchedulerTracing:${ - enableSchedulerTracing ? 'enabled' : 'disabled' - }`, () => { - beforeEach(() => { - jest.resetModules(); + expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); + expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); + expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test'); + }); - loadModules({ - enableProfilerNestedUpdateScheduledHook: true, - enableSchedulerTracing, - useNoopRenderer: true, + it('is called when a function component schedules a batched update during a layout effect', () => { + function Component() { + const [didMount, setDidMount] = React.useState(false); + React.useLayoutEffect(() => { + ReactNoop.batchedUpdates(() => { + setDidMount(true); }); - }); + }, []); + Scheduler.unstable_yieldValue(`Component:${didMount}`); + return didMount; + } - it('is not called when the legacy render API is used to schedule an update', () => { - const onNestedUpdateScheduled = jest.fn(); + const onNestedUpdateScheduled = jest.fn(); + const onRender = jest.fn(); + + ReactNoop.render( + + + , + ); + expect(Scheduler).toFlushAndYield(['Component:false', 'Component:true']); + + expect(onRender).toHaveBeenCalledTimes(2); + expect(onRender.mock.calls[0][1]).toBe('mount'); + expect(onRender.mock.calls[1][1]).toBe('nested-update'); + + expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); + expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('root'); + }); - ReactNoop.renderLegacySyncRoot( + it('bubbles up and calls all ancestor Profilers', () => { + function Component() { + const [didMount, setDidMount] = React.useState(false); + React.useLayoutEffect(() => { + setDidMount(true); + }, []); + Scheduler.unstable_yieldValue(`Component:${didMount}`); + return didMount; + } + const onNestedUpdateScheduledOne = jest.fn(); + const onNestedUpdateScheduledTwo = jest.fn(); + const onNestedUpdateScheduledThree = jest.fn(); + + ReactNoop.act(() => { + ReactNoop.render( + -
    initial
    -
    , - ); + id="two" + onNestedUpdateScheduled={onNestedUpdateScheduledTwo}> + <> + + + + +
    , + ); + }); - ReactNoop.renderLegacySyncRoot( - -
    update
    -
    , - ); - - expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); - }); - - it('is not called when the root API is used to schedule an update', () => { - const onNestedUpdateScheduled = jest.fn(); - - ReactNoop.render( - -
    initial
    -
    , - ); - - ReactNoop.render( - -
    update
    -
    , - ); - - expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); - }); - - it('is called when a function component schedules an update during a layout effect', () => { - function Component() { - const [didMount, setDidMount] = React.useState(false); - React.useLayoutEffect(() => { - setDidMount(true); - }, []); - Scheduler.unstable_yieldValue(`Component:${didMount}`); - return didMount; - } - - const interactionCreation = { - id: 0, - name: 'creation event', - timestamp: Scheduler.unstable_now(), - }; - - const onNestedUpdateScheduled = jest.fn(); - - SchedulerTracing.unstable_trace( - interactionCreation.name, - Scheduler.unstable_now(), - () => { - ReactNoop.act(() => { - ReactNoop.render( - - - , - ); - }); - }, - ); - - expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); - expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); - expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test'); - if (ReactFeatureFlags.enableSchedulerTracing) { - expect(onNestedUpdateScheduled.mock.calls[0][1]).toMatchInteractions([ - interactionCreation, - ]); - } - }); - - it('is called when a function component schedules a batched update during a layout effect', () => { - function Component() { - const [didMount, setDidMount] = React.useState(false); - React.useLayoutEffect(() => { - ReactNoop.batchedUpdates(() => { - setDidMount(true); - }); - }, []); - Scheduler.unstable_yieldValue(`Component:${didMount}`); - return didMount; - } - - const onNestedUpdateScheduled = jest.fn(); - const onRender = jest.fn(); - - ReactNoop.render( - - - , - ); - expect(Scheduler).toFlushAndYield([ - 'Component:false', - 'Component:true', - ]); - - expect(onRender).toHaveBeenCalledTimes(2); - expect(onRender.mock.calls[0][1]).toBe('mount'); - expect(onRender.mock.calls[1][1]).toBe('nested-update'); - - expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); - expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('root'); - }); - - it('bubbles up and calls all ancestor Profilers', () => { - function Component() { - const [didMount, setDidMount] = React.useState(false); - React.useLayoutEffect(() => { - setDidMount(true); - }, []); - Scheduler.unstable_yieldValue(`Component:${didMount}`); - return didMount; - } - const onNestedUpdateScheduledOne = jest.fn(); - const onNestedUpdateScheduledTwo = jest.fn(); - const onNestedUpdateScheduledThree = jest.fn(); - - ReactNoop.act(() => { - ReactNoop.render( - - - <> - - - - - , - ); - }); - - expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); - expect(onNestedUpdateScheduledOne).toHaveBeenCalledTimes(1); - expect(onNestedUpdateScheduledOne.mock.calls[0][0]).toBe('one'); - expect(onNestedUpdateScheduledTwo).toHaveBeenCalledTimes(1); - expect(onNestedUpdateScheduledTwo.mock.calls[0][0]).toBe('two'); - expect(onNestedUpdateScheduledThree).not.toHaveBeenCalled(); - }); - - it('is not called when an update is scheduled for another doort during a layout effect', () => { - const setStateRef = React.createRef(null); - - function ComponentRootOne() { - const [state, setState] = React.useState(false); - setStateRef.current = setState; - Scheduler.unstable_yieldValue(`ComponentRootOne:${state}`); - return state; - } - - function ComponentRootTwo() { - React.useLayoutEffect(() => { - setStateRef.current(true); - }, []); - Scheduler.unstable_yieldValue('ComponentRootTwo'); - return null; - } - - const interactionCreation = { - id: 0, - name: 'creation event', - timestamp: Scheduler.unstable_now(), - }; - - const onNestedUpdateScheduled = jest.fn(); - - SchedulerTracing.unstable_trace( - interactionCreation.name, - Scheduler.unstable_now(), - () => { - ReactNoop.act(() => { - ReactNoop.renderToRootWithID( - - - , - 1, - ); - - ReactNoop.renderToRootWithID( - - - , - 2, - ); - }); - }, - ); - - expect(Scheduler).toHaveYielded([ - 'ComponentRootOne:false', - 'ComponentRootTwo', - 'ComponentRootOne:true', - ]); - expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); - }); - - it('is not called when a function component schedules an update during a passive effect', () => { - function Component() { - const [didMount, setDidMount] = React.useState(false); - React.useEffect(() => { - setDidMount(true); - }, []); - Scheduler.unstable_yieldValue(`Component:${didMount}`); - return didMount; - } - - const onNestedUpdateScheduled = jest.fn(); - - ReactNoop.act(() => { - ReactNoop.render( - - - , - ); - }); - - expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); - expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); - }); - - it('is not called when a function component schedules an update outside of render', () => { - const updateFnRef = React.createRef(null); - - function Component() { - const [state, setState] = React.useState(false); - updateFnRef.current = () => setState(true); - Scheduler.unstable_yieldValue(`Component:${state}`); - return state; - } - - const onNestedUpdateScheduled = jest.fn(); - - ReactNoop.act(() => { - ReactNoop.render( - - - , - ); - }); - expect(Scheduler).toHaveYielded(['Component:false']); - - ReactNoop.act(() => { - updateFnRef.current(); - }); - expect(Scheduler).toHaveYielded(['Component:true']); - expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); - }); - - it('it is not called when a component schedules an update during render', () => { - function Component() { - const [state, setState] = React.useState(false); - if (state === false) { - setState(true); - } - Scheduler.unstable_yieldValue(`Component:${state}`); - return state; - } - - const onNestedUpdateScheduled = jest.fn(); - - ReactNoop.act(() => { - ReactNoop.render( - - - , - ); - }); - - expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); - expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); - }); - - it('it is called when a component schedules an update from a ref callback', () => { - function Component({mountChild}) { - const [refAttached, setRefAttached] = React.useState(false); - const [refDetached, setRefDetached] = React.useState(false); - const refSetter = React.useCallback(ref => { - if (ref !== null) { - setRefAttached(true); - } else { - setRefDetached(true); - } - }, []); - Scheduler.unstable_yieldValue( - `Component:${refAttached}:${refDetached}`, - ); - return mountChild ?
    : null; - } - - const onNestedUpdateScheduled = jest.fn(); - - const interactionCreation = { - id: 0, - name: 'creation event', - timestamp: Scheduler.unstable_now(), - }; - - SchedulerTracing.unstable_trace( - interactionCreation.name, - Scheduler.unstable_now(), - () => { - ReactNoop.act(() => { - ReactNoop.render( - - - , - ); - }); - }, - ); - - expect(Scheduler).toHaveYielded([ - 'Component:false:false', - 'Component:true:false', - ]); - expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); - expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test'); - if (ReactFeatureFlags.enableSchedulerTracing) { - expect(onNestedUpdateScheduled.mock.calls[0][1]).toMatchInteractions([ - interactionCreation, - ]); - } - - const interactionUpdate = { - id: 1, - name: 'update event', - timestamp: Scheduler.unstable_now(), - }; - - SchedulerTracing.unstable_trace( - interactionUpdate.name, - Scheduler.unstable_now(), - () => { - ReactNoop.act(() => { - ReactNoop.render( - - - , - ); - }); - }, - ); - - expect(Scheduler).toHaveYielded([ - 'Component:true:false', - 'Component:true:true', - ]); - expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(2); - expect(onNestedUpdateScheduled.mock.calls[1][0]).toBe('test'); - if (ReactFeatureFlags.enableSchedulerTracing) { - expect(onNestedUpdateScheduled.mock.calls[1][1]).toMatchInteractions([ - interactionUpdate, - ]); - } - }); - - it('is called when a class component schedules an update from the componentDidMount lifecycles', () => { - class Component extends React.Component { - state = { - value: false, - }; - componentDidMount() { - this.setState({value: true}); - } - render() { - const {value} = this.state; - Scheduler.unstable_yieldValue(`Component:${value}`); - return value; - } - } - - const interactionCreation = { - id: 0, - name: 'creation event', - timestamp: Scheduler.unstable_now(), - }; - - const onNestedUpdateScheduled = jest.fn(); - - SchedulerTracing.unstable_trace( - interactionCreation.name, - Scheduler.unstable_now(), - () => { - ReactNoop.act(() => { - ReactNoop.render( - - - , - ); - }); - }, - ); - - expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); - expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); - expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test'); - if (ReactFeatureFlags.enableSchedulerTracing) { - expect(onNestedUpdateScheduled.mock.calls[0][1]).toMatchInteractions([ - interactionCreation, - ]); - } - }); - - it('is called when a class component schedules an update from the componentDidUpdate lifecycles', () => { - class Component extends React.Component { - state = { - nestedUpdateSheduled: false, - }; - componentDidUpdate(prevProps, prevState) { - if ( - this.props.scheduleNestedUpdate && - !this.state.nestedUpdateSheduled - ) { - this.setState({nestedUpdateSheduled: true}); - } - } - render() { - const {scheduleNestedUpdate} = this.props; - const {nestedUpdateSheduled} = this.state; - Scheduler.unstable_yieldValue( - `Component:${scheduleNestedUpdate}:${nestedUpdateSheduled}`, - ); - return nestedUpdateSheduled; - } - } - - const onNestedUpdateScheduled = jest.fn(); - - ReactNoop.act(() => { - ReactNoop.render( - - - , - ); - }); - expect(Scheduler).toHaveYielded(['Component:false:false']); - expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); - - const interactionCreation = { - id: 0, - name: 'creation event', - timestamp: Scheduler.unstable_now(), - }; - - SchedulerTracing.unstable_trace( - interactionCreation.name, - Scheduler.unstable_now(), - () => { - ReactNoop.act(() => { - ReactNoop.render( - - - , - ); - }); - }, - ); - - expect(Scheduler).toHaveYielded([ - 'Component:true:false', - 'Component:true:true', - ]); - expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); - expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test'); - if (ReactFeatureFlags.enableSchedulerTracing) { - expect(onNestedUpdateScheduled.mock.calls[0][1]).toMatchInteractions([ - interactionCreation, - ]); - } - }); - - it('is not called when a class component schedules an update outside of render', () => { - const updateFnRef = React.createRef(null); - - class Component extends React.Component { - state = { - value: false, - }; - render() { - const {value} = this.state; - updateFnRef.current = () => this.setState({value: true}); - Scheduler.unstable_yieldValue(`Component:${value}`); - return value; - } - } - - const onNestedUpdateScheduled = jest.fn(); - - ReactNoop.act(() => { - ReactNoop.render( - - - , - ); - }); - expect(Scheduler).toHaveYielded(['Component:false']); - - ReactNoop.act(() => { - updateFnRef.current(); - }); - expect(Scheduler).toHaveYielded(['Component:true']); - expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); - }); - - // TODO Add hydration tests to ensure we don't have false positives called. - }); - }); - - describe('interaction tracing', () => { - let onInteractionScheduledWorkCompleted; - let onInteractionTraced; - let onWorkCanceled; - let onWorkScheduled; - let onWorkStarted; - let onWorkStopped; - let throwInOnInteractionScheduledWorkCompleted; - let throwInOnWorkScheduled; - let throwInOnWorkStarted; - let throwInOnWorkStopped; - - const getWorkForReactThreads = mockFn => - mockFn.mock.calls.filter(([interactions, threadID]) => threadID > 0); - - function loadModulesForTracing(params) { - jest.resetModules(); - - loadModules({ - enableSchedulerTracing: true, - ...params, - }); - - throwInOnInteractionScheduledWorkCompleted = false; - throwInOnWorkScheduled = false; - throwInOnWorkStarted = false; - throwInOnWorkStopped = false; - - onInteractionScheduledWorkCompleted = jest.fn(() => { - if (throwInOnInteractionScheduledWorkCompleted) { - throw Error('Expected error onInteractionScheduledWorkCompleted'); - } - }); - onInteractionTraced = jest.fn(); - onWorkCanceled = jest.fn(); - onWorkScheduled = jest.fn(() => { - if (throwInOnWorkScheduled) { - throw Error('Expected error onWorkScheduled'); - } - }); - onWorkStarted = jest.fn(() => { - if (throwInOnWorkStarted) { - throw Error('Expected error onWorkStarted'); - } - }); - onWorkStopped = jest.fn(() => { - if (throwInOnWorkStopped) { - throw Error('Expected error onWorkStopped'); - } - }); - - // Verify interaction subscriber methods are called as expected. - SchedulerTracing.unstable_subscribe({ - onInteractionScheduledWorkCompleted, - onInteractionTraced, - onWorkCanceled, - onWorkScheduled, - onWorkStarted, - onWorkStopped, - }); - } - - beforeEach(() => loadModulesForTracing()); - - describe('error handling', () => { - it('should cover errors thrown in onWorkScheduled', () => { - function Component({children}) { - Scheduler.unstable_yieldValue('Component:' + children); - return children; - } - - let renderer; - - // Errors that happen inside of a subscriber should throw, - throwInOnWorkScheduled = true; - expect(() => { - SchedulerTracing.unstable_trace( - 'event', - Scheduler.unstable_now(), - () => { - renderer = ReactTestRenderer.create(fail, { - unstable_isConcurrent: true, - }); - }, - ); - }).toThrow('Expected error onWorkScheduled'); - expect(Scheduler).toFlushAndYield(['Component:fail']); - throwInOnWorkScheduled = false; - expect(onWorkScheduled).toHaveBeenCalled(); - - // But should not leave React in a broken state for subsequent renders. - renderer = ReactTestRenderer.create(succeed, { - unstable_isConcurrent: true, - }); - expect(Scheduler).toFlushAndYield(['Component:succeed']); - const tree = renderer.toTree(); - expect(tree.type).toBe(Component); - expect(tree.props.children).toBe('succeed'); - }); - - it('should cover errors thrown in onWorkStarted', () => { - function Component({children}) { - Scheduler.unstable_yieldValue('Component:' + children); - return children; - } - - let renderer; - 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, - throwInOnWorkStarted = true; - expect(Scheduler).toFlushAndThrow('Expected error onWorkStarted'); - throwInOnWorkStarted = false; - // Rendering was interrupted by the error that was thrown, then - // continued and finished in the next task. - expect(Scheduler).toHaveYielded(['Component:text']); - expect(onWorkStarted).toHaveBeenCalled(); - const tree = renderer.toTree(); - expect(tree.type).toBe(Component); - expect(tree.props.children).toBe('text'); - }); - - it('should cover errors thrown in onWorkStopped', () => { - function Component({children}) { - Scheduler.unstable_yieldValue('Component:' + children); - return children; - } - - let renderer; - 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(() => { - expect(Scheduler).toFlushAndYield(['Component:text']); - }).toThrow('Expected error onWorkStopped'); - throwInOnWorkStopped = false; - expect(onWorkStopped).toHaveBeenCalledTimes(2); - - // Should still commit the update, - const tree = renderer.toTree(); - expect(tree.type).toBe(Component); - expect(tree.props.children).toBe('text'); - - // And still call onInteractionScheduledWorkCompleted if the interaction is finished. - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - }); - - it('should cover errors thrown in onInteractionScheduledWorkCompleted', () => { - function Component({children}) { - Scheduler.unstable_yieldValue('Component:' + children); - return children; - } - - const eventOne = { - id: 0, - name: 'event one', - timestamp: Scheduler.unstable_now(), - }; - const eventTwo = { - id: 1, - name: 'event two', - timestamp: Scheduler.unstable_now(), - }; - - 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(() => { - expect(Scheduler).toFlushAndYield(['Component:text']); - }).toThrow('Expected error onInteractionScheduledWorkCompleted'); - - // Even though an error is thrown for one completed interaction, - // The completed callback should be called for all completed interactions. - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2); - }); - }); - - it('should properly trace work scheduled during the begin render phase', () => { - const callback = jest.fn(); - let wrapped; - const Component = jest.fn(() => { - wrapped = SchedulerTracing.unstable_wrap(callback); - return null; - }); - - let interaction; - SchedulerTracing.unstable_trace('event', Scheduler.unstable_now(), () => { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - interaction = Array.from(interactions)[0]; - ReactTestRenderer.create(); - }); - - expect(Component).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(callback).not.toHaveBeenCalled(); - - wrapped(); - expect(callback).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - }); - - // @gate experimental || !enableSyncDefaultUpdates - it('should associate traced events with their subsequent commits', () => { - let instance = null; - - const Yield = ({duration = 10, value}) => { - Scheduler.unstable_advanceTime(duration); - Scheduler.unstable_yieldValue(value); - return null; - }; - - class Example extends React.Component { - state = { - count: 0, - }; - render() { - instance = this; - return ( - - - {this.state.count} - - - - ); - } - } - - ReactTestRendererAct(() => { - Scheduler.unstable_advanceTime(1); - - const interactionCreation = { - id: 0, - name: 'creation event', - timestamp: Scheduler.unstable_now(), - }; - - const onPostCommit = jest.fn(() => { - Scheduler.unstable_yieldValue('onPostCommit'); - }); - let renderer; - SchedulerTracing.unstable_trace( - interactionCreation.name, - Scheduler.unstable_now(), - () => { - renderer = ReactTestRenderer.create( - - - , - { - unstable_isConcurrent: true, - }, - ); - }, - ); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interactionCreation, - ); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - // 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([ - interactionCreation, - ]); - expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true); - - // Mount - expect(Scheduler).toFlushAndYield(['first', 'last', 'onPostCommit']); - expect(onPostCommit).toHaveBeenCalledTimes(1); - let call = onPostCommit.mock.calls[0]; - expect(call[0]).toEqual('test-profiler'); - expect(call[3]).toEqual(Scheduler.unstable_now()); - if (ReactFeatureFlags.enableSchedulerTracing) { - expect(call[4]).toMatchInteractions([interactionCreation]); - } - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interactionCreation); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1); - expect( - getWorkForReactThreads(onWorkStarted)[0][0], - ).toMatchInteractions([interactionCreation]); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1); - expect( - getWorkForReactThreads(onWorkStopped)[0][0], - ).toMatchInteractions([interactionCreation]); - - onPostCommit.mockClear(); - onWorkScheduled.mockClear(); - onWorkStarted.mockClear(); - onWorkStopped.mockClear(); - - Scheduler.unstable_advanceTime(3); - - let didRunCallback = false; - - const interactionOne = { - id: 1, - name: 'initial event', - timestamp: Scheduler.unstable_now(), - }; - SchedulerTracing.unstable_trace( - interactionOne.name, - Scheduler.unstable_now(), - () => { - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - instance.setState({count: 1}); - - // Update state again to verify our traced interaction isn't registered twice - instance.setState({count: 2}); - }); - } else { - 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(Scheduler).toFlushAndYieldThrough(['first']); - expect(onPostCommit).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); - - expect(Scheduler).toFlushAndYield(['last', 'onPostCommit']); - expect(onPostCommit).toHaveBeenCalledTimes(1); - - call = onPostCommit.mock.calls[0]; - expect(call[0]).toEqual('test-profiler'); - expect(call[3]).toEqual(Scheduler.unstable_now()); - if (ReactFeatureFlags.enableSchedulerTracing) { - expect(call[4]).toMatchInteractions([interactionOne]); - } - - 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(didRunCallback).toBe(true); - - onPostCommit.mockClear(); - onWorkScheduled.mockClear(); - onWorkStarted.mockClear(); - onWorkStopped.mockClear(); - - Scheduler.unstable_advanceTime(17); - - // Verify that updating state again does not re-log our interaction. - instance.setState({count: 3}); - expect(Scheduler).toFlushAndYield(['first', 'last', 'onPostCommit']); - - expect(onPostCommit).toHaveBeenCalledTimes(1); - call = onPostCommit.mock.calls[0]; - expect(call[0]).toEqual('test-profiler'); - expect(call[3]).toEqual(Scheduler.unstable_now()); - if (ReactFeatureFlags.enableSchedulerTracing) { - expect(call[4]).toMatchInteractions([]); - } - - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interactionOne); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); - - onPostCommit.mockClear(); - - Scheduler.unstable_advanceTime(3); - - // Verify that root updates are also associated with traced events. - const interactionTwo = { - id: 2, - name: 'root update event', - timestamp: Scheduler.unstable_now(), - }; - SchedulerTracing.unstable_trace( - interactionTwo.name, - Scheduler.unstable_now(), - () => { - renderer.update( - - - , - ); - }, - ); - - expect(onInteractionTraced).toHaveBeenCalledTimes(3); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interactionTwo, - ); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(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([ - interactionTwo, - ]); - expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true); - - expect(Scheduler).toFlushAndYield(['first', 'last', 'onPostCommit']); - - expect(onPostCommit).toHaveBeenCalledTimes(1); - call = onPostCommit.mock.calls[0]; - expect(call[0]).toEqual('test-profiler'); - expect(call[3]).toEqual(Scheduler.unstable_now()); - if (ReactFeatureFlags.enableSchedulerTracing) { - expect(call[4]).toMatchInteractions([interactionTwo]); - } - - expect(onInteractionTraced).toHaveBeenCalledTimes(3); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(3); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interactionTwo); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1); - expect( - getWorkForReactThreads(onWorkStarted)[0][0], - ).toMatchInteractions([interactionTwo]); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1); - expect( - getWorkForReactThreads(onWorkStopped)[0][0], - ).toMatchInteractions([interactionTwo]); - }); - }); - - it('should not mark an interaction complete while passive effects are outstanding', () => { - const onCommit = jest.fn(); - const onPostCommit = jest.fn(() => { - Scheduler.unstable_yieldValue('onPostCommit'); - }); - - const ComponentWithEffects = () => { - React.useEffect(() => { - Scheduler.unstable_yieldValue('passive effect'); - }); - React.useLayoutEffect(() => { - Scheduler.unstable_yieldValue('layout effect'); - }); - Scheduler.unstable_yieldValue('render'); - return null; - }; - - SchedulerTracing.unstable_trace('mount', Scheduler.unstable_now(), () => { - ReactTestRenderer.create( - - - , - ); - }); - - expect(Scheduler).toHaveYielded(['render', 'layout effect']); - - expect(onCommit).toHaveBeenCalled(); - expect(onPostCommit).not.toHaveBeenCalled(); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - expect(Scheduler).toFlushAndYield(['passive effect', 'onPostCommit']); - - expect(onCommit).toHaveBeenCalled(); - expect(onPostCommit).toHaveBeenCalled(); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - }); - - // @gate experimental || !enableSyncDefaultUpdates - it('should report the expected times when a high-priority update interrupts a low-priority update', () => { - const onPostCommit = jest.fn(() => { - Scheduler.unstable_yieldValue('onPostCommit'); - }); - - let first; - class FirstComponent extends React.Component { - state = {count: 0}; - render() { - first = this; - Scheduler.unstable_yieldValue('FirstComponent'); - return ; - } - } - let second; - class SecondComponent extends React.Component { - state = {count: 0}; - render() { - second = this; - Scheduler.unstable_yieldValue('SecondComponent'); - return ; - } - } - - Scheduler.unstable_advanceTime(5); - - ReactTestRendererAct(() => { - const renderer = ReactTestRenderer.create( - - - - , - {unstable_isConcurrent: true}, - ); - - // Initial mount. - expect(Scheduler).toFlushAndYield([ - 'FirstComponent', - 'SecondComponent', - 'onPostCommit', - ]); - - expect(onInteractionTraced).not.toHaveBeenCalled(); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - onPostCommit.mockClear(); - - Scheduler.unstable_advanceTime(100); - - const interactionLowPri = { - id: 0, - name: 'lowPri', - timestamp: Scheduler.unstable_now(), - }; - - SchedulerTracing.unstable_trace( - interactionLowPri.name, - Scheduler.unstable_now(), - () => { - // Render a partially update, but don't finish. - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - first.setState({count: 1}); - }); - } else { - first.setState({count: 1}); - } - - expect(onWorkScheduled).toHaveBeenCalled(); - expect(onWorkScheduled.mock.calls[0][0]).toMatchInteractions([ - interactionLowPri, - ]); + expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); + expect(onNestedUpdateScheduledOne).toHaveBeenCalledTimes(1); + expect(onNestedUpdateScheduledOne.mock.calls[0][0]).toBe('one'); + expect(onNestedUpdateScheduledTwo).toHaveBeenCalledTimes(1); + expect(onNestedUpdateScheduledTwo.mock.calls[0][0]).toBe('two'); + expect(onNestedUpdateScheduledThree).not.toHaveBeenCalled(); + }); - expect(Scheduler).toFlushAndYieldThrough(['FirstComponent']); - expect(onPostCommit).not.toHaveBeenCalled(); + it('is not called when an update is scheduled for another doort during a layout effect', () => { + const setStateRef = React.createRef(null); - 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); - - Scheduler.unstable_advanceTime(100); - - const interactionHighPri = { - id: 1, - name: 'highPri', - timestamp: Scheduler.unstable_now(), - }; - - // 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(); - - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); - }, - ); - }); - - expect(Scheduler).toHaveYielded([ - 'SecondComponent', - 'onPostCommit', - ]); - 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(onPostCommit).toHaveBeenCalledTimes(1); - let call = onPostCommit.mock.calls[0]; - expect(call[0]).toEqual('test'); - expect(call[3]).toEqual(Scheduler.unstable_now()); - expect(call[4]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing - ? [interactionLowPri, interactionHighPri] - : [], - ); + function ComponentRootOne() { + const [state, setState] = React.useState(false); + setStateRef.current = setState; + Scheduler.unstable_yieldValue(`ComponentRootOne:${state}`); + return state; + } - onPostCommit.mockClear(); - - Scheduler.unstable_advanceTime(100); - - // Resume the original low priority update, with rebased state. - // Verify the low priority update was retained. - expect(Scheduler).toFlushAndYield([ - 'FirstComponent', - 'onPostCommit', - ]); - expect(onPostCommit).toHaveBeenCalledTimes(1); - call = onPostCommit.mock.calls[0]; - expect(call[0]).toEqual('test'); - expect(call[3]).toEqual(Scheduler.unstable_now()); - expect(call[4]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing - ? [interactionLowPri] - : [], - ); + function ComponentRootTwo() { + React.useLayoutEffect(() => { + setStateRef.current(true); + }, []); + Scheduler.unstable_yieldValue('ComponentRootTwo'); + return null; + } - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes( - 1, - ); + const onNestedUpdateScheduled = jest.fn(); - // 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]); - }, - ); + ReactNoop.act(() => { + ReactNoop.renderToRootWithID( + + + , + 1, + ); - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interactionLowPri); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(3); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2); - }); + ReactNoop.renderToRootWithID( + + + , + 2, + ); }); - it('should trace work spawned by a commit phase lifecycle and setState callback', () => { - let instance; - class Example extends React.Component { - state = { - count: 0, - }; - componentDidMount() { - Scheduler.unstable_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) { - Scheduler.unstable_advanceTime(10); // Advance timer to keep commits separate - this.setState({count: 3}); // Intentional cascading update - } - } - render() { - instance = this; - Scheduler.unstable_yieldValue('Example:' + this.state.count); - return ; - } - } - - const interactionOne = { - id: 0, - name: 'componentDidMount test', - timestamp: Scheduler.unstable_now(), - }; - - // Initial mount. - const onPostCommit = jest.fn(() => { - Scheduler.unstable_yieldValue('onPostCommit'); - }); - - ReactTestRendererAct(() => { - 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( - interactionOne, - ); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); - - expect(Scheduler).toFlushAndYield([ - 'Example:0', - 'onPostCommit', - 'Example:1', - 'onPostCommit', - ]); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interactionOne); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(2); - expect( - getWorkForReactThreads(onWorkStarted)[0][0], - ).toMatchInteractions([interactionOne]); - expect( - getWorkForReactThreads(onWorkStarted)[1][0], - ).toMatchInteractions([interactionOne]); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2); - expect( - getWorkForReactThreads(onWorkStopped)[0][0], - ).toMatchInteractions([interactionOne]); - expect( - getWorkForReactThreads(onWorkStopped)[1][0], - ).toMatchInteractions([interactionOne]); - - expect(onPostCommit).toHaveBeenCalledTimes(2); - let call = onPostCommit.mock.calls[0]; - expect(call[0]).toEqual('test'); - expect(call[3]).toEqual(firstCommitTime); - expect(call[4]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing ? [interactionOne] : [], - ); - call = onPostCommit.mock.calls[1]; - expect(call[0]).toEqual('test'); - expect(call[3]).toEqual(Scheduler.unstable_now()); - expect(call[4]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing ? [interactionOne] : [], - ); - - onPostCommit.mockClear(); - - const interactionTwo = { - id: 1, - name: 'componentDidUpdate test', - timestamp: Scheduler.unstable_now(), - }; - - // Cause an traced, async update - SchedulerTracing.unstable_trace( - interactionTwo.name, - Scheduler.unstable_now(), - () => { - instance.setState({count: 2}); - }, - ); - expect(onPostCommit).not.toHaveBeenCalled(); - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interactionTwo, - ); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(2); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(2); - - Scheduler.unstable_advanceTime(5); - - // Flush async work (outside of traced scope) - // This will cause an intentional cascading update from did-update - firstCommitTime = Scheduler.unstable_now(); - expect(Scheduler).toFlushAndYield([ - 'Example:2', - 'onPostCommit', - 'Example:3', - 'onPostCommit', - ]); - - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interactionTwo); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(4); - expect( - getWorkForReactThreads(onWorkStarted)[2][0], - ).toMatchInteractions([interactionTwo]); - expect( - getWorkForReactThreads(onWorkStarted)[3][0], - ).toMatchInteractions([interactionTwo]); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(4); - expect( - getWorkForReactThreads(onWorkStopped)[2][0], - ).toMatchInteractions([interactionTwo]); - expect( - getWorkForReactThreads(onWorkStopped)[3][0], - ).toMatchInteractions([interactionTwo]); - - // Verify the cascading commit is associated with the origin event - expect(onPostCommit).toHaveBeenCalledTimes(2); - call = onPostCommit.mock.calls[0]; - expect(call[0]).toEqual('test'); - expect(call[3]).toEqual(firstCommitTime); - expect(call[4]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing ? [interactionTwo] : [], - ); - call = onPostCommit.mock.calls[1]; - expect(call[0]).toEqual('test'); - expect(call[3]).toEqual(Scheduler.unstable_now()); - expect(call[4]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing ? [interactionTwo] : [], - ); - - onPostCommit.mockClear(); + expect(Scheduler).toHaveYielded([ + 'ComponentRootOne:false', + 'ComponentRootTwo', + 'ComponentRootOne:true', + ]); + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); - const interactionThree = { - id: 2, - name: 'setState callback test', - timestamp: Scheduler.unstable_now(), - }; + it('is not called when a function component schedules an update during a passive effect', () => { + function Component() { + const [didMount, setDidMount] = React.useState(false); + React.useEffect(() => { + setDidMount(true); + }, []); + Scheduler.unstable_yieldValue(`Component:${didMount}`); + return didMount; + } - // Cause a cascading update from the setState callback - function callback() { - instance.setState({count: 6}); - } - SchedulerTracing.unstable_trace( - interactionThree.name, - Scheduler.unstable_now(), - () => { - instance.setState({count: 5}, callback); - }, - ); - expect(onPostCommit).not.toHaveBeenCalled(); + const onNestedUpdateScheduled = jest.fn(); - expect(onInteractionTraced).toHaveBeenCalledTimes(3); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interactionThree, - ); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(4); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(4); - - // Flush async work (outside of traced scope) - // This will cause an intentional cascading update from the setState callback - firstCommitTime = Scheduler.unstable_now(); - expect(Scheduler).toFlushAndYield([ - 'Example:5', - 'onPostCommit', - 'Example:6', - 'onPostCommit', - ]); - - expect(onInteractionTraced).toHaveBeenCalledTimes(3); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(3); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interactionThree); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(6); - expect( - getWorkForReactThreads(onWorkStarted)[4][0], - ).toMatchInteractions([interactionThree]); - expect( - getWorkForReactThreads(onWorkStarted)[5][0], - ).toMatchInteractions([interactionThree]); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(6); - expect( - getWorkForReactThreads(onWorkStopped)[4][0], - ).toMatchInteractions([interactionThree]); - expect( - getWorkForReactThreads(onWorkStopped)[5][0], - ).toMatchInteractions([interactionThree]); - - // Verify the cascading commit is associated with the origin event - expect(onPostCommit).toHaveBeenCalledTimes(2); - call = onPostCommit.mock.calls[0]; - expect(call[0]).toEqual('test'); - expect(call[3]).toEqual(firstCommitTime); - expect(call[4]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing ? [interactionThree] : [], - ); - call = onPostCommit.mock.calls[1]; - expect(call[0]).toEqual('test'); - expect(call[3]).toEqual(Scheduler.unstable_now()); - expect(call[4]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing ? [interactionThree] : [], - ); - }); + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); }); - it('should trace interactions associated with a parent component state update', () => { - const onPostCommit = jest.fn(() => { - Scheduler.unstable_yieldValue('onPostCommit'); - }); - let parentInstance = null; - - class Child extends React.Component { - render() { - Scheduler.unstable_yieldValue('Child:' + this.props.count); - return ; - } - } - - class Parent extends React.Component { - state = { - count: 0, - }; - render() { - parentInstance = this; - return ( - - - - ); - } - } - - Scheduler.unstable_advanceTime(1); + expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); - ReactTestRendererAct(() => { - ReactTestRenderer.create(, { - unstable_isConcurrent: true, - }); - expect(Scheduler).toFlushAndYield(['Child:0', 'onPostCommit']); - onPostCommit.mockClear(); + it('is not called when a function component schedules an update outside of render', () => { + const updateFnRef = React.createRef(null); - const interaction = { - id: 0, - name: 'parent interaction', - timestamp: Scheduler.unstable_now(), - }; + function Component() { + const [state, setState] = React.useState(false); + updateFnRef.current = () => setState(true); + Scheduler.unstable_yieldValue(`Component:${state}`); + return state; + } - SchedulerTracing.unstable_trace( - interaction.name, - Scheduler.unstable_now(), - () => { - parentInstance.setState({count: 1}); - }, - ); + const onNestedUpdateScheduled = jest.fn(); - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); - - expect(onPostCommit).not.toHaveBeenCalled(); - expect(Scheduler).toFlushAndYield(['Child:1', 'onPostCommit']); - expect(onPostCommit).toHaveBeenCalledTimes(1); - const call = onPostCommit.mock.calls[0]; - expect(call[0]).toEqual('test-profiler'); - expect(call[4]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [], - ); + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + expect(Scheduler).toHaveYielded(['Component:false']); - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(1); - expect( - getWorkForReactThreads(onWorkStarted)[0][0], - ).toMatchInteractions([interaction]); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(1); - expect( - getWorkForReactThreads(onWorkStopped)[0][0], - ).toMatchInteractions([interaction]); - }); + ReactNoop.act(() => { + updateFnRef.current(); }); + expect(Scheduler).toHaveYielded(['Component:true']); + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); - describe('suspense', () => { - function awaitableAdvanceTimers(ms) { - jest.advanceTimersByTime(ms); - // Wait until the end of the current tick - // We cannot use a timer since we're faking them - return Promise.resolve().then(() => {}); + it('it is not called when a component schedules an update during render', () => { + function Component() { + const [state, setState] = React.useState(false); + if (state === false) { + setState(true); } + Scheduler.unstable_yieldValue(`Component:${state}`); + return state; + } - it('traces both the temporary placeholder and the finished render for an interaction', async () => { - loadModulesForTracing({useNoopRenderer: true}); - - const interaction = { - id: 0, - name: 'initial render', - timestamp: Scheduler.unstable_now(), - }; - - const monkey = React.createRef(); - class Monkey extends React.Component { - render() { - Scheduler.unstable_yieldValue('Monkey'); - return null; - } - } - - const onCommit = jest.fn(() => { - Scheduler.unstable_yieldValue('onCommit'); - }); - await ReactNoop.act(async () => { - SchedulerTracing.unstable_trace( - interaction.name, - Scheduler.unstable_now(), - () => { - ReactNoop.render( - - }> - - - - - - , - ); - }, - ); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); - - expect(Scheduler).toFlushAndYield([ - 'Suspend [Async]', - 'Text [Loading...]', - 'Text [Sync]', - 'Monkey', - 'onCommit', - ]); - // Should have committed the placeholder. - expect(ReactNoop.getChildrenAsJSX()).toEqual('Loading...Sync'); - expect(onCommit).toHaveBeenCalledTimes(1); - - let call = onCommit.mock.calls[0]; - expect(call[0]).toEqual('test-profiler'); - expect(call[4]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [], - ); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - // An unrelated update in the middle shouldn't affect things... - monkey.current.forceUpdate(); - expect(Scheduler).toFlushAndYield(['Monkey', 'onCommit']); - expect(onCommit).toHaveBeenCalledTimes(2); - - // Once the promise resolves, we render the suspended view - await awaitableAdvanceTimers(20000); - expect(Scheduler).toHaveYielded(['Promise resolved [Async]']); - expect(Scheduler).toFlushAndYield(['AsyncText [Async]', 'onCommit']); - expect(ReactNoop.getChildrenAsJSX()).toEqual('AsyncSync'); - expect(onCommit).toHaveBeenCalledTimes(3); - - call = onCommit.mock.calls[2]; - expect(call[0]).toEqual('test-profiler'); - expect(call[4]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [], - ); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - }); - }); - - it('does not prematurely complete for suspended sync renders', async () => { - const interaction = { - id: 0, - name: 'initial render', - timestamp: Scheduler.unstable_now(), - }; - - const onPostCommit = jest.fn(() => - Scheduler.unstable_yieldValue('onPostCommit'), - ); - SchedulerTracing.unstable_trace( - interaction.name, - interaction.timestamp, - () => { - ReactTestRenderer.create( - - }> - - - - , - ); - }, - ); - - expect(Scheduler).toHaveYielded(['Suspend [loaded]', 'Text [loading]']); - - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - jest.runAllTimers(); - await resourcePromise; - - expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']); - expect(Scheduler).toFlushUntilNextPaint([ - 'onPostCommit', - 'AsyncText [loaded]', - ]); - expect(Scheduler).toFlushAndYield(['onPostCommit']); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - }); - - it('traces cascading work after suspended sync renders', async () => { - let wrappedCascadingFn; - class AsyncComponentWithCascadingWork extends React.Component { - state = { - hasMounted: false, - }; - - componentDidMount() { - wrappedCascadingFn = SchedulerTracing.unstable_wrap(() => { - this.setState({hasMounted: true}); - }); - } - - render() { - Scheduler.unstable_yieldValue('render'); - const {ms, text} = this.props; - TextResource.read([text, ms]); - return {this.state.hasMounted}; - } - } - - const interaction = { - id: 0, - name: 'initial render', - timestamp: Scheduler.unstable_now(), - }; - - const onPostCommit = jest.fn(() => - Scheduler.unstable_yieldValue('onPostCommit'), - ); - SchedulerTracing.unstable_trace( - interaction.name, - interaction.timestamp, - () => { - ReactTestRenderer.create( - - - }> - - - , - ); - }, - ); - - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - expect(Scheduler).toHaveYielded(['render', 'Text [loading]']); - - jest.runAllTimers(); - await resourcePromise; - - expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']); - expect(Scheduler).toFlushUntilNextPaint(['onPostCommit', 'render']); + const onNestedUpdateScheduled = jest.fn(); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); - wrappedCascadingFn(); - expect(Scheduler).toHaveYielded(['onPostCommit', 'render']); + expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); - // The new reconciler does not call onPostCommit again - // because the resolved suspended subtree doesn't contain any passive effects. - // If or its decendents had a passive effect, - // onPostCommit would be called again. - if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) { - expect(Scheduler).toFlushAndYield([]); + it('it is called when a component schedules an update from a ref callback', () => { + function Component({mountChild}) { + const [refAttached, setRefAttached] = React.useState(false); + const [refDetached, setRefDetached] = React.useState(false); + const refSetter = React.useCallback(ref => { + if (ref !== null) { + setRefAttached(true); } else { - expect(Scheduler).toFlushAndYield(['onPostCommit']); + setRefDetached(true); } + }, []); + Scheduler.unstable_yieldValue(`Component:${refAttached}:${refDetached}`); + return mountChild ?
    : null; + } - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - }); - - it('does not prematurely complete for suspended renders that have exceeded their deadline', async () => { - const interaction = { - id: 0, - name: 'initial render', - timestamp: Scheduler.unstable_now(), - }; - - const onCommit = jest.fn(() => { - Scheduler.unstable_yieldValue('onCommit'); - }); - SchedulerTracing.unstable_trace( - interaction.name, - interaction.timestamp, - () => { - ReactTestRenderer.create( - - }> - - - , - { - unstable_isConcurrent: true, - }, - ); - }, - ); - - Scheduler.unstable_advanceTime(400); - await awaitableAdvanceTimers(400); - - expect(Scheduler).toFlushAndYield([ - 'Suspend [loaded]', - 'Text [loading]', - 'onCommit', - ]); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - Scheduler.unstable_advanceTime(500); - await awaitableAdvanceTimers(500); - - expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']); - expect(Scheduler).toFlushAndYield(['AsyncText [loaded]', 'onCommit']); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - }); - - it('decrements interaction count correctly if suspense loads before placeholder is shown', async () => { - const interaction = { - id: 0, - name: 'initial render', - timestamp: Scheduler.unstable_now(), - }; - - const onCommit = jest.fn(() => { - Scheduler.unstable_yieldValue('onCommit'); - }); - SchedulerTracing.unstable_trace( - interaction.name, - interaction.timestamp, - () => { - ReactTestRenderer.create( - - }> - - - , - {unstable_isConcurrent: true}, - ); - }, - ); - expect(Scheduler).toFlushAndYield([ - 'Suspend [loaded]', - 'Text [loading]', - 'onCommit', - ]); - - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - jest.advanceTimersByTime(100); - await resourcePromise; - expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']); - expect(Scheduler).toFlushAndYield(['AsyncText [loaded]', 'onCommit']); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - }); - - it('handles high-pri renderers between suspended and resolved (sync) trees', async () => { - const initialRenderInteraction = { - id: 0, - name: 'initial render', - timestamp: Scheduler.unstable_now(), - }; - - const onPostCommit = jest.fn(() => - Scheduler.unstable_yieldValue('onPostCommit'), - ); - let renderer; - SchedulerTracing.unstable_trace( - initialRenderInteraction.name, - initialRenderInteraction.timestamp, - () => { - renderer = ReactTestRenderer.create( - - - }> - - - - , - ); - }, - ); - expect(renderer.toJSON()).toEqual(['loading', 'initial']); - expect(Scheduler).toHaveYielded([ - 'Suspend [loaded]', - 'Text [loading]', - 'Text [initial]', - ]); - expect(Scheduler).toFlushAndYield(['onPostCommit']); - - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onPostCommit).toHaveBeenCalledTimes(1); - expect(onPostCommit.mock.calls[0][4]).toMatchInteractions([ - initialRenderInteraction, - ]); - onPostCommit.mockClear(); - - const highPriUpdateInteraction = { - id: 1, - name: 'hiPriUpdate', - timestamp: Scheduler.unstable_now(), - }; - - const originalPromise = resourcePromise; - - renderer.unstable_flushSync(() => { - SchedulerTracing.unstable_trace( - highPriUpdateInteraction.name, - highPriUpdateInteraction.timestamp, - () => { - renderer.update( - - - }> - - - - , - ); - }, - ); - }); - expect(renderer.toJSON()).toEqual(['loading', 'updated']); - expect(Scheduler).toHaveYielded([ - 'Suspend [loaded]', - 'Text [loading]', - 'Text [updated]', - ]); - expect(Scheduler).toFlushAndYield(['onPostCommit']); - - expect(onPostCommit).toHaveBeenCalledTimes(1); - expect(onPostCommit.mock.calls[0][4]).toMatchInteractions([ - highPriUpdateInteraction, - ]); - onPostCommit.mockClear(); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted.mock.calls[0][0], - ).toMatchInteraction(highPriUpdateInteraction); - onInteractionScheduledWorkCompleted.mockClear(); - - Scheduler.unstable_advanceTime(100); - jest.advanceTimersByTime(100); - await originalPromise; - - expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']); - expect(Scheduler).toFlushUntilNextPaint(['AsyncText [loaded]']); - expect(renderer.toJSON()).toEqual(['loaded', 'updated']); - expect(Scheduler).toFlushAndYield(['onPostCommit']); - - expect(onPostCommit).toHaveBeenCalledTimes(1); - expect(onPostCommit.mock.calls[0][4]).toMatchInteractions([ - initialRenderInteraction, - ]); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted.mock.calls[0][0], - ).toMatchInteraction(initialRenderInteraction); - }); - - // @gate experimental || !enableSyncDefaultUpdates - it('handles high-pri renderers between suspended and resolved (async) trees', async () => { - // Set up an initial shell. We need to set this up before the test sceanrio - // because we want initial render to suspend on navigation to the initial state. - const renderer = ReactTestRenderer.create( - {}}> - } /> - , - {unstable_isConcurrent: true}, - ); - expect(Scheduler).toFlushAndYield([]); - - const initialRenderInteraction = { - id: 0, - name: 'initial render', - timestamp: Scheduler.unstable_now(), - }; - - const onCommit = jest.fn(() => { - Scheduler.unstable_yieldValue('onCommit'); - }); - SchedulerTracing.unstable_trace( - initialRenderInteraction.name, - initialRenderInteraction.timestamp, - () => { - if (gate(flags => flags.enableSyncDefaultUpdates)) { - React.unstable_startTransition(() => { - renderer.update( - - }> - - - - , - ); - }); - } else { - renderer.update( - - }> - - - - , - ); - } - }, - ); - expect(Scheduler).toFlushAndYield([ - 'Suspend [loaded]', - 'Text [loading]', - 'Text [initial]', - ]); - - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onCommit).not.toHaveBeenCalled(); - - Scheduler.unstable_advanceTime(50); - jest.advanceTimersByTime(50); - - const highPriUpdateInteraction = { - id: 1, - name: 'hiPriUpdate', - timestamp: Scheduler.unstable_now(), - }; - - const originalPromise = resourcePromise; - - renderer.unstable_flushSync(() => { - SchedulerTracing.unstable_trace( - highPriUpdateInteraction.name, - highPriUpdateInteraction.timestamp, - () => { - renderer.update( - - }> - - - - , - ); - }, - ); - }); - expect(Scheduler).toHaveYielded([ - 'Suspend [loaded]', - 'Text [loading]', - 'Text [updated]', - 'onCommit', - ]); - expect(renderer.toJSON()).toEqual(['loading', 'updated']); - - expect(onCommit).toHaveBeenCalledTimes(1); - expect(onCommit.mock.calls[0][4]).toMatchInteractions([ - highPriUpdateInteraction, - ]); - onCommit.mockClear(); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(0); - - Scheduler.unstable_advanceTime(50); - jest.advanceTimersByTime(50); - await originalPromise; - expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']); - expect(Scheduler).toFlushAndYield(['AsyncText [loaded]', 'onCommit']); - expect(renderer.toJSON()).toEqual(['loaded', 'updated']); - - expect(onCommit).toHaveBeenCalledTimes(1); - expect(onCommit.mock.calls[0][4]).toMatchInteractions([ - highPriUpdateInteraction, - ]); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2); - expect( - onInteractionScheduledWorkCompleted.mock.calls[0][0], - ).toMatchInteraction(initialRenderInteraction); - expect( - onInteractionScheduledWorkCompleted.mock.calls[1][0], - ).toMatchInteraction(highPriUpdateInteraction); - }); - - it('does not trace Promises flagged with __reactDoNotTraceInteractions', async () => { - loadModulesForTracing({useNoopRenderer: true}); - - const interaction = { - id: 0, - name: 'initial render', - timestamp: Scheduler.unstable_now(), - }; - - AsyncText = ({ms, text}) => { - try { - TextResource.read([text, ms]); - Scheduler.unstable_yieldValue(`AsyncText [${text}]`); - return text; - } catch (promise) { - promise.__reactDoNotTraceInteractions = true; - - if (typeof promise.then === 'function') { - Scheduler.unstable_yieldValue(`Suspend [${text}]`); - } else { - Scheduler.unstable_yieldValue(`Error [${text}]`); - } - throw promise; - } - }; - - const onCommit = jest.fn(() => { - Scheduler.unstable_yieldValue('onCommit'); - }); - SchedulerTracing.unstable_trace( - interaction.name, - Scheduler.unstable_now(), - () => { - ReactNoop.render( - - }> - - - - , - ); - }, - ); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0); - expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0); - - expect(Scheduler).toFlushAndYield([ - 'Suspend [Async]', - 'Text [Loading...]', - 'Text [Sync]', - 'onCommit', - ]); - // Should have committed the placeholder. - expect(ReactNoop.getChildrenAsJSX()).toEqual('Loading...Sync'); - expect(onCommit).toHaveBeenCalledTimes(1); - - let call = onCommit.mock.calls[0]; - expect(call[0]).toEqual('test-profiler'); - expect(call[4]).toMatchInteractions( - ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [], - ); + const onNestedUpdateScheduled = jest.fn(); - // The interaction is now complete. - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(interaction); - - // Once the promise resolves, we render the suspended view - await awaitableAdvanceTimers(20000); - expect(Scheduler).toHaveYielded(['Promise resolved [Async]']); - expect(Scheduler).toFlushAndYield(['AsyncText [Async]', 'onCommit']); - expect(ReactNoop.getChildrenAsJSX()).toEqual('AsyncSync'); - expect(onCommit).toHaveBeenCalledTimes(2); - - // No interactions should be associated with this update. - call = onCommit.mock.calls[1]; - expect(call[0]).toEqual('test-profiler'); - expect(call[4]).toMatchInteractions([]); - }); + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); - it('should properly report base duration wrt suspended subtrees', async () => { - loadModulesForTracing({useNoopRenderer: true}); + expect(Scheduler).toHaveYielded([ + 'Component:false:false', + 'Component:true:false', + ]); + expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); + expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test'); + + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); - const onRender = jest.fn(); + expect(Scheduler).toHaveYielded([ + 'Component:true:false', + 'Component:true:true', + ]); + expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(2); + expect(onNestedUpdateScheduled.mock.calls[1][0]).toBe('test'); + }); - let resolve = null; - const promise = new Promise(_resolve => { - resolve = _resolve; - }); + it('is called when a class component schedules an update from the componentDidMount lifecycles', () => { + class Component extends React.Component { + state = { + value: false, + }; + componentDidMount() { + this.setState({value: true}); + } + render() { + const {value} = this.state; + Scheduler.unstable_yieldValue(`Component:${value}`); + return value; + } + } - function Other() { - Scheduler.unstable_advanceTime(1); - Scheduler.unstable_yieldValue('Other'); - return
    Other
    ; - } + const onNestedUpdateScheduled = jest.fn(); - function Fallback() { - Scheduler.unstable_advanceTime(8); - Scheduler.unstable_yieldValue('Fallback'); - return
    Fallback
    ; - } + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); - let shouldSuspend = false; - function Suspender() { - Scheduler.unstable_advanceTime(15); - if (shouldSuspend) { - Scheduler.unstable_yieldValue('Suspender!'); - throw promise; - } - Scheduler.unstable_yieldValue('Suspender'); - return
    Suspender
    ; - } + expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); + expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); + expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test'); + }); - function App() { - return ( - - - }> - - - - ); + it('is called when a class component schedules an update from the componentDidUpdate lifecycles', () => { + class Component extends React.Component { + state = { + nestedUpdateSheduled: false, + }; + componentDidUpdate(prevProps, prevState) { + if ( + this.props.scheduleNestedUpdate && + !this.state.nestedUpdateSheduled + ) { + this.setState({nestedUpdateSheduled: true}); } - - ReactNoop.render(); - expect(Scheduler).toFlushAndYield(['Other', 'Suspender']); - expect(ReactNoop).toMatchRenderedOutput( - <> -
    Other
    -
    Suspender
    - , - ); - expect(onRender).toHaveBeenCalledTimes(1); - expect(onRender.mock.calls[0][2]).toBe(1 + 15); // actual - expect(onRender.mock.calls[0][3]).toBe(1 + 15); // base - - shouldSuspend = true; - ReactNoop.render(); - expect(Scheduler).toFlushAndYield(['Other', 'Suspender!', 'Fallback']); - await awaitableAdvanceTimers(20000); - expect(ReactNoop).toMatchRenderedOutput( - <> -
    Other
    - -
    Fallback
    - , - ); - expect(onRender).toHaveBeenCalledTimes(2); - expect(onRender.mock.calls[1][2]).toBe(1 + 15 + 8); // actual - expect(onRender.mock.calls[1][3]).toBe(1 + 8); // base - - shouldSuspend = false; - resolve(); - await promise; - expect(Scheduler).toFlushAndYield(['Suspender']); - expect(ReactNoop).toMatchRenderedOutput( - <> -
    Other
    -
    Suspender
    - , + } + render() { + const {scheduleNestedUpdate} = this.props; + const {nestedUpdateSheduled} = this.state; + Scheduler.unstable_yieldValue( + `Component:${scheduleNestedUpdate}:${nestedUpdateSheduled}`, ); - expect(onRender).toHaveBeenCalledTimes(3); - expect(onRender.mock.calls[2][2]).toBe(15); // actual - expect(onRender.mock.calls[2][3]).toBe(1 + 15); // base - }); + return nestedUpdateSheduled; + } + } - if (__DEV__) { - it('double invoking does not disconnect wrapped async work', () => { - ReactFeatureFlags.enableStrictEffects = true; - ReactFeatureFlags.createRootStrictEffectsByDefault = true; + const onNestedUpdateScheduled = jest.fn(); - const callback = jest.fn(() => { - const wrappedInteractions = SchedulerTracing.unstable_getCurrent(); - // Expect wrappedInteractions and interactions to be the same set. - expect(wrappedInteractions).toMatchInteractions([interaction]); - }); + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + expect(Scheduler).toHaveYielded(['Component:false:false']); + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); - const Component = jest.fn(() => { - React.useEffect(() => { - setTimeout(SchedulerTracing.unstable_wrap(callback), 0); - }); - React.useLayoutEffect(() => { - setTimeout(SchedulerTracing.unstable_wrap(callback), 0); - }); + expect(Scheduler).toHaveYielded([ + 'Component:true:false', + 'Component:true:true', + ]); + expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); + expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test'); + }); - return null; - }); + it('is not called when a class component schedules an update outside of render', () => { + const updateFnRef = React.createRef(null); - let interaction; - SchedulerTracing.unstable_trace( - 'event', - Scheduler.unstable_now(), - () => { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - interaction = Array.from(interactions)[0]; - ReactTestRendererAct(() => { - ReactTestRenderer.create(, { - unstable_isConcurrent: true, - }); - }); - }, - ); - Scheduler.unstable_flushAll(); + class Component extends React.Component { + state = { + value: false, + }; + render() { + const {value} = this.state; + updateFnRef.current = () => this.setState({value: true}); + Scheduler.unstable_yieldValue(`Component:${value}`); + return value; + } + } - jest.runAllTimers(); + const onNestedUpdateScheduled = jest.fn(); - expect(callback).toHaveBeenCalledTimes(4); // 2x per effect + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + expect(Scheduler).toHaveYielded(['Component:false']); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - }); - } + ReactNoop.act(() => { + updateFnRef.current(); }); + expect(Scheduler).toHaveYielded(['Component:true']); + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); }); + + // TODO Add hydration tests to ensure we don't have false positives called. }); diff --git a/packages/react/src/__tests__/ReactProfilerDOM-test.internal.js b/packages/react/src/__tests__/ReactProfilerDOM-test.internal.js deleted file mode 100644 index 6c1df098d6843..0000000000000 --- a/packages/react/src/__tests__/ReactProfilerDOM-test.internal.js +++ /dev/null @@ -1,129 +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. - * - * @emails react-core - */ - -'use strict'; - -let React; -let ReactFeatureFlags; -let ReactDOM; -let SchedulerTracing; -let Scheduler; - -function loadModules() { - ReactFeatureFlags = require('shared/ReactFeatureFlags'); - - ReactFeatureFlags.enableProfilerTimer = true; - ReactFeatureFlags.enableSchedulerTracing = true; - - React = require('react'); - SchedulerTracing = require('scheduler/tracing'); - ReactDOM = require('react-dom'); - Scheduler = require('scheduler'); -} - -describe('ProfilerDOM', () => { - let onInteractionScheduledWorkCompleted; - let onInteractionTraced; - - beforeEach(() => { - loadModules(); - - onInteractionScheduledWorkCompleted = jest.fn(); - onInteractionTraced = jest.fn(); - - // Verify interaction subscriber methods are called as expected. - SchedulerTracing.unstable_subscribe({ - onInteractionScheduledWorkCompleted, - onInteractionTraced, - onWorkCanceled: () => {}, - onWorkScheduled: () => {}, - onWorkStarted: () => {}, - onWorkStopped: () => {}, - }); - }); - - function Text(props) { - Scheduler.unstable_yieldValue(props.text); - return props.text; - } - - // @gate experimental - it('should correctly trace interactions for async roots', async () => { - let resolve; - let thenable = { - then(res) { - resolve = () => { - thenable = null; - res(); - }; - }, - }; - - function Async() { - if (thenable !== null) { - Scheduler.unstable_yieldValue('Suspend! [Async]'); - throw thenable; - } - Scheduler.unstable_yieldValue('Async'); - return 'Async'; - } - - const element = document.createElement('div'); - const root = ReactDOM.createRoot(element); - - let interaction; - let wrappedResolve; - SchedulerTracing.unstable_trace('initial_event', performance.now(), () => { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions.size).toBe(1); - interaction = Array.from(interactions)[0]; - - root.render( - }> - - , - ); - - wrappedResolve = SchedulerTracing.unstable_wrap(() => resolve()); - }); - - // Render, suspend, and commit fallback - expect(Scheduler).toFlushAndYield(['Suspend! [Async]', 'Loading...']); - expect(element.textContent).toEqual('Loading...'); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - // Ping React to try rendering again - wrappedResolve(); - - // Complete the tree without committing it - expect(Scheduler).toFlushAndYieldThrough(['Async']); - // Still showing the fallback - expect(element.textContent).toEqual('Loading...'); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - expect(Scheduler).toFlushAndYield([]); - expect(element.textContent).toEqual('Async'); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js b/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js index bd3f168af4eeb..d1b5bd9b44a9a 100644 --- a/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js +++ b/packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js @@ -15,7 +15,6 @@ describe('ReactProfiler DevTools integration', () => { let ReactFeatureFlags; let ReactTestRenderer; let Scheduler; - let SchedulerTracing; let AdvanceTime; let hook; @@ -31,9 +30,7 @@ describe('ReactProfiler DevTools integration', () => { 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'); @@ -76,15 +73,7 @@ describe('ReactProfiler DevTools integration', () => { // The time spent in App (above the Profiler) won't be included in the durations, // But needs to be accounted for in the offset times. expect(onRender).toHaveBeenCalledTimes(1); - expect(onRender).toHaveBeenCalledWith( - 'Profiler', - 'mount', - 10, - 10, - 2, - 12, - new Set(), - ); + expect(onRender).toHaveBeenCalledWith('Profiler', 'mount', 10, 10, 2, 12); onRender.mockClear(); // Measure unobservable timing required by the DevTools profiler. @@ -101,15 +90,7 @@ describe('ReactProfiler DevTools integration', () => { // The time spent in App (above the Profiler) won't be included in the durations, // But needs to be accounted for in the offset times. expect(onRender).toHaveBeenCalledTimes(1); - expect(onRender).toHaveBeenCalledWith( - 'Profiler', - 'update', - 6, - 13, - 14, - 20, - new Set(), - ); + expect(onRender).toHaveBeenCalledWith('Profiler', 'update', 6, 13, 14, 20); // Measure unobservable timing required by the DevTools profiler. // At this point, the base time should include both: @@ -157,27 +138,6 @@ describe('ReactProfiler DevTools integration', () => { ).toBe(7); }); - it('should store traced interactions on the HostNode so DevTools can access them', () => { - // Render without an interaction - const rendered = ReactTestRenderer.create(
    ); - - const root = rendered.root._currentFiber().return; - expect(root.stateNode.memoizedInteractions).toContainNoInteractions(); - - Scheduler.unstable_advanceTime(10); - - const eventTime = Scheduler.unstable_now(); - - // Render with an interaction - SchedulerTracing.unstable_trace('some event', eventTime, () => { - rendered.update(
    ); - }); - - expect(root.stateNode.memoizedInteractions).toMatchInteractions([ - {name: 'some event', timestamp: eventTime}, - ]); - }); - // @gate experimental || !enableSyncDefaultUpdates it('regression test: #17159', () => { function Text({text}) { diff --git a/packages/react/src/__tests__/__snapshots__/ReactProfiler-test.internal.js.snap b/packages/react/src/__tests__/__snapshots__/ReactProfiler-test.internal.js.snap index 91d0b594dcf0b..35da4c29611ab 100644 --- a/packages/react/src/__tests__/__snapshots__/ReactProfiler-test.internal.js.snap +++ b/packages/react/src/__tests__/__snapshots__/ReactProfiler-test.internal.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:disabled should render children 1`] = ` +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:disabled should render children 1`] = `
    outside span @@ -14,11 +14,11 @@ exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTr
    `; -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:disabled should support an empty Profiler (with no children) 1`] = `null`; +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:disabled should support an empty Profiler (with no children) 1`] = `null`; -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:disabled should support an empty Profiler (with no children) 2`] = `
    `; +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:disabled should support an empty Profiler (with no children) 2`] = `
    `; -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:disabled should support nested Profilers 1`] = ` +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:disabled should support nested Profilers 1`] = ` Array [
    outer function component @@ -32,7 +32,7 @@ Array [ ] `; -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:enabled should render children 1`] = ` +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:enabled should render children 1`] = `
    outside span @@ -46,75 +46,11 @@ exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTr
    `; -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:enabled should support an empty Profiler (with no children) 1`] = `null`; +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:enabled should support an empty Profiler (with no children) 1`] = `null`; -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:enabled should support an empty Profiler (with no children) 2`] = `
    `; +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:enabled should support an empty Profiler (with no children) 2`] = `
    `; -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:disabled enableProfilerTimer:enabled should support nested Profilers 1`] = ` -Array [ -
    - outer function component -
    , - - inner class component - , - - inner span - , -] -`; - -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:disabled should render children 1`] = ` -
    - - outside span - - - inside span - - - function component - -
    -`; - -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:disabled should support an empty Profiler (with no children) 1`] = `null`; - -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:disabled should support an empty Profiler (with no children) 2`] = `
    `; - -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:disabled should support nested Profilers 1`] = ` -Array [ -
    - outer function component -
    , - - inner class component - , - - inner span - , -] -`; - -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:enabled should render children 1`] = ` -
    - - outside span - - - inside span - - - function component - -
    -`; - -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:enabled should support an empty Profiler (with no children) 1`] = `null`; - -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:enabled should support an empty Profiler (with no children) 2`] = `
    `; - -exports[`Profiler works in profiling and non-profiling bundles enableSchedulerTracing:enabled enableProfilerTimer:enabled should support nested Profilers 1`] = ` +exports[`Profiler works in profiling and non-profiling bundles enableProfilerTimer:enabled should support nested Profilers 1`] = ` Array [
    outer function component diff --git a/packages/react/src/forks/ReactSharedInternals.umd.js b/packages/react/src/forks/ReactSharedInternals.umd.js index 268b792da1e66..8082fd0eef033 100644 --- a/packages/react/src/forks/ReactSharedInternals.umd.js +++ b/packages/react/src/forks/ReactSharedInternals.umd.js @@ -7,7 +7,6 @@ import assign from 'object-assign'; import * as Scheduler from 'scheduler'; -import * as SchedulerTracing from 'scheduler/tracing'; import ReactCurrentDispatcher from '../ReactCurrentDispatcher'; import ReactCurrentOwner from '../ReactCurrentOwner'; import ReactDebugCurrentFrame from '../ReactDebugCurrentFrame'; @@ -28,7 +27,6 @@ const ReactSharedInternals = { // This re-export is only required for UMD bundles; // CJS bundles use the shared NPM package. Scheduler, - SchedulerTracing, }; if (__DEV__) { diff --git a/packages/scheduler/npm/tracing-profiling.js b/packages/scheduler/npm/tracing-profiling.js deleted file mode 100644 index cb2d20c025226..0000000000000 --- a/packages/scheduler/npm/tracing-profiling.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/scheduler-tracing.profiling.min.js'); -} else { - module.exports = require('./cjs/scheduler-tracing.development.js'); -} diff --git a/packages/scheduler/npm/tracing.js b/packages/scheduler/npm/tracing.js deleted file mode 100644 index 1e318bd901b5d..0000000000000 --- a/packages/scheduler/npm/tracing.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/scheduler-tracing.production.min.js'); -} else { - module.exports = require('./cjs/scheduler-tracing.development.js'); -} diff --git a/packages/scheduler/npm/umd/scheduler-tracing.development.js b/packages/scheduler/npm/umd/scheduler-tracing.development.js deleted file mode 100644 index a81bf8fe2f98d..0000000000000 --- a/packages/scheduler/npm/umd/scheduler-tracing.development.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @license React - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -'use strict'; - -(function(global, factory) { - // eslint-disable-next-line no-unused-expressions - typeof exports === 'object' && typeof module !== 'undefined' - ? (module.exports = factory(require('react'))) - : typeof define === 'function' && define.amd // eslint-disable-line no-undef - ? define(['react'], factory) // eslint-disable-line no-undef - : (global.SchedulerTracing = factory(global)); -})(this, function(global) { - function unstable_clear() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_clear.apply( - this, - arguments - ); - } - - function unstable_getCurrent() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_getCurrent.apply( - this, - arguments - ); - } - - function unstable_getThreadID() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_getThreadID.apply( - this, - arguments - ); - } - - function unstable_subscribe() { - // eslint-disable-next-line max-len - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_subscribe.apply( - this, - arguments - ); - } - - function unstable_trace() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_trace.apply( - this, - arguments - ); - } - - function unstable_unsubscribe() { - // eslint-disable-next-line max-len - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_unsubscribe.apply( - this, - arguments - ); - } - - function unstable_wrap() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_wrap.apply( - this, - arguments - ); - } - - return Object.freeze({ - unstable_clear: unstable_clear, - unstable_getCurrent: unstable_getCurrent, - unstable_getThreadID: unstable_getThreadID, - unstable_subscribe: unstable_subscribe, - unstable_trace: unstable_trace, - unstable_unsubscribe: unstable_unsubscribe, - unstable_wrap: unstable_wrap, - }); -}); diff --git a/packages/scheduler/npm/umd/scheduler-tracing.production.min.js b/packages/scheduler/npm/umd/scheduler-tracing.production.min.js deleted file mode 100644 index a81bf8fe2f98d..0000000000000 --- a/packages/scheduler/npm/umd/scheduler-tracing.production.min.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @license React - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -'use strict'; - -(function(global, factory) { - // eslint-disable-next-line no-unused-expressions - typeof exports === 'object' && typeof module !== 'undefined' - ? (module.exports = factory(require('react'))) - : typeof define === 'function' && define.amd // eslint-disable-line no-undef - ? define(['react'], factory) // eslint-disable-line no-undef - : (global.SchedulerTracing = factory(global)); -})(this, function(global) { - function unstable_clear() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_clear.apply( - this, - arguments - ); - } - - function unstable_getCurrent() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_getCurrent.apply( - this, - arguments - ); - } - - function unstable_getThreadID() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_getThreadID.apply( - this, - arguments - ); - } - - function unstable_subscribe() { - // eslint-disable-next-line max-len - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_subscribe.apply( - this, - arguments - ); - } - - function unstable_trace() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_trace.apply( - this, - arguments - ); - } - - function unstable_unsubscribe() { - // eslint-disable-next-line max-len - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_unsubscribe.apply( - this, - arguments - ); - } - - function unstable_wrap() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_wrap.apply( - this, - arguments - ); - } - - return Object.freeze({ - unstable_clear: unstable_clear, - unstable_getCurrent: unstable_getCurrent, - unstable_getThreadID: unstable_getThreadID, - unstable_subscribe: unstable_subscribe, - unstable_trace: unstable_trace, - unstable_unsubscribe: unstable_unsubscribe, - unstable_wrap: unstable_wrap, - }); -}); diff --git a/packages/scheduler/npm/umd/scheduler-tracing.profiling.min.js b/packages/scheduler/npm/umd/scheduler-tracing.profiling.min.js deleted file mode 100644 index a81bf8fe2f98d..0000000000000 --- a/packages/scheduler/npm/umd/scheduler-tracing.profiling.min.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @license React - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -'use strict'; - -(function(global, factory) { - // eslint-disable-next-line no-unused-expressions - typeof exports === 'object' && typeof module !== 'undefined' - ? (module.exports = factory(require('react'))) - : typeof define === 'function' && define.amd // eslint-disable-line no-undef - ? define(['react'], factory) // eslint-disable-line no-undef - : (global.SchedulerTracing = factory(global)); -})(this, function(global) { - function unstable_clear() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_clear.apply( - this, - arguments - ); - } - - function unstable_getCurrent() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_getCurrent.apply( - this, - arguments - ); - } - - function unstable_getThreadID() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_getThreadID.apply( - this, - arguments - ); - } - - function unstable_subscribe() { - // eslint-disable-next-line max-len - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_subscribe.apply( - this, - arguments - ); - } - - function unstable_trace() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_trace.apply( - this, - arguments - ); - } - - function unstable_unsubscribe() { - // eslint-disable-next-line max-len - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_unsubscribe.apply( - this, - arguments - ); - } - - function unstable_wrap() { - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.SchedulerTracing.unstable_wrap.apply( - this, - arguments - ); - } - - return Object.freeze({ - unstable_clear: unstable_clear, - unstable_getCurrent: unstable_getCurrent, - unstable_getThreadID: unstable_getThreadID, - unstable_subscribe: unstable_subscribe, - unstable_trace: unstable_trace, - unstable_unsubscribe: unstable_unsubscribe, - unstable_wrap: unstable_wrap, - }); -}); diff --git a/packages/scheduler/package.json b/packages/scheduler/package.json index 04daf3e6a3133..e8ac46571b73b 100644 --- a/packages/scheduler/package.json +++ b/packages/scheduler/package.json @@ -25,8 +25,6 @@ "README.md", "build-info.json", "index.js", - "tracing.js", - "tracing-profiling.js", "unstable_mock.js", "unstable_no_dom.js", "unstable_post_task.js", diff --git a/packages/scheduler/src/Tracing.js b/packages/scheduler/src/Tracing.js deleted file mode 100644 index cc71b03f9065f..0000000000000 --- a/packages/scheduler/src/Tracing.js +++ /dev/null @@ -1,264 +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 - */ - -import {enableSchedulerTracing} from 'shared/ReactFeatureFlags'; - -export type Interaction = {| - __count: number, - id: number, - name: string, - timestamp: number, -|}; - -export type Subscriber = { - // A new interaction has been created via the trace() method. - onInteractionTraced: (interaction: Interaction) => void, - - // All scheduled async work for an interaction has finished. - onInteractionScheduledWorkCompleted: (interaction: Interaction) => void, - - // New async work has been scheduled for a set of interactions. - // When this work is later run, onWorkStarted/onWorkStopped will be called. - // A batch of async/yieldy work may be scheduled multiple times before completing. - // In that case, onWorkScheduled may be called more than once before onWorkStopped. - // Work is scheduled by a "thread" which is identified by a unique ID. - onWorkScheduled: (interactions: Set, threadID: number) => void, - - // A batch of scheduled work has been canceled. - // Work is done by a "thread" which is identified by a unique ID. - onWorkCanceled: (interactions: Set, threadID: number) => void, - - // A batch of work has started for a set of interactions. - // When this work is complete, onWorkStopped will be called. - // Work is not always completed synchronously; yielding may occur in between. - // A batch of async/yieldy work may also be re-started before completing. - // In that case, onWorkStarted may be called more than once before onWorkStopped. - // Work is done by a "thread" which is identified by a unique ID. - onWorkStarted: (interactions: Set, threadID: number) => void, - - // A batch of work has completed for a set of interactions. - // Work is done by a "thread" which is identified by a unique ID. - onWorkStopped: (interactions: Set, threadID: number) => void, - ... -}; - -export type InteractionsRef = {|current: Set|}; - -export type SubscriberRef = {|current: Subscriber | null|}; - -const DEFAULT_THREAD_ID = 0; - -// Counters used to generate unique IDs. -let interactionIDCounter: number = 0; -let threadIDCounter: number = 0; - -// Set of currently traced interactions. -// Interactions "stack"– -// Meaning that newly traced interactions are appended to the previously active set. -// When an interaction goes out of scope, the previous set (if any) is restored. -let interactionsRef: InteractionsRef = (null: any); - -// Listener(s) to notify when interactions begin and end. -let subscriberRef: SubscriberRef = (null: any); - -if (enableSchedulerTracing) { - interactionsRef = { - current: new Set(), - }; - subscriberRef = { - current: null, - }; -} - -export {interactionsRef as __interactionsRef, subscriberRef as __subscriberRef}; - -export function unstable_clear(callback: Function): any { - if (!enableSchedulerTracing) { - return callback(); - } - - const prevInteractions = interactionsRef.current; - interactionsRef.current = new Set(); - - try { - return callback(); - } finally { - interactionsRef.current = prevInteractions; - } -} - -export function unstable_getCurrent(): Set | null { - if (!enableSchedulerTracing) { - return null; - } else { - return interactionsRef.current; - } -} - -export function unstable_getThreadID(): number { - return ++threadIDCounter; -} - -export function unstable_trace( - name: string, - timestamp: number, - callback: Function, - threadID: number = DEFAULT_THREAD_ID, -): any { - if (!enableSchedulerTracing) { - return callback(); - } - - const interaction: Interaction = { - __count: 1, - id: interactionIDCounter++, - name, - timestamp, - }; - - const prevInteractions = interactionsRef.current; - - // Traced interactions should stack/accumulate. - // To do that, clone the current interactions. - // The previous set will be restored upon completion. - const interactions = new Set(prevInteractions); - interactions.add(interaction); - interactionsRef.current = interactions; - - const subscriber = subscriberRef.current; - let returnValue; - - try { - if (subscriber !== null) { - subscriber.onInteractionTraced(interaction); - } - } finally { - try { - if (subscriber !== null) { - subscriber.onWorkStarted(interactions, threadID); - } - } finally { - try { - returnValue = callback(); - } finally { - interactionsRef.current = prevInteractions; - - try { - if (subscriber !== null) { - subscriber.onWorkStopped(interactions, threadID); - } - } finally { - interaction.__count--; - - // If no async work was scheduled for this interaction, - // Notify subscribers that it's completed. - if (subscriber !== null && interaction.__count === 0) { - subscriber.onInteractionScheduledWorkCompleted(interaction); - } - } - } - } - } - - return returnValue; -} - -export function unstable_wrap( - callback: Function, - threadID: number = DEFAULT_THREAD_ID, -): Function { - if (!enableSchedulerTracing) { - return callback; - } - - const wrappedInteractions = interactionsRef.current; - - let subscriber = subscriberRef.current; - if (subscriber !== null) { - subscriber.onWorkScheduled(wrappedInteractions, threadID); - } - - // Update the pending async work count for the current interactions. - // Update after calling subscribers in case of error. - wrappedInteractions.forEach(interaction => { - interaction.__count++; - }); - - let hasRun = false; - - function wrapped() { - const prevInteractions = interactionsRef.current; - interactionsRef.current = wrappedInteractions; - - subscriber = subscriberRef.current; - - try { - let returnValue; - - try { - if (subscriber !== null) { - subscriber.onWorkStarted(wrappedInteractions, threadID); - } - } finally { - try { - returnValue = callback.apply(undefined, arguments); - } finally { - interactionsRef.current = prevInteractions; - - if (subscriber !== null) { - subscriber.onWorkStopped(wrappedInteractions, threadID); - } - } - } - - return returnValue; - } finally { - if (!hasRun) { - // We only expect a wrapped function to be executed once, - // But in the event that it's executed more than once– - // Only decrement the outstanding interaction counts once. - hasRun = true; - - // Update pending async counts for all wrapped interactions. - // If this was the last scheduled async work for any of them, - // Mark them as completed. - wrappedInteractions.forEach(interaction => { - interaction.__count--; - - if (subscriber !== null && interaction.__count === 0) { - subscriber.onInteractionScheduledWorkCompleted(interaction); - } - }); - } - } - } - - wrapped.cancel = function cancel() { - subscriber = subscriberRef.current; - - try { - if (subscriber !== null) { - subscriber.onWorkCanceled(wrappedInteractions, threadID); - } - } finally { - // Update pending async counts for all wrapped interactions. - // If this was the last scheduled async work for any of them, - // Mark them as completed. - wrappedInteractions.forEach(interaction => { - interaction.__count--; - - if (subscriber && interaction.__count === 0) { - subscriber.onInteractionScheduledWorkCompleted(interaction); - } - }); - } - }; - - return wrapped; -} diff --git a/packages/scheduler/src/TracingSubscriptions.js b/packages/scheduler/src/TracingSubscriptions.js deleted file mode 100644 index 9fd687adf415b..0000000000000 --- a/packages/scheduler/src/TracingSubscriptions.js +++ /dev/null @@ -1,171 +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 - */ - -import type {Interaction, Subscriber} from './Tracing'; - -import {enableSchedulerTracing} from 'shared/ReactFeatureFlags'; -import {__subscriberRef} from './Tracing'; - -let subscribers: Set = (null: any); -if (enableSchedulerTracing) { - subscribers = new Set(); -} - -export function unstable_subscribe(subscriber: Subscriber): void { - if (enableSchedulerTracing) { - subscribers.add(subscriber); - - if (subscribers.size === 1) { - __subscriberRef.current = { - onInteractionScheduledWorkCompleted, - onInteractionTraced, - onWorkCanceled, - onWorkScheduled, - onWorkStarted, - onWorkStopped, - }; - } - } -} - -export function unstable_unsubscribe(subscriber: Subscriber): void { - if (enableSchedulerTracing) { - subscribers.delete(subscriber); - - if (subscribers.size === 0) { - __subscriberRef.current = null; - } - } -} - -function onInteractionTraced(interaction: Interaction): void { - let didCatchError = false; - let caughtError = null; - - subscribers.forEach(subscriber => { - try { - subscriber.onInteractionTraced(interaction); - } catch (error) { - if (!didCatchError) { - didCatchError = true; - caughtError = error; - } - } - }); - - if (didCatchError) { - throw caughtError; - } -} - -function onInteractionScheduledWorkCompleted(interaction: Interaction): void { - let didCatchError = false; - let caughtError = null; - - subscribers.forEach(subscriber => { - try { - subscriber.onInteractionScheduledWorkCompleted(interaction); - } catch (error) { - if (!didCatchError) { - didCatchError = true; - caughtError = error; - } - } - }); - - if (didCatchError) { - throw caughtError; - } -} - -function onWorkScheduled( - interactions: Set, - threadID: number, -): void { - let didCatchError = false; - let caughtError = null; - - subscribers.forEach(subscriber => { - try { - subscriber.onWorkScheduled(interactions, threadID); - } catch (error) { - if (!didCatchError) { - didCatchError = true; - caughtError = error; - } - } - }); - - if (didCatchError) { - throw caughtError; - } -} - -function onWorkStarted(interactions: Set, threadID: number): void { - let didCatchError = false; - let caughtError = null; - - subscribers.forEach(subscriber => { - try { - subscriber.onWorkStarted(interactions, threadID); - } catch (error) { - if (!didCatchError) { - didCatchError = true; - caughtError = error; - } - } - }); - - if (didCatchError) { - throw caughtError; - } -} - -function onWorkStopped(interactions: Set, threadID: number): void { - let didCatchError = false; - let caughtError = null; - - subscribers.forEach(subscriber => { - try { - subscriber.onWorkStopped(interactions, threadID); - } catch (error) { - if (!didCatchError) { - didCatchError = true; - caughtError = error; - } - } - }); - - if (didCatchError) { - throw caughtError; - } -} - -function onWorkCanceled( - interactions: Set, - threadID: number, -): void { - let didCatchError = false; - let caughtError = null; - - subscribers.forEach(subscriber => { - try { - subscriber.onWorkCanceled(interactions, threadID); - } catch (error) { - if (!didCatchError) { - didCatchError = true; - caughtError = error; - } - } - }); - - if (didCatchError) { - throw caughtError; - } -} diff --git a/packages/scheduler/src/__tests__/SchedulerUMDBundle-test.internal.js b/packages/scheduler/src/__tests__/SchedulerUMDBundle-test.internal.js index d6dfeb2081e4a..fde4ee8af20e3 100644 --- a/packages/scheduler/src/__tests__/SchedulerUMDBundle-test.internal.js +++ b/packages/scheduler/src/__tests__/SchedulerUMDBundle-test.internal.js @@ -37,30 +37,10 @@ describe('Scheduling UMD bundle', () => { global.MessageChannel = undefined; }); - function filterPrivateKeys(name) { - // Be very careful adding things to this filter! - // It's easy to introduce bugs by doing it: - // https://github.com/facebook/react/issues/14904 - switch (name) { - case '__interactionsRef': - case '__subscriberRef': - // Don't forward these. (TODO: why?) - return false; - default: - return true; - } - } - function validateForwardedAPIs(api, forwardedAPIs) { - const apiKeys = Object.keys(api) - .filter(filterPrivateKeys) - .sort(); + const apiKeys = Object.keys(api).sort(); forwardedAPIs.forEach(forwardedAPI => { - expect( - Object.keys(forwardedAPI) - .filter(filterPrivateKeys) - .sort(), - ).toEqual(apiKeys); + expect(Object.keys(forwardedAPI).sort()).toEqual(apiKeys); }); } @@ -78,19 +58,4 @@ describe('Scheduling UMD bundle', () => { secretAPI.Scheduler, ]); }); - - it('should define the same tracing API', () => { - const api = require('../../tracing'); - const umdAPIDev = require('../../npm/umd/scheduler-tracing.development'); - const umdAPIProd = require('../../npm/umd/scheduler-tracing.production.min'); - const umdAPIProfiling = require('../../npm/umd/scheduler-tracing.profiling.min'); - const secretAPI = require('react/src/forks/ReactSharedInternals.umd') - .default; - validateForwardedAPIs(api, [ - umdAPIDev, - umdAPIProd, - umdAPIProfiling, - secretAPI.SchedulerTracing, - ]); - }); }); diff --git a/packages/scheduler/src/__tests__/Tracing-test.internal.js b/packages/scheduler/src/__tests__/Tracing-test.internal.js deleted file mode 100644 index 658b69313ca5b..0000000000000 --- a/packages/scheduler/src/__tests__/Tracing-test.internal.js +++ /dev/null @@ -1,375 +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. - * - * @jest-environment node - */ -'use strict'; - -describe('Tracing', () => { - let SchedulerTracing; - let ReactFeatureFlags; - - let advanceTimeBy; - let currentTime; - - function loadModules({enableSchedulerTracing}) { - jest.resetModules(); - jest.useFakeTimers(); - - currentTime = 0; - Date.now = jest.fn().mockImplementation(() => currentTime); - - advanceTimeBy = amount => { - currentTime += amount; - }; - - ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing; - - SchedulerTracing = require('scheduler/tracing'); - } - - describe('enableSchedulerTracing enabled', () => { - beforeEach(() => loadModules({enableSchedulerTracing: true})); - - it('should return the value of a traced function', () => { - expect( - SchedulerTracing.unstable_trace('arbitrary', currentTime, () => 123), - ).toBe(123); - }); - - it('should return the value of a clear function', () => { - expect(SchedulerTracing.unstable_clear(() => 123)).toBe(123); - }); - - it('should return the value of a wrapped function', () => { - let wrapped; - SchedulerTracing.unstable_trace('arbitrary', currentTime, () => { - wrapped = SchedulerTracing.unstable_wrap(() => 123); - }); - expect(wrapped()).toBe(123); - }); - - it('should pass arguments through to a wrapped function', done => { - let wrapped; - SchedulerTracing.unstable_trace('arbitrary', currentTime, () => { - wrapped = SchedulerTracing.unstable_wrap((param1, param2) => { - expect(param1).toBe('foo'); - expect(param2).toBe('bar'); - done(); - }); - }); - wrapped('foo', 'bar'); - }); - - it('should return an empty set when outside of a traced event', () => { - expect(SchedulerTracing.unstable_getCurrent()).toContainNoInteractions(); - }); - - it('should report the traced interaction from within the trace callback', done => { - advanceTimeBy(100); - - SchedulerTracing.unstable_trace('some event', currentTime, () => { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions).toMatchInteractions([ - {name: 'some event', timestamp: 100}, - ]); - - done(); - }); - }); - - it('should report the traced interaction from within wrapped callbacks', done => { - let wrappedIndirection; - - function indirection() { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions).toMatchInteractions([ - {name: 'some event', timestamp: 100}, - ]); - - done(); - } - - advanceTimeBy(100); - - SchedulerTracing.unstable_trace('some event', currentTime, () => { - wrappedIndirection = SchedulerTracing.unstable_wrap(indirection); - }); - - advanceTimeBy(50); - - wrappedIndirection(); - }); - - it('should clear the interaction stack for traced callbacks', () => { - let innerTestReached = false; - - SchedulerTracing.unstable_trace('outer event', currentTime, () => { - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - {name: 'outer event'}, - ]); - - SchedulerTracing.unstable_clear(() => { - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions( - [], - ); - - SchedulerTracing.unstable_trace('inner event', currentTime, () => { - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - {name: 'inner event'}, - ]); - - innerTestReached = true; - }); - }); - - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - {name: 'outer event'}, - ]); - }); - - expect(innerTestReached).toBe(true); - }); - - it('should clear the interaction stack for wrapped callbacks', () => { - let innerTestReached = false; - let wrappedIndirection; - - const indirection = jest.fn(() => { - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - {name: 'outer event'}, - ]); - - SchedulerTracing.unstable_clear(() => { - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions( - [], - ); - - SchedulerTracing.unstable_trace('inner event', currentTime, () => { - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - {name: 'inner event'}, - ]); - - innerTestReached = true; - }); - }); - - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - {name: 'outer event'}, - ]); - }); - - SchedulerTracing.unstable_trace('outer event', currentTime, () => { - wrappedIndirection = SchedulerTracing.unstable_wrap(indirection); - }); - - wrappedIndirection(); - - expect(innerTestReached).toBe(true); - }); - - it('should support nested traced events', done => { - advanceTimeBy(100); - - let innerIndirectionTraced = false; - let outerIndirectionTraced = false; - - function innerIndirection() { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions).toMatchInteractions([ - {name: 'outer event', timestamp: 100}, - {name: 'inner event', timestamp: 150}, - ]); - - innerIndirectionTraced = true; - } - - function outerIndirection() { - const interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions).toMatchInteractions([ - {name: 'outer event', timestamp: 100}, - ]); - - outerIndirectionTraced = true; - } - - SchedulerTracing.unstable_trace('outer event', currentTime, () => { - // Verify the current traced event - let interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions).toMatchInteractions([ - {name: 'outer event', timestamp: 100}, - ]); - - advanceTimeBy(50); - - const wrapperOuterIndirection = SchedulerTracing.unstable_wrap( - outerIndirection, - ); - - let wrapperInnerIndirection; - let innerEventTraced = false; - - // Verify that a nested event is properly traced - SchedulerTracing.unstable_trace('inner event', currentTime, () => { - interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions).toMatchInteractions([ - {name: 'outer event', timestamp: 100}, - {name: 'inner event', timestamp: 150}, - ]); - - // Verify that a wrapped outer callback is properly traced - wrapperOuterIndirection(); - expect(outerIndirectionTraced).toBe(true); - - wrapperInnerIndirection = SchedulerTracing.unstable_wrap( - innerIndirection, - ); - - innerEventTraced = true; - }); - - expect(innerEventTraced).toBe(true); - - // Verify that the original event is restored - interactions = SchedulerTracing.unstable_getCurrent(); - expect(interactions).toMatchInteractions([ - {name: 'outer event', timestamp: 100}, - ]); - - // Verify that a wrapped nested callback is properly traced - wrapperInnerIndirection(); - expect(innerIndirectionTraced).toBe(true); - - done(); - }); - }); - - describe('error handling', () => { - it('should reset state appropriately when an error occurs in a trace callback', done => { - advanceTimeBy(100); - - SchedulerTracing.unstable_trace('outer event', currentTime, () => { - expect(() => { - SchedulerTracing.unstable_trace('inner event', currentTime, () => { - throw Error('intentional'); - }); - }).toThrow(); - - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - {name: 'outer event', timestamp: 100}, - ]); - - done(); - }); - }); - - it('should reset state appropriately when an error occurs in a wrapped callback', done => { - advanceTimeBy(100); - - SchedulerTracing.unstable_trace('outer event', currentTime, () => { - let wrappedCallback; - - SchedulerTracing.unstable_trace('inner event', currentTime, () => { - wrappedCallback = SchedulerTracing.unstable_wrap(() => { - throw Error('intentional'); - }); - }); - - expect(wrappedCallback).toThrow(); - - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - {name: 'outer event', timestamp: 100}, - ]); - - done(); - }); - }); - }); - - describe('advanced integration', () => { - it('should return a unique threadID per request', () => { - expect(SchedulerTracing.unstable_getThreadID()).not.toBe( - SchedulerTracing.unstable_getThreadID(), - ); - }); - - it('should expose the current set of interactions to be externally manipulated', () => { - SchedulerTracing.unstable_trace('outer event', currentTime, () => { - expect(SchedulerTracing.__interactionsRef.current).toBe( - SchedulerTracing.unstable_getCurrent(), - ); - - SchedulerTracing.__interactionsRef.current = new Set([ - {name: 'override event'}, - ]); - - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - {name: 'override event'}, - ]); - }); - }); - - it('should expose a subscriber ref to be externally manipulated', () => { - SchedulerTracing.unstable_trace('outer event', currentTime, () => { - expect(SchedulerTracing.__subscriberRef).toEqual({ - current: null, - }); - }); - }); - }); - }); - - describe('enableSchedulerTracing disabled', () => { - beforeEach(() => loadModules({enableSchedulerTracing: false})); - - it('should return the value of a traced function', () => { - expect( - SchedulerTracing.unstable_trace('arbitrary', currentTime, () => 123), - ).toBe(123); - }); - - it('should return the value of a wrapped function', () => { - let wrapped; - SchedulerTracing.unstable_trace('arbitrary', currentTime, () => { - wrapped = SchedulerTracing.unstable_wrap(() => 123); - }); - expect(wrapped()).toBe(123); - }); - - it('should return null for traced interactions', () => { - expect(SchedulerTracing.unstable_getCurrent()).toBe(null); - }); - - it('should execute traced callbacks', done => { - SchedulerTracing.unstable_trace('some event', currentTime, () => { - expect(SchedulerTracing.unstable_getCurrent()).toBe(null); - - done(); - }); - }); - - it('should return the value of a clear function', () => { - expect(SchedulerTracing.unstable_clear(() => 123)).toBe(123); - }); - - it('should execute wrapped callbacks', done => { - const wrappedCallback = SchedulerTracing.unstable_wrap(() => { - expect(SchedulerTracing.unstable_getCurrent()).toBe(null); - - done(); - }); - - wrappedCallback(); - }); - - describe('advanced integration', () => { - it('should not create unnecessary objects', () => { - expect(SchedulerTracing.__interactionsRef).toBe(null); - }); - }); - }); -}); diff --git a/packages/scheduler/src/__tests__/Tracing-test.js b/packages/scheduler/src/__tests__/Tracing-test.js deleted file mode 100644 index 5259be4792560..0000000000000 --- a/packages/scheduler/src/__tests__/Tracing-test.js +++ /dev/null @@ -1,51 +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. - * - * @jest-environment node - */ -'use strict'; - -describe('Tracing', () => { - let SchedulerTracing; - - beforeEach(() => { - jest.resetModules(); - - SchedulerTracing = require('scheduler/tracing'); - }); - - it('should return the value of a traced function', () => { - expect(SchedulerTracing.unstable_trace('arbitrary', 0, () => 123)).toBe( - 123, - ); - }); - - it('should return the value of a wrapped function', () => { - let wrapped; - SchedulerTracing.unstable_trace('arbitrary', 0, () => { - wrapped = SchedulerTracing.unstable_wrap(() => 123); - }); - expect(wrapped()).toBe(123); - }); - - it('should execute traced callbacks', done => { - SchedulerTracing.unstable_trace('some event', 0, () => { - done(); - }); - }); - - it('should return the value of a clear function', () => { - expect(SchedulerTracing.unstable_clear(() => 123)).toBe(123); - }); - - it('should execute wrapped callbacks', done => { - const wrappedCallback = SchedulerTracing.unstable_wrap(() => { - done(); - }); - - wrappedCallback(); - }); -}); diff --git a/packages/scheduler/src/__tests__/TracingSubscriptions-test.internal.js b/packages/scheduler/src/__tests__/TracingSubscriptions-test.internal.js deleted file mode 100644 index 6eff59a6cf864..0000000000000 --- a/packages/scheduler/src/__tests__/TracingSubscriptions-test.internal.js +++ /dev/null @@ -1,621 +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. - * - * @jest-environment node - */ -'use strict'; - -describe('TracingSubscriptions', () => { - let SchedulerTracing; - let ReactFeatureFlags; - - let currentTime; - - let onInteractionScheduledWorkCompleted; - let onInteractionTraced; - let onWorkCanceled; - let onWorkScheduled; - let onWorkStarted; - let onWorkStopped; - let throwInOnInteractionScheduledWorkCompleted; - let throwInOnInteractionTraced; - let throwInOnWorkCanceled; - let throwInOnWorkScheduled; - let throwInOnWorkStarted; - let throwInOnWorkStopped; - let firstSubscriber; - let secondSubscriber; - - const firstEvent = {id: 0, name: 'first', timestamp: 0}; - const secondEvent = {id: 1, name: 'second', timestamp: 0}; - const threadID = 123; - - function loadModules({enableSchedulerTracing, autoSubscribe = true}) { - jest.resetModules(); - jest.useFakeTimers(); - - currentTime = 0; - - ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing; - - SchedulerTracing = require('scheduler/tracing'); - - throwInOnInteractionScheduledWorkCompleted = false; - throwInOnInteractionTraced = false; - throwInOnWorkCanceled = false; - throwInOnWorkScheduled = false; - throwInOnWorkStarted = false; - throwInOnWorkStopped = false; - - onInteractionScheduledWorkCompleted = jest.fn(() => { - if (throwInOnInteractionScheduledWorkCompleted) { - throw Error('Expected error onInteractionScheduledWorkCompleted'); - } - }); - onInteractionTraced = jest.fn(() => { - if (throwInOnInteractionTraced) { - throw Error('Expected error onInteractionTraced'); - } - }); - onWorkCanceled = jest.fn(() => { - if (throwInOnWorkCanceled) { - throw Error('Expected error onWorkCanceled'); - } - }); - onWorkScheduled = jest.fn(() => { - if (throwInOnWorkScheduled) { - throw Error('Expected error onWorkScheduled'); - } - }); - onWorkStarted = jest.fn(() => { - if (throwInOnWorkStarted) { - throw Error('Expected error onWorkStarted'); - } - }); - onWorkStopped = jest.fn(() => { - if (throwInOnWorkStopped) { - throw Error('Expected error onWorkStopped'); - } - }); - - firstSubscriber = { - onInteractionScheduledWorkCompleted, - onInteractionTraced, - onWorkCanceled, - onWorkScheduled, - onWorkStarted, - onWorkStopped, - }; - - secondSubscriber = { - onInteractionScheduledWorkCompleted: jest.fn(), - onInteractionTraced: jest.fn(), - onWorkCanceled: jest.fn(), - onWorkScheduled: jest.fn(), - onWorkStarted: jest.fn(), - onWorkStopped: jest.fn(), - }; - - if (autoSubscribe) { - SchedulerTracing.unstable_subscribe(firstSubscriber); - SchedulerTracing.unstable_subscribe(secondSubscriber); - } - } - - describe('enabled', () => { - beforeEach(() => loadModules({enableSchedulerTracing: true})); - - it('should lazily subscribe to tracing and unsubscribe again if there are no external subscribers', () => { - loadModules({enableSchedulerTracing: true, autoSubscribe: false}); - - expect(SchedulerTracing.__subscriberRef.current).toBe(null); - SchedulerTracing.unstable_subscribe(firstSubscriber); - expect(SchedulerTracing.__subscriberRef.current).toBeDefined(); - SchedulerTracing.unstable_subscribe(secondSubscriber); - expect(SchedulerTracing.__subscriberRef.current).toBeDefined(); - SchedulerTracing.unstable_unsubscribe(secondSubscriber); - expect(SchedulerTracing.__subscriberRef.current).toBeDefined(); - SchedulerTracing.unstable_unsubscribe(firstSubscriber); - expect(SchedulerTracing.__subscriberRef.current).toBe(null); - }); - - describe('error handling', () => { - it('should cover onInteractionTraced/onWorkStarted within', done => { - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - const mock = jest.fn(); - - // It should call the callback before re-throwing - throwInOnInteractionTraced = true; - expect(() => - SchedulerTracing.unstable_trace( - secondEvent.name, - currentTime, - mock, - threadID, - ), - ).toThrow('Expected error onInteractionTraced'); - throwInOnInteractionTraced = false; - expect(mock).toHaveBeenCalledTimes(1); - - throwInOnWorkStarted = true; - expect(() => - SchedulerTracing.unstable_trace( - secondEvent.name, - currentTime, - mock, - threadID, - ), - ).toThrow('Expected error onWorkStarted'); - expect(mock).toHaveBeenCalledTimes(2); - - // It should restore the previous/outer interactions - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - firstEvent, - ]); - - // It should call other subscribers despite the earlier error - expect(secondSubscriber.onInteractionTraced).toHaveBeenCalledTimes(3); - expect(secondSubscriber.onWorkStarted).toHaveBeenCalledTimes(3); - - done(); - }); - }); - - it('should cover onWorkStopped within trace', done => { - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - let innerInteraction; - const mock = jest.fn(() => { - innerInteraction = Array.from( - SchedulerTracing.unstable_getCurrent(), - )[1]; - }); - - throwInOnWorkStopped = true; - expect(() => - SchedulerTracing.unstable_trace( - secondEvent.name, - currentTime, - mock, - ), - ).toThrow('Expected error onWorkStopped'); - throwInOnWorkStopped = false; - - // It should restore the previous/outer interactions - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - firstEvent, - ]); - - // It should update the interaction count so as not to interfere with subsequent calls - expect(innerInteraction.__count).toBe(0); - - // It should call other subscribers despite the earlier error - expect(secondSubscriber.onWorkStopped).toHaveBeenCalledTimes(1); - - done(); - }); - }); - - it('should cover onInteractionScheduledWorkCompleted within trace', done => { - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - const mock = jest.fn(); - - throwInOnInteractionScheduledWorkCompleted = true; - expect(() => - SchedulerTracing.unstable_trace( - secondEvent.name, - currentTime, - mock, - ), - ).toThrow('Expected error onInteractionScheduledWorkCompleted'); - throwInOnInteractionScheduledWorkCompleted = false; - - // It should restore the previous/outer interactions - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - firstEvent, - ]); - - // It should call other subscribers despite the earlier error - expect( - secondSubscriber.onInteractionScheduledWorkCompleted, - ).toHaveBeenCalledTimes(1); - - done(); - }); - }); - - it('should cover the callback within trace', done => { - expect(onWorkStarted).not.toHaveBeenCalled(); - expect(onWorkStopped).not.toHaveBeenCalled(); - - expect(() => { - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - throw Error('Expected error callback'); - }); - }).toThrow('Expected error callback'); - - expect(onWorkStarted).toHaveBeenCalledTimes(1); - expect(onWorkStopped).toHaveBeenCalledTimes(1); - - done(); - }); - - it('should cover onWorkScheduled within wrap', done => { - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - const interaction = Array.from( - SchedulerTracing.unstable_getCurrent(), - )[0]; - const beforeCount = interaction.__count; - - throwInOnWorkScheduled = true; - expect(() => SchedulerTracing.unstable_wrap(() => {})).toThrow( - 'Expected error onWorkScheduled', - ); - - // It should not update the interaction count so as not to interfere with subsequent calls - expect(interaction.__count).toBe(beforeCount); - - // It should call other subscribers despite the earlier error - expect(secondSubscriber.onWorkScheduled).toHaveBeenCalledTimes(1); - - done(); - }); - }); - - it('should cover onWorkStarted within wrap', () => { - const mock = jest.fn(); - let interaction, wrapped; - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - wrapped = SchedulerTracing.unstable_wrap(mock); - }); - expect(interaction.__count).toBe(1); - - throwInOnWorkStarted = true; - expect(wrapped).toThrow('Expected error onWorkStarted'); - - // It should call the callback before re-throwing - expect(mock).toHaveBeenCalledTimes(1); - - // It should update the interaction count so as not to interfere with subsequent calls - expect(interaction.__count).toBe(0); - - // It should call other subscribers despite the earlier error - expect(secondSubscriber.onWorkStarted).toHaveBeenCalledTimes(2); - }); - - it('should cover onWorkStopped within wrap', done => { - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - const outerInteraction = Array.from( - SchedulerTracing.unstable_getCurrent(), - )[0]; - expect(outerInteraction.__count).toBe(1); - - let wrapped; - let innerInteraction; - - SchedulerTracing.unstable_trace(secondEvent.name, currentTime, () => { - innerInteraction = Array.from( - SchedulerTracing.unstable_getCurrent(), - )[1]; - expect(outerInteraction.__count).toBe(1); - expect(innerInteraction.__count).toBe(1); - - wrapped = SchedulerTracing.unstable_wrap(jest.fn()); - expect(outerInteraction.__count).toBe(2); - expect(innerInteraction.__count).toBe(2); - }); - - expect(outerInteraction.__count).toBe(2); - expect(innerInteraction.__count).toBe(1); - - throwInOnWorkStopped = true; - expect(wrapped).toThrow('Expected error onWorkStopped'); - throwInOnWorkStopped = false; - - // It should restore the previous interactions - expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([ - outerInteraction, - ]); - - // It should update the interaction count so as not to interfere with subsequent calls - expect(outerInteraction.__count).toBe(1); - expect(innerInteraction.__count).toBe(0); - - expect(secondSubscriber.onWorkStopped).toHaveBeenCalledTimes(2); - - done(); - }); - }); - - it('should cover the callback within wrap', done => { - expect(onWorkStarted).not.toHaveBeenCalled(); - expect(onWorkStopped).not.toHaveBeenCalled(); - - let wrapped; - let interaction; - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - wrapped = SchedulerTracing.unstable_wrap(() => { - throw Error('Expected error wrap'); - }); - }); - - expect(onWorkStarted).toHaveBeenCalledTimes(1); - expect(onWorkStopped).toHaveBeenCalledTimes(1); - - expect(wrapped).toThrow('Expected error wrap'); - - expect(onWorkStarted).toHaveBeenCalledTimes(2); - expect(onWorkStopped).toHaveBeenCalledTimes(2); - expect(onWorkStopped).toHaveBeenLastNotifiedOfWork([interaction]); - - done(); - }); - - it('should cover onWorkCanceled within wrap', () => { - let interaction, wrapped; - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - wrapped = SchedulerTracing.unstable_wrap(jest.fn()); - }); - expect(interaction.__count).toBe(1); - - throwInOnWorkCanceled = true; - expect(wrapped.cancel).toThrow('Expected error onWorkCanceled'); - - expect(onWorkCanceled).toHaveBeenCalledTimes(1); - - // It should update the interaction count so as not to interfere with subsequent calls - expect(interaction.__count).toBe(0); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(firstEvent); - - // It should call other subscribers despite the earlier error - expect(secondSubscriber.onWorkCanceled).toHaveBeenCalledTimes(1); - }); - }); - - it('calls lifecycle methods for trace', () => { - expect(onInteractionTraced).not.toHaveBeenCalled(); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - SchedulerTracing.unstable_trace( - firstEvent.name, - currentTime, - () => { - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - firstEvent, - ); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onWorkStarted).toHaveBeenCalledTimes(1); - expect(onWorkStarted).toHaveBeenLastNotifiedOfWork( - new Set([firstEvent]), - threadID, - ); - expect(onWorkStopped).not.toHaveBeenCalled(); - - SchedulerTracing.unstable_trace( - secondEvent.name, - currentTime, - () => { - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - secondEvent, - ); - expect( - onInteractionScheduledWorkCompleted, - ).not.toHaveBeenCalled(); - expect(onWorkStarted).toHaveBeenCalledTimes(2); - expect(onWorkStarted).toHaveBeenLastNotifiedOfWork( - new Set([firstEvent, secondEvent]), - threadID, - ); - expect(onWorkStopped).not.toHaveBeenCalled(); - }, - threadID, - ); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(secondEvent); - expect(onWorkStopped).toHaveBeenCalledTimes(1); - expect(onWorkStopped).toHaveBeenLastNotifiedOfWork( - new Set([firstEvent, secondEvent]), - threadID, - ); - }, - threadID, - ); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(firstEvent); - expect(onWorkScheduled).not.toHaveBeenCalled(); - expect(onWorkCanceled).not.toHaveBeenCalled(); - expect(onWorkStarted).toHaveBeenCalledTimes(2); - expect(onWorkStopped).toHaveBeenCalledTimes(2); - expect(onWorkStopped).toHaveBeenLastNotifiedOfWork( - new Set([firstEvent]), - threadID, - ); - }); - - it('calls lifecycle methods for wrap', () => { - const unwrapped = jest.fn(); - let wrapped; - - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - firstEvent, - ); - - SchedulerTracing.unstable_trace(secondEvent.name, currentTime, () => { - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - secondEvent, - ); - - wrapped = SchedulerTracing.unstable_wrap(unwrapped, threadID); - expect(onWorkScheduled).toHaveBeenCalledTimes(1); - expect(onWorkScheduled).toHaveBeenLastNotifiedOfWork( - new Set([firstEvent, secondEvent]), - threadID, - ); - }); - }); - - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - wrapped(); - expect(unwrapped).toHaveBeenCalled(); - - expect(onWorkScheduled).toHaveBeenCalledTimes(1); - expect(onWorkCanceled).not.toHaveBeenCalled(); - expect(onWorkStarted).toHaveBeenCalledTimes(3); - expect(onWorkStarted).toHaveBeenLastNotifiedOfWork( - new Set([firstEvent, secondEvent]), - threadID, - ); - expect(onWorkStopped).toHaveBeenCalledTimes(3); - expect(onWorkStopped).toHaveBeenLastNotifiedOfWork( - new Set([firstEvent, secondEvent]), - threadID, - ); - - expect( - onInteractionScheduledWorkCompleted.mock.calls[0][0], - ).toMatchInteraction(firstEvent); - expect( - onInteractionScheduledWorkCompleted.mock.calls[1][0], - ).toMatchInteraction(secondEvent); - }); - - it('should call the correct interaction subscriber methods when a wrapped callback is canceled', () => { - const fnOne = jest.fn(); - const fnTwo = jest.fn(); - let wrappedOne, wrappedTwo; - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - wrappedOne = SchedulerTracing.unstable_wrap(fnOne, threadID); - SchedulerTracing.unstable_trace(secondEvent.name, currentTime, () => { - wrappedTwo = SchedulerTracing.unstable_wrap(fnTwo, threadID); - }); - }); - - expect(onInteractionTraced).toHaveBeenCalledTimes(2); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onWorkCanceled).not.toHaveBeenCalled(); - expect(onWorkStarted).toHaveBeenCalledTimes(2); - expect(onWorkStopped).toHaveBeenCalledTimes(2); - - wrappedTwo.cancel(); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(secondEvent); - expect(onWorkCanceled).toHaveBeenCalledTimes(1); - expect(onWorkCanceled).toHaveBeenLastNotifiedOfWork( - new Set([firstEvent, secondEvent]), - threadID, - ); - - wrappedOne.cancel(); - - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(firstEvent); - expect(onWorkCanceled).toHaveBeenCalledTimes(2); - expect(onWorkCanceled).toHaveBeenLastNotifiedOfWork( - new Set([firstEvent]), - threadID, - ); - - expect(fnOne).not.toHaveBeenCalled(); - expect(fnTwo).not.toHaveBeenCalled(); - }); - - it('should not end an interaction twice if wrap is used to schedule follow up work within another wrap', () => { - const fnOne = jest.fn(() => { - wrappedTwo = SchedulerTracing.unstable_wrap(fnTwo, threadID); - }); - const fnTwo = jest.fn(); - let wrappedOne, wrappedTwo; - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - wrappedOne = SchedulerTracing.unstable_wrap(fnOne, threadID); - }); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - wrappedOne(); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - wrappedTwo(); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(firstEvent); - }); - - it('should not decrement the interaction count twice if a wrapped function is run twice', () => { - const unwrappedOne = jest.fn(); - const unwrappedTwo = jest.fn(); - let wrappedOne, wrappedTwo; - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => { - wrappedOne = SchedulerTracing.unstable_wrap(unwrappedOne, threadID); - wrappedTwo = SchedulerTracing.unstable_wrap(unwrappedTwo, threadID); - }); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - wrappedOne(); - - expect(unwrappedOne).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - wrappedOne(); - - expect(unwrappedOne).toHaveBeenCalledTimes(2); - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - - wrappedTwo(); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); - expect( - onInteractionScheduledWorkCompleted, - ).toHaveBeenLastNotifiedOfInteraction(firstEvent); - }); - - it('should unsubscribe', () => { - SchedulerTracing.unstable_unsubscribe(firstSubscriber); - SchedulerTracing.unstable_trace(firstEvent.name, currentTime, () => {}); - - expect(onInteractionTraced).not.toHaveBeenCalled(); - }); - }); - - describe('disabled', () => { - beforeEach(() => loadModules({enableSchedulerTracing: false})); - - // TODO - }); -}); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index c8c2375babc90..757d9ab28b784 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -51,9 +51,6 @@ export const enableProfilerNestedUpdatePhase = false; // This callback accepts the component type (class instance or function) the update is scheduled for. export const enableProfilerNestedUpdateScheduledHook = false; -// Trace which interactions trigger each commit. -export const enableSchedulerTracing = __PROFILE__; - // Track which Fiber(s) schedule render work. export const enableUpdaterTracking = __PROFILE__; diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 19c89f77f9f12..03b70aae07e6d 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -155,8 +155,6 @@ export type MutableSource> = {| // This doesn't require a value to be passed to either handler. export interface Wakeable { then(onFulfill: () => mixed, onReject: () => mixed): void | Wakeable; - // Special flag to opt out of tracing interactions across a Suspense boundary. - __reactDoNotTraceInteractions?: boolean; } // The subset of a Promise that React APIs rely on. This resolves a value. diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index e712288cf11c3..ea9224d5a9445 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -17,7 +17,6 @@ export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; export const enableProfilerNestedUpdateScheduledHook = false; -export const enableSchedulerTracing = __PROFILE__; export const enableUpdaterTracking = false; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 18375388be0a5..251bb310c1fb6 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -19,7 +19,6 @@ export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; export const enableProfilerNestedUpdateScheduledHook = false; -export const enableSchedulerTracing = __PROFILE__; export const enableUpdaterTracking = false; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index d7605e5ebb44e..aeee968922f12 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -19,7 +19,6 @@ export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; export const enableProfilerNestedUpdateScheduledHook = false; -export const enableSchedulerTracing = __PROFILE__; export const enableUpdaterTracking = false; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index e3e4ea0f58a46..88f2287cd81b4 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -19,7 +19,6 @@ export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; export const enableProfilerNestedUpdateScheduledHook = false; -export const enableSchedulerTracing = __PROFILE__; export const enableUpdaterTracking = false; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 4d762c08277c3..5d43505fa1279 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -19,7 +19,6 @@ export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; export const enableProfilerNestedUpdateScheduledHook = false; -export const enableSchedulerTracing = __PROFILE__; export const enableUpdaterTracking = false; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index bd4137d3e7cb0..0edbfad4ab170 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -19,7 +19,6 @@ export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; export const enableProfilerNestedUpdateScheduledHook = false; -export const enableSchedulerTracing = __PROFILE__; export const enableUpdaterTracking = false; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index dacb018e3bea6..98dbae4505e08 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -19,7 +19,6 @@ export const enableProfilerTimer = false; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; export const enableProfilerNestedUpdateScheduledHook = false; -export const enableSchedulerTracing = false; export const enableUpdaterTracking = false; export const enableSuspenseServerRenderer = true; export const enableSelectiveHydration = true; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 871d4cc48ff80..afae32b4082fb 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -55,7 +55,6 @@ export const enableSchedulingProfiler = // Note: we'll want to remove this when we to userland implementation. // For now, we'll turn it on for everyone because it's *already* on for everyone in practice. // At least this will let us stop shipping implementation to all users. -export const enableSchedulerTracing = true; export const enableSchedulerDebugging = true; export const warnAboutDeprecatedLifecycles = true; diff --git a/packages/shared/forks/SchedulerTracing.umd.js b/packages/shared/forks/SchedulerTracing.umd.js deleted file mode 100644 index 2fb835e95b89f..0000000000000 --- a/packages/shared/forks/SchedulerTracing.umd.js +++ /dev/null @@ -1,36 +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 - */ - -import * as React from 'react'; - -const ReactInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; - -const { - __interactionsRef, - __subscriberRef, - unstable_clear, - unstable_getCurrent, - unstable_getThreadID, - unstable_subscribe, - unstable_trace, - unstable_unsubscribe, - unstable_wrap, -} = ReactInternals.SchedulerTracing; - -export { - __interactionsRef, - __subscriberRef, - unstable_clear, - unstable_getCurrent, - unstable_getThreadID, - unstable_subscribe, - unstable_trace, - unstable_unsubscribe, - unstable_wrap, -}; diff --git a/scripts/jest/matchers/interactionTracingMatchers.js b/scripts/jest/matchers/interactionTracingMatchers.js deleted file mode 100644 index 8281d7d862eb5..0000000000000 --- a/scripts/jest/matchers/interactionTracingMatchers.js +++ /dev/null @@ -1,101 +0,0 @@ -'use strict'; - -const jestDiff = require('jest-diff').default; - -function toContainNoInteractions(actualSet) { - return { - message: () => - this.isNot - ? `Expected interactions but there were none.` - : `Expected no interactions but there were ${actualSet.size}.`, - pass: actualSet.size === 0, - }; -} - -function toHaveBeenLastNotifiedOfInteraction( - mockFunction, - expectedInteraction -) { - const calls = mockFunction.mock.calls; - if (calls.length === 0) { - return { - message: () => 'Mock function was not called', - pass: false, - }; - } - - const [actualInteraction] = calls[calls.length - 1]; - - return toMatchInteraction(actualInteraction, expectedInteraction); -} - -function toHaveBeenLastNotifiedOfWork( - mockFunction, - expectedInteractions, - expectedThreadID = undefined -) { - const calls = mockFunction.mock.calls; - if (calls.length === 0) { - return { - message: () => 'Mock function was not called', - pass: false, - }; - } - - const [actualInteractions, actualThreadID] = calls[calls.length - 1]; - - if (expectedThreadID !== undefined) { - if (expectedThreadID !== actualThreadID) { - return { - message: () => jestDiff(expectedThreadID + '', actualThreadID + ''), - pass: false, - }; - } - } - - return toMatchInteractions(actualInteractions, expectedInteractions); -} - -function toMatchInteraction(actual, expected) { - let attribute; - for (attribute in expected) { - if (actual[attribute] !== expected[attribute]) { - return { - message: () => jestDiff(expected, actual), - pass: false, - }; - } - } - - return {pass: true}; -} - -function toMatchInteractions(actualSetOrArray, expectedSetOrArray) { - const actualArray = Array.from(actualSetOrArray); - const expectedArray = Array.from(expectedSetOrArray); - - if (actualArray.length !== expectedArray.length) { - return { - message: () => - `Expected ${expectedArray.length} interactions but there were ${actualArray.length}`, - pass: false, - }; - } - - for (let i = 0; i < actualArray.length; i++) { - const result = toMatchInteraction(actualArray[i], expectedArray[i]); - if (result.pass === false) { - return result; - } - } - - return {pass: true}; -} - -module.exports = { - toContainNoInteractions, - toHaveBeenLastNotifiedOfInteraction, - toHaveBeenLastNotifiedOfWork, - toMatchInteraction, - toMatchInteractions, -}; diff --git a/scripts/jest/matchers/profilerMatchers.js b/scripts/jest/matchers/profilerMatchers.js index bc851ffd9e9e2..e69de29bb2d1d 100644 --- a/scripts/jest/matchers/profilerMatchers.js +++ b/scripts/jest/matchers/profilerMatchers.js @@ -1,72 +0,0 @@ -'use strict'; - -const jestDiff = require('jest-diff').default; - -function toHaveLastRenderedWithNoInteractions(onRenderMockFn) { - const calls = onRenderMockFn.mock.calls; - if (calls.length === 0) { - return { - message: () => 'Mock onRender function was not called', - pass: false, - }; - } -} - -function toHaveLastRenderedWithInteractions( - onRenderMockFn, - expectedInteractions -) { - const calls = onRenderMockFn.mock.calls; - if (calls.length === 0) { - return { - message: () => 'Mock onRender function was not called', - pass: false, - }; - } - - const lastCall = calls[calls.length - 1]; - const actualInteractions = lastCall[6]; - - return toMatchInteractions(actualInteractions, expectedInteractions); -} - -function toMatchInteraction(actual, expected) { - let attribute; - for (attribute in expected) { - if (actual[attribute] !== expected[attribute]) { - return { - message: () => jestDiff(expected, actual), - pass: false, - }; - } - } - - return {pass: true}; -} - -function toMatchInteractions(actualSetOrArray, expectedSetOrArray) { - const actualArray = Array.from(actualSetOrArray); - const expectedArray = Array.from(expectedSetOrArray); - - if (actualArray.length !== expectedArray.length) { - return { - message: () => - `Expected ${expectedArray.length} interactions but there were ${actualArray.length}`, - pass: false, - }; - } - - for (let i = 0; i < actualArray.length; i++) { - const result = toMatchInteraction(actualArray[i], expectedArray[i]); - if (result.pass === false) { - return result; - } - } - - return {pass: true}; -} - -module.exports = { - toHaveLastRenderedWithInteractions, - toHaveLastRenderedWithNoInteractions, -}; diff --git a/scripts/jest/setupTests.js b/scripts/jest/setupTests.js index 34f5633ea372e..abb358f817aa2 100644 --- a/scripts/jest/setupTests.js +++ b/scripts/jest/setupTests.js @@ -45,8 +45,6 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) { } expect.extend({ - ...require('./matchers/interactionTracingMatchers'), - ...require('./matchers/profilerMatchers'), ...require('./matchers/toWarnDev'), ...require('./matchers/reactTestMatchers'), }); diff --git a/scripts/jest/spec-equivalence-reporter/setupTests.js b/scripts/jest/spec-equivalence-reporter/setupTests.js index 997abd76fb052..31814bb754d95 100644 --- a/scripts/jest/spec-equivalence-reporter/setupTests.js +++ b/scripts/jest/spec-equivalence-reporter/setupTests.js @@ -46,8 +46,6 @@ global.spyOnProd = function(...args) { }; expect.extend({ - ...require('../matchers/interactionTracingMatchers'), - ...require('../matchers/profilerMatchers'), ...require('../matchers/toWarnDev'), ...require('../matchers/reactTestMatchers'), }); diff --git a/scripts/release/shared-commands/test-tracing-fixture.js b/scripts/release/shared-commands/test-tracing-fixture.js deleted file mode 100644 index 781e4001472ea..0000000000000 --- a/scripts/release/shared-commands/test-tracing-fixture.js +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const {join} = require('path'); -const puppeteer = require('puppeteer'); -const theme = require('../theme'); -const {logPromise} = require('../utils'); - -const validate = async ({cwd}) => { - const browser = await puppeteer.launch(); - const page = await browser.newPage(); - - await page.goto( - 'file://' + join(cwd, 'fixtures/tracing/index.html?puppeteer=true') - ); - - try { - return await page.evaluate(() => { - const button = document.getElementById('run-test-button'); - button.click(); - - const items = document.querySelectorAll('[data-value]'); - - if (items.length === 0) { - return 'No results were found.'; - } - - for (let i = 0; i < items.length; i++) { - const item = items[i]; - if (item.getAttribute('data-value') !== 'All checks pass') { - return `Unexpected result, "${item.getAttribute('data-value')}"`; - } - } - - return null; - }); - } finally { - await browser.close(); - } -}; - -const run = async ({cwd}) => { - const errorMessage = await logPromise( - validate({cwd}), - 'Verifying "scheduler/tracing" fixture' - ); - if (errorMessage) { - console.error( - theme.error('✗'), - 'Verifying "scheduler/tracing" fixture\n ', - theme.error(errorMessage) - ); - process.exit(1); - } -}; - -module.exports = run; diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 2890c5518104e..b34c0077873a6 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -805,24 +805,6 @@ const bundles = [ global: 'ReactFreshRuntime', externals: [], }, - - { - bundleTypes: [ - FB_WWW_DEV, - FB_WWW_PROD, - FB_WWW_PROFILING, - NODE_DEV, - NODE_PROD, - NODE_PROFILING, - RN_FB_DEV, - RN_FB_PROD, - RN_FB_PROFILING, - ], - moduleType: ISOMORPHIC, - entry: 'scheduler/tracing', - global: 'SchedulerTracing', - externals: [], - }, ]; // Based on deep-freeze by substack (public domain) diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js index 2c64c6219e35d..84211da5c5125 100644 --- a/scripts/rollup/forks.js +++ b/scripts/rollup/forks.js @@ -171,25 +171,6 @@ const forks = Object.freeze({ } }, - 'scheduler/tracing': (bundleType, entry, dependencies) => { - switch (bundleType) { - case UMD_DEV: - case UMD_PROD: - case UMD_PROFILING: - if (dependencies.indexOf('react') === -1) { - // It's only safe to use this fork for modules that depend on React, - // because they read the re-exported API from the SECRET_INTERNALS object. - return null; - } - // Optimization: for UMDs, use the API that is already a part of the React - // package instead of requiring it to be loaded via a separate