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/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 82b74cef3c6c0..3e93a2c8988b6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,9 +4,10 @@ 2. Run `yarn` in the repository root. 3. If you've fixed a bug or added code that should be tested, add tests! 4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development. -5. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`). -6. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files. -7. Run the [Flow](https://flowtype.org/) typechecks (`yarn flow`). -8. If you haven't already, complete the CLA. +5. Run `yarn test-prod` to test in the production environment. It supports the same options as `yarn test`. +6. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`). +7. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files. +8. Run the [Flow](https://flowtype.org/) typechecks (`yarn flow`). +9. If you haven't already, complete the CLA. **Learn more about contributing:** https://reactjs.org/docs/how-to-contribute.html diff --git a/.gitignore b/.gitignore index 46f908d0cc31c..6de4ca21a6e44 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,4 @@ chrome-user-data *.iml .vscode *.swp -*.swo +*.swo \ No newline at end of file diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/fixtures/dom/src/components/fixtures/input-change-events/RadioNameChangeFixture.js b/fixtures/dom/src/components/fixtures/input-change-events/RadioNameChangeFixture.js new file mode 100644 index 0000000000000..3df34baaf6e73 --- /dev/null +++ b/fixtures/dom/src/components/fixtures/input-change-events/RadioNameChangeFixture.js @@ -0,0 +1,48 @@ +const React = window.React; +const noop = n => n; + +class RadioNameChangeFixture extends React.Component { + state = { + updated: false, + }; + onClick = () => { + this.setState(state => { + return {updated: !state.updated}; + }); + }; + render() { + const {updated} = this.state; + const radioName = updated ? 'firstName' : 'secondName'; + return ( +
+ + + + +
+ +
+
+ ); + } +} + +export default RadioNameChangeFixture; diff --git a/fixtures/dom/src/components/fixtures/input-change-events/index.js b/fixtures/dom/src/components/fixtures/input-change-events/index.js index 8390b080a44f4..a6d6ee4df2d1a 100644 --- a/fixtures/dom/src/components/fixtures/input-change-events/index.js +++ b/fixtures/dom/src/components/fixtures/input-change-events/index.js @@ -3,6 +3,7 @@ import TestCase from '../../TestCase'; import RangeKeyboardFixture from './RangeKeyboardFixture'; import RadioClickFixture from './RadioClickFixture'; import RadioGroupFixture from './RadioGroupFixture'; +import RadioNameChangeFixture from './RadioNameChangeFixture'; import InputPlaceholderFixture from './InputPlaceholderFixture'; const React = window.React; @@ -88,6 +89,25 @@ class InputChangeEvents extends React.Component { + + +
  • Click the toggle button
  • +
    + + + The checked radio button should switch between the first and second + radio button + + + +
    ); } diff --git a/package.json b/package.json index 2db1c34839af2..c8ce7b99bce5a 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "art": "^0.10.1", "async": "^1.5.0", "babel-cli": "^6.6.5", + "babel-code-frame": "^6.26.0", "babel-core": "^6.0.0", "babel-eslint": "^7.1.0", "babel-jest": "^21.3.0-beta.4", @@ -34,6 +35,7 @@ "babel-plugin-transform-es3-property-literals": "^6.5.0", "babel-plugin-transform-object-rest-spread": "^6.6.5", "babel-plugin-transform-react-jsx-source": "^6.8.0", + "babel-plugin-transform-regenerator": "^6.26.0", "babel-preset-react": "^6.5.0", "babel-traverse": "^6.9.0", "babylon": "6.15.0", @@ -44,6 +46,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", @@ -76,7 +79,7 @@ "prettier": "1.8.1", "prop-types": "^15.6.0", "rimraf": "^2.6.1", - "rollup": "^0.49.3", + "rollup": "^0.51.7", "rollup-plugin-alias": "^1.2.1", "rollup-plugin-babel": "^2.7.1", "rollup-plugin-closure-compiler-js": "^1.0.4", @@ -101,40 +104,14 @@ "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 --config ./scripts/jest/config.source.js", + "test-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source.js", + "test-prod-build": "yarn test-build-prod", + "test-build": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build.js", + "test-build-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.build.js", "flow": "node ./scripts/tasks/flow.js", "prettier": "node ./scripts/prettier/index.js write-changed", "prettier-all": "node ./scripts/prettier/index.js write", "version-check": "node ./scripts/tasks/version-check.js" - }, - "jest": { - "modulePathIgnorePatterns": [ - "/scripts/rollup/shims/", - "/scripts/bench/" - ], - "transform": { - ".*": "./scripts/jest/preprocessor.js" - }, - "setupFiles": [ - "./scripts/jest/setup.js", - "./scripts/jest/environment.js" - ], - "setupTestFrameworkScriptFile": "./scripts/jest/test-framework-setup.js", - "testRegex": "/__tests__/.*(\\.js|\\.coffee|[^d]\\.ts)$", - "moduleFileExtensions": [ - "js", - "json", - "node", - "coffee", - "ts" - ], - "roots": [ - "/packages", - "/scripts" - ], - "collectCoverageFrom": [ - "packages/**/*.js" - ], - "timers": "fake" } } diff --git a/packages/events/__tests__/EventPluginRegistry-test.js b/packages/events/__tests__/EventPluginRegistry-test.internal.js similarity index 96% rename from packages/events/__tests__/EventPluginRegistry-test.js rename to packages/events/__tests__/EventPluginRegistry-test.internal.js index 6273e7779901d..3af0a782378b0 100644 --- a/packages/events/__tests__/EventPluginRegistry-test.js +++ b/packages/events/__tests__/EventPluginRegistry-test.internal.js @@ -15,7 +15,10 @@ describe('EventPluginRegistry', () => { beforeEach(() => { jest.resetModuleRegistry(); - // TODO: can we express this test with only public API? + // These tests are intentionally testing the private injection interface. + // The public API surface of this is covered by other tests so + // if `EventPluginRegistry` is ever deleted, these tests should be + // safe to remove too. EventPluginRegistry = require('events/EventPluginRegistry'); createPlugin = function(properties) { diff --git a/packages/events/__tests__/ResponderEventPlugin-test.js b/packages/events/__tests__/ResponderEventPlugin-test.internal.js similarity index 100% rename from packages/events/__tests__/ResponderEventPlugin-test.js rename to packages/events/__tests__/ResponderEventPlugin-test.internal.js diff --git a/packages/events/__tests__/accumulateInto-test.js b/packages/events/__tests__/accumulateInto-test.internal.js similarity index 100% rename from packages/events/__tests__/accumulateInto-test.js rename to packages/events/__tests__/accumulateInto-test.internal.js diff --git a/packages/react-call-return/src/ReactCallReturn.js b/packages/react-call-return/src/ReactCallReturn.js index c39c71dcea2c3..c6979dd436eeb 100644 --- a/packages/react-call-return/src/ReactCallReturn.js +++ b/packages/react-call-return/src/ReactCallReturn.js @@ -7,19 +7,9 @@ * @flow */ -import type {ReactCall, ReactNodeList, ReactReturn} from 'shared/ReactTypes'; +import {REACT_CALL_TYPE, REACT_RETURN_TYPE} from 'shared/ReactSymbols'; -// The Symbol used to tag the special React types. If there is no native Symbol -// nor polyfill, then a plain number is used for performance. -var REACT_CALL_TYPE; -var REACT_RETURN_TYPE; -if (typeof Symbol === 'function' && Symbol.for) { - REACT_CALL_TYPE = Symbol.for('react.call'); - REACT_RETURN_TYPE = Symbol.for('react.return'); -} else { - REACT_CALL_TYPE = 0xeac8; - REACT_RETURN_TYPE = 0xeac9; -} +import type {ReactCall, ReactNodeList, ReactReturn} from 'shared/ReactTypes'; type CallHandler = (props: T, returns: Array) => ReactNodeList; diff --git a/packages/react-cs-renderer/src/ReactNativeCSFeatureFlags.js b/packages/react-cs-renderer/src/ReactNativeCSFeatureFlags.js index ccb7c09619dd1..e4c4d192e6e9e 100644 --- a/packages/react-cs-renderer/src/ReactNativeCSFeatureFlags.js +++ b/packages/react-cs-renderer/src/ReactNativeCSFeatureFlags.js @@ -12,6 +12,7 @@ import invariant from 'fbjs/lib/invariant'; import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags'; import typeof * as CSFeatureFlagsType from './ReactNativeCSFeatureFlags'; +export const debugRenderPhaseSideEffects = false; export const enableAsyncSubtreeAPI = true; export const enableAsyncSchedulingByDefaultInReactDOM = false; export const enableReactFragment = false; diff --git a/packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.js b/packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.internal.js similarity index 100% rename from packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.js rename to packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.internal.js 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.internal.js similarity index 99% rename from packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js rename to packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.internal.js index 647109a907b3d..cd4bff6102660 100644 --- a/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js +++ b/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.internal.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..1bac93ec046c1 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', () => { @@ -1591,8 +1634,75 @@ describe('ReactCompositeComponent', () => { expect(mockArgs.length).toEqual(0); }); + it('this.state should be updated on setState callback inside componentWillMount', () => { + const div = document.createElement('div'); + let stateSuccessfullyUpdated = false; + + class Component extends React.Component { + constructor(props, context) { + super(props, context); + this.state = { + hasUpdatedState: false, + }; + } + + componentWillMount() { + this.setState( + {hasUpdatedState: true}, + () => (stateSuccessfullyUpdated = this.state.hasUpdatedState), + ); + } + + render() { + return
    {this.props.children}
    ; + } + } + + ReactDOM.render(, div); + expect(stateSuccessfullyUpdated).toBe(true); + }); + + it('should call the setState callback even if shouldComponentUpdate = false', done => { + const mockFn = jest.fn().mockReturnValue(false); + const div = document.createElement('div'); + + let instance; + + class Component extends React.Component { + constructor(props, context) { + super(props, context); + this.state = { + hasUpdatedState: false, + }; + } + + componentWillMount() { + instance = this; + } + + shouldComponentUpdate() { + return mockFn(); + } + + render() { + return
    {this.state.hasUpdatedState}
    ; + } + } + + ReactDOM.render(, div); + + expect(instance).toBeDefined(); + expect(mockFn).not.toBeCalled(); + + instance.setState({hasUpdatedState: true}, () => { + expect(mockFn).toBeCalled(); + expect(instance.state.hasUpdatedState).toBe(true); + done(); + }); + }); + 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 +1718,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..4414dd37e33ae 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(
    + ); + } + } + + var stub = ReactTestUtils.renderIntoDocument(); + var buttonNode = ReactDOM.findDOMNode(stub).childNodes[0]; + var firstRadioNode = ReactDOM.findDOMNode(stub).childNodes[1]; + expect(firstRadioNode.checked).toBe(false); + ReactTestUtils.Simulate.click(buttonNode); + expect(firstRadioNode.checked).toBe(true); + }); + it('should control radio buttons if the tree updates during render', () => { var sharedParent = document.createElement('div'); var container1 = document.createElement('div'); @@ -746,19 +787,23 @@ describe('ReactDOMInput', () => { ReactTestUtils.renderIntoDocument( , ); - expectDev(console.error.calls.count()).toBe(0); + if (__DEV__) { + expect(console.error.calls.count()).toBe(0); + } ReactTestUtils.renderIntoDocument( , ); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Failed prop type: You provided a `value` prop to a form ' + - 'field without an `onChange` handler. This will render a read-only ' + - 'field. If the field should be mutable use `defaultValue`. ' + - 'Otherwise, set either `onChange` or `readOnly`.\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Failed prop type: You provided a `value` prop to a form ' + + 'field without an `onChange` handler. This will render a read-only ' + + 'field. If the field should be mutable use `defaultValue`. ' + + 'Otherwise, set either `onChange` or `readOnly`.\n' + + ' in input (at **)', + ); + } }); it('should have a this value of undefined if bind is not used', () => { @@ -776,12 +821,16 @@ describe('ReactDOMInput', () => { ReactTestUtils.renderIntoDocument( , ); - expectDev(console.error.calls.count()).toBe(0); + if (__DEV__) { + expect(console.error.calls.count()).toBe(0); + } ReactTestUtils.renderIntoDocument( , ); - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + } }); it('should update defaultValue to empty string', () => { @@ -793,14 +842,18 @@ describe('ReactDOMInput', () => { it('should warn if value is null', () => { ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - '`value` prop on `input` should not be null. ' + - 'Consider using an empty string to clear the component or `undefined` ' + - 'for uncontrolled components.', - ); + if (__DEV__) { + expect(console.error.calls.argsFor(0)[0]).toContain( + '`value` prop on `input` should not be null. ' + + 'Consider using an empty string to clear the component or `undefined` ' + + 'for uncontrolled components.', + ); + } ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + } }); it('should warn if checked and defaultChecked props are specified', () => { @@ -812,14 +865,16 @@ describe('ReactDOMInput', () => { readOnly={true} />, ); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component contains an input of type radio with both checked and defaultChecked props. ' + - 'Input elements must be either controlled or uncontrolled ' + - '(specify either the checked prop, or the defaultChecked prop, but not ' + - 'both). Decide between using a controlled or uncontrolled input ' + - 'element and remove one of these props. More info: ' + - 'https://fb.me/react-controlled-components', - ); + if (__DEV__) { + expect(console.error.calls.argsFor(0)[0]).toContain( + 'A component contains an input of type radio with both checked and defaultChecked props. ' + + 'Input elements must be either controlled or uncontrolled ' + + '(specify either the checked prop, or the defaultChecked prop, but not ' + + 'both). Decide between using a controlled or uncontrolled input ' + + 'element and remove one of these props. More info: ' + + 'https://fb.me/react-controlled-components', + ); + } ReactTestUtils.renderIntoDocument( { readOnly={true} />, ); - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + } }); it('should warn if value and defaultValue props are specified', () => { ReactTestUtils.renderIntoDocument( , ); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component contains an input of type text with both value and defaultValue props. ' + - 'Input elements must be either controlled or uncontrolled ' + - '(specify either the value prop, or the defaultValue prop, but not ' + - 'both). Decide between using a controlled or uncontrolled input ' + - 'element and remove one of these props. More info: ' + - 'https://fb.me/react-controlled-components', - ); + if (__DEV__) { + expect(console.error.calls.argsFor(0)[0]).toContain( + 'A component contains an input of type text with both value and defaultValue props. ' + + 'Input elements must be either controlled or uncontrolled ' + + '(specify either the value prop, or the defaultValue prop, but not ' + + 'both). Decide between using a controlled or uncontrolled input ' + + 'element and remove one of these props. More info: ' + + 'https://fb.me/react-controlled-components', + ); + } ReactTestUtils.renderIntoDocument( , ); - expectDev(console.error.calls.count()).toBe(1); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + } }); it('should warn if controlled input switches to uncontrolled (value is undefined)', () => { @@ -858,14 +919,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if controlled input switches to uncontrolled (value is null)', () => { @@ -875,14 +938,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBeGreaterThan(0); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( - 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBeGreaterThan(0); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( + 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if controlled input switches to uncontrolled with defaultValue', () => { @@ -895,14 +960,16 @@ describe('ReactDOMInput', () => { , container, ); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if uncontrolled input (value is undefined) switches to controlled', () => { @@ -910,14 +977,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing an uncontrolled input of type text to be controlled. ' + - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing an uncontrolled input of type text to be controlled. ' + + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if uncontrolled input (value is null) switches to controlled', () => { @@ -925,14 +994,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBeGreaterThan(0); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( - 'Warning: A component is changing an uncontrolled input of type text to be controlled. ' + - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBeGreaterThan(0); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( + 'Warning: A component is changing an uncontrolled input of type text to be controlled. ' + + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if controlled checkbox switches to uncontrolled (checked is undefined)', () => { @@ -942,14 +1013,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if controlled checkbox switches to uncontrolled (checked is null)', () => { @@ -959,14 +1032,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if controlled checkbox switches to uncontrolled with defaultChecked', () => { @@ -976,14 +1051,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if uncontrolled checkbox (checked is undefined) switches to controlled', () => { @@ -991,14 +1068,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' + - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' + + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if uncontrolled checkbox (checked is null) switches to controlled', () => { @@ -1006,14 +1085,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' + - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' + + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if controlled radio switches to uncontrolled (checked is undefined)', () => { @@ -1021,14 +1102,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if controlled radio switches to uncontrolled (checked is null)', () => { @@ -1036,14 +1119,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if controlled radio switches to uncontrolled with defaultChecked', () => { @@ -1051,14 +1136,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if uncontrolled radio (checked is undefined) switches to controlled', () => { @@ -1066,14 +1153,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' + - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' + + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should warn if uncontrolled radio (checked is null) switches to controlled', () => { @@ -1081,14 +1170,16 @@ describe('ReactDOMInput', () => { var container = document.createElement('div'); ReactDOM.render(stub, container); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' + - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' + + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('should not warn if radio value changes but never becomes controlled', () => { @@ -1104,7 +1195,9 @@ describe('ReactDOMInput', () => { container, ); ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(0); + if (__DEV__) { + expect(console.error.calls.count()).toBe(0); + } }); it('should not warn if radio value changes but never becomes uncontrolled', () => { @@ -1123,7 +1216,9 @@ describe('ReactDOMInput', () => { />, container, ); - expectDev(console.error.calls.count()).toBe(0); + if (__DEV__) { + expect(console.error.calls.count()).toBe(0); + } }); it('should warn if radio checked false changes to become uncontrolled', () => { @@ -1138,13 +1233,15 @@ describe('ReactDOMInput', () => { container, ); ReactDOM.render(, container); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + - 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + - ' in input (at **)', - ); + if (__DEV__) { + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', + ); + } }); it('sets type, step, min, max before value always', () => { diff --git a/packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js b/packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js index 1106f12df32a4..518e177de5b35 100644 --- a/packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js @@ -26,62 +26,74 @@ describe('ReactDOMInvalidARIAHook', () => { describe('aria-* props', () => { it('should allow valid aria-* props', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); mountComponent({'aria-label': 'Bumble bees'}); - expectDev(console.error.calls.count()).toBe(0); + if (__DEV__) { + expect(console.error.calls.count()).toBe(0); + } }); it('should warn for one invalid aria-* prop', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); mountComponent({'aria-badprop': 'maybe'}); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Warning: Invalid aria prop `aria-badprop` on
    tag. ' + - 'For details, see https://fb.me/invalid-aria-prop', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Warning: Invalid aria prop `aria-badprop` on
    tag. ' + + 'For details, see https://fb.me/invalid-aria-prop', + ); + } }); it('should warn for many invalid aria-* props', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); mountComponent({ 'aria-badprop': 'Very tall trees', 'aria-malprop': 'Turbulent seas', }); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Warning: Invalid aria props `aria-badprop`, `aria-malprop` on
    ' + - 'tag. For details, see https://fb.me/invalid-aria-prop', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Warning: Invalid aria props `aria-badprop`, `aria-malprop` on
    ' + + 'tag. For details, see https://fb.me/invalid-aria-prop', + ); + } }); it('should warn for an improperly cased aria-* prop', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); // The valid attribute name is aria-haspopup. mountComponent({'aria-hasPopup': 'true'}); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Warning: Unknown ARIA attribute `aria-hasPopup`. ' + - 'Did you mean `aria-haspopup`?', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Warning: Unknown ARIA attribute `aria-hasPopup`. ' + + 'Did you mean `aria-haspopup`?', + ); + } }); it('should warn for use of recognized camel case aria attributes', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); // The valid attribute name is aria-haspopup. mountComponent({ariaHasPopup: 'true'}); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Warning: Invalid ARIA attribute `ariaHasPopup`. ' + - 'Did you mean `aria-haspopup`?', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Warning: Invalid ARIA attribute `ariaHasPopup`. ' + + 'Did you mean `aria-haspopup`?', + ); + } }); it('should warn for use of unrecognized camel case aria attributes', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); // The valid attribute name is aria-haspopup. mountComponent({ariaSomethingInvalid: 'true'}); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Warning: Invalid ARIA attribute `ariaSomethingInvalid`. ARIA ' + - 'attributes follow the pattern aria-* and must be lowercase.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Warning: Invalid ARIA attribute `ariaSomethingInvalid`. ARIA ' + + 'attributes follow the pattern aria-* and must be lowercase.', + ); + } }); }); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMOption-test.js b/packages/react-dom/src/__tests__/ReactDOMOption-test.js index b05ae5edb6946..8a28a32297b00 100644 --- a/packages/react-dom/src/__tests__/ReactDOMOption-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMOption-test.js @@ -37,7 +37,7 @@ describe('ReactDOMOption', () => { }); it('should ignore and warn invalid children types', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var el = (