From 5cff9a16b6fb986642d878d4e758f8803062cdd5 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 22 Nov 2017 13:02:26 +0000 Subject: [PATCH] Run Jest in production mode (#11616) * 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 https://github.com/facebook/react/issues/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. --- .eslintrc.js | 2 +- package.json | 4 +- .../__tests__/CSSPropertyOperations-test.js | 136 +- .../__tests__/DOMPropertyOperations-test.js | 12 +- .../src/__tests__/EventPluginHub-test.js | 12 +- .../ReactBrowserEventEmitter-test.js | 6 +- .../__tests__/ReactChildReconciler-test.js | 96 +- .../src/__tests__/ReactComponent-test.js | 210 ++-- .../__tests__/ReactComponentLifeCycle-test.js | 70 +- .../__tests__/ReactCompositeComponent-test.js | 265 ++-- .../ReactCompositeComponentState-test.js | 36 +- .../react-dom/src/__tests__/ReactDOM-test.js | 90 +- .../src/__tests__/ReactDOMAttribute-test.js | 100 +- .../src/__tests__/ReactDOMComponent-test.js | 1109 ++++++++++------- .../__tests__/ReactDOMComponentTree-test.js | 62 +- .../src/__tests__/ReactDOMFiber-test.js | 108 +- .../src/__tests__/ReactDOMInput-test.js | 390 +++--- .../__tests__/ReactDOMInvalidARIAHook-test.js | 76 +- .../src/__tests__/ReactDOMOption-test.js | 26 +- .../src/__tests__/ReactDOMProduction-test.js | 361 ------ .../src/__tests__/ReactDOMRoot-test.js | 14 +- .../src/__tests__/ReactDOMSelect-test.js | 62 +- .../ReactDOMServerIntegration-test.js | 65 +- .../src/__tests__/ReactDOMTextarea-test.js | 76 +- .../__tests__/ReactErrorBoundaries-test.js | 2 +- .../__tests__/ReactMockedComponent-test.js | 38 +- .../src/__tests__/ReactMount-test.js | 108 +- .../__tests__/ReactMountDestruction-test.js | 36 +- .../src/__tests__/ReactMultiChild-test.js | 82 +- .../src/__tests__/ReactMultiChildText-test.js | 18 +- .../src/__tests__/ReactRenderDocument-test.js | 88 +- .../__tests__/ReactServerRendering-test.js | 158 ++- .../__tests__/ReactStatelessComponent-test.js | 197 +-- .../src/__tests__/ReactTestUtils-test.js | 3 - .../src/__tests__/ReactUpdates-test.js | 80 +- packages/react-dom/src/__tests__/refs-test.js | 59 +- .../renderSubtreeIntoContainer-test.js | 14 +- .../src/__tests__/validateDOMNesting-test.js | 60 +- .../events/__tests__/SyntheticEvent-test.js | 132 +- .../src/__tests__/ReactFragment-test.js | 26 +- .../ReactIncrementalErrorHandling-test.js | 177 ++- .../__tests__/ReactIncrementalPerf-test.js | 2 + .../ReactIncrementalSideEffects-test.js | 20 +- .../__tests__/ReactIncrementalUpdates-test.js | 22 +- .../__tests__/ReactShallowRenderer-test.js | 48 +- .../src/__tests__/ReactTestRenderer-test.js | 18 +- .../react/src/__tests__/ReactChildren-test.js | 72 +- .../__tests__/ReactClassEquivalence-test.js | 5 +- .../ReactCoffeeScriptClass-test.coffee | 105 +- .../__tests__/ReactContextValidator-test.js | 116 +- .../react/src/__tests__/ReactES6Class-test.js | 116 +- .../react/src/__tests__/ReactElement-test.js | 156 ++- .../src/__tests__/ReactElementClone-test.js | 58 +- .../__tests__/ReactElementValidator-test.js | 350 +++--- .../src/__tests__/ReactJSXElement-test.js | 17 +- .../ReactJSXElementValidator-test.js | 298 +++-- .../src/__tests__/ReactPureComponent-test.js | 36 +- .../__tests__/ReactTypeScriptClass-test.ts | 144 ++- .../createReactClassIntegration-test.js | 154 ++- .../__tests__/ReactDOMFrameScheduling-test.js | 12 +- .../shared/__tests__/ReactErrorUtils-test.js | 368 +++--- .../__tests__/reactProdInvariant-test.js | 18 + scripts/circleci/test_coverage.sh | 5 +- scripts/circleci/test_entry_point.sh | 6 +- scripts/jest/environment.js | 8 +- scripts/jest/jest.d.ts | 2 + scripts/jest/preprocessor.js | 2 - scripts/jest/setupSpecEquivalenceReporter.js | 6 + scripts/jest/test-framework-setup.js | 56 +- scripts/tasks/jest.js | 31 - yarn.lock | 13 +- 71 files changed, 3707 insertions(+), 3223 deletions(-) delete mode 100644 packages/react-dom/src/__tests__/ReactDOMProduction-test.js delete mode 100644 scripts/tasks/jest.js diff --git a/.eslintrc.js b/.eslintrc.js index 3938a6cfe531b..ac446e7258025 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -59,6 +59,6 @@ module.exports = { }, globals: { - expectDev: true, + spyOnDev: true, }, }; diff --git a/package.json b/package.json index 901af480d2aba..97d5ef1906bc5 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "core-js": "^2.2.1", "coveralls": "^2.11.6", "create-react-class": "^15.6.2", + "cross-env": "^5.1.1", "del": "^2.0.2", "derequire": "^2.0.3", "escape-string-regexp": "^1.0.5", @@ -102,7 +103,8 @@ "linc": "node ./scripts/tasks/linc.js", "lint": "node ./scripts/tasks/eslint.js", "postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json", - "test": "jest", + "test": "cross-env NODE_ENV=development jest", + "test-prod": "cross-env NODE_ENV=production jest", "flow": "node ./scripts/tasks/flow.js", "prettier": "node ./scripts/prettier/index.js write-changed", "prettier-all": "node ./scripts/prettier/index.js write", diff --git a/packages/react-dom/src/__tests__/CSSPropertyOperations-test.js b/packages/react-dom/src/__tests__/CSSPropertyOperations-test.js index 81b3efe48f76a..10190db51ef9e 100644 --- a/packages/react-dom/src/__tests__/CSSPropertyOperations-test.js +++ b/packages/react-dom/src/__tests__/CSSPropertyOperations-test.js @@ -91,15 +91,17 @@ describe('CSSPropertyOperations', () => { } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var root = document.createElement('div'); ReactDOM.render(, root); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( - 'Warning: Unsupported style property background-color. Did you mean backgroundColor?' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + 'Warning: Unsupported style property background-color. Did you mean backgroundColor?' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + } }); it('should warn when updating hyphenated style names', () => { @@ -111,7 +113,7 @@ describe('CSSPropertyOperations', () => { } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var styles = { '-ms-transform': 'translate3d(0, 0, 0)', '-webkit-transform': 'translate3d(0, 0, 0)', @@ -120,17 +122,19 @@ describe('CSSPropertyOperations', () => { ReactDOM.render(, root); ReactDOM.render(, root); - expectDev(console.error.calls.count()).toBe(2); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( - 'Warning: Unsupported style property -ms-transform. Did you mean msTransform?' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual( - 'Warning: Unsupported style property -webkit-transform. Did you mean WebkitTransform?' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(2); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + 'Warning: Unsupported style property -ms-transform. Did you mean msTransform?' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual( + 'Warning: Unsupported style property -webkit-transform. Did you mean WebkitTransform?' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + } }); it('warns when miscapitalizing vendored style names', () => { @@ -150,23 +154,25 @@ describe('CSSPropertyOperations', () => { } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var root = document.createElement('div'); ReactDOM.render(, root); - // msTransform is correct already and shouldn't warn - expectDev(console.error.calls.count()).toBe(2); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( - 'Warning: Unsupported vendor-prefixed style property oTransform. ' + - 'Did you mean OTransform?' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual( - 'Warning: Unsupported vendor-prefixed style property webkitTransform. ' + - 'Did you mean WebkitTransform?' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); + if (__DEV__) { + // msTransform is correct already and shouldn't warn + expect(console.error.calls.count()).toBe(2); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + 'Warning: Unsupported vendor-prefixed style property oTransform. ' + + 'Did you mean OTransform?' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual( + 'Warning: Unsupported vendor-prefixed style property webkitTransform. ' + + 'Did you mean WebkitTransform?' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + } }); it('should warn about style having a trailing semicolon', () => { @@ -187,22 +193,24 @@ describe('CSSPropertyOperations', () => { } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var root = document.createElement('div'); ReactDOM.render(, root); - expectDev(console.error.calls.count()).toBe(2); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( - "Warning: Style property values shouldn't contain a semicolon. " + - 'Try "backgroundColor: blue" instead.' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual( - "Warning: Style property values shouldn't contain a semicolon. " + - 'Try "color: red" instead.' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(2); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + "Warning: Style property values shouldn't contain a semicolon. " + + 'Try "backgroundColor: blue" instead.' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual( + "Warning: Style property values shouldn't contain a semicolon. " + + 'Try "color: red" instead.' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + } }); it('should warn about style containing a NaN value', () => { @@ -214,16 +222,18 @@ describe('CSSPropertyOperations', () => { } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var root = document.createElement('div'); ReactDOM.render(, root); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( - 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + } }); it('should not warn when setting CSS custom properties', () => { @@ -246,16 +256,18 @@ describe('CSSPropertyOperations', () => { } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var root = document.createElement('div'); ReactDOM.render(, root); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( - 'Warning: `Infinity` is an invalid value for the `fontSize` css style property.' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + 'Warning: `Infinity` is an invalid value for the `fontSize` css style property.' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + } }); it('should not add units to CSS custom properties', () => { diff --git a/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js b/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js index 7f544506a5f35..343adc05318d5 100644 --- a/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js +++ b/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js @@ -151,7 +151,7 @@ describe('DOMPropertyOperations', () => { it('should not remove attributes for special properties', () => { var container = document.createElement('div'); - spyOn(console, 'error'); + spyOnDev(console, 'error'); ReactDOM.render( , container, @@ -164,10 +164,12 @@ describe('DOMPropertyOperations', () => { ); expect(container.firstChild.getAttribute('value')).toBe('foo'); expect(container.firstChild.value).toBe('foo'); - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing a controlled input of type text to be uncontrolled', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'A component is changing a controlled input of type text to be uncontrolled', + ); + } }); }); }); diff --git a/packages/react-dom/src/__tests__/EventPluginHub-test.js b/packages/react-dom/src/__tests__/EventPluginHub-test.js index eaf40602ab6b6..ec1f83d0ee821 100644 --- a/packages/react-dom/src/__tests__/EventPluginHub-test.js +++ b/packages/react-dom/src/__tests__/EventPluginHub-test.js @@ -22,7 +22,7 @@ describe('EventPluginHub', () => { }); it('should prevent non-function listeners, at dispatch', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var node = ReactTestUtils.renderIntoDocument(
, ); @@ -31,10 +31,12 @@ describe('EventPluginHub', () => { }).toThrowError( 'Expected `onClick` listener to be a function, instead got a value of `string` type.', ); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Expected `onClick` listener to be a function, instead got a value of `string` type.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Expected `onClick` listener to be a function, instead got a value of `string` type.', + ); + } }); it('should not prevent null listeners, at dispatch', () => { diff --git a/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js b/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js index 647109a907b3d..cd4bff6102660 100644 --- a/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js +++ b/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js @@ -285,13 +285,15 @@ describe('ReactBrowserEventEmitter', () => { putListener(CHILD, ON_CLICK_KEY, recordIDAndReturnFalse.bind(null, CHILD)); putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT)); putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT)); - spyOn(console, 'error'); + spyOnDev(console, 'error'); ReactTestUtils.Simulate.click(CHILD); expect(idCallOrder.length).toBe(3); expect(idCallOrder[0]).toBe(CHILD); expect(idCallOrder[1]).toBe(PARENT); expect(idCallOrder[2]).toBe(GRANDPARENT); - expectDev(console.error.calls.count()).toEqual(0); + if (__DEV__) { + expect(console.error.calls.count()).toEqual(0); + } }); /** diff --git a/packages/react-dom/src/__tests__/ReactChildReconciler-test.js b/packages/react-dom/src/__tests__/ReactChildReconciler-test.js index 09fa3d436e124..9bbf9176cf69a 100644 --- a/packages/react-dom/src/__tests__/ReactChildReconciler-test.js +++ b/packages/react-dom/src/__tests__/ReactChildReconciler-test.js @@ -46,7 +46,7 @@ describe('ReactChildReconciler', () => { } it('warns for duplicated array keys', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { render() { @@ -56,17 +56,19 @@ describe('ReactChildReconciler', () => { ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Keys should be unique so that components maintain their identity ' + - 'across updates. Non-unique keys may cause children to be ' + - 'duplicated and/or omitted — the behavior is unsupported and ' + - 'could change in a future version.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Keys should be unique so that components maintain their identity ' + + 'across updates. Non-unique keys may cause children to be ' + + 'duplicated and/or omitted — the behavior is unsupported and ' + + 'could change in a future version.', + ); + } }); it('warns for duplicated array keys with component stack info', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { render() { @@ -88,24 +90,24 @@ describe('ReactChildReconciler', () => { ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev( - normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]), - ).toContain( - 'Encountered two children with the same key, `1`. ' + - 'Keys should be unique so that components maintain their identity ' + - 'across updates. Non-unique keys may cause children to be ' + - 'duplicated and/or omitted — the behavior is unsupported and ' + - 'could change in a future version.', - ' in div (at **)\n' + - ' in Component (at **)\n' + - ' in Parent (at **)\n' + - ' in GrandParent (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain( + 'Encountered two children with the same key, `1`. ' + + 'Keys should be unique so that components maintain their identity ' + + 'across updates. Non-unique keys may cause children to be ' + + 'duplicated and/or omitted — the behavior is unsupported and ' + + 'could change in a future version.', + ' in div (at **)\n' + + ' in Component (at **)\n' + + ' in Parent (at **)\n' + + ' in GrandParent (at **)', + ); + } }); it('warns for duplicated iterable keys', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { render() { @@ -115,17 +117,19 @@ describe('ReactChildReconciler', () => { ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Keys should be unique so that components maintain their identity ' + - 'across updates. Non-unique keys may cause children to be ' + - 'duplicated and/or omitted — the behavior is unsupported and ' + - 'could change in a future version.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Keys should be unique so that components maintain their identity ' + + 'across updates. Non-unique keys may cause children to be ' + + 'duplicated and/or omitted — the behavior is unsupported and ' + + 'could change in a future version.', + ); + } }); it('warns for duplicated iterable keys with component stack info', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { render() { @@ -147,19 +151,19 @@ describe('ReactChildReconciler', () => { ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev( - normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]), - ).toContain( - 'Encountered two children with the same key, `1`. ' + - 'Keys should be unique so that components maintain their identity ' + - 'across updates. Non-unique keys may cause children to be ' + - 'duplicated and/or omitted — the behavior is unsupported and ' + - 'could change in a future version.', - ' in div (at **)\n' + - ' in Component (at **)\n' + - ' in Parent (at **)\n' + - ' in GrandParent (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain( + 'Encountered two children with the same key, `1`. ' + + 'Keys should be unique so that components maintain their identity ' + + 'across updates. Non-unique keys may cause children to be ' + + 'duplicated and/or omitted — the behavior is unsupported and ' + + 'could change in a future version.', + ' in div (at **)\n' + + ' in Component (at **)\n' + + ' in Parent (at **)\n' + + ' in GrandParent (at **)', + ); + } }); }); diff --git a/packages/react-dom/src/__tests__/ReactComponent-test.js b/packages/react-dom/src/__tests__/ReactComponent-test.js index 0a63915429c2a..1f7da05821317 100644 --- a/packages/react-dom/src/__tests__/ReactComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactComponent-test.js @@ -45,13 +45,23 @@ describe('ReactComponent', () => { }).toThrow(); }); - it('should warn when children are mutated during render', () => { - spyOn(console, 'error'); + it('should throw (in dev) when children are mutated during render', () => { + spyOnDev(console, 'error'); function Wrapper(props) { props.children[1] =

; // Mutation is illegal return

{props.children}
; } - expect(() => { + if (__DEV__) { + expect(() => { + ReactTestUtils.renderIntoDocument( + + + + + , + ); + }).toThrowError(/Cannot assign to read only property.*/); + } else { ReactTestUtils.renderIntoDocument( @@ -59,11 +69,11 @@ describe('ReactComponent', () => { , ); - }).toThrowError(/Cannot assign to read only property.*/); + } }); - it('should warn when children are mutated during update', () => { - spyOn(console, 'error'); + it('should throw (in dev) when children are mutated during update', () => { + spyOnDev(console, 'error'); class Wrapper extends React.Component { componentDidMount() { @@ -76,7 +86,17 @@ describe('ReactComponent', () => { } } - expect(() => { + if (__DEV__) { + expect(() => { + ReactTestUtils.renderIntoDocument( + + + + + , + ); + }).toThrowError(/Cannot assign to read only property.*/); + } else { ReactTestUtils.renderIntoDocument( @@ -84,7 +104,7 @@ describe('ReactComponent', () => { , ); - }).toThrowError(/Cannot assign to read only property.*/); + } }); it('should support refs on owned components', () => { @@ -335,14 +355,16 @@ describe('ReactComponent', () => { }); it('throws usefully when rendering badly-typed elements', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var X = undefined; expect(() => ReactTestUtils.renderIntoDocument()).toThrowError( 'Element type is invalid: expected a string (for built-in components) ' + - 'or a class/function (for composite components) but got: undefined. ' + - "You likely forgot to export your component from the file it's " + - 'defined in, or you might have mixed up default and named imports.', + 'or a class/function (for composite components) but got: undefined.' + + (__DEV__ + ? " You likely forgot to export your component from the file it's " + + 'defined in, or you might have mixed up default and named imports.' + : ''), ); var Y = null; @@ -351,12 +373,14 @@ describe('ReactComponent', () => { 'or a class/function (for composite components) but got: null.', ); - // One warning for each element creation - expectDev(console.error.calls.count()).toBe(2); + if (__DEV__) { + // One warning for each element creation + expect(console.error.calls.count()).toBe(2); + } }); it('includes owner name in the error about badly-typed elements', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var X = undefined; @@ -378,14 +402,18 @@ describe('ReactComponent', () => { expect(() => ReactTestUtils.renderIntoDocument()).toThrowError( 'Element type is invalid: expected a string (for built-in components) ' + - 'or a class/function (for composite components) but got: undefined. ' + - "You likely forgot to export your component from the file it's " + - 'defined in, or you might have mixed up default and named imports.' + - '\n\nCheck the render method of `Bar`.', + 'or a class/function (for composite components) but got: undefined.' + + (__DEV__ + ? " You likely forgot to export your component from the file it's " + + 'defined in, or you might have mixed up default and named imports.' + + '\n\nCheck the render method of `Bar`.' + : ''), ); - // One warning for each element creation - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + // One warning for each element creation + expect(console.error.calls.count()).toBe(1); + } }); it('throws if a plain object is used as a child', () => { @@ -404,10 +432,12 @@ describe('ReactComponent', () => { } expect(ex).toBeDefined(); expect(normalizeCodeLocInfo(ex.message)).toBe( - 'Objects are not valid as a React child (found: object with keys ' + - '{x, y, z}). If you meant to render a collection of children, use ' + - 'an array instead.' + - '\n in div (at **)', + 'Objects are not valid as a React child (found: object with keys {x, y, z}).' + + (__DEV__ + ? ' If you meant to render a collection of children, use ' + + 'an array instead.' + + '\n in div (at **)' + : ''), ); }); @@ -431,11 +461,13 @@ describe('ReactComponent', () => { } expect(ex).toBeDefined(); expect(normalizeCodeLocInfo(ex.message)).toBe( - 'Objects are not valid as a React child (found: object with keys ' + - '{a, b, c}). If you meant to render a collection of children, use ' + - 'an array instead.\n' + - ' in div (at **)\n' + - ' in Foo (at **)', + 'Objects are not valid as a React child (found: object with keys {a, b, c}).' + + (__DEV__ + ? ' If you meant to render a collection of children, use ' + + 'an array instead.\n' + + ' in div (at **)\n' + + ' in Foo (at **)' + : ''), ); }); @@ -454,10 +486,12 @@ describe('ReactComponent', () => { } expect(ex).toBeDefined(); expect(normalizeCodeLocInfo(ex.message)).toBe( - 'Objects are not valid as a React child (found: object with keys ' + - '{x, y, z}). If you meant to render a collection of children, use ' + - 'an array instead.' + - '\n in div (at **)', + 'Objects are not valid as a React child (found: object with keys {x, y, z}).' + + (__DEV__ + ? ' If you meant to render a collection of children, use ' + + 'an array instead.' + + '\n in div (at **)' + : ''), ); }); @@ -481,11 +515,13 @@ describe('ReactComponent', () => { } expect(ex).toBeDefined(); expect(normalizeCodeLocInfo(ex.message)).toBe( - 'Objects are not valid as a React child (found: object with keys ' + - '{a, b, c}). If you meant to render a collection of children, use ' + - 'an array instead.\n' + - ' in div (at **)\n' + - ' in Foo (at **)', + 'Objects are not valid as a React child (found: object with keys {a, b, c}).' + + (__DEV__ + ? ' If you meant to render a collection of children, use ' + + 'an array instead.\n' + + ' in div (at **)\n' + + ' in Foo (at **)' + : ''), ); }); @@ -494,16 +530,18 @@ describe('ReactComponent', () => { function Foo() { return Foo; } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Functions are not valid as a React child. This may happen if ' + - 'you return a Component instead of from render. ' + - 'Or maybe you meant to call this function rather than return it.\n' + - ' in Foo (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Functions are not valid as a React child. This may happen if ' + + 'you return a Component instead of from render. ' + + 'Or maybe you meant to call this function rather than return it.\n' + + ' in Foo (at **)', + ); + } }); it('warns on function as a return value from a class', () => { @@ -512,16 +550,18 @@ describe('ReactComponent', () => { return Foo; } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Functions are not valid as a React child. This may happen if ' + - 'you return a Component instead of from render. ' + - 'Or maybe you meant to call this function rather than return it.\n' + - ' in Foo (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Functions are not valid as a React child. This may happen if ' + + 'you return a Component instead of from render. ' + + 'Or maybe you meant to call this function rather than return it.\n' + + ' in Foo (at **)', + ); + } }); it('warns on function as a child to host component', () => { @@ -532,18 +572,20 @@ describe('ReactComponent', () => {
); } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Functions are not valid as a React child. This may happen if ' + - 'you return a Component instead of from render. ' + - 'Or maybe you meant to call this function rather than return it.\n' + - ' in span (at **)\n' + - ' in div (at **)\n' + - ' in Foo (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Functions are not valid as a React child. This may happen if ' + + 'you return a Component instead of from render. ' + + 'Or maybe you meant to call this function rather than return it.\n' + + ' in span (at **)\n' + + ' in div (at **)\n' + + ' in Foo (at **)', + ); + } }); it('does not warn for function-as-a-child that gets resolved', () => { @@ -553,15 +595,13 @@ describe('ReactComponent', () => { function Foo() { return {() => 'Hello'}; } - spyOn(console, 'error'); var container = document.createElement('div'); ReactDOM.render(, container); expect(container.innerHTML).toBe('Hello'); - expectDev(console.error.calls.count()).toBe(0); }); it('deduplicates function type warnings based on component type', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Foo extends React.PureComponent { constructor() { super(); @@ -583,22 +623,24 @@ describe('ReactComponent', () => { var container = document.createElement('div'); var component = ReactDOM.render(, container); component.setState({type: 'portobello mushrooms'}); - expectDev(console.error.calls.count()).toBe(2); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Functions are not valid as a React child. This may happen if ' + - 'you return a Component instead of from render. ' + - 'Or maybe you meant to call this function rather than return it.\n' + - ' in div (at **)\n' + - ' in Foo (at **)', - ); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( - 'Warning: Functions are not valid as a React child. This may happen if ' + - 'you return a Component instead of from render. ' + - 'Or maybe you meant to call this function rather than return it.\n' + - ' in span (at **)\n' + - ' in div (at **)\n' + - ' in Foo (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(2); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Functions are not valid as a React child. This may happen if ' + + 'you return a Component instead of from render. ' + + 'Or maybe you meant to call this function rather than return it.\n' + + ' in div (at **)\n' + + ' in Foo (at **)', + ); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( + 'Warning: Functions are not valid as a React child. This may happen if ' + + 'you return a Component instead of from render. ' + + 'Or maybe you meant to call this function rather than return it.\n' + + ' in span (at **)\n' + + ' in div (at **)\n' + + ' in Foo (at **)', + ); + } }); }); }); diff --git a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js index 66eaecbcf2e57..4da75f8629121 100644 --- a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js +++ b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js @@ -198,7 +198,7 @@ describe('ReactComponentLifeCycle', () => { }); it('should not allow update state inside of getInitialState', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class StatefulComponent extends React.Component { constructor(props, context) { @@ -214,21 +214,25 @@ describe('ReactComponentLifeCycle', () => { } ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: setState(...): Can only update a mounted or ' + - 'mounting component. This usually means you called setState() on an ' + - 'unmounted component. This is a no-op.\n\nPlease check the code for the ' + - 'StatefulComponent component.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: setState(...): Can only update a mounted or ' + + 'mounting component. This usually means you called setState() on an ' + + 'unmounted component. This is a no-op.\n\nPlease check the code for the ' + + 'StatefulComponent component.', + ); + } // Check deduplication ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + } }); it('should correctly determine if a component is mounted', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { _isMounted() { // No longer a public API, but we can test that it works internally by @@ -252,14 +256,16 @@ describe('ReactComponentLifeCycle', () => { var instance = ReactTestUtils.renderIntoDocument(element); expect(instance._isMounted()).toBeTruthy(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Component is accessing isMounted inside its render()', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Component is accessing isMounted inside its render()', + ); + } }); it('should correctly determine if a null component is mounted', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { _isMounted() { // No longer a public API, but we can test that it works internally by @@ -283,10 +289,12 @@ describe('ReactComponentLifeCycle', () => { var instance = ReactTestUtils.renderIntoDocument(element); expect(instance._isMounted()).toBeTruthy(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Component is accessing isMounted inside its render()', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Component is accessing isMounted inside its render()', + ); + } }); it('isMounted should return false when unmounted', () => { @@ -309,7 +317,7 @@ describe('ReactComponentLifeCycle', () => { }); it('warns if findDOMNode is used inside render', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { state = {isMounted: false}; componentDidMount() { @@ -324,14 +332,16 @@ describe('ReactComponentLifeCycle', () => { } ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Component is accessing findDOMNode inside its render()', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Component is accessing findDOMNode inside its render()', + ); + } }); it('should carry through each of the phases of setup', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class LifeCycleComponent extends React.Component { constructor(props, context) { @@ -440,10 +450,12 @@ describe('ReactComponentLifeCycle', () => { expect(getLifeCycleState(instance)).toBe('UNMOUNTED'); expect(instance.state).toEqual(POST_WILL_UNMOUNT_STATE); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'LifeCycleComponent is accessing isMounted inside its render() function', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'LifeCycleComponent is accessing isMounted inside its render() function', + ); + } }); it('should not throw when updating an auxiliary component', () => { diff --git a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js index 17b54543fdf02..5797e4b942934 100644 --- a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js @@ -122,26 +122,29 @@ describe('ReactCompositeComponent', () => { } } - spyOn(console, 'warn'); + spyOnDev(console, 'warn'); var markup = ReactDOMServer.renderToString(); // Old API based on heuristic var container = document.createElement('div'); container.innerHTML = markup; ReactDOM.render(, container); - expectDev(console.warn.calls.count()).toBe(1); - expectDev(console.warn.calls.argsFor(0)[0]).toContain( - 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + - 'will stop working in React v17. Replace the ReactDOM.render() call ' + - 'with ReactDOM.hydrate() if you want React to attach to the server HTML.', - ); - + if (__DEV__) { + expect(console.warn.calls.count()).toBe(1); + expect(console.warn.calls.argsFor(0)[0]).toContain( + 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + + 'will stop working in React v17. Replace the ReactDOM.render() call ' + + 'with ReactDOM.hydrate() if you want React to attach to the server HTML.', + ); + console.warn.calls.reset(); + } // New explicit API - console.warn.calls.reset(); container = document.createElement('div'); container.innerHTML = markup; ReactDOM.hydrate(, container); - expectDev(console.warn.calls.count()).toBe(0); + if (__DEV__) { + expect(console.warn.calls.count()).toBe(0); + } }); it('should react to state changes from callbacks', () => { @@ -231,7 +234,7 @@ describe('ReactCompositeComponent', () => { }); it('should warn about `forceUpdate` on unmounted components', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); document.body.appendChild(container); @@ -248,25 +251,31 @@ describe('ReactCompositeComponent', () => { instance = ReactDOM.render(instance, container); instance.forceUpdate(); - expectDev(console.error.calls.count()).toBe(0); + if (__DEV__) { + expect(console.error.calls.count()).toBe(0); + } ReactDOM.unmountComponentAtNode(container); instance.forceUpdate(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Can only update a mounted or mounting component. This usually means ' + - 'you called setState, replaceState, or forceUpdate on an unmounted ' + - 'component. This is a no-op.\n\nPlease check the code for the ' + - 'Component component.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Can only update a mounted or mounting component. This usually means ' + + 'you called setState, replaceState, or forceUpdate on an unmounted ' + + 'component. This is a no-op.\n\nPlease check the code for the ' + + 'Component component.', + ); + } instance.forceUpdate(); - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + } }); it('should warn about `setState` on unmounted components', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); document.body.appendChild(container); @@ -291,7 +300,9 @@ describe('ReactCompositeComponent', () => { instance.setState({value: 1}); - expectDev(console.error.calls.count()).toBe(0); + if (__DEV__) { + expect(console.error.calls.count()).toBe(0); + } expect(renders).toBe(2); @@ -300,13 +311,15 @@ describe('ReactCompositeComponent', () => { expect(renders).toBe(2); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Can only update a mounted or mounting component. This usually means ' + - 'you called setState, replaceState, or forceUpdate on an unmounted ' + - 'component. This is a no-op.\n\nPlease check the code for the ' + - 'Component component.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Can only update a mounted or mounting component. This usually means ' + + 'you called setState, replaceState, or forceUpdate on an unmounted ' + + 'component. This is a no-op.\n\nPlease check the code for the ' + + 'Component component.', + ); + } }); it('should silently allow `setState`, not call cb on unmounting components', () => { @@ -338,27 +351,31 @@ describe('ReactCompositeComponent', () => { }); it('should warn when rendering a class with a render method that does not extend React.Component', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); class ClassWithRenderNotExtended { render() { return
; } } - expectDev(console.error.calls.count()).toBe(0); + if (__DEV__) { + expect(console.error.calls.count()).toBe(0); + } expect(() => { ReactDOM.render(, container); }).toThrow(TypeError); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Warning: The component appears to have a render method, ' + - "but doesn't extend React.Component. This is likely to cause errors. " + - 'Change ClassWithRenderNotExtended to extend React.Component instead.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Warning: The component appears to have a render method, ' + + "but doesn't extend React.Component. This is likely to cause errors. " + + 'Change ClassWithRenderNotExtended to extend React.Component instead.', + ); + } }); it('should warn about `setState` in render', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); @@ -378,17 +395,21 @@ describe('ReactCompositeComponent', () => { } } - expectDev(console.error.calls.count()).toBe(0); + if (__DEV__) { + expect(console.error.calls.count()).toBe(0); + } var instance = ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Cannot update during an existing state transition (such as within ' + - "`render` or another component's constructor). Render methods should " + - 'be a pure function of props and state; constructor side-effects are ' + - 'an anti-pattern, but can be moved to `componentWillMount`.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Cannot update during an existing state transition (such as within ' + + "`render` or another component's constructor). Render methods should " + + 'be a pure function of props and state; constructor side-effects are ' + + 'an anti-pattern, but can be moved to `componentWillMount`.', + ); + } // The setState call is queued and then executed as a second pass. This // behavior is undefined though so we're free to change it to suit the @@ -406,11 +427,13 @@ describe('ReactCompositeComponent', () => { // Test deduplication ReactDOM.unmountComponentAtNode(container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + } }); it('should warn about `setState` in getChildContext', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); @@ -432,19 +455,25 @@ describe('ReactCompositeComponent', () => { } Component.childContextTypes = {}; - expectDev(console.error.calls.count()).toBe(0); + if (__DEV__) { + expect(console.error.calls.count()).toBe(0); + } var instance = ReactDOM.render(, container); expect(renderPasses).toBe(2); expect(instance.state.value).toBe(1); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: setState(...): Cannot call setState() inside getChildContext()', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: setState(...): Cannot call setState() inside getChildContext()', + ); + } // Test deduplication ReactDOM.unmountComponentAtNode(container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + } }); it('should cleanup even if render() fatals', () => { @@ -496,7 +525,7 @@ describe('ReactCompositeComponent', () => { }); it('should warn when shouldComponentUpdate() returns undefined', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { state = {bogus: false}; @@ -513,15 +542,17 @@ describe('ReactCompositeComponent', () => { var instance = ReactTestUtils.renderIntoDocument(); instance.setState({bogus: true}); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: Component.shouldComponentUpdate(): Returned undefined instead of a ' + - 'boolean value. Make sure to return true or false.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: Component.shouldComponentUpdate(): Returned undefined instead of a ' + + 'boolean value. Make sure to return true or false.', + ); + } }); it('should warn when componentDidUnmount method is defined', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { componentDidUnmount = () => {}; @@ -533,16 +564,18 @@ describe('ReactCompositeComponent', () => { ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: Component has a method called ' + - 'componentDidUnmount(). But there is no such lifecycle method. ' + - 'Did you mean componentWillUnmount()?', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: Component has a method called ' + + 'componentDidUnmount(). But there is no such lifecycle method. ' + + 'Did you mean componentWillUnmount()?', + ); + } }); it('should warn when componentDidReceiveProps method is defined', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { componentDidReceiveProps = () => {}; @@ -554,18 +587,20 @@ describe('ReactCompositeComponent', () => { ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: Component has a method called ' + - 'componentDidReceiveProps(). But there is no such lifecycle method. ' + - 'If you meant to update the state in response to changing props, ' + - 'use componentWillReceiveProps(). If you meant to fetch data or ' + - 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: Component has a method called ' + + 'componentDidReceiveProps(). But there is no such lifecycle method. ' + + 'If you meant to update the state in response to changing props, ' + + 'use componentWillReceiveProps(). If you meant to fetch data or ' + + 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().', + ); + } }); it('should warn when defaultProps was defined as an instance property', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { constructor(props) { @@ -580,11 +615,13 @@ describe('ReactCompositeComponent', () => { ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: Setting defaultProps as an instance property on Component is not supported ' + - 'and will be ignored. Instead, define defaultProps as a static property on Component.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: Setting defaultProps as an instance property on Component is not supported ' + + 'and will be ignored. Instead, define defaultProps as a static property on Component.', + ); + } }); it('should pass context to children when not owner', () => { @@ -1056,7 +1093,7 @@ describe('ReactCompositeComponent', () => { }); it('should disallow nested render calls', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Inner extends React.Component { render() { @@ -1072,13 +1109,15 @@ describe('ReactCompositeComponent', () => { } ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toMatch( - 'Render methods should be a pure function of props and state; ' + - 'triggering nested component updates from render is not allowed. If ' + - 'necessary, trigger nested updates in componentDidUpdate.\n\nCheck the ' + - 'render method of Outer.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toMatch( + 'Render methods should be a pure function of props and state; ' + + 'triggering nested component updates from render is not allowed. If ' + + 'necessary, trigger nested updates in componentDidUpdate.\n\nCheck the ' + + 'render method of Outer.', + ); + } }); it('only renders once if updated in componentWillReceiveProps', () => { @@ -1353,7 +1392,7 @@ describe('ReactCompositeComponent', () => { }); it('should warn when mutated props are passed', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); @@ -1368,15 +1407,19 @@ describe('ReactCompositeComponent', () => { } } - expectDev(console.error.calls.count()).toBe(0); + if (__DEV__) { + expect(console.error.calls.count()).toBe(0); + } ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Foo(...): When calling super() in `Foo`, make sure to pass ' + - "up the same props that your component's constructor was passed.", - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Foo(...): When calling super() in `Foo`, make sure to pass ' + + "up the same props that your component's constructor was passed.", + ); + } }); it('should only call componentWillUnmount once', () => { @@ -1592,7 +1635,7 @@ describe('ReactCompositeComponent', () => { }); it('should return a meaningful warning when constructor is returned', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class RenderTextInvalidConstructor extends React.Component { constructor(props) { super(props); @@ -1608,25 +1651,29 @@ describe('ReactCompositeComponent', () => { ReactTestUtils.renderIntoDocument(); }).toThrow(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.mostRecent().args[0]).toBe( - 'Warning: RenderTextInvalidConstructor(...): No `render` method found on the returned component instance: ' + - 'did you accidentally return an object from the constructor?', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.mostRecent().args[0]).toBe( + 'Warning: RenderTextInvalidConstructor(...): No `render` method found on the returned component instance: ' + + 'did you accidentally return an object from the constructor?', + ); + } }); it('should return error if render is not defined', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class RenderTestUndefinedRender extends React.Component {} expect(function() { ReactTestUtils.renderIntoDocument(); }).toThrow(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.mostRecent().args[0]).toBe( - 'Warning: RenderTestUndefinedRender(...): No `render` method found on the returned ' + - 'component instance: you may have forgotten to define `render`.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.mostRecent().args[0]).toBe( + 'Warning: RenderTestUndefinedRender(...): No `render` method found on the returned ' + + 'component instance: you may have forgotten to define `render`.', + ); + } }); }); diff --git a/packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js b/packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js index 93b4d9280e72d..131488a500e91 100644 --- a/packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js +++ b/packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js @@ -381,7 +381,7 @@ describe('ReactCompositeComponent-state', () => { }); it('should treat assigning to this.state inside cWRP as a replaceState, with a warning', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); let ops = []; class Test extends React.Component { @@ -416,20 +416,24 @@ describe('ReactCompositeComponent-state', () => { 'render -- step: 3, extra: false', 'callback -- step: 3, extra: false', ]); - expect(console.error.calls.count()).toEqual(1); - expect(console.error.calls.argsFor(0)[0]).toEqual( - 'Warning: Test.componentWillReceiveProps(): Assigning directly to ' + - "this.state is deprecated (except inside a component's constructor). " + - 'Use setState instead.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toEqual(1); + expect(console.error.calls.argsFor(0)[0]).toEqual( + 'Warning: Test.componentWillReceiveProps(): Assigning directly to ' + + "this.state is deprecated (except inside a component's constructor). " + + 'Use setState instead.', + ); + } // Check deduplication ReactDOM.render(, container); - expect(console.error.calls.count()).toEqual(1); + if (__DEV__) { + expect(console.error.calls.count()).toEqual(1); + } }); it('should treat assigning to this.state inside cWM as a replaceState, with a warning', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); let ops = []; class Test extends React.Component { @@ -461,11 +465,13 @@ describe('ReactCompositeComponent-state', () => { 'render -- step: 3, extra: false', 'callback -- step: 3, extra: false', ]); - expect(console.error.calls.count()).toEqual(1); - expect(console.error.calls.argsFor(0)[0]).toEqual( - 'Warning: Test.componentWillMount(): Assigning directly to ' + - "this.state is deprecated (except inside a component's constructor). " + - 'Use setState instead.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toEqual(1); + expect(console.error.calls.argsFor(0)[0]).toEqual( + 'Warning: Test.componentWillMount(): Assigning directly to ' + + "this.state is deprecated (except inside a component's constructor). " + + 'Use setState instead.', + ); + } }); }); diff --git a/packages/react-dom/src/__tests__/ReactDOM-test.js b/packages/react-dom/src/__tests__/ReactDOM-test.js index 9caa898ff1c9b..dde2608bfa3bd 100644 --- a/packages/react-dom/src/__tests__/ReactDOM-test.js +++ b/packages/react-dom/src/__tests__/ReactDOM-test.js @@ -109,7 +109,7 @@ describe('ReactDOM', () => { }); it('throws in render() if the mount callback is not a function', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); function Foo() { this.a = 1; @@ -129,31 +129,37 @@ describe('ReactDOM', () => { 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: no', ); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'render(...): Expected the last optional `callback` argument to be ' + - 'a function. Instead received: no.', - ); + if (__DEV__) { + expect(console.error.calls.argsFor(0)[0]).toContain( + 'render(...): Expected the last optional `callback` argument to be ' + + 'a function. Instead received: no.', + ); + } expect(() => ReactDOM.render(, myDiv, {foo: 'bar'})).toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: [object Object]', ); - expectDev(console.error.calls.argsFor(1)[0]).toContain( - 'render(...): Expected the last optional `callback` argument to be ' + - 'a function. Instead received: [object Object].', - ); + if (__DEV__) { + expect(console.error.calls.argsFor(1)[0]).toContain( + 'render(...): Expected the last optional `callback` argument to be ' + + 'a function. Instead received: [object Object].', + ); + } expect(() => ReactDOM.render(, myDiv, new Foo())).toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: [object Object]', ); - expectDev(console.error.calls.argsFor(2)[0]).toContain( - 'render(...): Expected the last optional `callback` argument to be ' + - 'a function. Instead received: [object Object].', - ); - expect(console.error.calls.count()).toBe(3); + if (__DEV__) { + expect(console.error.calls.argsFor(2)[0]).toContain( + 'render(...): Expected the last optional `callback` argument to be ' + + 'a function. Instead received: [object Object].', + ); + expect(console.error.calls.count()).toBe(3); + } }); it('throws in render() if the update callback is not a function', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); function Foo() { this.a = 1; @@ -174,29 +180,35 @@ describe('ReactDOM', () => { 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: no', ); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'render(...): Expected the last optional `callback` argument to be ' + - 'a function. Instead received: no.', - ); + if (__DEV__) { + expect(console.error.calls.argsFor(0)[0]).toContain( + 'render(...): Expected the last optional `callback` argument to be ' + + 'a function. Instead received: no.', + ); + } ReactDOM.render(, myDiv); // Re-mount expect(() => ReactDOM.render(, myDiv, {foo: 'bar'})).toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: [object Object]', ); - expectDev(console.error.calls.argsFor(1)[0]).toContain( - 'render(...): Expected the last optional `callback` argument to be ' + - 'a function. Instead received: [object Object].', - ); + if (__DEV__) { + expect(console.error.calls.argsFor(1)[0]).toContain( + 'render(...): Expected the last optional `callback` argument to be ' + + 'a function. Instead received: [object Object].', + ); + } ReactDOM.render(, myDiv); // Re-mount expect(() => ReactDOM.render(, myDiv, new Foo())).toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: [object Object]', ); - expectDev(console.error.calls.argsFor(2)[0]).toContain( - 'render(...): Expected the last optional `callback` argument to be ' + - 'a function. Instead received: [object Object].', - ); - expect(console.error.calls.count()).toBe(3); + if (__DEV__) { + expect(console.error.calls.argsFor(2)[0]).toContain( + 'render(...): Expected the last optional `callback` argument to be ' + + 'a function. Instead received: [object Object].', + ); + expect(console.error.calls.count()).toBe(3); + } }); it('preserves focus', () => { @@ -338,4 +350,26 @@ describe('ReactDOM', () => { ]; expect(actual).toEqual(expected); }); + + it('should not crash with devtools installed', () => { + try { + global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { + inject: function() {}, + onCommitFiberRoot: function() {}, + onCommitFiberUnmount: function() {}, + supportsFiber: true, + }; + jest.resetModules(); + React = require('react'); + ReactDOM = require('react-dom'); + class Component extends React.Component { + render() { + return
; + } + } + ReactDOM.render(, document.createElement('container')); + } finally { + delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__; + } + }); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js b/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js index 67589233e9652..fe2d647b7b292 100644 --- a/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js @@ -46,20 +46,20 @@ describe('ReactDOM unknown attribute', () => { }); it('changes values true, false to null, and also warns once', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); testUnknownAttributeAssignment(true, null); testUnknownAttributeAssignment(false, null); - expectDev( - normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]), - ).toMatch( - 'Received `true` for a non-boolean attribute `unknown`.\n\n' + - 'If you want to write it to the DOM, pass a string instead: ' + - 'unknown="true" or unknown={value.toString()}.\n' + - ' in div (at **)', - ); - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toMatch( + 'Received `true` for a non-boolean attribute `unknown`.\n\n' + + 'If you want to write it to the DOM, pass a string instead: ' + + 'unknown="true" or unknown={value.toString()}.\n' + + ' in div (at **)', + ); + expect(console.error.calls.count()).toBe(1); + } }); it('removes unknown attributes that were rendered but are now missing', () => { @@ -82,17 +82,17 @@ describe('ReactDOM unknown attribute', () => { }); it('coerces NaN to strings and warns', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); testUnknownAttributeAssignment(NaN, 'NaN'); - expectDev( - normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]), - ).toMatch( - 'Warning: Received NaN for the `unknown` attribute. ' + - 'If this is expected, cast the value to a string.\n' + - ' in div (at **)', - ); - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toMatch( + 'Warning: Received NaN for the `unknown` attribute. ' + + 'If this is expected, cast the value to a string.\n' + + ' in div (at **)', + ); + expect(console.error.calls.count()).toBe(1); + } }); it('coerces objects to strings and warns', () => { @@ -107,50 +107,54 @@ describe('ReactDOM unknown attribute', () => { }); it('removes symbols and warns', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); testUnknownAttributeRemoval(Symbol('foo')); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Invalid value for prop `unknown` on
tag. Either remove it ' + - 'from the element, or pass a string or number value to keep it ' + - 'in the DOM. For details, see https://fb.me/react-attribute-behavior\n' + - ' in div (at **)', - ); - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Invalid value for prop `unknown` on
tag. Either remove it ' + + 'from the element, or pass a string or number value to keep it ' + + 'in the DOM. For details, see https://fb.me/react-attribute-behavior\n' + + ' in div (at **)', + ); + expect(console.error.calls.count()).toBe(1); + } }); it('removes functions and warns', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); testUnknownAttributeRemoval(function someFunction() {}); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Invalid value for prop `unknown` on
tag. Either remove ' + - 'it from the element, or pass a string or number value to ' + - 'keep it in the DOM. For details, see ' + - 'https://fb.me/react-attribute-behavior\n' + - ' in div (at **)', - ); - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Invalid value for prop `unknown` on
tag. Either remove ' + + 'it from the element, or pass a string or number value to ' + + 'keep it in the DOM. For details, see ' + + 'https://fb.me/react-attribute-behavior\n' + + ' in div (at **)', + ); + expect(console.error.calls.count()).toBe(1); + } }); it('allows camelCase unknown attributes and warns', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var el = document.createElement('div'); ReactDOM.render(
, el); expect(el.firstChild.getAttribute('helloworld')).toBe('something'); - expectDev(console.error.calls.count()).toBe(1); - expectDev( - normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]), - ).toMatch( - 'React does not recognize the `helloWorld` prop on a DOM element. ' + - 'If you intentionally want it to appear in the DOM as a custom ' + - 'attribute, spell it as lowercase `helloworld` instead. ' + - 'If you accidentally passed it from a parent component, remove ' + - 'it from the DOM element.\n' + - ' in div (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toMatch( + 'React does not recognize the `helloWorld` prop on a DOM element. ' + + 'If you intentionally want it to appear in the DOM as a custom ' + + 'attribute, spell it as lowercase `helloworld` instead. ' + + 'If you accidentally passed it from a parent component, remove ' + + 'it from the DOM element.\n' + + ' in div (at **)', + ); + } }); }); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js index e9b1911952452..2c40743854c36 100644 --- a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js @@ -132,47 +132,55 @@ describe('ReactDOMComponent', () => { } ReactTestUtils.renderIntoDocument(); - expectDev(() => (style.position = 'absolute')).toThrow(); + if (__DEV__) { + expect(() => (style.position = 'absolute')).toThrow(); + } }); it('should warn for unknown prop', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); ReactDOM.render(
{}} />, container); - expectDev(console.error.calls.count(0)).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Invalid value for prop `foo` on
tag. Either remove it ' + - 'from the element, or pass a string or number value to keep ' + - 'it in the DOM. For details, see https://fb.me/react-attribute-behavior' + - '\n in div (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count(0)).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Invalid value for prop `foo` on
tag. Either remove it ' + + 'from the element, or pass a string or number value to keep ' + + 'it in the DOM. For details, see https://fb.me/react-attribute-behavior' + + '\n in div (at **)', + ); + } }); it('should group multiple unknown prop warnings together', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); ReactDOM.render(
{}} baz={() => {}} />, container); - expectDev(console.error.calls.count(0)).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Invalid values for props `foo`, `baz` on
tag. Either remove ' + - 'them from the element, or pass a string or number value to keep ' + - 'them in the DOM. For details, see https://fb.me/react-attribute-behavior' + - '\n in div (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count(0)).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Invalid values for props `foo`, `baz` on
tag. Either remove ' + + 'them from the element, or pass a string or number value to keep ' + + 'them in the DOM. For details, see https://fb.me/react-attribute-behavior' + + '\n in div (at **)', + ); + } }); it('should warn for onDblClick prop', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); ReactDOM.render(
{}} />, container); - expectDev(console.error.calls.count(0)).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Invalid event handler property `onDblClick`. Did you mean `onDoubleClick`?\n in div (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count(0)).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Invalid event handler property `onDblClick`. Did you mean `onDoubleClick`?\n in div (at **)', + ); + } }); it('should warn for unknown string event handlers', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); ReactDOM.render(
, container); expect(container.firstChild.hasAttribute('onUnknown')).toBe(false); @@ -183,20 +191,22 @@ describe('ReactDOMComponent', () => { ReactDOM.render(
, container); expect(container.firstChild.hasAttribute('on-unknown')).toBe(false); expect(container.firstChild['on-unknown']).toBe(undefined); - expectDev(console.error.calls.count(0)).toBe(3); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)', - ); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( - 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)', - ); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe( - 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count(0)).toBe(3); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)', + ); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( + 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)', + ); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe( + 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)', + ); + } }); it('should warn for unknown function event handlers', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); ReactDOM.render(
, container); expect(container.firstChild.hasAttribute('onUnknown')).toBe(false); @@ -207,32 +217,34 @@ describe('ReactDOMComponent', () => { ReactDOM.render(
, container); expect(container.firstChild.hasAttribute('on-unknown')).toBe(false); expect(container.firstChild['on-unknown']).toBe(undefined); - expectDev(console.error.calls.count(0)).toBe(3); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)', - ); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( - 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)', - ); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe( - 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count(0)).toBe(3); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)', + ); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( + 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)', + ); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe( + 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)', + ); + } }); it('should warn for badly cased React attributes', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); ReactDOM.render(
, container); expect(container.firstChild.getAttribute('CHILDREN')).toBe('5'); - expectDev(console.error.calls.count(0)).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Invalid DOM property `CHILDREN`. Did you mean `children`?\n in div (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count(0)).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Invalid DOM property `CHILDREN`. Did you mean `children`?\n in div (at **)', + ); + } }); it('should not warn for "0" as a unitless style value', () => { - spyOn(console, 'error'); - class Component extends React.Component { render() { return
; @@ -240,24 +252,23 @@ describe('ReactDOMComponent', () => { } ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(0); }); it('should warn nicely about NaN in style', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var style = {fontSize: NaN}; var div = document.createElement('div'); ReactDOM.render(, div); ReactDOM.render(, div); - expectDev(console.error.calls.count()).toBe(1); - expectDev( - normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]), - ).toEqual( - 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' + - '\n in span (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' + + '\n in span (at **)', + ); + } }); it('should update styles if initially null', () => { @@ -445,7 +456,7 @@ describe('ReactDOMComponent', () => { }); it('should reject attribute key injection attack on markup', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); for (var i = 0; i < 3; i++) { var container = document.createElement('div'); var element = React.createElement( @@ -455,14 +466,16 @@ describe('ReactDOMComponent', () => { ); ReactDOM.render(element, container); } - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toEqual( - 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toEqual( + 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`', + ); + } }); it('should reject attribute key injection attack on update', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); for (var i = 0; i < 3; i++) { var container = document.createElement('div'); var beforeUpdate = React.createElement('x-foo-component', {}, null); @@ -475,10 +488,12 @@ describe('ReactDOMComponent', () => { ); ReactDOM.render(afterUpdate, container); } - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toEqual( - 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toEqual( + 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`', + ); + } }); it('should update arbitrary attributes for tags containing dashes', () => { @@ -723,15 +738,17 @@ describe('ReactDOMComponent', () => { }); it('should warn about non-string "is" attribute', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var container = document.createElement('div'); ReactDOM.render(