Jest + test renderer helpers for concurrent mode (#13751)

* Jest + test renderer helpers for concurrent mode

Most of our concurrent React tests use the noop renderer. But most
of those tests don't test the renderer API, and could instead be
written with the test renderer. We should switch to using the test
renderer whenever possible, because that's what we expect product devs
and library authors to do. If test renderer is sufficient for writing
most React core tests, it should be sufficient for others, too. (The
converse isn't true but we should aim to dogfood test renderer as much
as possible.)

This PR adds a new package, jest-react (thanks @cpojer). I've moved
our existing Jest matchers into that package and added some new ones.

I'm not expecting to figure out the final API in this PR. My goal is
to land something good enough that we can start dogfooding in www.

TODO: Continue migrating Suspense tests, decide on better API names

* Add additional invariants to prevent common errors

- Errors if user attempts to flush when log of yields is not empty
- Throws if argument passed to toClearYields is not ReactTestRenderer

* Better method names

- toFlushAll -> toFlushAndYield
- toFlushAndYieldThrough ->
- toClearYields -> toHaveYielded

Also added toFlushWithoutYielding

* Fix jest-react exports

* Tweak README
This commit is contained in:
Andrew Clark
2018-10-03 18:37:41 -06:00
committed by GitHub
parent 7ba1e21dbc
commit bc1a29df20
15 changed files with 536 additions and 150 deletions
-57
View File
@@ -1,57 +0,0 @@
'use strict';
function captureAssertion(fn) {
// Trick to use a Jest matcher inside another Jest matcher. `fn` contains an
// assertion; if it throws, we capture the error and return it, so the stack
// trace presented to the user points to the original assertion in the
// test file.
try {
fn();
} catch (error) {
return {
pass: false,
message: () => error.message,
};
}
return {pass: true};
}
function toFlushAll(renderer, expectedYields) {
const actualYields = renderer.unstable_flushAll();
return captureAssertion(() => expect(actualYields).toEqual(expectedYields));
}
function toFlushThrough(renderer, expectedYields) {
const actualYields = renderer.unstable_flushNumberOfYields(
expectedYields.length
);
return captureAssertion(() => expect(actualYields).toEqual(expectedYields));
}
function toClearYields(ReactTestRenderer, expectedYields) {
const actualYields = ReactTestRenderer.unstable_clearYields();
return captureAssertion(() => expect(actualYields).toEqual(expectedYields));
}
function toFlushAndThrow(renderer, expectedYields, ...rest) {
return captureAssertion(() => {
try {
expect(() => {
renderer.unstable_flushAll();
}).toThrow(...rest);
} catch (error) {
const actualYields = renderer.unstable_clearYields();
expect(actualYields).toEqual(expectedYields);
throw error;
}
const actualYields = renderer.unstable_clearYields();
expect(actualYields).toEqual(expectedYields);
});
}
module.exports = {
toFlushAll,
toFlushThrough,
toFlushAndThrow,
toClearYields,
};
+8 -1
View File
@@ -43,10 +43,17 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
global.spyOnDevAndProd = spyOn;
}
const JestReact = require('jest-react');
expect.extend({
...require('./matchers/interactionTracing'),
...require('./matchers/toWarnDev'),
...require('./matchers/testRenderer'),
toFlushWithoutYielding: JestReact.toFlushWithoutYielding,
toFlushAndYield: JestReact.toFlushAndYield,
toFlushAndYieldThrough: JestReact.toFlushAndYieldThrough,
toHaveYielded: JestReact.toHaveYielded,
toFlushAndThrow: JestReact.toFlushAndThrow,
toMatchRenderedOutput: JestReact.toMatchRenderedOutput,
});
// We have a Babel transform that inserts guards against infinite loops.
@@ -45,10 +45,17 @@ global.spyOnProd = function(...args) {
}
};
const JestReact = require('jest-react');
expect.extend({
...require('../matchers/interactionTracing'),
...require('../matchers/toWarnDev'),
...require('../matchers/testRenderer'),
toFlushWithoutYielding: JestReact.toFlushWithoutYielding,
toFlushAndYield: JestReact.toFlushAndYield,
toFlushAndYieldThrough: JestReact.toFlushAndYieldThrough,
toHaveYielded: JestReact.toHaveYielded,
toFlushAndThrow: JestReact.toFlushAndThrow,
toMatchRenderedOutput: JestReact.toMatchRenderedOutput,
});
beforeEach(() => (numExpectations = 0));
+10
View File
@@ -400,6 +400,16 @@ const bundles = [
externals: [],
},
/******* Jest React (experimental) *******/
{
label: 'jest-react',
bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD],
moduleType: ISOMORPHIC,
entry: 'jest-react',
global: 'JestReact',
externals: ['jest-diff'],
},
{
label: 'scheduler-tracing',
bundleTypes: [
+4 -1
View File
@@ -1,4 +1,7 @@
# Don't validate noop renderer bundle.
# Unlike others, it currently uses ES6 syntax (generators).
# We also don't use or publish it.
**/react-noop-renderer/**
**/react-noop-renderer/**
JestReact-prod.js
JestReact-dev.js
**/jest-react/**