Skip to content

Commit

Permalink
Run Jest in production mode (#11616)
Browse files Browse the repository at this point in the history
* Move Jest setup files to /dev/ subdirectory

* Clone Jest /dev/ files into /prod/

* Move shared code into scripts/jest

* Move Jest config into the scripts folder

* Fix the equivalence test

It fails because the config is now passed to Jest explicitly.
But the test doesn't know about the config.

To fix this, we just run it via `yarn test` (which includes the config).
We already depend on Yarn for development anyway.

* Add yarn test-prod to run Jest with production environment

* Actually flip the production tests to run in prod environment

This produces a bunch of errors:

Test Suites: 64 failed, 58 passed, 122 total
Tests:       740 failed, 26 skipped, 1809 passed, 2575 total
Snapshots:   16 failed, 4 passed, 20 total

* Ignore expectDev() calls in production

Down from 740 to 175 failed.

Test Suites: 44 failed, 78 passed, 122 total
Tests:       175 failed, 26 skipped, 2374 passed, 2575 total
Snapshots:   16 failed, 4 passed, 20 total

* Decode errors so tests can assert on their messages

Down from 175 to 129.

Test Suites: 33 failed, 89 passed, 122 total
Tests:       129 failed, 1029 skipped, 1417 passed, 2575 total
Snapshots:   16 failed, 4 passed, 20 total

* Remove ReactDOMProduction-test

There is no need for it now. The only test that was special is moved into ReactDOM-test.

* Remove production switches from ReactErrorUtils

The tests now run in production in a separate pass.

* Add and use spyOnDev() for warnings

This ensures that by default we expect no warnings in production bundles.
If the warning *is* expected, use the regular spyOn() method.

This currently breaks all expectDev() assertions without __DEV__ blocks so we go back to:

Test Suites: 56 failed, 65 passed, 121 total
Tests:       379 failed, 1029 skipped, 1148 passed, 2556 total
Snapshots:   16 failed, 4 passed, 20 total

* Replace expectDev() with expect() in __DEV__ blocks

We started using spyOnDev() for console warnings to ensure we don't *expect* them to occur in production. As a consequence, expectDev() assertions on console.error.calls fail because console.error.calls doesn't exist. This is actually good because it would help catch accidental warnings in production.

To solve this, we are getting rid of expectDev() altogether, and instead introduce explicit expectation branches. We'd need them anyway for testing intentional behavior differences.

This commit replaces all expectDev() calls with expect() calls in __DEV__ blocks. It also removes a few unnecessary expect() checks that no warnings were produced (by also removing the corresponding spyOnDev() calls).

Some DEV-only assertions used plain expect(). Those were also moved into __DEV__ blocks.

ReactFiberErrorLogger was special because it console.error()'s in production too. So in that case I intentionally used spyOn() instead of spyOnDev(), and added extra assertions.

This gets us down to:

Test Suites: 21 failed, 100 passed, 121 total
Tests:       72 failed, 26 skipped, 2458 passed, 2556 total
Snapshots:   16 failed, 4 passed, 20 total

* Enable User Timing API for production testing

We could've disabled it, but seems like a good idea to test since we use it at FB.

* Test for explicit Object.freeze() differences between PROD and DEV

This is one of the few places where DEV and PROD behavior differs for performance reasons.
Now we explicitly test both branches.

* Run Jest via "yarn test" on CI

* Remove unused variable

* Assert different error messages

* Fix error handling tests

This logic is really complicated because of the global ReactFiberErrorLogger mock.
I understand it now, so I added TODOs for later.

It can be much simpler if we change the rest of the tests that assert uncaught errors to also assert they are logged as warnings.
Which mirrors what happens in practice anyway.

* Fix more assertions

* Change tests to document the DEV/PROD difference for state invariant

It is very likely unintentional but I don't want to change behavior in this PR.
Filed a follow up as facebook/react#11618.

* Remove unnecessary split between DEV/PROD ref tests

* Fix more test message assertions

* Make validateDOMNesting tests DEV-only

* Fix error message assertions

* Document existing DEV/PROD message difference (possible bug)

* Change mocking assertions to be DEV-only

* Fix the error code test

* Fix more error message assertions

* Fix the last failing test due to known issue

* Run production tests on CI

* Unify configuration

* Fix coverage script

* Remove expectDev from eslintrc

* Run everything in band

We used to before, too. I just forgot to add the arguments after deleting the script.
  • Loading branch information
gaearon committed Nov 22, 2017
1 parent 4020657 commit c8e0aa3
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 29 deletions.
48 changes: 27 additions & 21 deletions src/__tests__/ReactShallowRenderer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ describe('ReactShallowRenderer', () => {
});

it('can fail context when shallowly rendering', () => {
spyOn(console, 'error');
spyOnDev(console, 'error');

class SimpleComponent extends React.Component {
static contextTypes = {
Expand All @@ -784,18 +784,20 @@ describe('ReactShallowRenderer', () => {

const shallowRenderer = createRenderer();
shallowRenderer.render(<SimpleComponent />);
expectDev(console.error.calls.count()).toBe(1);
expect(
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
).toBe(
'Warning: Failed context type: The context `name` is marked as ' +
'required in `SimpleComponent`, but its value is `undefined`.\n' +
' in SimpleComponent (at **)',
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
).toBe(
'Warning: Failed context type: The context `name` is marked as ' +
'required in `SimpleComponent`, but its value is `undefined`.\n' +
' in SimpleComponent (at **)',
);
}
});

it('should warn about propTypes (but only once)', () => {
spyOn(console, 'error');
spyOnDev(console, 'error');

class SimpleComponent extends React.Component {
render() {
Expand All @@ -810,14 +812,16 @@ describe('ReactShallowRenderer', () => {
const shallowRenderer = createRenderer();
shallowRenderer.render(React.createElement(SimpleComponent, {name: 123}));

expect(console.error.calls.count()).toBe(1);
expect(
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
).toBe(
'Warning: Failed prop type: Invalid prop `name` of type `number` ' +
'supplied to `SimpleComponent`, expected `string`.\n' +
' in SimpleComponent',
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
).toBe(
'Warning: Failed prop type: Invalid prop `name` of type `number` ' +
'supplied to `SimpleComponent`, expected `string`.\n' +
' in SimpleComponent',
);
}
});

it('should enable rendering of cloned element', () => {
Expand Down Expand Up @@ -846,7 +850,7 @@ describe('ReactShallowRenderer', () => {
});

it('throws usefully when rendering badly-typed elements', () => {
spyOn(console, 'error');
spyOnDev(console, 'error');
const shallowRenderer = createRenderer();

var Undef = undefined;
Expand All @@ -873,7 +877,9 @@ describe('ReactShallowRenderer', () => {
'components, but the provided element type was `object`.',
);

// One warning for each element creation
expectDev(console.error.calls.count()).toBe(4);
if (__DEV__) {
// One warning for each element creation
expect(console.error.calls.count()).toBe(4);
}
});
});
18 changes: 10 additions & 8 deletions src/__tests__/ReactTestRenderer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ describe('ReactTestRenderer', () => {
});

it('warns correctly for refs on SFCs', () => {
spyOn(console, 'error');
spyOnDev(console, 'error');
function Bar() {
return <div>Hello, world</div>;
}
Expand All @@ -279,13 +279,15 @@ describe('ReactTestRenderer', () => {
}
ReactTestRenderer.create(<Baz />);
ReactTestRenderer.create(<Foo />);
expectDev(console.error.calls.count()).toBe(1);
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: Stateless function components cannot be given refs. Attempts ' +
'to access this ref will fail.\n\nCheck the render method of `Foo`.\n' +
' in Bar (at **)\n' +
' in Foo (at **)',
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: Stateless function components cannot be given refs. Attempts ' +
'to access this ref will fail.\n\nCheck the render method of `Foo`.\n' +
' in Bar (at **)\n' +
' in Foo (at **)',
);
}
});

it('allows an optional createNodeMock function', () => {
Expand Down

0 comments on commit c8e0aa3

Please sign in to comment.