mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
415 lines
13 KiB
JavaScript
415 lines
13 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @emails react-core
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
// TODO: All these warnings should become static errors using Flow instead
|
|
// of dynamic errors when using JSX with Flow.
|
|
let React;
|
|
let ReactDOM;
|
|
let ReactTestUtils;
|
|
let PropTypes;
|
|
|
|
describe('ReactJSXElementValidator', () => {
|
|
let Component;
|
|
let RequiredPropComponent;
|
|
|
|
beforeEach(() => {
|
|
jest.resetModules();
|
|
|
|
PropTypes = require('prop-types');
|
|
React = require('react');
|
|
ReactDOM = require('react-dom');
|
|
ReactTestUtils = require('react-dom/test-utils');
|
|
|
|
Component = class extends React.Component {
|
|
render() {
|
|
return <div />;
|
|
}
|
|
};
|
|
|
|
RequiredPropComponent = class extends React.Component {
|
|
render() {
|
|
return <span>{this.props.prop}</span>;
|
|
}
|
|
};
|
|
RequiredPropComponent.displayName = 'RequiredPropComponent';
|
|
RequiredPropComponent.propTypes = {prop: PropTypes.string.isRequired};
|
|
});
|
|
|
|
it('warns for keys for arrays of elements in children position', () => {
|
|
expect(() =>
|
|
ReactTestUtils.renderIntoDocument(
|
|
<Component>{[<Component />, <Component />]}</Component>,
|
|
),
|
|
).toWarnDev('Each child in a list should have a unique "key" prop.');
|
|
});
|
|
|
|
it('warns for keys for arrays of elements with owner info', () => {
|
|
class InnerComponent extends React.Component {
|
|
render() {
|
|
return <Component>{this.props.childSet}</Component>;
|
|
}
|
|
}
|
|
|
|
class ComponentWrapper extends React.Component {
|
|
render() {
|
|
return <InnerComponent childSet={[<Component />, <Component />]} />;
|
|
}
|
|
}
|
|
|
|
expect(() =>
|
|
ReactTestUtils.renderIntoDocument(<ComponentWrapper />),
|
|
).toWarnDev(
|
|
'Each child in a list 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', () => {
|
|
const iterable = {
|
|
'@@iterator': function() {
|
|
let i = 0;
|
|
return {
|
|
next: function() {
|
|
const done = ++i > 2;
|
|
return {value: done ? undefined : <Component />, done: done};
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
expect(() =>
|
|
ReactTestUtils.renderIntoDocument(<Component>{iterable}</Component>),
|
|
).toWarnDev('Each child in a list should have a unique "key" prop.');
|
|
});
|
|
|
|
it('does not warn for arrays of elements with keys', () => {
|
|
ReactTestUtils.renderIntoDocument(
|
|
<Component>{[<Component key="#1" />, <Component key="#2" />]}</Component>,
|
|
);
|
|
});
|
|
|
|
it('does not warn for iterable elements with keys', () => {
|
|
const iterable = {
|
|
'@@iterator': function() {
|
|
let i = 0;
|
|
return {
|
|
next: function() {
|
|
const done = ++i > 2;
|
|
return {
|
|
value: done ? undefined : <Component key={'#' + i} />,
|
|
done: done,
|
|
};
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
ReactTestUtils.renderIntoDocument(<Component>{iterable}</Component>);
|
|
});
|
|
|
|
it('does not warn for numeric keys in entry iterable as a child', () => {
|
|
const iterable = {
|
|
'@@iterator': function() {
|
|
let i = 0;
|
|
return {
|
|
next: function() {
|
|
const done = ++i > 2;
|
|
return {value: done ? undefined : [i, <Component />], done: done};
|
|
},
|
|
};
|
|
},
|
|
};
|
|
iterable.entries = iterable['@@iterator'];
|
|
|
|
ReactTestUtils.renderIntoDocument(<Component>{iterable}</Component>);
|
|
});
|
|
|
|
it('does not warn when the element is directly as children', () => {
|
|
ReactTestUtils.renderIntoDocument(
|
|
<Component>
|
|
<Component />
|
|
<Component />
|
|
</Component>,
|
|
);
|
|
});
|
|
|
|
it('does not warn when the child array contains non-elements', () => {
|
|
void <Component>{[{}, {}]}</Component>;
|
|
});
|
|
|
|
it('should give context for PropType errors in nested components.', () => {
|
|
// 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.
|
|
class MyComp extends React.Component {
|
|
render() {
|
|
return <div>My color is {this.color}</div>;
|
|
}
|
|
}
|
|
MyComp.propTypes = {
|
|
color: PropTypes.string,
|
|
};
|
|
class ParentComp extends React.Component {
|
|
render() {
|
|
return <MyComp color={123} />;
|
|
}
|
|
}
|
|
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', () => {
|
|
function MyComp() {
|
|
return null;
|
|
}
|
|
MyComp.propTypes = {
|
|
color: PropTypes.string,
|
|
};
|
|
function MiddleComp(props) {
|
|
return <MyComp color={props.color} />;
|
|
}
|
|
function ParentComp(props) {
|
|
if (props.warn) {
|
|
// This element has a source thanks to JSX.
|
|
return <MiddleComp color={42} />;
|
|
}
|
|
// This element has no source.
|
|
return React.createElement(MiddleComp, {color: 'blue'});
|
|
}
|
|
|
|
const container = document.createElement('div');
|
|
ReactDOM.render(<ParentComp warn={false} />, container);
|
|
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', () => {
|
|
const Undefined = undefined;
|
|
const Null = null;
|
|
const True = true;
|
|
const Div = 'div';
|
|
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 **.',
|
|
{withoutStack: true},
|
|
);
|
|
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 **.',
|
|
{withoutStack: true},
|
|
);
|
|
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 **.',
|
|
{withoutStack: true},
|
|
);
|
|
// No error expected
|
|
void <Div />;
|
|
});
|
|
|
|
it('should check default prop values', () => {
|
|
RequiredPropComponent.defaultProps = {prop: null};
|
|
|
|
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', () => {
|
|
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', () => {
|
|
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" />);
|
|
});
|
|
|
|
it('should warn on invalid prop types', () => {
|
|
// Since there is no prevalidation step for ES6 classes, there is no hook
|
|
// 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.
|
|
class NullPropTypeComponent extends React.Component {
|
|
render() {
|
|
return <span>{this.props.prop}</span>;
|
|
}
|
|
}
|
|
NullPropTypeComponent.propTypes = {
|
|
prop: null,
|
|
};
|
|
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', () => {
|
|
class NullContextTypeComponent extends React.Component {
|
|
render() {
|
|
return <span>{this.props.prop}</span>;
|
|
}
|
|
}
|
|
NullContextTypeComponent.contextTypes = {
|
|
prop: null,
|
|
};
|
|
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 specified on the class', () => {
|
|
class GetDefaultPropsComponent extends React.Component {
|
|
render() {
|
|
return <span>{this.props.prop}</span>;
|
|
}
|
|
}
|
|
GetDefaultPropsComponent.getDefaultProps = () => ({
|
|
prop: 'foo',
|
|
});
|
|
expect(() =>
|
|
ReactTestUtils.renderIntoDocument(<GetDefaultPropsComponent />),
|
|
).toWarnDev(
|
|
'getDefaultProps is only used on classic React.createClass definitions.' +
|
|
' Use a static property named `defaultProps` instead.',
|
|
{withoutStack: true},
|
|
);
|
|
});
|
|
|
|
it('should warn if component declares PropTypes instead of propTypes', () => {
|
|
class MisspelledPropTypesComponent extends React.Component {
|
|
render() {
|
|
return <span>{this.props.prop}</span>;
|
|
}
|
|
}
|
|
MisspelledPropTypesComponent.PropTypes = {
|
|
prop: PropTypes.string,
|
|
};
|
|
expect(() =>
|
|
ReactTestUtils.renderIntoDocument(
|
|
<MisspelledPropTypesComponent prop="hi" />,
|
|
),
|
|
).toWarnDev(
|
|
'Warning: Component MisspelledPropTypesComponent declared `PropTypes` ' +
|
|
'instead of `propTypes`. Did you misspell the property assignment?',
|
|
{withoutStack: true},
|
|
);
|
|
});
|
|
|
|
it('warns for fragments with illegal attributes', () => {
|
|
class Foo extends React.Component {
|
|
render() {
|
|
return <React.Fragment a={1}>hello</React.Fragment>;
|
|
}
|
|
}
|
|
|
|
expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toWarnDev(
|
|
'Invalid prop `a` supplied to `React.Fragment`. React.Fragment ' +
|
|
'can only have `key` and `children` props.',
|
|
);
|
|
});
|
|
|
|
it('warns for fragments with refs', () => {
|
|
class Foo extends React.Component {
|
|
render() {
|
|
return (
|
|
<React.Fragment
|
|
ref={bar => {
|
|
this.foo = bar;
|
|
}}>
|
|
hello
|
|
</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', () => {
|
|
ReactTestUtils.renderIntoDocument(
|
|
<>
|
|
<span>1</span>
|
|
<span>2</span>
|
|
</>,
|
|
);
|
|
});
|
|
|
|
it('warns for fragments of multiple elements with same key', () => {
|
|
expect(() =>
|
|
ReactTestUtils.renderIntoDocument(
|
|
<>
|
|
<span key="a">1</span>
|
|
<span key="a">2</span>
|
|
<span key="b">3</span>
|
|
</>,
|
|
),
|
|
).toWarnDev('Encountered two children with the same key, `a`.', {
|
|
withoutStack: true,
|
|
});
|
|
});
|
|
});
|