From bf443f21b439223142ed8723718b57d94f824ee0 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Thu, 16 May 2019 17:12:36 +0100 Subject: [PATCH] `act()` - s / flushPassiveEffects / Scheduler.unstable_flushWithoutYielding (#15591) * s/flushPassiveEffects/unstable_flushWithoutYielding a first crack at flushing the scheduler manually from inside act(). uses unstable_flushWithoutYielding(). The tests that changed, mostly replaced toFlushAndYield(...) with toHaveYielded(). For some tests that tested the state of the tree before flushing effects (but still after updates), I replaced act() with bacthedUpdates(). * ugh lint * pass build, flushPassiveEffects returns nothing now * pass test-fire * flush all work (not just effects), add a compatibility mode of note, unstable_flushWithoutYielding now returns a boolean much like flushPassiveEffects * umd build for scheduler/unstable_mock, pass the fixture with it * add a comment to Shcduler.umd.js for why we're exporting unstable_flushWithoutYielding * run testsutilsact tests in both sync/concurrent modes * augh lint * use a feature flag for the missing mock scheduler warning I also tried writing a test for it, but couldn't get the scheduler to unmock. included the failing test. * Update ReactTestUtilsAct-test.js - pass the mock scheduler warning test, - rewrite some tests to use Scheduler.yieldValue - structure concurrent/legacy suites neatly * pass failing tests in batchedmode-test * fix pretty/lint/import errors * pass test-build * nit: pull .create(null) out of the act() call --- src/ReactTestRendererAct.js | 42 +++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/ReactTestRendererAct.js b/src/ReactTestRendererAct.js index 37ced3f..a3d1e4e 100644 --- a/src/ReactTestRendererAct.js +++ b/src/ReactTestRendererAct.js @@ -14,23 +14,42 @@ import { } from 'react-reconciler/inline.test'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import warningWithoutStack from 'shared/warningWithoutStack'; +import {warnAboutMissingMockScheduler} from 'shared/ReactFeatureFlags'; import enqueueTask from 'shared/enqueueTask'; +import * as Scheduler from 'scheduler'; const {ReactShouldWarnActingUpdates} = ReactSharedInternals; // this implementation should be exactly the same in // ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js -// we track the 'depth' of the act() calls with this counter, -// so we can tell if any async act() calls try to run in parallel. -let actingUpdatesScopeDepth = 0; +let hasWarnedAboutMissingMockScheduler = false; +const flushWork = + Scheduler.unstable_flushWithoutYielding || + function() { + if (warnAboutMissingMockScheduler === true) { + if (hasWarnedAboutMissingMockScheduler === false) { + warningWithoutStack( + null, + 'Starting from React v17, the "scheduler" module will need to be mocked ' + + 'to guarantee consistent behaviour across tests and browsers. To fix this, add the following ' + + "to the top of your tests, or in your framework's global config file -\n\n" + + 'As an example, for jest - \n' + + "jest.mock('scheduler', () => require.requireActual('scheduler/unstable_mock'));\n\n" + + 'For more info, visit https://fb.me/react-mock-scheduler', + ); + hasWarnedAboutMissingMockScheduler = true; + } + } + while (flushPassiveEffects()) {} + }; -function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) { +function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) { try { - flushPassiveEffects(); + flushWork(); enqueueTask(() => { - if (flushPassiveEffects()) { - flushEffectsAndMicroTasks(onDone); + if (flushWork()) { + flushWorkAndMicroTasks(onDone); } else { onDone(); } @@ -40,6 +59,11 @@ function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) { } } +// we track the 'depth' of the act() calls with this counter, +// so we can tell if any async act() calls try to run in parallel. + +let actingUpdatesScopeDepth = 0; + function act(callback: () => Thenable) { let previousActingUpdatesScopeDepth; if (__DEV__) { @@ -100,7 +124,7 @@ function act(callback: () => Thenable) { called = true; result.then( () => { - flushEffectsAndMicroTasks((err: ?Error) => { + flushWorkAndMicroTasks((err: ?Error) => { onDone(); if (err) { reject(err); @@ -128,7 +152,7 @@ function act(callback: () => Thenable) { // flush effects until none remain, and cleanup try { - while (flushPassiveEffects()) {} + flushWork(); onDone(); } catch (err) { onDone();