diff --git a/.eslintrc.js b/.eslintrc.js index 3938a6cfe531b..ac446e7258025 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -59,6 +59,6 @@ module.exports = { }, globals: { - expectDev: true, + spyOnDev: true, }, }; diff --git a/package.json b/package.json index 901af480d2aba..97d5ef1906bc5 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "core-js": "^2.2.1", "coveralls": "^2.11.6", "create-react-class": "^15.6.2", + "cross-env": "^5.1.1", "del": "^2.0.2", "derequire": "^2.0.3", "escape-string-regexp": "^1.0.5", @@ -102,7 +103,8 @@ "linc": "node ./scripts/tasks/linc.js", "lint": "node ./scripts/tasks/eslint.js", "postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json", - "test": "jest", + "test": "cross-env NODE_ENV=development jest", + "test-prod": "cross-env NODE_ENV=production jest", "flow": "node ./scripts/tasks/flow.js", "prettier": "node ./scripts/prettier/index.js write-changed", "prettier-all": "node ./scripts/prettier/index.js write", diff --git a/packages/react-dom/src/__tests__/CSSPropertyOperations-test.js b/packages/react-dom/src/__tests__/CSSPropertyOperations-test.js index 81b3efe48f76a..10190db51ef9e 100644 --- a/packages/react-dom/src/__tests__/CSSPropertyOperations-test.js +++ b/packages/react-dom/src/__tests__/CSSPropertyOperations-test.js @@ -91,15 +91,17 @@ describe('CSSPropertyOperations', () => { } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var root = document.createElement('div'); ReactDOM.render(, root); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( - 'Warning: Unsupported style property background-color. Did you mean backgroundColor?' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + 'Warning: Unsupported style property background-color. Did you mean backgroundColor?' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + } }); it('should warn when updating hyphenated style names', () => { @@ -111,7 +113,7 @@ describe('CSSPropertyOperations', () => { } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var styles = { '-ms-transform': 'translate3d(0, 0, 0)', '-webkit-transform': 'translate3d(0, 0, 0)', @@ -120,17 +122,19 @@ describe('CSSPropertyOperations', () => { ReactDOM.render(, root); ReactDOM.render(, root); - expectDev(console.error.calls.count()).toBe(2); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( - 'Warning: Unsupported style property -ms-transform. Did you mean msTransform?' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual( - 'Warning: Unsupported style property -webkit-transform. Did you mean WebkitTransform?' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(2); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + 'Warning: Unsupported style property -ms-transform. Did you mean msTransform?' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual( + 'Warning: Unsupported style property -webkit-transform. Did you mean WebkitTransform?' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + } }); it('warns when miscapitalizing vendored style names', () => { @@ -150,23 +154,25 @@ describe('CSSPropertyOperations', () => { } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var root = document.createElement('div'); ReactDOM.render(, root); - // msTransform is correct already and shouldn't warn - expectDev(console.error.calls.count()).toBe(2); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( - 'Warning: Unsupported vendor-prefixed style property oTransform. ' + - 'Did you mean OTransform?' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual( - 'Warning: Unsupported vendor-prefixed style property webkitTransform. ' + - 'Did you mean WebkitTransform?' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); + if (__DEV__) { + // msTransform is correct already and shouldn't warn + expect(console.error.calls.count()).toBe(2); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + 'Warning: Unsupported vendor-prefixed style property oTransform. ' + + 'Did you mean OTransform?' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual( + 'Warning: Unsupported vendor-prefixed style property webkitTransform. ' + + 'Did you mean WebkitTransform?' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + } }); it('should warn about style having a trailing semicolon', () => { @@ -187,22 +193,24 @@ describe('CSSPropertyOperations', () => { } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var root = document.createElement('div'); ReactDOM.render(, root); - expectDev(console.error.calls.count()).toBe(2); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( - "Warning: Style property values shouldn't contain a semicolon. " + - 'Try "backgroundColor: blue" instead.' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual( - "Warning: Style property values shouldn't contain a semicolon. " + - 'Try "color: red" instead.' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(2); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + "Warning: Style property values shouldn't contain a semicolon. " + + 'Try "backgroundColor: blue" instead.' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual( + "Warning: Style property values shouldn't contain a semicolon. " + + 'Try "color: red" instead.' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + } }); it('should warn about style containing a NaN value', () => { @@ -214,16 +222,18 @@ describe('CSSPropertyOperations', () => { } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var root = document.createElement('div'); ReactDOM.render(, root); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( - 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + } }); it('should not warn when setting CSS custom properties', () => { @@ -246,16 +256,18 @@ describe('CSSPropertyOperations', () => { } } - spyOn(console, 'error'); + spyOnDev(console, 'error'); var root = document.createElement('div'); ReactDOM.render(, root); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( - 'Warning: `Infinity` is an invalid value for the `fontSize` css style property.' + - '\n in div (at **)' + - '\n in Comp (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual( + 'Warning: `Infinity` is an invalid value for the `fontSize` css style property.' + + '\n in div (at **)' + + '\n in Comp (at **)', + ); + } }); it('should not add units to CSS custom properties', () => { diff --git a/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js b/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js index 7f544506a5f35..343adc05318d5 100644 --- a/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js +++ b/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js @@ -151,7 +151,7 @@ describe('DOMPropertyOperations', () => { it('should not remove attributes for special properties', () => { var container = document.createElement('div'); - spyOn(console, 'error'); + spyOnDev(console, 'error'); ReactDOM.render( , container, @@ -164,10 +164,12 @@ describe('DOMPropertyOperations', () => { ); expect(container.firstChild.getAttribute('value')).toBe('foo'); expect(container.firstChild.value).toBe('foo'); - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing a controlled input of type text to be uncontrolled', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'A component is changing a controlled input of type text to be uncontrolled', + ); + } }); }); }); diff --git a/packages/react-dom/src/__tests__/EventPluginHub-test.js b/packages/react-dom/src/__tests__/EventPluginHub-test.js index eaf40602ab6b6..ec1f83d0ee821 100644 --- a/packages/react-dom/src/__tests__/EventPluginHub-test.js +++ b/packages/react-dom/src/__tests__/EventPluginHub-test.js @@ -22,7 +22,7 @@ describe('EventPluginHub', () => { }); it('should prevent non-function listeners, at dispatch', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); var node = ReactTestUtils.renderIntoDocument(
, ); @@ -31,10 +31,12 @@ describe('EventPluginHub', () => { }).toThrowError( 'Expected `onClick` listener to be a function, instead got a value of `string` type.', ); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Expected `onClick` listener to be a function, instead got a value of `string` type.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Expected `onClick` listener to be a function, instead got a value of `string` type.', + ); + } }); it('should not prevent null listeners, at dispatch', () => { diff --git a/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js b/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js index 647109a907b3d..cd4bff6102660 100644 --- a/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js +++ b/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js @@ -285,13 +285,15 @@ describe('ReactBrowserEventEmitter', () => { putListener(CHILD, ON_CLICK_KEY, recordIDAndReturnFalse.bind(null, CHILD)); putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT)); putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT)); - spyOn(console, 'error'); + spyOnDev(console, 'error'); ReactTestUtils.Simulate.click(CHILD); expect(idCallOrder.length).toBe(3); expect(idCallOrder[0]).toBe(CHILD); expect(idCallOrder[1]).toBe(PARENT); expect(idCallOrder[2]).toBe(GRANDPARENT); - expectDev(console.error.calls.count()).toEqual(0); + if (__DEV__) { + expect(console.error.calls.count()).toEqual(0); + } }); /** diff --git a/packages/react-dom/src/__tests__/ReactChildReconciler-test.js b/packages/react-dom/src/__tests__/ReactChildReconciler-test.js index 09fa3d436e124..9bbf9176cf69a 100644 --- a/packages/react-dom/src/__tests__/ReactChildReconciler-test.js +++ b/packages/react-dom/src/__tests__/ReactChildReconciler-test.js @@ -46,7 +46,7 @@ describe('ReactChildReconciler', () => { } it('warns for duplicated array keys', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { render() { @@ -56,17 +56,19 @@ describe('ReactChildReconciler', () => { ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Keys should be unique so that components maintain their identity ' + - 'across updates. Non-unique keys may cause children to be ' + - 'duplicated and/or omitted — the behavior is unsupported and ' + - 'could change in a future version.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Keys should be unique so that components maintain their identity ' + + 'across updates. Non-unique keys may cause children to be ' + + 'duplicated and/or omitted — the behavior is unsupported and ' + + 'could change in a future version.', + ); + } }); it('warns for duplicated array keys with component stack info', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { render() { @@ -88,24 +90,24 @@ describe('ReactChildReconciler', () => { ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev( - normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]), - ).toContain( - 'Encountered two children with the same key, `1`. ' + - 'Keys should be unique so that components maintain their identity ' + - 'across updates. Non-unique keys may cause children to be ' + - 'duplicated and/or omitted — the behavior is unsupported and ' + - 'could change in a future version.', - ' in div (at **)\n' + - ' in Component (at **)\n' + - ' in Parent (at **)\n' + - ' in GrandParent (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain( + 'Encountered two children with the same key, `1`. ' + + 'Keys should be unique so that components maintain their identity ' + + 'across updates. Non-unique keys may cause children to be ' + + 'duplicated and/or omitted — the behavior is unsupported and ' + + 'could change in a future version.', + ' in div (at **)\n' + + ' in Component (at **)\n' + + ' in Parent (at **)\n' + + ' in GrandParent (at **)', + ); + } }); it('warns for duplicated iterable keys', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { render() { @@ -115,17 +117,19 @@ describe('ReactChildReconciler', () => { ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Keys should be unique so that components maintain their identity ' + - 'across updates. Non-unique keys may cause children to be ' + - 'duplicated and/or omitted — the behavior is unsupported and ' + - 'could change in a future version.', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Keys should be unique so that components maintain their identity ' + + 'across updates. Non-unique keys may cause children to be ' + + 'duplicated and/or omitted — the behavior is unsupported and ' + + 'could change in a future version.', + ); + } }); it('warns for duplicated iterable keys with component stack info', () => { - spyOn(console, 'error'); + spyOnDev(console, 'error'); class Component extends React.Component { render() { @@ -147,19 +151,19 @@ describe('ReactChildReconciler', () => { ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev( - normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]), - ).toContain( - 'Encountered two children with the same key, `1`. ' + - 'Keys should be unique so that components maintain their identity ' + - 'across updates. Non-unique keys may cause children to be ' + - 'duplicated and/or omitted — the behavior is unsupported and ' + - 'could change in a future version.', - ' in div (at **)\n' + - ' in Component (at **)\n' + - ' in Parent (at **)\n' + - ' in GrandParent (at **)', - ); + if (__DEV__) { + expect(console.error.calls.count()).toBe(1); + expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain( + 'Encountered two children with the same key, `1`. ' + + 'Keys should be unique so that components maintain their identity ' + + 'across updates. Non-unique keys may cause children to be ' + + 'duplicated and/or omitted — the behavior is unsupported and ' + + 'could change in a future version.', + ' in div (at **)\n' + + ' in Component (at **)\n' + + ' in Parent (at **)\n' + + ' in GrandParent (at **)', + ); + } }); }); diff --git a/packages/react-dom/src/__tests__/ReactComponent-test.js b/packages/react-dom/src/__tests__/ReactComponent-test.js index 0a63915429c2a..1f7da05821317 100644 --- a/packages/react-dom/src/__tests__/ReactComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactComponent-test.js @@ -45,13 +45,23 @@ describe('ReactComponent', () => { }).toThrow(); }); - it('should warn when children are mutated during render', () => { - spyOn(console, 'error'); + it('should throw (in dev) when children are mutated during render', () => { + spyOnDev(console, 'error'); function Wrapper(props) { props.children[1] =

; // Mutation is illegal return

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