toWarnInDev matcher; throw on unexpected console.error (#11786)

* Added toWarnInDev matcher and connected to 1 test
* Added .toLowPriorityWarnDev() matcher
* Reply Jest spy with custom spy. Unregister spy after toWarnDev() so unexpected console.error/warn calls will fail tests.
* console warn/error throws immediately in tests by default (if not spied on)
* Pass-thru console message before erroring to make it easier to identify
* More robustly handle unexpected warnings within try/catch
* Error message includes remaining expected warnings in addition to unexpected warning
This commit is contained in:
Brian Vaughn 2018-01-02 11:06:41 -08:00 committed by GitHub
parent 22e2bf7684
commit b5334a44e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 974 additions and 1377 deletions

View File

@ -336,10 +336,6 @@ describe('ReactART', () => {
});
describe('ReactARTComponents', () => {
function normalizeCodeLocInfo(str) {
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
}
it('should generate a <Shape> with props for drawing the Circle', () => {
const circle = renderer.create(
<Circle radius={10} stroke="green" strokeWidth={3} fill="blue" />,
@ -348,16 +344,13 @@ describe('ReactARTComponents', () => {
});
it('should warn if radius is missing on a Circle component', () => {
spyOnDev(console, 'error');
renderer.create(<Circle stroke="green" strokeWidth={3} fill="blue" />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
expect(() =>
renderer.create(<Circle stroke="green" strokeWidth={3} fill="blue" />),
).toWarnDev(
'Warning: Failed prop type: The prop `radius` is marked as required in `Circle`, ' +
'but its value is `undefined`.' +
'\n in Circle (at **)',
);
}
});
it('should generate a <Shape> with props for drawing the Rectangle', () => {
@ -368,21 +361,16 @@ describe('ReactARTComponents', () => {
});
it('should warn if width/height is missing on a Rectangle component', () => {
spyOnDev(console, 'error');
renderer.create(<Rectangle stroke="green" fill="blue" />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(2);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
expect(() =>
renderer.create(<Rectangle stroke="green" fill="blue" />),
).toWarnDev([
'Warning: Failed prop type: The prop `width` is marked as required in `Rectangle`, ' +
'but its value is `undefined`.' +
'\n in Rectangle (at **)',
);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
'Warning: Failed prop type: The prop `height` is marked as required in `Rectangle`, ' +
'but its value is `undefined`.' +
'\n in Rectangle (at **)',
);
}
]);
});
it('should generate a <Shape> with props for drawing the Wedge', () => {
@ -400,25 +388,16 @@ describe('ReactARTComponents', () => {
});
it('should warn if outerRadius/startAngle/endAngle is missing on a Wedge component', () => {
spyOnDev(console, 'error');
renderer.create(<Wedge fill="blue" />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(3);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
expect(() => renderer.create(<Wedge fill="blue" />)).toWarnDev([
'Warning: Failed prop type: The prop `outerRadius` is marked as required in `Wedge`, ' +
'but its value is `undefined`.' +
'\n in Wedge (at **)',
);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
'Warning: Failed prop type: The prop `startAngle` is marked as required in `Wedge`, ' +
'but its value is `undefined`.' +
'\n in Wedge (at **)',
);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toEqual(
'Warning: Failed prop type: The prop `endAngle` is marked as required in `Wedge`, ' +
'but its value is `undefined`.' +
'\n in Wedge (at **)',
);
}
]);
});
});

View File

@ -13,10 +13,6 @@ const React = require('react');
const ReactDOM = require('react-dom');
const ReactDOMServer = require('react-dom/server');
function normalizeCodeLocInfo(str) {
return str && str.replace(/at .+?:\d+/g, 'at **');
}
describe('CSSPropertyOperations', () => {
it('should automatically append `px` to relevant styles', () => {
const styles = {
@ -91,17 +87,13 @@ describe('CSSPropertyOperations', () => {
}
}
spyOnDev(console, 'error');
const root = document.createElement('div');
ReactDOM.render(<Comp />, root);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
expect(() => ReactDOM.render(<Comp />, root)).toWarnDev(
'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', () => {
@ -113,28 +105,21 @@ describe('CSSPropertyOperations', () => {
}
}
spyOnDev(console, 'error');
const styles = {
'-ms-transform': 'translate3d(0, 0, 0)',
'-webkit-transform': 'translate3d(0, 0, 0)',
};
const root = document.createElement('div');
ReactDOM.render(<Comp />, root);
ReactDOM.render(<Comp style={styles} />, root);
if (__DEV__) {
expect(console.error.calls.count()).toBe(2);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
expect(() => ReactDOM.render(<Comp style={styles} />, root)).toWarnDev([
'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', () => {
@ -154,25 +139,19 @@ describe('CSSPropertyOperations', () => {
}
}
spyOnDev(console, 'error');
const root = document.createElement('div');
ReactDOM.render(<Comp />, root);
if (__DEV__) {
expect(() => ReactDOM.render(<Comp />, root)).toWarnDev([
// 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', () => {
@ -193,24 +172,18 @@ describe('CSSPropertyOperations', () => {
}
}
spyOnDev(console, 'error');
const root = document.createElement('div');
ReactDOM.render(<Comp />, root);
if (__DEV__) {
expect(console.error.calls.count()).toBe(2);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
expect(() => ReactDOM.render(<Comp />, root)).toWarnDev([
"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', () => {
@ -222,18 +195,13 @@ describe('CSSPropertyOperations', () => {
}
}
spyOnDev(console, 'error');
const root = document.createElement('div');
ReactDOM.render(<Comp />, root);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
expect(() => ReactDOM.render(<Comp />, root)).toWarnDev(
'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', () => {
@ -256,18 +224,13 @@ describe('CSSPropertyOperations', () => {
}
}
spyOnDev(console, 'error');
const root = document.createElement('div');
ReactDOM.render(<Comp />, root);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
expect(() => ReactDOM.render(<Comp />, root)).toWarnDev(
'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', () => {

View File

@ -151,25 +151,22 @@ describe('DOMPropertyOperations', () => {
it('should not remove attributes for special properties', () => {
const container = document.createElement('div');
spyOnDev(console, 'error');
ReactDOM.render(
<input type="text" value="foo" onChange={function() {}} />,
container,
);
expect(container.firstChild.getAttribute('value')).toBe('foo');
expect(container.firstChild.value).toBe('foo');
expect(() =>
ReactDOM.render(
<input type="text" onChange={function() {}} />,
container,
),
).toWarnDev(
'A component is changing a controlled input of type text to be uncontrolled',
);
expect(container.firstChild.getAttribute('value')).toBe('foo');
expect(container.firstChild.value).toBe('foo');
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',
);
}
});
});
});

View File

@ -22,21 +22,17 @@ describe('EventPluginHub', () => {
});
it('should prevent non-function listeners, at dispatch', () => {
spyOnDev(console, 'error');
const node = ReactTestUtils.renderIntoDocument(
let node;
expect(() => {
node = ReactTestUtils.renderIntoDocument(
<div onClick="not a function" />,
);
expect(function() {
ReactTestUtils.SimulateNative.click(node);
}).toThrowError(
}).toWarnDev(
'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(
expect(() => ReactTestUtils.SimulateNative.click(node)).toThrowError(
'Expected `onClick` listener to be a function, instead got a value of `string` type.',
);
}
});
it('should not prevent null listeners, at dispatch', () => {

View File

@ -16,10 +16,6 @@ let React;
let ReactTestUtils;
describe('ReactChildReconciler', () => {
function normalizeCodeLocInfo(str) {
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
}
beforeEach(() => {
jest.resetModules();
@ -46,30 +42,21 @@ describe('ReactChildReconciler', () => {
}
it('warns for duplicated array keys', () => {
spyOnDev(console, 'error');
class Component extends React.Component {
render() {
return <div>{[<div key="1" />, <div key="1" />]}</div>;
}
}
ReactTestUtils.renderIntoDocument(<Component />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() => ReactTestUtils.renderIntoDocument(<Component />)).toWarnDev(
'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', () => {
spyOnDev(console, 'error');
class Component extends React.Component {
render() {
return <div>{[<div key="1" />, <div key="1" />]}</div>;
@ -88,11 +75,7 @@ describe('ReactChildReconciler', () => {
}
}
ReactTestUtils.renderIntoDocument(<GrandParent />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
expect(() => ReactTestUtils.renderIntoDocument(<GrandParent />)).toWarnDev(
'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 ' +
@ -103,34 +86,24 @@ describe('ReactChildReconciler', () => {
' in Parent (at **)\n' +
' in GrandParent (at **)',
);
}
});
it('warns for duplicated iterable keys', () => {
spyOnDev(console, 'error');
class Component extends React.Component {
render() {
return <div>{createIterable([<div key="1" />, <div key="1" />])}</div>;
}
}
ReactTestUtils.renderIntoDocument(<Component />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() => ReactTestUtils.renderIntoDocument(<Component />)).toWarnDev(
'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', () => {
spyOnDev(console, 'error');
class Component extends React.Component {
render() {
return <div>{createIterable([<div key="1" />, <div key="1" />])}</div>;
@ -149,11 +122,7 @@ describe('ReactChildReconciler', () => {
}
}
ReactTestUtils.renderIntoDocument(<GrandParent />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
expect(() => ReactTestUtils.renderIntoDocument(<GrandParent />)).toWarnDev(
'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 ' +
@ -164,6 +133,5 @@ describe('ReactChildReconciler', () => {
' in Parent (at **)\n' +
' in GrandParent (at **)',
);
}
});
});

View File

@ -13,10 +13,6 @@ describe('ReactChildren', () => {
let React;
let ReactTestUtils;
function normalizeCodeLocInfo(str) {
return str && str.replace(/at .+?:\d+/g, 'at **');
}
beforeEach(() => {
jest.resetModules();
React = require('react');
@ -352,7 +348,6 @@ describe('ReactChildren', () => {
});
it('should be called for each child in an iterable without keys', () => {
spyOnDev(console, 'error');
const threeDivIterable = {
'@@iterator': function() {
let i = 0;
@ -374,7 +369,10 @@ describe('ReactChildren', () => {
return kid;
});
const instance = <div>{threeDivIterable}</div>;
let instance;
expect(() => (instance = <div>{threeDivIterable}</div>)).toWarnDev(
'Warning: Each child in an array or iterator should have a unique "key" prop.',
);
function assertCalls() {
expect(callback.calls.count()).toBe(3);
@ -386,13 +384,6 @@ describe('ReactChildren', () => {
React.Children.forEach(instance.props.children, callback, context);
assertCalls();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Warning: Each child in an array or iterator should have a unique "key" prop.',
);
console.error.calls.reset();
}
const mappedChildren = React.Children.map(
instance.props.children,
@ -400,9 +391,6 @@ describe('ReactChildren', () => {
context,
);
assertCalls();
if (__DEV__) {
expect(console.error.calls.count()).toBe(0);
}
expect(mappedChildren).toEqual([
<div key=".0" />,
<div key=".1" />,
@ -958,28 +946,23 @@ describe('ReactChildren', () => {
describe('with fragments enabled', () => {
it('warns for keys for arrays of elements in a fragment', () => {
spyOnDev(console, 'error');
class ComponentReturningArray extends React.Component {
render() {
return [<div />, <div />];
}
}
ReactTestUtils.renderIntoDocument(<ComponentReturningArray />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() =>
ReactTestUtils.renderIntoDocument(<ComponentReturningArray />),
).toWarnDev(
'Warning: ' +
'Each child in an array or iterator should have a unique "key" prop.' +
' See https://fb.me/react-warning-keys for more information.' +
'\n in ComponentReturningArray (at **)',
);
}
});
it('does not warn when there are keys on elements in a fragment', () => {
spyOnDev(console, 'error');
class ComponentReturningArray extends React.Component {
render() {
return [<div key="foo" />, <div key="bar" />];
@ -987,25 +970,16 @@ describe('ReactChildren', () => {
}
ReactTestUtils.renderIntoDocument(<ComponentReturningArray />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(0);
}
});
it('warns for keys for arrays at the top level', () => {
spyOnDev(console, 'error');
ReactTestUtils.renderIntoDocument([<div />, <div />]);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() =>
ReactTestUtils.renderIntoDocument([<div />, <div />]),
).toWarnDev(
'Warning: ' +
'Each child in an array or iterator should have a unique "key" prop.' +
' See https://fb.me/react-warning-keys for more information.',
);
}
});
});
});

View File

@ -46,14 +46,12 @@ describe 'ReactCoffeeScriptClass', ->
expect(Foo.name).toBe 'Foo'
it 'throws if no render function is defined', ->
spyOnDev console, 'error'
class Foo extends React.Component
expect(->
expect(->
ReactDOM.render React.createElement(Foo), container
).toThrow()
if __DEV__
expect(console.error.calls.count()).toBe(1)
expect(console.error.calls.argsFor(0)[0]).toContain('No `render` method found on the returned component instance')
).toWarnDev('No `render` method found on the returned component instance')
undefined
it 'renders a simple stateless component with prop', ->
@ -150,8 +148,6 @@ describe 'ReactCoffeeScriptClass', ->
undefined
it 'should warn with non-object in the initial state property', ->
spyOnDev console, 'error'
[['an array'], 'a string', 1234].forEach (state) ->
class Foo extends React.Component
constructor: ->
@ -160,14 +156,9 @@ describe 'ReactCoffeeScriptClass', ->
render: ->
span()
expect(->
test React.createElement(Foo), 'SPAN', ''
if __DEV__
expect(console.error.calls.count()).toBe 1
expect(console.error.calls.argsFor(0)[0]).toContain(
'Foo.state: must be set to an object or null'
)
console.error.calls.reset()
).toWarnDev('Foo.state: must be set to an object or null')
undefined
it 'should render with null in the initial state property', ->
@ -287,7 +278,6 @@ describe 'ReactCoffeeScriptClass', ->
it 'warns when classic properties are defined on the instance,
but does not invoke them.', ->
spyOnDev console, 'error'
getInitialStateWasCalled = false
getDefaultPropsWasCalled = false
class Foo extends React.Component
@ -307,28 +297,20 @@ describe 'ReactCoffeeScriptClass', ->
span
className: 'foo'
expect(->
test React.createElement(Foo), 'SPAN', 'foo'
).toWarnDev([
'getInitialState was defined on Foo, a plain JavaScript class.',
'getDefaultProps was defined on Foo, a plain JavaScript class.',
'propTypes was defined as an instance property on Foo.',
'contextTypes was defined as an instance property on Foo.',
])
expect(getInitialStateWasCalled).toBe false
expect(getDefaultPropsWasCalled).toBe false
if __DEV__
expect(console.error.calls.count()).toBe 4
expect(console.error.calls.argsFor(0)[0]).toContain(
'getInitialState was defined on Foo, a plain JavaScript class.'
)
expect(console.error.calls.argsFor(1)[0]).toContain(
'getDefaultProps was defined on Foo, a plain JavaScript class.'
)
expect(console.error.calls.argsFor(2)[0]).toContain(
'propTypes was defined as an instance property on Foo.'
)
expect(console.error.calls.argsFor(3)[0]).toContain(
'contextTypes was defined as an instance property on Foo.'
)
undefined
it 'does not warn about getInitialState() on class components
if state is also defined.', ->
spyOnDev console, 'error'
class Foo extends React.Component
constructor: (props) ->
super props
@ -342,12 +324,9 @@ describe 'ReactCoffeeScriptClass', ->
className: 'foo'
test React.createElement(Foo), 'SPAN', 'foo'
if __DEV__
expect(console.error.calls.count()).toBe 0
undefined
it 'should warn when misspelling shouldComponentUpdate', ->
spyOnDev console, 'error'
class NamedComponent extends React.Component
componentShouldUpdate: ->
false
@ -356,10 +335,9 @@ describe 'ReactCoffeeScriptClass', ->
span
className: 'foo'
expect(->
test React.createElement(NamedComponent), 'SPAN', 'foo'
if __DEV__
expect(console.error.calls.count()).toBe 1
expect(console.error.calls.argsFor(0)[0]).toBe(
).toWarnDev(
'Warning: NamedComponent has a method called componentShouldUpdate().
Did you mean shouldComponentUpdate()? The name is phrased as a
question because the function is expected to return a value.'
@ -367,7 +345,6 @@ describe 'ReactCoffeeScriptClass', ->
undefined
it 'should warn when misspelling componentWillReceiveProps', ->
spyOnDev console, 'error'
class NamedComponent extends React.Component
componentWillRecieveProps: ->
false
@ -376,27 +353,25 @@ describe 'ReactCoffeeScriptClass', ->
span
className: 'foo'
expect(->
test React.createElement(NamedComponent), 'SPAN', 'foo'
if __DEV__
expect(console.error.calls.count()).toBe 1
expect(console.error.calls.argsFor(0)[0]).toBe(
).toWarnDev(
'Warning: NamedComponent has a method called componentWillRecieveProps().
Did you mean componentWillReceiveProps()?'
)
undefined
it 'should throw AND warn when trying to access classic APIs', ->
spyOnDev console, 'warn'
instance =
test Inner(name: 'foo'), 'DIV', 'foo'
expect(->
expect(-> instance.replaceState {}).toThrow()
expect(-> instance.isMounted()).toThrow()
if __DEV__
expect(console.warn.calls.count()).toBe 2
expect(console.warn.calls.argsFor(0)[0]).toContain(
).toLowPriorityWarnDev(
'replaceState(...) is deprecated in plain JavaScript React classes'
)
expect(console.warn.calls.argsFor(1)[0]).toContain(
expect(->
expect(-> instance.isMounted()).toThrow()
).toLowPriorityWarnDev(
'isMounted(...) is deprecated in plain JavaScript React classes'
)
undefined

View File

@ -21,10 +21,6 @@ let ReactDOM;
let ReactTestUtils;
describe('ReactContextValidator', () => {
function normalizeCodeLocInfo(str) {
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
}
beforeEach(() => {
jest.resetModules();
@ -161,8 +157,6 @@ describe('ReactContextValidator', () => {
});
it('should check context types', () => {
spyOnDev(console, 'error');
class Component extends React.Component {
render() {
return <div />;
@ -172,17 +166,12 @@ describe('ReactContextValidator', () => {
foo: PropTypes.string.isRequired,
};
ReactTestUtils.renderIntoDocument(<Component />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() => ReactTestUtils.renderIntoDocument(<Component />)).toWarnDev(
'Warning: Failed context type: ' +
'The context `foo` is marked as required in `Component`, but its value ' +
'is `undefined`.\n' +
' in Component (at **)',
);
}
class ComponentInFooStringContext extends React.Component {
getChildContext() {
@ -199,15 +188,11 @@ describe('ReactContextValidator', () => {
foo: PropTypes.string,
};
// No additional errors expected
ReactTestUtils.renderIntoDocument(
<ComponentInFooStringContext fooValue={'bar'} />,
);
// Previous call should not error
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
}
class ComponentInFooNumberContext extends React.Component {
getChildContext() {
return {
@ -223,25 +208,20 @@ describe('ReactContextValidator', () => {
foo: PropTypes.number,
};
expect(() =>
ReactTestUtils.renderIntoDocument(
<ComponentInFooNumberContext fooValue={123} />,
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(2);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
),
).toWarnDev(
'Warning: Failed context type: ' +
'Invalid context `foo` of type `number` supplied ' +
'to `Component`, expected `string`.\n' +
' in Component (at **)\n' +
' in ComponentInFooNumberContext (at **)',
);
}
});
it('should check child context types', () => {
spyOnDev(console, 'error');
class Component extends React.Component {
getChildContext() {
return this.props.testContext;
@ -256,46 +236,35 @@ describe('ReactContextValidator', () => {
bar: PropTypes.number,
};
ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() =>
ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />),
).toWarnDev(
'Warning: Failed child context type: ' +
'The child context `foo` is marked as required in `Component`, but its ' +
'value is `undefined`.\n' +
' in Component (at **)',
);
}
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(2);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
expect(() =>
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />),
).toWarnDev(
'Warning: Failed child context type: ' +
'Invalid child context `foo` of type `number` ' +
'supplied to `Component`, expected `string`.\n' +
' in Component (at **)',
);
}
// No additional errors expected
ReactTestUtils.renderIntoDocument(
<Component testContext={{foo: 'foo', bar: 123}} />,
);
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 'foo'}} />);
if (__DEV__) {
// Previous calls should not log errors
expect(console.error.calls.count()).toBe(2);
}
});
// TODO (bvaughn) Remove this test and the associated behavior in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
it('should warn (but not error) if getChildContext method is missing', () => {
spyOnDev(console, 'error');
class ComponentA extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
@ -313,40 +282,28 @@ describe('ReactContextValidator', () => {
}
}
ReactTestUtils.renderIntoDocument(<ComponentA />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() => ReactTestUtils.renderIntoDocument(<ComponentA />)).toWarnDev(
'Warning: ComponentA.childContextTypes is specified but there is no ' +
'getChildContext() method on the instance. You can either define ' +
'getChildContext() on ComponentA or remove childContextTypes from it.',
);
}
// Warnings should be deduped by component type
ReactTestUtils.renderIntoDocument(<ComponentA />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
}
ReactTestUtils.renderIntoDocument(<ComponentB />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(2);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
expect(() => ReactTestUtils.renderIntoDocument(<ComponentB />)).toWarnDev(
'Warning: ComponentB.childContextTypes is specified but there is no ' +
'getChildContext() method on the instance. You can either define ' +
'getChildContext() on ComponentB or remove childContextTypes from it.',
);
}
});
// TODO (bvaughn) Remove this test and the associated behavior in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
it('should pass parent context if getChildContext method is missing', () => {
spyOnDev(console, 'error');
class ParentContextProvider extends React.Component {
static childContextTypes = {
foo: PropTypes.number,
foo: PropTypes.string,
};
getChildContext() {
return {
@ -379,7 +336,15 @@ describe('ReactContextValidator', () => {
foo: PropTypes.string.isRequired,
};
ReactTestUtils.renderIntoDocument(<ParentContextProvider />);
expect(() =>
ReactTestUtils.renderIntoDocument(<ParentContextProvider />),
).toWarnDev([
'Warning: MiddleMissingContext.childContextTypes is specified but there is no getChildContext() method on the ' +
'instance. You can either define getChildContext() on MiddleMissingContext or remove childContextTypes from ' +
'it.',
'Warning: Failed context type: The context `bar` is marked as required in `ChildContextConsumer`, but its ' +
'value is `undefined`.',
]);
expect(childContext.bar).toBeUndefined();
expect(childContext.foo).toBe('FOO');
});

View File

@ -56,17 +56,13 @@ describe('ReactES6Class', () => {
});
it('throws if no render function is defined', () => {
spyOnDev(console, 'error');
class Foo extends React.Component {}
expect(() => ReactDOM.render(<Foo />, container)).toThrow();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
expect(() =>
expect(() => ReactDOM.render(<Foo />, container)).toThrow(),
).toWarnDev(
'Warning: Foo(...): No `render` method found on the returned component ' +
'instance: you may have forgotten to define `render`.',
);
}
});
it('renders a simple stateless component with prop', () => {
@ -164,7 +160,6 @@ describe('ReactES6Class', () => {
});
it('should warn with non-object in the initial state property', () => {
spyOnDev(console, 'error');
[['an array'], 'a string', 1234].forEach(function(state) {
class Foo extends React.Component {
constructor() {
@ -175,14 +170,9 @@ describe('ReactES6Class', () => {
return <span />;
}
}
test(<Foo />, 'SPAN', '');
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() => test(<Foo />, 'SPAN', '')).toWarnDev(
'Foo.state: must be set to an object or null',
);
console.error.calls.reset();
}
});
});
@ -310,7 +300,6 @@ describe('ReactES6Class', () => {
});
it('warns when classic properties are defined on the instance, but does not invoke them.', () => {
spyOnDev(console, 'error');
let getDefaultPropsWasCalled = false;
let getInitialStateWasCalled = false;
class Foo extends React.Component {
@ -331,28 +320,18 @@ describe('ReactES6Class', () => {
return <span className="foo" />;
}
}
test(<Foo />, 'SPAN', 'foo');
expect(() => test(<Foo />, 'SPAN', 'foo')).toWarnDev([
'getInitialState was defined on Foo, a plain JavaScript class.',
'getDefaultProps was defined on Foo, a plain JavaScript class.',
'propTypes was defined as an instance property on Foo.',
'contextTypes was defined as an instance property on Foo.',
]);
expect(getInitialStateWasCalled).toBe(false);
expect(getDefaultPropsWasCalled).toBe(false);
if (__DEV__) {
expect(console.error.calls.count()).toBe(4);
expect(console.error.calls.argsFor(0)[0]).toContain(
'getInitialState was defined on Foo, a plain JavaScript class.',
);
expect(console.error.calls.argsFor(1)[0]).toContain(
'getDefaultProps was defined on Foo, a plain JavaScript class.',
);
expect(console.error.calls.argsFor(2)[0]).toContain(
'propTypes was defined as an instance property on Foo.',
);
expect(console.error.calls.argsFor(3)[0]).toContain(
'contextTypes was defined as an instance property on Foo.',
);
}
});
it('does not warn about getInitialState() on class components if state is also defined.', () => {
spyOnDev(console, 'error');
class Foo extends React.Component {
state = this.getInitialState();
getInitialState() {
@ -363,14 +342,9 @@ describe('ReactES6Class', () => {
}
}
test(<Foo />, 'SPAN', 'foo');
if (__DEV__) {
expect(console.error.calls.count()).toBe(0);
}
});
it('should warn when misspelling shouldComponentUpdate', () => {
spyOnDev(console, 'error');
class NamedComponent extends React.Component {
componentShouldUpdate() {
return false;
@ -379,22 +353,16 @@ describe('ReactES6Class', () => {
return <span className="foo" />;
}
}
test(<NamedComponent />, 'SPAN', 'foo');
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
expect(() => test(<NamedComponent />, 'SPAN', 'foo')).toWarnDev(
'Warning: ' +
'NamedComponent has a method called componentShouldUpdate(). Did you ' +
'mean shouldComponentUpdate()? The name is phrased as a question ' +
'because the function is expected to return a value.',
);
}
});
it('should warn when misspelling componentWillReceiveProps', () => {
spyOnDev(console, 'error');
class NamedComponent extends React.Component {
componentWillRecieveProps() {
return false;
@ -403,32 +371,26 @@ describe('ReactES6Class', () => {
return <span className="foo" />;
}
}
test(<NamedComponent />, 'SPAN', 'foo');
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
expect(() => test(<NamedComponent />, 'SPAN', 'foo')).toWarnDev(
'Warning: ' +
'NamedComponent has a method called componentWillRecieveProps(). Did ' +
'you mean componentWillReceiveProps()?',
);
}
});
it('should throw AND warn when trying to access classic APIs', () => {
spyOnDev(console, 'warn');
const instance = test(<Inner name="foo" />, 'DIV', 'foo');
expect(() => instance.replaceState({})).toThrow();
expect(() => instance.isMounted()).toThrow();
if (__DEV__) {
expect(console.warn.calls.count()).toBe(2);
expect(console.warn.calls.argsFor(0)[0]).toContain(
expect(() =>
expect(() => instance.replaceState({})).toThrow(),
).toLowPriorityWarnDev(
'replaceState(...) is deprecated in plain JavaScript React classes',
);
expect(console.warn.calls.argsFor(1)[0]).toContain(
expect(() =>
expect(() => instance.isMounted()).toThrow(),
).toLowPriorityWarnDev(
'isMounted(...) is deprecated in plain JavaScript React classes',
);
}
});
it('supports this.context passed via getChildContext', () => {

View File

@ -58,7 +58,6 @@ describe('ReactElement', () => {
});
it('should warn when `key` is being accessed on composite element', () => {
spyOnDev(console, 'error');
const container = document.createElement('div');
class Child extends React.Component {
render() {
@ -76,41 +75,25 @@ describe('ReactElement', () => {
);
}
}
if (__DEV__) {
expect(console.error.calls.count()).toBe(0);
}
ReactDOM.render(<Parent />, container);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() => ReactDOM.render(<Parent />, container)).toWarnDev(
'Child: `key` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://fb.me/react-special-props)',
);
}
});
it('should warn when `key` is being accessed on a host element', () => {
spyOnDev(console, 'error');
const element = <div key="3" />;
if (__DEV__) {
expect(console.error.calls.count()).toBe(0);
}
void element.props.key;
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() => void element.props.key).toWarnDev(
'div: `key` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://fb.me/react-special-props)',
);
}
});
it('should warn when `ref` is being accessed', () => {
spyOnDev(console, 'error');
const container = document.createElement('div');
class Child extends React.Component {
render() {
@ -126,19 +109,12 @@ describe('ReactElement', () => {
);
}
}
if (__DEV__) {
expect(console.error.calls.count()).toBe(0);
}
ReactDOM.render(<Parent />, container);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() => ReactDOM.render(<Parent />, container)).toWarnDev(
'Child: `ref` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://fb.me/react-special-props)',
);
}
});
it('allows a string to be passed as the type', () => {

View File

@ -257,16 +257,11 @@ describe('ReactElementClone', () => {
});
it('warns for keys for arrays of elements in rest args', () => {
spyOnDev(console, 'error');
React.cloneElement(<div />, null, [<div />, <div />]);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() =>
React.cloneElement(<div />, null, [<div />, <div />]),
).toWarnDev(
'Each child in an array or iterator should have a unique "key" prop.',
);
}
});
it('does not warns for arrays of elements with keys', () => {
@ -282,7 +277,6 @@ describe('ReactElementClone', () => {
});
it('should check declared prop types after clone', () => {
spyOnDev(console, 'error');
class Component extends React.Component {
static propTypes = {
color: PropTypes.string.isRequired,
@ -303,10 +297,9 @@ describe('ReactElementClone', () => {
});
}
}
ReactTestUtils.renderIntoDocument(React.createElement(GrandParent));
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
expect(() =>
ReactTestUtils.renderIntoDocument(React.createElement(GrandParent)),
).toWarnDev(
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `Component`, ' +
'expected `string`.\n' +
@ -314,7 +307,6 @@ describe('ReactElementClone', () => {
' in Parent (created by GrandParent)\n' +
' in GrandParent',
);
}
});
it('should ignore key and ref warning getters', () => {

View File

@ -18,10 +18,6 @@ let ReactDOM;
let ReactTestUtils;
describe('ReactElementValidator', () => {
function normalizeCodeLocInfo(str) {
return str && str.replace(/at .+?:\d+/g, 'at **');
}
let ComponentClass;
beforeEach(() => {
@ -39,21 +35,16 @@ describe('ReactElementValidator', () => {
});
it('warns for keys for arrays of elements in rest args', () => {
spyOnDev(console, 'error');
const Component = React.createFactory(ComponentClass);
expect(() => {
Component(null, [Component(), Component()]);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
}).toWarnDev(
'Each child in an array or iterator should have a unique "key" prop.',
);
}
});
it('warns for keys for arrays of elements with owner info', () => {
spyOnDev(console, 'error');
const Component = React.createFactory(ComponentClass);
class InnerClass extends React.Component {
@ -70,59 +61,46 @@ describe('ReactElementValidator', () => {
}
}
expect(() => {
ReactTestUtils.renderIntoDocument(React.createElement(ComponentWrapper));
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
}).toWarnDev(
'Each child in an array or iterator should have a unique "key" prop.' +
'\n\nCheck the render method of `InnerClass`. ' +
'It was passed a child from ComponentWrapper. ',
);
}
});
it('warns for keys for arrays with no owner or parent info', () => {
spyOnDev(console, 'error');
function Anonymous() {
return <div />;
}
Object.defineProperty(Anonymous, 'name', {value: undefined});
const divs = [<div />, <div />];
ReactTestUtils.renderIntoDocument(<Anonymous>{divs}</Anonymous>);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() => {
ReactTestUtils.renderIntoDocument(<Anonymous>{divs}</Anonymous>);
}).toWarnDev(
'Warning: Each child in an array or iterator should have a unique ' +
'"key" prop. See https://fb.me/react-warning-keys for more information.\n' +
' in div (at **)',
);
}
});
it('warns for keys for arrays of elements with no owner info', () => {
spyOnDev(console, 'error');
const divs = [<div />, <div />];
ReactTestUtils.renderIntoDocument(<div>{divs}</div>);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() => {
ReactTestUtils.renderIntoDocument(<div>{divs}</div>);
}).toWarnDev(
'Warning: Each child in an array or iterator should have a unique ' +
'"key" prop.\n\nCheck the top-level render call using <div>. See ' +
'https://fb.me/react-warning-keys for more information.\n' +
' in div (at **)',
);
}
});
it('warns for keys with component stack info', () => {
spyOnDev(console, 'error');
function Component() {
return <div>{[<div />, <div />]}</div>;
}
@ -135,11 +113,7 @@ describe('ReactElementValidator', () => {
return <Parent child={<Component />} />;
}
ReactTestUtils.renderIntoDocument(<GrandParent />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() => ReactTestUtils.renderIntoDocument(<GrandParent />)).toWarnDev(
'Warning: Each child in an array or iterator should have a unique ' +
'"key" prop.\n\nCheck the render method of `Component`. See ' +
'https://fb.me/react-warning-keys for more information.\n' +
@ -148,7 +122,6 @@ describe('ReactElementValidator', () => {
' in Parent (at **)\n' +
' in GrandParent (at **)',
);
}
});
it('does not warn for keys when passing children down', () => {
@ -170,7 +143,6 @@ describe('ReactElementValidator', () => {
});
it('warns for keys for iterables of elements in rest args', () => {
spyOnDev(console, 'error');
const Component = React.createFactory(ComponentClass);
const iterable = {
@ -185,14 +157,9 @@ describe('ReactElementValidator', () => {
},
};
Component(null, iterable);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() => Component(null, iterable)).toWarnDev(
'Each child in an array or iterator should have a unique "key" prop.',
);
}
});
it('does not warns for arrays of elements with keys', () => {
@ -238,7 +205,6 @@ describe('ReactElementValidator', () => {
// In this test, we're making sure that if a proptype error is found in a
// component, we give a small hint as to which parent instantiated that
// component as per warnings about key usage in ReactElementValidator.
spyOnDev(console, 'error');
function MyComp(props) {
return React.createElement('div', null, 'My color is ' + props.color);
}
@ -248,89 +214,72 @@ describe('ReactElementValidator', () => {
function ParentComp() {
return React.createElement(MyComp, {color: 123});
}
expect(() => {
ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
if (__DEV__) {
expect(console.error.calls.argsFor(0)[0]).toBe(
}).toWarnDev(
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
'expected `string`.\n' +
' in MyComp (created by ParentComp)\n' +
' in ParentComp',
);
}
});
it('gives a helpful error when passing invalid types', () => {
spyOnDev(console, 'error');
expect(() => {
React.createElement(undefined);
React.createElement(null);
React.createElement(true);
React.createElement({x: 17});
React.createElement({});
if (__DEV__) {
expect(console.error.calls.count()).toBe(5);
expect(console.error.calls.argsFor(0)[0]).toBe(
}).toWarnDev([
'Warning: React.createElement: 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.',
);
expect(console.error.calls.argsFor(1)[0]).toBe(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
'components) but got: null.',
);
expect(console.error.calls.argsFor(2)[0]).toBe(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
'components) but got: boolean.',
);
expect(console.error.calls.argsFor(3)[0]).toBe(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
'components) but got: object.',
);
expect(console.error.calls.argsFor(4)[0]).toBe(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
'components) but got: object. You likely forgot to export your ' +
"component from the file it's defined in, or you might have mixed up " +
'default and named imports.',
);
}
]);
// Should not log any additional warnings
React.createElement('div');
if (__DEV__) {
expect(console.error.calls.count()).toBe(5);
}
});
it('includes the owner name when passing null, undefined, boolean, or number', () => {
spyOnDev(console, 'error');
function ParentComp() {
return React.createElement(null);
}
expect(function() {
expect(() => {
expect(() => {
ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
}).toThrowError(
'Element type is invalid: expected a string (for built-in components) ' +
'or a class/function (for composite components) but got: null.' +
(__DEV__ ? '\n\nCheck the render method of `ParentComp`.' : ''),
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
}).toWarnDev(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
'components) but got: null.' +
'\n\nCheck the render method of `ParentComp`.\n in ParentComp',
);
}
});
it('should check default prop values', () => {
spyOnDev(console, 'error');
class Component extends React.Component {
static propTypes = {prop: PropTypes.string.isRequired};
static defaultProps = {prop: null};
@ -339,21 +288,16 @@ describe('ReactElementValidator', () => {
}
}
ReactTestUtils.renderIntoDocument(React.createElement(Component));
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
expect(() =>
ReactTestUtils.renderIntoDocument(React.createElement(Component)),
).toWarnDev(
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
'`Component`, but its value is `null`.\n' +
' in Component',
);
}
});
it('should not check the default for explicit null', () => {
spyOnDev(console, 'error');
class Component extends React.Component {
static propTypes = {prop: PropTypes.string.isRequired};
static defaultProps = {prop: 'text'};
@ -362,23 +306,18 @@ describe('ReactElementValidator', () => {
}
}
expect(() => {
ReactTestUtils.renderIntoDocument(
React.createElement(Component, {prop: null}),
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
}).toWarnDev(
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
'`Component`, but its value is `null`.\n' +
' in Component',
);
}
});
it('should check declared prop types', () => {
spyOnDev(console, 'error');
class Component extends React.Component {
static propTypes = {
prop: PropTypes.string.isRequired,
@ -388,36 +327,26 @@ describe('ReactElementValidator', () => {
}
}
expect(() => {
ReactTestUtils.renderIntoDocument(React.createElement(Component));
ReactTestUtils.renderIntoDocument(
React.createElement(Component, {prop: 42}),
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(2);
expect(console.error.calls.argsFor(0)[0]).toBe(
}).toWarnDev([
'Warning: Failed prop type: ' +
'The prop `prop` is marked as required in `Component`, but its value ' +
'is `undefined`.\n' +
' in Component',
);
expect(console.error.calls.argsFor(1)[0]).toBe(
'Warning: Failed prop type: ' +
'Invalid prop `prop` of type `number` supplied to ' +
'`Component`, expected `string`.\n' +
' in Component',
);
}
]);
// Should not error for strings
ReactTestUtils.renderIntoDocument(
React.createElement(Component, {prop: 'string'}),
);
if (__DEV__) {
// Should not error for strings
expect(console.error.calls.count()).toBe(2);
}
});
it('should warn if a PropType creator is used as a PropType', () => {
@ -432,24 +361,20 @@ describe('ReactElementValidator', () => {
}
}
expect(() => {
ReactTestUtils.renderIntoDocument(
React.createElement(Component, {myProp: {value: 'hi'}}),
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
}).toWarnDev(
'Warning: Component: type specification of prop `myProp` is invalid; ' +
'the type checker function must return `null` or an `Error` but ' +
'returned a function. You may have forgotten to pass an argument to ' +
'the type checker creator (arrayOf, instanceOf, objectOf, oneOf, ' +
'oneOfType, and shape all require an argument).',
);
}
});
it('should warn if component declares PropTypes instead of propTypes', () => {
spyOnDevAndProd(console, 'error');
class MisspelledPropTypesComponent extends React.Component {
static PropTypes = {
prop: PropTypes.string,
@ -459,37 +384,29 @@ describe('ReactElementValidator', () => {
}
}
expect(() => {
ReactTestUtils.renderIntoDocument(
React.createElement(MisspelledPropTypesComponent, {prop: 'Hi'}),
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
}).toWarnDev(
'Warning: Component MisspelledPropTypesComponent declared `PropTypes` ' +
'instead of `propTypes`. Did you misspell the property assignment?',
);
}
});
it('should warn when accessing .type on an element factory', () => {
spyOnDev(console, 'warn');
function TestComponent() {
return <div />;
}
const TestFactory = React.createFactory(TestComponent);
expect(TestFactory.type).toBe(TestComponent);
if (__DEV__) {
expect(console.warn.calls.count()).toBe(1);
expect(console.warn.calls.argsFor(0)[0]).toBe(
let TestFactory = React.createFactory(TestComponent);
expect(() => TestFactory.type).toLowPriorityWarnDev(
'Warning: Factory.type is deprecated. Access the class directly before ' +
'passing it to createFactory.',
);
}
// Warn once, not again
expect(TestFactory.type).toBe(TestComponent);
if (__DEV__) {
expect(console.warn.calls.count()).toBe(1);
}
});
it('does not warn when using DOM node as children', () => {
@ -544,18 +461,15 @@ describe('ReactElementValidator', () => {
});
it('does not blow up on key warning with undefined type', () => {
spyOnDev(console, 'error');
const Foo = undefined;
expect(() => {
void <Foo>{[<div />]}</Foo>;
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
}).toWarnDev(
'Warning: React.createElement: 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 your code at **.',
);
}
});
});

View File

@ -17,10 +17,6 @@ let ReactTestUtils;
let PropTypes;
describe('ReactJSXElementValidator', () => {
function normalizeCodeLocInfo(str) {
return str && str.replace(/at .+?:\d+/g, 'at **');
}
let Component;
let RequiredPropComponent;
@ -48,23 +44,16 @@ describe('ReactJSXElementValidator', () => {
});
it('warns for keys for arrays of elements in children position', () => {
spyOnDev(console, 'error');
expect(() =>
ReactTestUtils.renderIntoDocument(
<Component>{[<Component />, <Component />]}</Component>,
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
),
).toWarnDev(
'Each child in an array or iterator should have a unique "key" prop.',
);
}
});
it('warns for keys for arrays of elements with owner info', () => {
spyOnDev(console, 'error');
class InnerComponent extends React.Component {
render() {
return <Component>{this.props.childSet}</Component>;
@ -77,21 +66,16 @@ describe('ReactJSXElementValidator', () => {
}
}
ReactTestUtils.renderIntoDocument(<ComponentWrapper />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() =>
ReactTestUtils.renderIntoDocument(<ComponentWrapper />),
).toWarnDev(
'Each child in an array or iterator should have a unique "key" prop.' +
'\n\nCheck the render method of `InnerComponent`. ' +
'It was passed a child from ComponentWrapper. ',
);
}
});
it('warns for keys for iterables of elements in rest args', () => {
spyOnDev(console, 'error');
const iterable = {
'@@iterator': function() {
let i = 0;
@ -104,14 +88,11 @@ describe('ReactJSXElementValidator', () => {
},
};
ReactTestUtils.renderIntoDocument(<Component>{iterable}</Component>);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() =>
ReactTestUtils.renderIntoDocument(<Component>{iterable}</Component>),
).toWarnDev(
'Each child in an array or iterator should have a unique "key" prop.',
);
}
});
it('does not warn for arrays of elements with keys', () => {
@ -173,7 +154,6 @@ describe('ReactJSXElementValidator', () => {
// In this test, we're making sure that if a proptype error is found in a
// component, we give a small hint as to which parent instantiated that
// component as per warnings about key usage in ReactElementValidator.
spyOnDev(console, 'error');
class MyComp extends React.Component {
render() {
return <div>My color is {this.color}</div>;
@ -187,20 +167,16 @@ describe('ReactJSXElementValidator', () => {
return <MyComp color={123} />;
}
}
ReactTestUtils.renderIntoDocument(<ParentComp />);
if (__DEV__) {
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() => ReactTestUtils.renderIntoDocument(<ParentComp />)).toWarnDev(
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
'expected `string`.\n' +
' in MyComp (at **)\n' +
' in ParentComp (at **)',
);
}
});
it('should update component stack after receiving next element', () => {
spyOnDev(console, 'error');
function MyComp() {
return null;
}
@ -221,13 +197,9 @@ describe('ReactJSXElementValidator', () => {
const container = document.createElement('div');
ReactDOM.render(<ParentComp warn={false} />, container);
ReactDOM.render(<ParentComp warn={true} />, container);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
// The warning should have the full stack with line numbers.
// If it doesn't, it means we're using information from the old element.
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() =>
ReactDOM.render(<ParentComp warn={true} />, container),
).toWarnDev(
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
'expected `string`.\n' +
@ -235,7 +207,6 @@ describe('ReactJSXElementValidator', () => {
' in MiddleComp (at **)\n' +
' in ParentComp (at **)',
);
}
});
it('gives a helpful error when passing null, undefined, or boolean', () => {
@ -243,13 +214,7 @@ describe('ReactJSXElementValidator', () => {
const Null = null;
const True = true;
const Div = 'div';
spyOnDev(console, 'error');
void <Undefined />;
void <Null />;
void <True />;
if (__DEV__) {
expect(console.error.calls.count()).toBe(3);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() => void <Undefined />).toWarnDev(
'Warning: React.createElement: 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 ' +
@ -257,86 +222,64 @@ describe('ReactJSXElementValidator', () => {
'default and named imports.' +
'\n\nCheck your code at **.',
);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
expect(() => void <Null />).toWarnDev(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
'components) but got: null.' +
'\n\nCheck your code at **.',
);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe(
expect(() => void <True />).toWarnDev(
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
'components) but got: boolean.' +
'\n\nCheck your code at **.',
);
}
// No error expected
void <Div />;
if (__DEV__) {
expect(console.error.calls.count()).toBe(3);
}
});
it('should check default prop values', () => {
spyOnDev(console, 'error');
RequiredPropComponent.defaultProps = {prop: null};
ReactTestUtils.renderIntoDocument(<RequiredPropComponent />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() =>
ReactTestUtils.renderIntoDocument(<RequiredPropComponent />),
).toWarnDev(
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
'`RequiredPropComponent`, but its value is `null`.\n' +
' in RequiredPropComponent (at **)',
);
}
});
it('should not check the default for explicit null', () => {
spyOnDev(console, 'error');
ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop={null} />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() =>
ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop={null} />),
).toWarnDev(
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
'`RequiredPropComponent`, but its value is `null`.\n' +
' in RequiredPropComponent (at **)',
);
}
});
it('should check declared prop types', () => {
spyOnDev(console, 'error');
ReactTestUtils.renderIntoDocument(<RequiredPropComponent />);
ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop={42} />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(2);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
expect(() =>
ReactTestUtils.renderIntoDocument(<RequiredPropComponent />),
).toWarnDev(
'Warning: Failed prop type: ' +
'The prop `prop` is marked as required in `RequiredPropComponent`, but ' +
'its value is `undefined`.\n' +
' in RequiredPropComponent (at **)',
);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
expect(() =>
ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop={42} />),
).toWarnDev(
'Warning: Failed prop type: ' +
'Invalid prop `prop` of type `number` supplied to ' +
'`RequiredPropComponent`, expected `string`.\n' +
' in RequiredPropComponent (at **)',
);
}
ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop="string" />);
if (__DEV__) {
// Should not error for strings
expect(console.error.calls.count()).toBe(2);
}
ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop="string" />);
});
it('should warn on invalid prop types', () => {
@ -344,7 +287,6 @@ describe('ReactJSXElementValidator', () => {
// for us to issue a warning earlier than element creation when the error
// actually occurs. Since this step is skipped in production, we should just
// warn instead of throwing for this case.
spyOnDev(console, 'error');
class NullPropTypeComponent extends React.Component {
render() {
return <span>{this.props.prop}</span>;
@ -353,18 +295,15 @@ describe('ReactJSXElementValidator', () => {
NullPropTypeComponent.propTypes = {
prop: null,
};
ReactTestUtils.renderIntoDocument(<NullPropTypeComponent />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() =>
ReactTestUtils.renderIntoDocument(<NullPropTypeComponent />),
).toWarnDev(
'NullPropTypeComponent: prop type `prop` is invalid; it must be a ' +
'function, usually from the `prop-types` package,',
);
}
});
it('should warn on invalid context types', () => {
spyOnDev(console, 'error');
class NullContextTypeComponent extends React.Component {
render() {
return <span>{this.props.prop}</span>;
@ -373,18 +312,15 @@ describe('ReactJSXElementValidator', () => {
NullContextTypeComponent.contextTypes = {
prop: null,
};
ReactTestUtils.renderIntoDocument(<NullContextTypeComponent />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() =>
ReactTestUtils.renderIntoDocument(<NullContextTypeComponent />),
).toWarnDev(
'NullContextTypeComponent: context type `prop` is invalid; it must ' +
'be a function, usually from the `prop-types` package,',
);
}
});
it('should warn if getDefaultProps is specificed on the class', () => {
spyOnDev(console, 'error');
class GetDefaultPropsComponent extends React.Component {
render() {
return <span>{this.props.prop}</span>;
@ -393,18 +329,15 @@ describe('ReactJSXElementValidator', () => {
GetDefaultPropsComponent.getDefaultProps = () => ({
prop: 'foo',
});
ReactTestUtils.renderIntoDocument(<GetDefaultPropsComponent />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() =>
ReactTestUtils.renderIntoDocument(<GetDefaultPropsComponent />),
).toWarnDev(
'getDefaultProps is only used on classic React.createClass definitions.' +
' Use a static property named `defaultProps` instead.',
);
}
});
it('should warn if component declares PropTypes instead of propTypes', () => {
spyOnDevAndProd(console, 'error');
class MisspelledPropTypesComponent extends React.Component {
render() {
return <span>{this.props.prop}</span>;
@ -413,46 +346,30 @@ describe('ReactJSXElementValidator', () => {
MisspelledPropTypesComponent.PropTypes = {
prop: PropTypes.string,
};
expect(() =>
ReactTestUtils.renderIntoDocument(
<MisspelledPropTypesComponent prop="hi" />,
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
),
).toWarnDev(
'Warning: Component MisspelledPropTypesComponent declared `PropTypes` ' +
'instead of `propTypes`. Did you misspell the property assignment?',
);
}
});
it('warns for fragments with illegal attributes', () => {
spyOnDev(console, 'error');
class Foo extends React.Component {
render() {
return (
<React.Fragment a={1} b={2}>
hello
</React.Fragment>
);
return <React.Fragment a={1}>hello</React.Fragment>;
}
}
ReactTestUtils.renderIntoDocument(<Foo />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain('Invalid prop `');
expect(console.error.calls.argsFor(0)[0]).toContain(
'` supplied to `React.Fragment`. React.Fragment ' +
expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toWarnDev(
'Invalid prop `a` supplied to `React.Fragment`. React.Fragment ' +
'can only have `key` and `children` props.',
);
}
});
it('warns for fragments with refs', () => {
spyOnDev(console, 'error');
class Foo extends React.Component {
render() {
return (
@ -466,14 +383,9 @@ describe('ReactJSXElementValidator', () => {
}
}
ReactTestUtils.renderIntoDocument(<Foo />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toWarnDev(
'Invalid attribute `ref` supplied to `React.Fragment`.',
);
}
});
it('does not warn for fragments of multiple elements without keys', () => {
@ -486,21 +398,14 @@ describe('ReactJSXElementValidator', () => {
});
it('warns for fragments of multiple elements with same key', () => {
spyOnDev(console, 'error');
expect(() =>
ReactTestUtils.renderIntoDocument(
<React.Fragment>
<span key="a">1</span>
<span key="a">2</span>
<span key="b">3</span>
</React.Fragment>,
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Encountered two children with the same key, `a`.',
);
}
),
).toWarnDev('Encountered two children with the same key, `a`.');
});
});

View File

@ -62,7 +62,6 @@ describe('ReactPureComponent', () => {
});
it('can override shouldComponentUpdate', () => {
spyOnDev(console, 'error');
let renders = 0;
class Component extends React.PureComponent {
render() {
@ -73,18 +72,15 @@ describe('ReactPureComponent', () => {
return true;
}
}
const container = document.createElement('div');
ReactDOM.render(<Component />, container);
ReactDOM.render(<Component />, container);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
'Warning: ' +
'Component has a method called shouldComponentUpdate(). ' +
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
'Please extend React.Component if shouldComponentUpdate is used.',
);
}
ReactDOM.render(<Component />, container);
expect(renders).toBe(2);
});
@ -103,8 +99,6 @@ describe('ReactPureComponent', () => {
});
it('should warn when shouldComponentUpdate is defined on React.PureComponent', () => {
spyOnDev(console, 'error');
class PureComponent extends React.PureComponent {
shouldComponentUpdate() {
return true;
@ -114,16 +108,11 @@ describe('ReactPureComponent', () => {
}
}
const container = document.createElement('div');
ReactDOM.render(<PureComponent />, container);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
expect(() => ReactDOM.render(<PureComponent />, container)).toWarnDev(
'Warning: ' +
'PureComponent has a method called shouldComponentUpdate(). ' +
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
'Please extend React.Component if shouldComponentUpdate is used.',
);
}
});
});

View File

@ -58,7 +58,7 @@ class SimpleStateless extends React.Component {
// it renders based on state using initial values in this.props
class InitialState extends React.Component {
state = {
bar: this.props.initialValue
bar: this.props.initialValue,
};
render() {
return React.createElement('span', {className: this.state.bar});
@ -86,11 +86,11 @@ class StateBasedOnProps extends React.Component {
class StateBasedOnContext extends React.Component {
static contextTypes = {
tag: PropTypes.string,
className: PropTypes.string
className: PropTypes.string,
};
state = {
tag: this.context.tag,
className: this.context.className
className: this.context.className,
};
render() {
const Tag = this.state.tag;
@ -101,7 +101,7 @@ class StateBasedOnContext extends React.Component {
class ProvideChildContextTypes extends React.Component {
static childContextTypes = {
tag: PropTypes.string,
className: PropTypes.string
className: PropTypes.string,
};
getChildContext() {
return {tag: 'span', className: 'foo'};
@ -115,7 +115,7 @@ class ProvideChildContextTypes extends React.Component {
let renderCount = 0;
class RenderOnce extends React.Component {
state = {
bar: this.props.initialValue
bar: this.props.initialValue,
};
componentWillMount() {
this.setState({bar: 'bar'});
@ -157,33 +157,32 @@ class NullState extends React.Component {
// it setState through an event handler
class BoundEventHandler extends React.Component {
state = {
bar: this.props.initialValue
bar: this.props.initialValue,
};
handleClick = () => {
this.setState({bar: 'bar'});
};
render() {
return (
React.createElement(Inner, {
return React.createElement(Inner, {
name: this.state.bar,
onClick: this.handleClick
})
);
onClick: this.handleClick,
});
}
}
// it should not implicitly bind event handlers
class UnboundEventHandler extends React.Component {
state = {
bar: this.props.initialValue
bar: this.props.initialValue,
};
handleClick() {
this.setState({bar: 'bar'});
}
render() {
return React.createElement(
Inner, { name: this.state.bar, onClick: this.handleClick }
);
return React.createElement(Inner, {
name: this.state.bar,
onClick: this.handleClick,
});
}
}
@ -195,12 +194,10 @@ class ForceUpdateWithNoState extends React.Component {
this.forceUpdate();
}
render() {
return (
React.createElement(Inner, {
return React.createElement(Inner, {
name: this.mutativeValue,
onClick: this.handleClick.bind(this)}
)
);
onClick: this.handleClick.bind(this),
});
}
}
@ -303,7 +300,6 @@ class ClassicRefs extends React.Component {
// Describe the actual test cases.
describe('ReactTypeScriptClass', function() {
beforeEach(function() {
container = document.createElement('div');
attachedListener = null;
@ -315,17 +311,14 @@ describe('ReactTypeScriptClass', function() {
});
it('throws if no render function is defined', function() {
spyOnDev(console, 'error');
expect(() => ReactDOM.render(React.createElement(Empty), container)).toThrow();
if (__DEV__) {
expect((<any>console.error).calls.count()).toBe(1);
expect((<any>console.error).calls.argsFor(0)[0]).toBe(
expect(() =>
expect(() =>
ReactDOM.render(React.createElement(Empty), container)
).toThrow()
).toWarnDev(
'Warning: Empty(...): No `render` method found on the returned ' +
'component instance: you may have forgotten to define `render`.'
);
}
});
it('renders a simple stateless component with prop', function() {
@ -362,31 +355,15 @@ describe('ReactTypeScriptClass', function() {
});
it('should warn with non-object in the initial state property', function() {
spyOnDev(console, 'error');
test(React.createElement(ArrayState), 'SPAN', '');
if (__DEV__) {
expect((<any>console.error).calls.count()).toBe(1);
expect((<any>console.error).calls.argsFor(0)[0]).toContain(
expect(() => test(React.createElement(ArrayState), 'SPAN', '')).toWarnDev(
'ArrayState.state: must be set to an object or null'
);
(<any>console.error).calls.reset()
}
test(React.createElement(StringState), 'SPAN', '');
if (__DEV__) {
expect((<any>console.error).calls.count()).toBe(1);
expect((<any>console.error).calls.argsFor(0)[0]).toContain(
expect(() => test(React.createElement(StringState), 'SPAN', '')).toWarnDev(
'StringState.state: must be set to an object or null'
);
(<any>console.error).calls.reset()
}
test(React.createElement(NumberState), 'SPAN', '');
if (__DEV__) {
expect((<any>console.error).calls.count()).toBe(1);
expect((<any>console.error).calls.argsFor(0)[0]).toContain(
expect(() => test(React.createElement(NumberState), 'SPAN', '')).toWarnDev(
'NumberState.state: must be set to an object or null'
);
(<any>console.error).calls.reset()
}
});
it('should render with null in the initial state property', function() {
@ -425,57 +402,52 @@ describe('ReactTypeScriptClass', function() {
it('will call all the normal life cycle methods', function() {
lifeCycles = [];
test(React.createElement(NormalLifeCycles, {value: 'foo'}), 'SPAN', 'foo');
expect(lifeCycles).toEqual([
'will-mount',
'did-mount'
]);
expect(lifeCycles).toEqual(['will-mount', 'did-mount']);
lifeCycles = []; // reset
test(React.createElement(NormalLifeCycles, {value: 'bar'}), 'SPAN', 'bar');
expect(lifeCycles).toEqual([
'receive-props', { value: 'bar' },
'should-update', { value: 'bar' }, {},
'will-update', { value: 'bar' }, {},
'did-update', { value: 'foo' }, {}
'receive-props',
{value: 'bar'},
'should-update',
{value: 'bar'},
{},
'will-update',
{value: 'bar'},
{},
'did-update',
{value: 'foo'},
{},
]);
lifeCycles = []; // reset
ReactDOM.unmountComponentAtNode(container);
expect(lifeCycles).toEqual([
'will-unmount'
]);
expect(lifeCycles).toEqual(['will-unmount']);
});
it('warns when classic properties are defined on the instance, ' +
'but does not invoke them.', function() {
spyOnDev(console, 'error');
it(
'warns when classic properties are defined on the instance, ' +
'but does not invoke them.',
function() {
getInitialStateWasCalled = false;
getDefaultPropsWasCalled = false;
test(React.createElement(ClassicProperties), 'SPAN', 'foo');
expect(() =>
test(React.createElement(ClassicProperties), 'SPAN', 'foo')
).toWarnDev([
'getInitialState was defined on ClassicProperties, ' +
'a plain JavaScript class.',
'getDefaultProps was defined on ClassicProperties, ' +
'a plain JavaScript class.',
'propTypes was defined as an instance property on ClassicProperties.',
'contextTypes was defined as an instance property on ClassicProperties.',
]);
expect(getInitialStateWasCalled).toBe(false);
expect(getDefaultPropsWasCalled).toBe(false);
if (__DEV__) {
expect((<any>console.error).calls.count()).toBe(4);
expect((<any>console.error).calls.argsFor(0)[0]).toContain(
'getInitialState was defined on ClassicProperties, ' +
'a plain JavaScript class.'
);
expect((<any>console.error).calls.argsFor(1)[0]).toContain(
'getDefaultProps was defined on ClassicProperties, ' +
'a plain JavaScript class.'
);
expect((<any>console.error).calls.argsFor(2)[0]).toContain(
'propTypes was defined as an instance property on ClassicProperties.'
);
expect((<any>console.error).calls.argsFor(3)[0]).toContain(
'contextTypes was defined as an instance property on ClassicProperties.'
);
}
});
it('does not warn about getInitialState() on class components ' +
'if state is also defined.', () => {
spyOnDev(console, 'error');
);
it(
'does not warn about getInitialState() on class components ' +
'if state is also defined.',
() => {
class Example extends React.Component {
state = {};
getInitialState() {
@ -487,59 +459,46 @@ describe('ReactTypeScriptClass', function() {
}
test(React.createElement(Example), 'SPAN', 'foo');
if (__DEV__) {
expect((<any>console.error).calls.count()).toBe(0);
}
});
);
it('should warn when misspelling shouldComponentUpdate', function() {
spyOnDev(console, 'error');
test(React.createElement(MisspelledComponent1), 'SPAN', 'foo');
if (__DEV__) {
expect((<any>console.error).calls.count()).toBe(1);
expect((<any>console.error).calls.argsFor(0)[0]).toBe(
expect(() =>
test(React.createElement(MisspelledComponent1), 'SPAN', 'foo')
).toWarnDev(
'Warning: ' +
'MisspelledComponent1 has a method called componentShouldUpdate(). Did ' +
'you mean shouldComponentUpdate()? The name is phrased as a question ' +
'because the function is expected to return a value.'
);
}
});
it('should warn when misspelling componentWillReceiveProps', function() {
spyOnDev(console, 'error');
test(React.createElement(MisspelledComponent2), 'SPAN', 'foo');
if (__DEV__) {
expect((<any>console.error).calls.count()).toBe(1);
expect((<any>console.error).calls.argsFor(0)[0]).toBe(
expect(() =>
test(React.createElement(MisspelledComponent2), 'SPAN', 'foo')
).toWarnDev(
'Warning: ' +
'MisspelledComponent2 has a method called componentWillRecieveProps(). ' +
'Did you mean componentWillReceiveProps()?'
);
}
});
it('should throw AND warn when trying to access classic APIs', function() {
spyOnDev(console, 'warn');
const instance = test(
React.createElement(Inner, {name: 'foo'}),
'DIV','foo'
'DIV',
'foo'
);
expect(() => instance.replaceState({})).toThrow();
expect(() => instance.isMounted()).toThrow();
if (__DEV__) {
expect((<any>console.warn).calls.count()).toBe(2);
expect((<any>console.warn).calls.argsFor(0)[0]).toContain(
expect(() =>
expect(() => instance.replaceState({})).toThrow()
).toLowPriorityWarnDev(
'replaceState(...) is deprecated in plain JavaScript React classes'
);
expect((<any>console.warn).calls.argsFor(1)[0]).toContain(
expect(() =>
expect(() => instance.isMounted()).toThrow()
).toLowPriorityWarnDev(
'isMounted(...) is deprecated in plain JavaScript React classes'
);
}
});
it('supports this.context passed via getChildContext', function() {
@ -560,5 +519,4 @@ describe('ReactTypeScriptClass', function() {
const node = ReactDOM.findDOMNode(instance);
expect(node).toBe(container.firstChild);
});
});

View File

@ -52,7 +52,7 @@ describe('create-react-class-integration', () => {
});
it('should warn on invalid prop types', () => {
spyOnDev(console, 'error');
expect(() =>
createReactClass({
displayName: 'Component',
propTypes: {
@ -61,18 +61,15 @@ describe('create-react-class-integration', () => {
render: function() {
return <span>{this.props.prop}</span>;
},
});
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
}),
).toWarnDev(
'Warning: Component: prop type `prop` is invalid; ' +
'it must be a function, usually from React.PropTypes.',
);
}
});
it('should warn on invalid context types', () => {
spyOnDev(console, 'error');
expect(() =>
createReactClass({
displayName: 'Component',
contextTypes: {
@ -81,18 +78,15 @@ describe('create-react-class-integration', () => {
render: function() {
return <span>{this.props.prop}</span>;
},
});
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
}),
).toWarnDev(
'Warning: Component: context type `prop` is invalid; ' +
'it must be a function, usually from React.PropTypes.',
);
}
});
it('should throw on invalid child context types', () => {
spyOnDev(console, 'error');
expect(() =>
createReactClass({
displayName: 'Component',
childContextTypes: {
@ -101,19 +95,15 @@ describe('create-react-class-integration', () => {
render: function() {
return <span>{this.props.prop}</span>;
},
});
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
}),
).toWarnDev(
'Warning: Component: child context type `prop` is invalid; ' +
'it must be a function, usually from React.PropTypes.',
);
}
});
it('should warn when misspelling shouldComponentUpdate', () => {
spyOnDev(console, 'error');
expect(() =>
createReactClass({
componentShouldUpdate: function() {
return false;
@ -121,16 +111,14 @@ describe('create-react-class-integration', () => {
render: function() {
return <div />;
},
});
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
}),
).toWarnDev(
'Warning: A component has a method called componentShouldUpdate(). Did you ' +
'mean shouldComponentUpdate()? The name is phrased as a question ' +
'because the function is expected to return a value.',
);
}
expect(() =>
createReactClass({
displayName: 'NamedComponent',
componentShouldUpdate: function() {
@ -139,19 +127,16 @@ describe('create-react-class-integration', () => {
render: function() {
return <div />;
},
});
if (__DEV__) {
expect(console.error.calls.count()).toBe(2);
expect(console.error.calls.argsFor(1)[0]).toBe(
}),
).toWarnDev(
'Warning: NamedComponent has a method called componentShouldUpdate(). Did you ' +
'mean shouldComponentUpdate()? The name is phrased as a question ' +
'because the function is expected to return a value.',
);
}
});
it('should warn when misspelling componentWillReceiveProps', () => {
spyOnDev(console, 'error');
expect(() =>
createReactClass({
componentWillRecieveProps: function() {
return false;
@ -159,14 +144,11 @@ describe('create-react-class-integration', () => {
render: function() {
return <div />;
},
});
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
}),
).toWarnDev(
'Warning: A component has a method called componentWillRecieveProps(). Did you ' +
'mean componentWillReceiveProps()?',
);
}
});
it('should throw if a reserved property is in statics', () => {
@ -195,7 +177,7 @@ describe('create-react-class-integration', () => {
// TODO: Consider actually moving these to statics or drop this unit test.
xit('should warn when using deprecated non-static spec keys', () => {
spyOnDev(console, 'error');
expect(() =>
createReactClass({
mixins: [{}],
propTypes: {
@ -210,26 +192,17 @@ describe('create-react-class-integration', () => {
render: function() {
return <div />;
},
});
if (__DEV__) {
expect(console.error.calls.count()).toBe(4);
expect(console.error.calls.argsFor(0)[0]).toBe(
}),
).toWarnDev([
'createClass(...): `mixins` is now a static property and should ' +
'be defined inside "statics".',
);
expect(console.error.calls.argsFor(1)[0]).toBe(
'createClass(...): `propTypes` is now a static property and should ' +
'be defined inside "statics".',
);
expect(console.error.calls.argsFor(2)[0]).toBe(
'createClass(...): `contextTypes` is now a static property and ' +
'should be defined inside "statics".',
);
expect(console.error.calls.argsFor(3)[0]).toBe(
'createClass(...): `childContextTypes` is now a static property and ' +
'should be defined inside "statics".',
);
}
]);
});
it('should support statics', () => {
@ -342,21 +315,16 @@ describe('create-react-class-integration', () => {
});
it('should throw when using legacy factories', () => {
spyOnDev(console, 'error');
const Component = createReactClass({
render() {
return <div />;
},
});
expect(() => Component()).toThrow();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
expect(() => expect(() => Component()).toThrow()).toWarnDev(
'Warning: Something is calling a React component directly. Use a ' +
'factory or JSX instead. See: https://fb.me/react-legacyfactory',
);
}
});
it('replaceState and callback works', () => {
@ -379,8 +347,6 @@ describe('create-react-class-integration', () => {
});
it('isMounted works', () => {
spyOnDev(console, 'error');
const ops = [];
let instance;
const Component = createReactClass({
@ -434,7 +400,13 @@ describe('create-react-class-integration', () => {
});
const container = document.createElement('div');
ReactDOM.render(<Component />, container);
expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' +
'clean up subscriptions and pending requests in componentWillUnmount ' +
'to prevent memory leaks.',
);
ReactDOM.render(<Component />, container);
ReactDOM.unmountComponentAtNode(container);
instance.log('after unmount');
@ -454,14 +426,5 @@ describe('create-react-class-integration', () => {
'componentWillUnmount: true',
'after unmount: false',
]);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toEqual(
'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' +
'clean up subscriptions and pending requests in componentWillUnmount ' +
'to prevent memory leaks.',
);
}
});
});

View File

@ -0,0 +1,86 @@
'use strict';
function normalizeCodeLocInfo(str) {
return str && str.replace(/at .+?:\d+/g, 'at **');
}
const createMatcherFor = consoleMethod =>
function matcher(callback, expectedMessages) {
if (__DEV__) {
// Warn about incorrect usage of matcher.
if (typeof expectedMessages === 'string') {
expectedMessages = [expectedMessages];
} else if (!Array.isArray(expectedMessages)) {
throw Error(
`toWarnDev() requires a parameter of type string or an array of strings ` +
`but was given ${typeof expectedMessages}.`
);
}
const consoleSpy = message => {
const normalizedMessage = normalizeCodeLocInfo(message);
for (let index = 0; index < expectedMessages.length; index++) {
const expectedMessage = expectedMessages[index];
if (
normalizedMessage === expectedMessage ||
normalizedMessage.includes(expectedMessage)
) {
expectedMessages.splice(index, 1);
return;
}
}
// Fail early for unexpected warnings to preserve the call stack.
throw Error(
`Unexpected warning recorded:\n ${this.utils.printReceived(
message
)}\n\nThe following expected warnings were not yet seen:\n ${this.utils.printExpected(
expectedMessages.join('\n')
)}`
);
};
// TODO Decide whether we need to support nested toWarn* expectations.
// If we don't need id, add a check here to see if this is already our spy,
// And throw an error.
const originalMethod = console[consoleMethod];
// Avoid using Jest's built-in spy since it can't be removed.
console[consoleMethod] = consoleSpy;
try {
callback();
// Any remaining messages indicate a failed expectations.
if (expectedMessages.length > 0) {
return {
message: () =>
`Expected warning was not recorded:\n ${this.utils.printReceived(
expectedMessages.join('\n')
)}`,
pass: false,
};
}
return {pass: true};
} catch (error) {
// TODO Flag this error so Jest doesn't override its stack
// See https://tinyurl.com/y9unakwb
throw error;
} finally {
// Restore the unspied method so that unexpected errors fail tests.
console[consoleMethod] = originalMethod;
}
} else {
// Any uncaught errors or warnings should fail tests in production mode.
callback();
return {pass: true};
}
};
module.exports = {
toLowPriorityWarnDev: createMatcherFor('warn'),
toWarnDev: createMatcherFor('error'),
};

View File

@ -1,5 +1,7 @@
'use strict';
const chalk = require('chalk');
if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
// Inside the class equivalence tester, we have a custom environment, let's
// require that instead.
@ -39,6 +41,10 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
global.spyOnDevAndProd = spyOn;
}
expect.extend({
...require('./matchers/toWarnDev'),
});
// We have a Babel transform that inserts guards against infinite loops.
// If a loop runs for too many iterations, we throw an error and set this
// global variable. The global lets us detect an infinite loop even if
@ -56,16 +62,22 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
});
['error', 'warn'].forEach(methodName => {
const oldMethod = console[methodName];
const newMethod = function() {
newMethod.__callCount++;
oldMethod.apply(this, arguments);
const unexpectedConsoleCallStacks = [];
const newMethod = function(message) {
// Capture the call stack now so we can warn about it later.
// The call stack has helpful information for the test author.
// Don't throw yet though b'c it might be accidentally caught and suppressed.
const stack = new Error().stack;
unexpectedConsoleCallStacks.push([
stack.substr(stack.indexOf('\n') + 1),
message,
]);
};
newMethod.__callCount = 0;
console[methodName] = newMethod;
env.beforeEach(() => {
newMethod.__callCount = 0;
unexpectedConsoleCallStacks.length = 0;
});
env.afterEach(() => {
@ -74,15 +86,32 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
`Test did not tear down console.${methodName} mock properly.`
);
}
if (console[methodName].__callCount !== 0) {
throw new Error(
`Expected test not to call console.${methodName}(). ` +
'If the warning is expected, mock it out using ' +
`spyOnDev(console, '${methodName}') or spyOnProd(console, '${
methodName
}'), ` +
'and test that the warning occurs.'
if (unexpectedConsoleCallStacks.length > 0) {
const messages = unexpectedConsoleCallStacks.map(
([stack, message]) =>
`${chalk.red(message)}\n` +
`${stack
.split('\n')
.map(line => chalk.gray(line))
.join('\n')}`
);
const message =
`Expected test not to call ${chalk.bold(
`console.${methodName}()`
)}.\n\n` +
'If the warning is expected, test for it explicitly by:\n' +
`1. Using the ${chalk.bold('.toWarnDev()')} / ${chalk.bold(
'.toLowPriorityWarnDev()'
)} matchers, or...\n` +
`2. Mock it out using ${chalk.bold('spyOnDev')}(console, '${
methodName
}') or ${chalk.bold('spyOnProd')}(console, '${
methodName
}'), and test that the warning occurs.`;
throw new Error(`${message}\n\n${messages.join('\n\n')}`);
}
});
});

View File

@ -45,6 +45,10 @@ global.spyOnProd = function(...args) {
}
};
expect.extend({
...require('../matchers/toWarnDev'),
});
beforeEach(() => (numExpectations = 0));
jasmine.currentEnv_.addReporter({

View File

@ -21,6 +21,8 @@ interface Expect {
not: Expect
toThrow(message?: string): void
toThrowError(message?: string): void
toWarnDev(message?: string | Array<string>): void
toLowPriorityWarnDev(message?: string | Array<string>): void
toBe(value: any): void
toEqual(value: any): void
toBeFalsy(): void