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:
parent
22e2bf7684
commit
b5334a44e9
|
@ -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(
|
||||
'Warning: Failed prop type: The prop `radius` is marked as required in `Circle`, ' +
|
||||
'but its value is `undefined`.' +
|
||||
'\n in Circle (at **)',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'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 **)',
|
||||
);
|
||||
}
|
||||
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 **)',
|
||||
'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(
|
||||
'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 **)',
|
||||
);
|
||||
}
|
||||
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 **)',
|
||||
'Warning: Failed prop type: The prop `startAngle` is marked as required in `Wedge`, ' +
|
||||
'but its value is `undefined`.' +
|
||||
'\n in Wedge (at **)',
|
||||
'Warning: Failed prop type: The prop `endAngle` is marked as required in `Wedge`, ' +
|
||||
'but its value is `undefined`.' +
|
||||
'\n in Wedge (at **)',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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(
|
||||
'Warning: Unsupported style property background-color. Did you mean backgroundColor?' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
'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 **)',
|
||||
);
|
||||
}
|
||||
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 **)',
|
||||
'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 **)',
|
||||
);
|
||||
}
|
||||
'Warning: Unsupported vendor-prefixed style property oTransform. ' +
|
||||
'Did you mean OTransform?' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
'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(
|
||||
"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 **)',
|
||||
);
|
||||
}
|
||||
|
||||
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 **)',
|
||||
"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(
|
||||
'Warning: `NaN` is an invalid value for the `fontSize` css style property.' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'Warning: `Infinity` is an invalid value for the `fontSize` css style property.' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
}
|
||||
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', () => {
|
||||
|
|
|
@ -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');
|
||||
ReactDOM.render(
|
||||
<input type="text" onChange={function() {}} />,
|
||||
container,
|
||||
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',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,21 +22,17 @@ describe('EventPluginHub', () => {
|
|||
});
|
||||
|
||||
it('should prevent non-function listeners, at dispatch', () => {
|
||||
spyOnDev(console, 'error');
|
||||
const node = ReactTestUtils.renderIntoDocument(
|
||||
<div onClick="not a function" />,
|
||||
);
|
||||
expect(function() {
|
||||
ReactTestUtils.SimulateNative.click(node);
|
||||
}).toThrowError(
|
||||
let node;
|
||||
expect(() => {
|
||||
node = ReactTestUtils.renderIntoDocument(
|
||||
<div onClick="not a function" />,
|
||||
);
|
||||
}).toWarnDev(
|
||||
'Expected `onClick` listener to be a function, instead got a value of `string` type.',
|
||||
);
|
||||
expect(() => ReactTestUtils.SimulateNative.click(node)).toThrowError(
|
||||
'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', () => {
|
||||
|
|
|
@ -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(
|
||||
'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.',
|
||||
);
|
||||
}
|
||||
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,49 +75,35 @@ describe('ReactChildReconciler', () => {
|
|||
}
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<GrandParent />);
|
||||
|
||||
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 **)',
|
||||
);
|
||||
}
|
||||
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 ' +
|
||||
'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', () => {
|
||||
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(
|
||||
'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.',
|
||||
);
|
||||
}
|
||||
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,21 +122,16 @@ describe('ReactChildReconciler', () => {
|
|||
}
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<GrandParent />);
|
||||
|
||||
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 **)',
|
||||
);
|
||||
}
|
||||
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 ' +
|
||||
'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 **)',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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(
|
||||
'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 **)',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'Warning: ' +
|
||||
'Each child in an array or iterator should have a unique "key" prop.' +
|
||||
' See https://fb.me/react-warning-keys for more information.',
|
||||
);
|
||||
}
|
||||
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.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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(->
|
||||
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')
|
||||
expect(->
|
||||
ReactDOM.render React.createElement(Foo), container
|
||||
).toThrow()
|
||||
).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()
|
||||
|
||||
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()
|
||||
|
||||
expect(->
|
||||
test React.createElement(Foo), 'SPAN', ''
|
||||
).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'
|
||||
|
||||
test React.createElement(Foo), 'SPAN', '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,18 +335,16 @@ describe 'ReactCoffeeScriptClass', ->
|
|||
span
|
||||
className: 'foo'
|
||||
|
||||
test React.createElement(NamedComponent), 'SPAN', 'foo'
|
||||
if __DEV__
|
||||
expect(console.error.calls.count()).toBe 1
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'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.'
|
||||
)
|
||||
expect(->
|
||||
test React.createElement(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.'
|
||||
)
|
||||
undefined
|
||||
|
||||
it 'should warn when misspelling componentWillReceiveProps', ->
|
||||
spyOnDev console, 'error'
|
||||
class NamedComponent extends React.Component
|
||||
componentWillRecieveProps: ->
|
||||
false
|
||||
|
@ -376,29 +353,27 @@ describe 'ReactCoffeeScriptClass', ->
|
|||
span
|
||||
className: 'foo'
|
||||
|
||||
test React.createElement(NamedComponent), 'SPAN', 'foo'
|
||||
if __DEV__
|
||||
expect(console.error.calls.count()).toBe 1
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: NamedComponent has a method called componentWillRecieveProps().
|
||||
Did you mean componentWillReceiveProps()?'
|
||||
)
|
||||
expect(->
|
||||
test React.createElement(NamedComponent), 'SPAN', 'foo'
|
||||
).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(-> instance.replaceState {}).toThrow()
|
||||
expect(-> instance.isMounted()).toThrow()
|
||||
if __DEV__
|
||||
expect(console.warn.calls.count()).toBe 2
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes'
|
||||
)
|
||||
expect(console.warn.calls.argsFor(1)[0]).toContain(
|
||||
'isMounted(...) is deprecated in plain JavaScript React classes'
|
||||
)
|
||||
expect(->
|
||||
expect(-> instance.replaceState {}).toThrow()
|
||||
).toLowPriorityWarnDev(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes'
|
||||
)
|
||||
expect(->
|
||||
expect(-> instance.isMounted()).toThrow()
|
||||
).toLowPriorityWarnDev(
|
||||
'isMounted(...) is deprecated in plain JavaScript React classes'
|
||||
)
|
||||
undefined
|
||||
|
||||
it 'supports this.context passed via getChildContext', ->
|
||||
|
|
|
@ -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(
|
||||
'Warning: Failed context type: ' +
|
||||
'The context `foo` is marked as required in `Component`, but its value ' +
|
||||
'is `undefined`.\n' +
|
||||
' in Component (at **)',
|
||||
);
|
||||
}
|
||||
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,
|
||||
};
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<ComponentInFooNumberContext fooValue={123} />,
|
||||
expect(() =>
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<ComponentInFooNumberContext fooValue={123} />,
|
||||
),
|
||||
).toWarnDev(
|
||||
'Warning: Failed context type: ' +
|
||||
'Invalid context `foo` of type `number` supplied ' +
|
||||
'to `Component`, expected `string`.\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in ComponentInFooNumberContext (at **)',
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'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(
|
||||
'Warning: Failed child context type: ' +
|
||||
'The child context `foo` is marked as required in `Component`, but its ' +
|
||||
'value is `undefined`.\n' +
|
||||
' in Component (at **)',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'Warning: Failed child context type: ' +
|
||||
'Invalid child context `foo` of type `number` ' +
|
||||
'supplied to `Component`, expected `string`.\n' +
|
||||
' in Component (at **)',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'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.',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'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.',
|
||||
);
|
||||
}
|
||||
|
||||
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');
|
||||
});
|
||||
|
|
|
@ -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(
|
||||
'Warning: Foo(...): No `render` method found on the returned component ' +
|
||||
'instance: you may have forgotten to define `render`.',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'Foo.state: must be set to an object or null',
|
||||
);
|
||||
console.error.calls.reset();
|
||||
}
|
||||
expect(() => test(<Foo />, 'SPAN', '')).toWarnDev(
|
||||
'Foo.state: must be set to an object or null',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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(
|
||||
'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.',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'Warning: ' +
|
||||
'NamedComponent has a method called componentWillRecieveProps(). Did ' +
|
||||
'you mean componentWillReceiveProps()?',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes',
|
||||
);
|
||||
expect(console.warn.calls.argsFor(1)[0]).toContain(
|
||||
'isMounted(...) is deprecated in plain JavaScript React classes',
|
||||
);
|
||||
}
|
||||
expect(() =>
|
||||
expect(() => instance.replaceState({})).toThrow(),
|
||||
).toLowPriorityWarnDev(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes',
|
||||
);
|
||||
expect(() =>
|
||||
expect(() => instance.isMounted()).toThrow(),
|
||||
).toLowPriorityWarnDev(
|
||||
'isMounted(...) is deprecated in plain JavaScript React classes',
|
||||
);
|
||||
});
|
||||
|
||||
it('supports this.context passed via getChildContext', () => {
|
||||
|
|
|
@ -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(
|
||||
'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)',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'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)',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'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)',
|
||||
);
|
||||
}
|
||||
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', () => {
|
||||
|
|
|
@ -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(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
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,18 +297,16 @@ describe('ReactElementClone', () => {
|
|||
});
|
||||
}
|
||||
}
|
||||
ReactTestUtils.renderIntoDocument(React.createElement(GrandParent));
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `Component`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in Component (created by GrandParent)\n' +
|
||||
' in Parent (created by GrandParent)\n' +
|
||||
' in GrandParent',
|
||||
);
|
||||
}
|
||||
expect(() =>
|
||||
ReactTestUtils.renderIntoDocument(React.createElement(GrandParent)),
|
||||
).toWarnDev(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `Component`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in Component (created by GrandParent)\n' +
|
||||
' in Parent (created by GrandParent)\n' +
|
||||
' in GrandParent',
|
||||
);
|
||||
});
|
||||
|
||||
it('should ignore key and ref warning getters', () => {
|
||||
|
|
|
@ -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);
|
||||
|
||||
Component(null, [Component(), Component()]);
|
||||
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
expect(() => {
|
||||
Component(null, [Component(), Component()]);
|
||||
}).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', () => {
|
|||
}
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(React.createElement(ComponentWrapper));
|
||||
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'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. ',
|
||||
);
|
||||
}
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(React.createElement(ComponentWrapper));
|
||||
}).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(
|
||||
'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 **)',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'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 **)',
|
||||
);
|
||||
}
|
||||
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,20 +113,15 @@ 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(
|
||||
'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' +
|
||||
' in div (at **)\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in Parent (at **)\n' +
|
||||
' in GrandParent (at **)',
|
||||
);
|
||||
}
|
||||
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' +
|
||||
' in div (at **)\n' +
|
||||
' in Component (at **)\n' +
|
||||
' 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(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
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});
|
||||
}
|
||||
ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in MyComp (created by ParentComp)\n' +
|
||||
' in ParentComp',
|
||||
);
|
||||
}
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
|
||||
}).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');
|
||||
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(
|
||||
'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.',
|
||||
);
|
||||
}
|
||||
expect(() => {
|
||||
React.createElement(undefined);
|
||||
React.createElement(null);
|
||||
React.createElement(true);
|
||||
React.createElement({x: 17});
|
||||
React.createElement({});
|
||||
}).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.',
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: null.',
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: boolean.',
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: object.',
|
||||
'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() {
|
||||
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(
|
||||
'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',
|
||||
|
||||
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`.' : ''),
|
||||
);
|
||||
}
|
||||
}).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(
|
||||
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
|
||||
'`Component`, but its value is `null`.\n' +
|
||||
' in Component',
|
||||
);
|
||||
}
|
||||
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', () => {
|
|||
}
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
React.createElement(Component, {prop: null}),
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
|
||||
'`Component`, but its value is `null`.\n' +
|
||||
' in Component',
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
React.createElement(Component, {prop: null}),
|
||||
);
|
||||
}
|
||||
}).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', () => {
|
|||
}
|
||||
}
|
||||
|
||||
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(
|
||||
'Warning: Failed prop type: ' +
|
||||
'The prop `prop` is marked as required in `Component`, but its value ' +
|
||||
'is `undefined`.\n' +
|
||||
' in Component',
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(React.createElement(Component));
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
React.createElement(Component, {prop: 42}),
|
||||
);
|
||||
}).toWarnDev([
|
||||
'Warning: Failed prop type: ' +
|
||||
'The prop `prop` is marked as required in `Component`, but its value ' +
|
||||
'is `undefined`.\n' +
|
||||
' in Component',
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `prop` of type `number` supplied to ' +
|
||||
'`Component`, expected `string`.\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', () => {
|
|||
}
|
||||
}
|
||||
|
||||
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(
|
||||
'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).',
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
React.createElement(Component, {myProp: {value: 'hi'}}),
|
||||
);
|
||||
}
|
||||
}).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', () => {
|
|||
}
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
React.createElement(MisspelledPropTypesComponent, {prop: 'Hi'}),
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component MisspelledPropTypesComponent declared `PropTypes` ' +
|
||||
'instead of `propTypes`. Did you misspell the property assignment?',
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
React.createElement(MisspelledPropTypesComponent, {prop: 'Hi'}),
|
||||
);
|
||||
}
|
||||
}).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(
|
||||
'Warning: Factory.type is deprecated. Access the class directly before ' +
|
||||
'passing it to createFactory.',
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
void <Foo>{[<div />]}</Foo>;
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'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 **.',
|
||||
);
|
||||
}
|
||||
expect(() => {
|
||||
void <Foo>{[<div />]}</Foo>;
|
||||
}).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 **.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component>{[<Component />, <Component />]}</Component>,
|
||||
expect(() =>
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component>{[<Component />, <Component />]}</Component>,
|
||||
),
|
||||
).toWarnDev(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'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(
|
||||
'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. ',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in MyComp (at **)\n' +
|
||||
' in ParentComp (at **)',
|
||||
);
|
||||
}
|
||||
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,21 +197,16 @@ 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(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in MyComp (at **)\n' +
|
||||
' in MiddleComp (at **)\n' +
|
||||
' in ParentComp (at **)',
|
||||
);
|
||||
}
|
||||
expect(() =>
|
||||
ReactDOM.render(<ParentComp warn={true} />, container),
|
||||
).toWarnDev(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in MyComp (at **)\n' +
|
||||
' in MiddleComp (at **)\n' +
|
||||
' in ParentComp (at **)',
|
||||
);
|
||||
});
|
||||
|
||||
it('gives a helpful error when passing null, undefined, or boolean', () => {
|
||||
|
@ -243,100 +214,72 @@ 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(
|
||||
'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 **.',
|
||||
);
|
||||
expect(normalizeCodeLocInfo(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.' +
|
||||
'\n\nCheck your code at **.',
|
||||
);
|
||||
expect(normalizeCodeLocInfo(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.' +
|
||||
'\n\nCheck your code at **.',
|
||||
);
|
||||
}
|
||||
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 ' +
|
||||
"component from the file it's defined in, or you might have mixed up " +
|
||||
'default and named imports.' +
|
||||
'\n\nCheck your code at **.',
|
||||
);
|
||||
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(() => 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(
|
||||
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
|
||||
'`RequiredPropComponent`, but its value is `null`.\n' +
|
||||
' in RequiredPropComponent (at **)',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
|
||||
'`RequiredPropComponent`, but its value is `null`.\n' +
|
||||
' in RequiredPropComponent (at **)',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'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(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `prop` of type `number` supplied to ' +
|
||||
'`RequiredPropComponent`, expected `string`.\n' +
|
||||
' in RequiredPropComponent (at **)',
|
||||
);
|
||||
}
|
||||
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(() =>
|
||||
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 **)',
|
||||
);
|
||||
|
||||
// Should not error for strings
|
||||
ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop="string" />);
|
||||
|
||||
if (__DEV__) {
|
||||
// Should not error for strings
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
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(
|
||||
'NullPropTypeComponent: prop type `prop` is invalid; it must be a ' +
|
||||
'function, usually from the `prop-types` package,',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'NullContextTypeComponent: context type `prop` is invalid; it must ' +
|
||||
'be a function, usually from the `prop-types` package,',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'getDefaultProps is only used on classic React.createClass definitions.' +
|
||||
' Use a static property named `defaultProps` instead.',
|
||||
);
|
||||
}
|
||||
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,
|
||||
};
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<MisspelledPropTypesComponent prop="hi" />,
|
||||
expect(() =>
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<MisspelledPropTypesComponent prop="hi" />,
|
||||
),
|
||||
).toWarnDev(
|
||||
'Warning: Component MisspelledPropTypesComponent declared `PropTypes` ' +
|
||||
'instead of `propTypes`. Did you misspell the property assignment?',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'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 ' +
|
||||
'can only have `key` and `children` props.',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'Invalid attribute `ref` supplied to `React.Fragment`.',
|
||||
);
|
||||
}
|
||||
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');
|
||||
|
||||
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`.',
|
||||
);
|
||||
}
|
||||
expect(() =>
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<React.Fragment>
|
||||
<span key="a">1</span>
|
||||
<span key="a">2</span>
|
||||
<span key="b">3</span>
|
||||
</React.Fragment>,
|
||||
),
|
||||
).toWarnDev('Encountered two children with the same key, `a`.');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
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);
|
||||
ReactDOM.render(<Component />, container);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'Component has a method called shouldComponentUpdate(). ' +
|
||||
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
|
||||
'Please extend React.Component if shouldComponentUpdate is used.',
|
||||
);
|
||||
}
|
||||
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(
|
||||
'Warning: ' +
|
||||
'PureComponent has a method called shouldComponentUpdate(). ' +
|
||||
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
|
||||
'Please extend React.Component if shouldComponentUpdate is used.',
|
||||
);
|
||||
}
|
||||
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.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,7 +26,7 @@ class Inner extends React.Component {
|
|||
render() {
|
||||
attachedListener = this.props.onClick;
|
||||
renderedName = this.props.name;
|
||||
return React.createElement('div', { className: this.props.name });
|
||||
return React.createElement('div', {className: this.props.name});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,11 +45,11 @@ function test(element, expectedTag, expectedClassName) {
|
|||
|
||||
// it preserves the name of the class for use in error messages
|
||||
// it throws if no render function is defined
|
||||
class Empty extends React.Component { }
|
||||
class Empty extends React.Component {}
|
||||
|
||||
// it renders a simple stateless component with prop
|
||||
class SimpleStateless extends React.Component {
|
||||
props : any;
|
||||
props: any;
|
||||
render() {
|
||||
return React.createElement(Inner, {name: this.props.bar});
|
||||
}
|
||||
|
@ -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});
|
||||
|
@ -69,10 +69,10 @@ class InitialState extends React.Component {
|
|||
class StateBasedOnProps extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { bar: props.initialValue };
|
||||
this.state = {bar: props.initialValue};
|
||||
}
|
||||
changeState() {
|
||||
this.setState({ bar: 'bar' });
|
||||
this.setState({bar: 'bar'});
|
||||
}
|
||||
render() {
|
||||
if (this.state.bar === 'foo') {
|
||||
|
@ -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,10 +101,10 @@ 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' };
|
||||
return {tag: 'span', className: 'foo'};
|
||||
}
|
||||
render() {
|
||||
return React.createElement(StateBasedOnContext);
|
||||
|
@ -115,10 +115,10 @@ 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' });
|
||||
this.setState({bar: 'bar'});
|
||||
}
|
||||
render() {
|
||||
renderCount++;
|
||||
|
@ -157,57 +157,54 @@ 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' });
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 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' });
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// it renders using forceUpdate even when there is no state
|
||||
class ForceUpdateWithNoState extends React.Component {
|
||||
mutativeValue : string = this.props.initialValue;
|
||||
mutativeValue: string = this.props.initialValue;
|
||||
handleClick() {
|
||||
this.mutativeValue = 'bar';
|
||||
this.forceUpdate();
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
React.createElement(Inner, {
|
||||
name: this.mutativeValue,
|
||||
onClick: this.handleClick.bind(this)}
|
||||
)
|
||||
);
|
||||
return React.createElement(Inner, {
|
||||
name: this.mutativeValue,
|
||||
onClick: this.handleClick.bind(this),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// it will call all the normal life cycle methods
|
||||
let lifeCycles = [];
|
||||
class NormalLifeCycles extends React.Component {
|
||||
props : any;
|
||||
props: any;
|
||||
state = {};
|
||||
componentWillMount() {
|
||||
lifeCycles.push('will-mount');
|
||||
|
@ -278,15 +275,15 @@ class MisspelledComponent2 extends React.Component {
|
|||
|
||||
// it supports this.context passed via getChildContext
|
||||
class ReadContext extends React.Component {
|
||||
static contextTypes = { bar: PropTypes.string };
|
||||
static contextTypes = {bar: PropTypes.string};
|
||||
render() {
|
||||
return React.createElement('div', { className: this.context.bar });
|
||||
return React.createElement('div', {className: this.context.bar});
|
||||
}
|
||||
}
|
||||
class ProvideContext extends React.Component {
|
||||
static childContextTypes = { bar: PropTypes.string };
|
||||
static childContextTypes = {bar: PropTypes.string};
|
||||
getChildContext() {
|
||||
return { bar: 'bar-through-context' };
|
||||
return {bar: 'bar-through-context'};
|
||||
}
|
||||
render() {
|
||||
return React.createElement(ReadContext);
|
||||
|
@ -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(
|
||||
'Warning: Empty(...): No `render` method found on the returned ' +
|
||||
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(
|
||||
'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(
|
||||
'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(
|
||||
'NumberState.state: must be set to an object or null'
|
||||
);
|
||||
(<any>console.error).calls.reset()
|
||||
}
|
||||
expect(() => test(React.createElement(ArrayState), 'SPAN', '')).toWarnDev(
|
||||
'ArrayState.state: must be set to an object or null'
|
||||
);
|
||||
expect(() => test(React.createElement(StringState), 'SPAN', '')).toWarnDev(
|
||||
'StringState.state: must be set to an object or null'
|
||||
);
|
||||
expect(() => test(React.createElement(NumberState), 'SPAN', '')).toWarnDev(
|
||||
'NumberState.state: must be set to an object or null'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render with null in the initial state property', function() {
|
||||
|
@ -425,121 +402,103 @@ 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');
|
||||
getInitialStateWasCalled = false;
|
||||
getDefaultPropsWasCalled = false;
|
||||
test(React.createElement(ClassicProperties), 'SPAN', 'foo');
|
||||
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(
|
||||
it(
|
||||
'warns when classic properties are defined on the instance, ' +
|
||||
'but does not invoke them.',
|
||||
function() {
|
||||
getInitialStateWasCalled = false;
|
||||
getDefaultPropsWasCalled = false;
|
||||
expect(() =>
|
||||
test(React.createElement(ClassicProperties), 'SPAN', 'foo')
|
||||
).toWarnDev([
|
||||
'getInitialState was defined on ClassicProperties, ' +
|
||||
'a plain JavaScript class.'
|
||||
);
|
||||
expect((<any>console.error).calls.argsFor(1)[0]).toContain(
|
||||
'a plain JavaScript class.',
|
||||
'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.'
|
||||
);
|
||||
'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);
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
it('does not warn about getInitialState() on class components ' +
|
||||
'if state is also defined.', () => {
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Example extends React.Component {
|
||||
state = {};
|
||||
getInitialState() {
|
||||
return {};
|
||||
it(
|
||||
'does not warn about getInitialState() on class components ' +
|
||||
'if state is also defined.',
|
||||
() => {
|
||||
class Example extends React.Component {
|
||||
state = {};
|
||||
getInitialState() {
|
||||
return {};
|
||||
}
|
||||
render() {
|
||||
return React.createElement('span', {className: 'foo'});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return React.createElement('span', {className: 'foo'});
|
||||
}
|
||||
}
|
||||
|
||||
test(React.createElement(Example), 'SPAN', 'foo');
|
||||
if (__DEV__) {
|
||||
expect((<any>console.error).calls.count()).toBe(0);
|
||||
test(React.createElement(Example), 'SPAN', 'foo');
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
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(
|
||||
'Warning: ' +
|
||||
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(
|
||||
'Warning: ' +
|
||||
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(() =>
|
||||
expect(() => instance.replaceState({})).toThrow()
|
||||
).toLowPriorityWarnDev(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes'
|
||||
);
|
||||
expect(() =>
|
||||
expect(() => instance.isMounted()).toThrow()
|
||||
).toLowPriorityWarnDev(
|
||||
'isMounted(...) is deprecated in plain JavaScript React classes'
|
||||
);
|
||||
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(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes'
|
||||
);
|
||||
expect((<any>console.warn).calls.argsFor(1)[0]).toContain(
|
||||
'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);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -52,121 +52,103 @@ describe('create-react-class-integration', () => {
|
|||
});
|
||||
|
||||
it('should warn on invalid prop types', () => {
|
||||
spyOnDev(console, 'error');
|
||||
createReactClass({
|
||||
displayName: 'Component',
|
||||
propTypes: {
|
||||
prop: null,
|
||||
},
|
||||
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(
|
||||
'Warning: Component: prop type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.',
|
||||
);
|
||||
}
|
||||
expect(() =>
|
||||
createReactClass({
|
||||
displayName: 'Component',
|
||||
propTypes: {
|
||||
prop: null,
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
},
|
||||
}),
|
||||
).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');
|
||||
createReactClass({
|
||||
displayName: 'Component',
|
||||
contextTypes: {
|
||||
prop: null,
|
||||
},
|
||||
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(
|
||||
'Warning: Component: context type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.',
|
||||
);
|
||||
}
|
||||
expect(() =>
|
||||
createReactClass({
|
||||
displayName: 'Component',
|
||||
contextTypes: {
|
||||
prop: null,
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
},
|
||||
}),
|
||||
).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');
|
||||
createReactClass({
|
||||
displayName: 'Component',
|
||||
childContextTypes: {
|
||||
prop: null,
|
||||
},
|
||||
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(
|
||||
'Warning: Component: child context type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.',
|
||||
);
|
||||
}
|
||||
expect(() =>
|
||||
createReactClass({
|
||||
displayName: 'Component',
|
||||
childContextTypes: {
|
||||
prop: null,
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
},
|
||||
}),
|
||||
).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;
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
}),
|
||||
).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.',
|
||||
);
|
||||
|
||||
createReactClass({
|
||||
componentShouldUpdate: function() {
|
||||
return false;
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'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.',
|
||||
);
|
||||
}
|
||||
|
||||
createReactClass({
|
||||
displayName: 'NamedComponent',
|
||||
componentShouldUpdate: function() {
|
||||
return false;
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'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.',
|
||||
);
|
||||
}
|
||||
expect(() =>
|
||||
createReactClass({
|
||||
displayName: 'NamedComponent',
|
||||
componentShouldUpdate: function() {
|
||||
return false;
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
}),
|
||||
).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');
|
||||
createReactClass({
|
||||
componentWillRecieveProps: function() {
|
||||
return false;
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: A component has a method called componentWillRecieveProps(). Did you ' +
|
||||
'mean componentWillReceiveProps()?',
|
||||
);
|
||||
}
|
||||
expect(() =>
|
||||
createReactClass({
|
||||
componentWillRecieveProps: function() {
|
||||
return false;
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
}),
|
||||
).toWarnDev(
|
||||
'Warning: A component has a method called componentWillRecieveProps(). Did you ' +
|
||||
'mean componentWillReceiveProps()?',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if a reserved property is in statics', () => {
|
||||
|
@ -195,41 +177,32 @@ 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');
|
||||
createReactClass({
|
||||
mixins: [{}],
|
||||
propTypes: {
|
||||
foo: PropTypes.string,
|
||||
},
|
||||
contextTypes: {
|
||||
foo: PropTypes.string,
|
||||
},
|
||||
childContextTypes: {
|
||||
foo: PropTypes.string,
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(4);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'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".',
|
||||
);
|
||||
}
|
||||
expect(() =>
|
||||
createReactClass({
|
||||
mixins: [{}],
|
||||
propTypes: {
|
||||
foo: PropTypes.string,
|
||||
},
|
||||
contextTypes: {
|
||||
foo: PropTypes.string,
|
||||
},
|
||||
childContextTypes: {
|
||||
foo: PropTypes.string,
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
}),
|
||||
).toWarnDev([
|
||||
'createClass(...): `mixins` is now a static property and should ' +
|
||||
'be defined inside "statics".',
|
||||
'createClass(...): `propTypes` is now a static property and should ' +
|
||||
'be defined inside "statics".',
|
||||
'createClass(...): `contextTypes` is now a static property and ' +
|
||||
'should be defined inside "statics".',
|
||||
'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(
|
||||
'Warning: Something is calling a React component directly. Use a ' +
|
||||
'factory or JSX instead. See: https://fb.me/react-legacyfactory',
|
||||
);
|
||||
}
|
||||
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.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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'),
|
||||
};
|
|
@ -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')}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,6 +45,10 @@ global.spyOnProd = function(...args) {
|
|||
}
|
||||
};
|
||||
|
||||
expect.extend({
|
||||
...require('../matchers/toWarnDev'),
|
||||
});
|
||||
|
||||
beforeEach(() => (numExpectations = 0));
|
||||
|
||||
jasmine.currentEnv_.addReporter({
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue