Skip to content

Commit

Permalink
act should work without mock Scheduler
Browse files Browse the repository at this point in the history
Updates `act` so that it works in Concurrent and Blocking Mode without
a mock Scheduler.
  • Loading branch information
acdlite committed Sep 8, 2020
1 parent a3a5877 commit 0c0db27
Show file tree
Hide file tree
Showing 10 changed files with 349 additions and 341 deletions.
118 changes: 74 additions & 44 deletions packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ afterEach(() => {
document.body.removeChild(container);
});

// @gate __DEV__
it('can use act to flush effects', () => {
function App() {
React.useEffect(() => {
Expand All @@ -62,6 +63,7 @@ it('can use act to flush effects', () => {
expect(clearYields()).toEqual([100]);
});

// @gate __DEV__
it('flushes effects on every call', () => {
function App() {
const [ctr, setCtr] = React.useState(0);
Expand Down Expand Up @@ -100,6 +102,7 @@ it('flushes effects on every call', () => {
expect(button.innerHTML).toEqual('5');
});

// @gate __DEV__
it("should keep flushing effects until they're done", () => {
function App() {
const [ctr, setCtr] = React.useState(0);
Expand All @@ -118,6 +121,7 @@ it("should keep flushing effects until they're done", () => {
expect(container.innerHTML).toEqual('5');
});

// @gate __DEV__
it('should flush effects only on exiting the outermost act', () => {
function App() {
React.useEffect(() => {
Expand All @@ -138,6 +142,7 @@ it('should flush effects only on exiting the outermost act', () => {
expect(clearYields()).toEqual([0]);
});

// @gate __DEV__
it('can handle cascading promises', async () => {
// this component triggers an effect, that waits a tick,
// then sets state. repeats this 5 times.
Expand Down

This file was deleted.

170 changes: 128 additions & 42 deletions packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,142 @@
* @emails react-core
*/

let React;
let ReactDOM;

function App() {
return null;
}

beforeEach(() => {
jest.resetModules();
jest.unmock('scheduler');
React = require('react');
ReactDOM = require('react-dom');

// Unmock the Scheduler, which is mocked by default in our test setup
jest.mock('scheduler', () => require.requireActual('scheduler'));
jest.mock('scheduler/src/SchedulerHostConfig', () =>
require.requireActual('scheduler/src/forks/SchedulerHostConfig.default.js'),
);
});

// @gate __DEV__
it('public implementation of `act` works without mocking scheduler (DOM)', async () => {
const React = require('react');
const ReactDOM = require('react-dom');
const TestUtils = require('react-dom/test-utils');

const log = [];

function App() {
React.useEffect(() => {
log.push('Did mount');
}, []);
return 'App';
}

const container = document.createElement('div');
await TestUtils.act(async () => {
ReactDOM.render(<App />, container);
});
expect(container.textContent).toEqual('App');
expect(log).toEqual(['Did mount']);
});

it('does not warn when rendering in legacy mode', () => {
expect(() => {
ReactDOM.render(<App />, document.createElement('div'));
}).toErrorDev([]);
it('internal implementation of `act` throws if Scheduler is not mocked (DOM)', async () => {
const React = require('react');
const ReactDOM = require('react-dom');
const TestUtils = require('react-dom/test-utils');

const log = [];

function App() {
React.useEffect(() => {
log.push('Did mount');
}, []);
return 'App';
}

const container = document.createElement('div');
let error = null;
try {
await TestUtils.unstable_concurrentAct(async () => {
ReactDOM.render(<App />, container);
});
} catch (e) {
error = e;
}
expect(error).not.toBe(null);
expect(error.message).toEqual(
'This version of `act` requires a special mock build of Scheduler.',
);
});

// @gate experimental
it('should warn when rendering in concurrent mode', () => {
expect(() => {
ReactDOM.unstable_createRoot(document.createElement('div')).render(<App />);
}).toErrorDev(
'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
'to guarantee consistent behaviour across tests and browsers.',
{withoutStack: true},
it('internal implementation of `act` throws if Scheduler is not mocked (noop)', async () => {
const React = require('react');
const ReactNoop = require('react-noop-renderer');

const log = [];

function App() {
React.useEffect(() => {
log.push('Did mount');
}, []);
return 'App';
}

const root = ReactNoop.createRoot();
let error = null;
try {
await ReactNoop.act(async () => {
root.render(<App />);
});
} catch (e) {
error = e;
}
expect(error).not.toBe(null);
expect(error.message).toEqual(
'This version of `act` requires a special mock build of Scheduler.',
);
// does not warn twice
expect(() => {
ReactDOM.unstable_createRoot(document.createElement('div')).render(<App />);
}).toErrorDev([]);
});

// @gate experimental
it('should warn when rendering in blocking mode', () => {
expect(() => {
ReactDOM.unstable_createBlockingRoot(document.createElement('div')).render(
<App />,
);
}).toErrorDev(
'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
'to guarantee consistent behaviour across tests and browsers.',
{withoutStack: true},
// @gate __DEV__
it('public implementation of `act` works without mocking scheduler (test renderer)', async () => {
const React = require('react');
const ReactTestRenderer = require('react-test-renderer');

const log = [];

function App() {
React.useEffect(() => {
log.push('Did mount');
}, []);
return 'App';
}

const root = ReactTestRenderer.create(null);
await ReactTestRenderer.act(async () => {
root.update(<App />);
});
expect(root.toJSON()).toEqual('App');
expect(log).toEqual(['Did mount']);
});

it('internal implementation of `act` throws if Scheduler is not mocked (test renderer)', async () => {
const React = require('react');
const ReactTestRenderer = require('react-test-renderer');

const log = [];

function App() {
React.useEffect(() => {
log.push('Did mount');
}, []);
return 'App';
}

const root = ReactTestRenderer.create(null);
let error = null;
try {
await ReactTestRenderer.unstable_concurrentAct(async () => {
root.update(<App />);
});
} catch (e) {
error = e;
}
expect(error).not.toBe(null);
expect(error.message).toEqual(
'This version of `act` requires a special mock build of Scheduler.',
);
// does not warn twice
expect(() => {
ReactDOM.unstable_createBlockingRoot(document.createElement('div')).render(
<App />,
);
}).toErrorDev([]);
});
1 change: 0 additions & 1 deletion packages/react-reconciler/src/ReactFiberReconciler.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,6 @@ export function updateContainer(
if (__DEV__) {
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
if ('undefined' !== typeof jest) {
warnIfUnmockedScheduler(current);
warnIfNotScopedWithMatchingAct(current);
}
}
Expand Down
2 changes: 0 additions & 2 deletions packages/react-reconciler/src/ReactFiberReconciler.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ import {
flushDiscreteUpdates,
flushPassiveEffects,
warnIfNotScopedWithMatchingAct,
warnIfUnmockedScheduler,
IsThisRendererActing,
act,
} from './ReactFiberWorkLoop.old';
Expand Down Expand Up @@ -261,7 +260,6 @@ export function updateContainer(
if (__DEV__) {
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
if ('undefined' !== typeof jest) {
warnIfUnmockedScheduler(current);
warnIfNotScopedWithMatchingAct(current);
}
}
Expand Down
Loading

0 comments on commit 0c0db27

Please sign in to comment.