Update additional tests to use .toWarnDev() matcher (#11957)

* Migrated several additional tests to use new .toWarnDev() matcher

* Migrated ReactDOMComponent-test to use .toWarnDev() matcher

Note this test previous had some hacky logic to verify errors were reported against unique line numbers. Since the new matcher doesn't suppor this, I replaced this check with an equivalent (I think) comparison of unique DOM elements (eg div -> span)

* Updated several additional tests to use the new .toWarnDev() matcher

* Updated many more tests to use .toWarnDev()

* Updated several additional tests to use .toWarnDev() matcher

* Updated ReactElementValidator to distinguish between Array and Object in its warning. Also updated its test to use .toWarnDev() matcher.

* Updated a couple of additional tests

* Removed unused normalizeCodeLocInfo() methods
This commit is contained in:
Brian Vaughn 2018-01-03 13:55:37 -08:00 committed by GitHub
parent a442d9bc08
commit 9f848f8ebe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 450 additions and 718 deletions

View File

@ -423,8 +423,6 @@ describe('ReactCompositeComponent-state', () => {
});
it('should treat assigning to this.state inside cWM as a replaceState, with a warning', () => {
spyOnDev(console, 'error');
let ops = [];
class Test extends React.Component {
state = {step: 1, extra: true};

View File

@ -1909,8 +1909,6 @@ describe('ReactErrorBoundaries', () => {
});
it('discards a bad root if the root component fails', () => {
spyOnDev(console, 'error');
const X = null;
const Y = undefined;
let err1;
@ -1918,13 +1916,21 @@ describe('ReactErrorBoundaries', () => {
try {
let container = document.createElement('div');
ReactDOM.render(<X />, container);
expect(() => ReactDOM.render(<X />, container)).toWarnDev(
'React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function ' +
'(for composite components) but got: null.',
);
} catch (err) {
err1 = err;
}
try {
let container = document.createElement('div');
ReactDOM.render(<Y />, container);
expect(() => ReactDOM.render(<Y />, container)).toWarnDev(
'React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function ' +
'(for composite components) but got: undefined.',
);
} catch (err) {
err2 = err;
}

View File

@ -73,104 +73,98 @@ const expectChildren = function(container, children) {
*/
describe('ReactMultiChildText', () => {
it('should correctly handle all possible children for render and update', () => {
spyOnDev(console, 'error');
// prettier-ignore
testAllPermutations([
// basic values
undefined, [],
null, [],
false, [],
true, [],
0, '0',
1.2, '1.2',
'', '',
'foo', 'foo',
expect(() => {
// prettier-ignore
testAllPermutations([
// basic values
undefined, [],
null, [],
false, [],
true, [],
0, '0',
1.2, '1.2',
'', '',
'foo', 'foo',
[], [],
[undefined], [],
[null], [],
[false], [],
[true], [],
[0], ['0'],
[1.2], ['1.2'],
[''], [''],
['foo'], ['foo'],
[<div />], [<div />],
[], [],
[undefined], [],
[null], [],
[false], [],
[true], [],
[0], ['0'],
[1.2], ['1.2'],
[''], [''],
['foo'], ['foo'],
[<div />], [<div />],
// two adjacent values
[true, 0], ['0'],
[0, 0], ['0', '0'],
[1.2, 0], ['1.2', '0'],
[0, ''], ['0', ''],
['foo', 0], ['foo', '0'],
[0, <div />], ['0', <div />],
// two adjacent values
[true, 0], ['0'],
[0, 0], ['0', '0'],
[1.2, 0], ['1.2', '0'],
[0, ''], ['0', ''],
['foo', 0], ['foo', '0'],
[0, <div />], ['0', <div />],
[true, 1.2], ['1.2'],
[1.2, 0], ['1.2', '0'],
[1.2, 1.2], ['1.2', '1.2'],
[1.2, ''], ['1.2', ''],
['foo', 1.2], ['foo', '1.2'],
[1.2, <div />], ['1.2', <div />],
[true, 1.2], ['1.2'],
[1.2, 0], ['1.2', '0'],
[1.2, 1.2], ['1.2', '1.2'],
[1.2, ''], ['1.2', ''],
['foo', 1.2], ['foo', '1.2'],
[1.2, <div />], ['1.2', <div />],
[true, ''], [''],
['', 0], ['', '0'],
[1.2, ''], ['1.2', ''],
['', ''], ['', ''],
['foo', ''], ['foo', ''],
['', <div />], ['', <div />],
[true, ''], [''],
['', 0], ['', '0'],
[1.2, ''], ['1.2', ''],
['', ''], ['', ''],
['foo', ''], ['foo', ''],
['', <div />], ['', <div />],
[true, 'foo'], ['foo'],
['foo', 0], ['foo', '0'],
[1.2, 'foo'], ['1.2', 'foo'],
['foo', ''], ['foo', ''],
['foo', 'foo'], ['foo', 'foo'],
['foo', <div />], ['foo', <div />],
[true, 'foo'], ['foo'],
['foo', 0], ['foo', '0'],
[1.2, 'foo'], ['1.2', 'foo'],
['foo', ''], ['foo', ''],
['foo', 'foo'], ['foo', 'foo'],
['foo', <div />], ['foo', <div />],
// values separated by an element
[true, <div />, true], [<div />],
[1.2, <div />, 1.2], ['1.2', <div />, '1.2'],
['', <div />, ''], ['', <div />, ''],
['foo', <div />, 'foo'], ['foo', <div />, 'foo'],
// values separated by an element
[true, <div />, true], [<div />],
[1.2, <div />, 1.2], ['1.2', <div />, '1.2'],
['', <div />, ''], ['', <div />, ''],
['foo', <div />, 'foo'], ['foo', <div />, 'foo'],
[true, 1.2, <div />, '', 'foo'], ['1.2', <div />, '', 'foo'],
[1.2, '', <div />, 'foo', true], ['1.2', '', <div />, 'foo'],
['', 'foo', <div />, true, 1.2], ['', 'foo', <div />, '1.2'],
[true, 1.2, <div />, '', 'foo'], ['1.2', <div />, '', 'foo'],
[1.2, '', <div />, 'foo', true], ['1.2', '', <div />, 'foo'],
['', 'foo', <div />, true, 1.2], ['', 'foo', <div />, '1.2'],
[true, 1.2, '', <div />, 'foo', true, 1.2], ['1.2', '', <div />, 'foo', '1.2'],
['', 'foo', true, <div />, 1.2, '', 'foo'], ['', 'foo', <div />, '1.2', '', 'foo'],
[true, 1.2, '', <div />, 'foo', true, 1.2], ['1.2', '', <div />, 'foo', '1.2'],
['', 'foo', true, <div />, 1.2, '', 'foo'], ['', 'foo', <div />, '1.2', '', 'foo'],
// values inside arrays
[[true], [true]], [],
[[1.2], [1.2]], ['1.2', '1.2'],
[[''], ['']], ['', ''],
[['foo'], ['foo']], ['foo', 'foo'],
[[<div />], [<div />]], [<div />, <div />],
// values inside arrays
[[true], [true]], [],
[[1.2], [1.2]], ['1.2', '1.2'],
[[''], ['']], ['', ''],
[['foo'], ['foo']], ['foo', 'foo'],
[[<div />], [<div />]], [<div />, <div />],
[[true, 1.2, <div />], '', 'foo'], ['1.2', <div />, '', 'foo'],
[1.2, '', [<div />, 'foo', true]], ['1.2', '', <div />, 'foo'],
['', ['foo', <div />, true], 1.2], ['', 'foo', <div />, '1.2'],
[[true, 1.2, <div />], '', 'foo'], ['1.2', <div />, '', 'foo'],
[1.2, '', [<div />, 'foo', true]], ['1.2', '', <div />, 'foo'],
['', ['foo', <div />, true], 1.2], ['', 'foo', <div />, '1.2'],
[true, [1.2, '', <div />, 'foo'], true, 1.2], ['1.2', '', <div />, 'foo', '1.2'],
['', 'foo', [true, <div />, 1.2, ''], 'foo'], ['', 'foo', <div />, '1.2', '', 'foo'],
[true, [1.2, '', <div />, 'foo'], true, 1.2], ['1.2', '', <div />, 'foo', '1.2'],
['', 'foo', [true, <div />, 1.2, ''], 'foo'], ['', 'foo', <div />, '1.2', '', 'foo'],
// values inside elements
[<div>{true}{1.2}{<div />}</div>, '', 'foo'], [<div />, '', 'foo'],
[1.2, '', <div>{<div />}{'foo'}{true}</div>], ['1.2', '', <div />],
['', <div>{'foo'}{<div />}{true}</div>, 1.2], ['', <div />, '1.2'],
// values inside elements
[<div>{true}{1.2}{<div />}</div>, '', 'foo'], [<div />, '', 'foo'],
[1.2, '', <div>{<div />}{'foo'}{true}</div>], ['1.2', '', <div />],
['', <div>{'foo'}{<div />}{true}</div>, 1.2], ['', <div />, '1.2'],
[true, <div>{1.2}{''}{<div />}{'foo'}</div>, true, 1.2], [<div />, '1.2'],
['', 'foo', <div>{true}{<div />}{1.2}{''}</div>, 'foo'], ['', 'foo', <div />, 'foo'],
[true, <div>{1.2}{''}{<div />}{'foo'}</div>, true, 1.2], [<div />, '1.2'],
['', 'foo', <div>{true}{<div />}{1.2}{''}</div>, 'foo'], ['', 'foo', <div />, 'foo'],
]);
}).toWarnDev([
'Warning: Each child in an array or iterator should have a unique "key" prop.',
'Warning: Each child in an array or iterator should have a unique "key" prop.',
]);
if (__DEV__) {
expect(console.error.calls.count()).toBe(2);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Warning: Each child in an array or iterator should have a unique "key" prop.',
);
expect(console.error.calls.argsFor(1)[0]).toContain(
'Warning: Each child in an array or iterator should have a unique "key" prop.',
);
}
});
it('should throw if rendering both HTML and children', () => {

View File

@ -34,19 +34,15 @@ describe('rendering React components at document', () => {
});
describe('with old implicit hydration API', () => {
function expectDeprecationWarningWithFiber() {
if (__DEV__) {
expect(console.warn.calls.count()).toBe(1);
expect(console.warn.calls.argsFor(0)[0]).toContain(
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
}
function expectDeprecationWarningWithFiber(callback) {
expect(callback).toLowPriorityWarnDev(
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
}
it('should be able to adopt server markup', () => {
spyOnDev(console, 'warn');
class Root extends React.Component {
render() {
return (
@ -64,18 +60,18 @@ describe('rendering React components at document', () => {
const testDocument = getTestDocument(markup);
const body = testDocument.body;
ReactDOM.render(<Root hello="world" />, testDocument);
expectDeprecationWarningWithFiber(() =>
ReactDOM.render(<Root hello="world" />, testDocument),
);
expect(testDocument.body.innerHTML).toBe('Hello world');
ReactDOM.render(<Root hello="moon" />, testDocument);
expect(testDocument.body.innerHTML).toBe('Hello moon');
expect(body === testDocument.body).toBe(true);
expectDeprecationWarningWithFiber();
});
it('should not be able to unmount component from document node', () => {
spyOnDev(console, 'warn');
class Root extends React.Component {
render() {
return (
@ -91,18 +87,17 @@ describe('rendering React components at document', () => {
const markup = ReactDOMServer.renderToString(<Root />);
const testDocument = getTestDocument(markup);
ReactDOM.render(<Root />, testDocument);
expectDeprecationWarningWithFiber(() =>
ReactDOM.render(<Root />, testDocument),
);
expect(testDocument.body.innerHTML).toBe('Hello world');
// In Fiber this actually works. It might not be a good idea though.
ReactDOM.unmountComponentAtNode(testDocument);
expect(testDocument.firstChild).toBe(null);
expectDeprecationWarningWithFiber();
});
it('should not be able to switch root constructors', () => {
spyOnDev(console, 'warn');
class Component extends React.Component {
render() {
return (
@ -132,18 +127,18 @@ describe('rendering React components at document', () => {
const markup = ReactDOMServer.renderToString(<Component />);
const testDocument = getTestDocument(markup);
ReactDOM.render(<Component />, testDocument);
expectDeprecationWarningWithFiber(() =>
ReactDOM.render(<Component />, testDocument),
);
expect(testDocument.body.innerHTML).toBe('Hello world');
// This works but is probably a bad idea.
ReactDOM.render(<Component2 />, testDocument);
expect(testDocument.body.innerHTML).toBe('Goodbye world');
expectDeprecationWarningWithFiber();
});
it('should be able to mount into document', () => {
spyOnDev(console, 'warn');
class Component extends React.Component {
render() {
return (
@ -162,10 +157,11 @@ describe('rendering React components at document', () => {
);
const testDocument = getTestDocument(markup);
ReactDOM.render(<Component text="Hello world" />, testDocument);
expectDeprecationWarningWithFiber(() =>
ReactDOM.render(<Component text="Hello world" />, testDocument),
);
expect(testDocument.body.innerHTML).toBe('Hello world');
expectDeprecationWarningWithFiber();
});
it('renders over an existing text child without throwing', () => {
@ -197,22 +193,15 @@ describe('rendering React components at document', () => {
);
const testDocument = getTestDocument(markup);
spyOnDev(console, 'warn');
spyOnDev(console, 'error');
ReactDOM.render(<Component text="Hello world" />, testDocument);
expect(testDocument.body.innerHTML).toBe('Hello world');
if (__DEV__) {
expect(console.warn.calls.count()).toBe(1);
expect(console.warn.calls.argsFor(0)[0]).toContain(
expect(() => {
expect(() =>
ReactDOM.render(<Component text="Hello world" />, testDocument),
).toLowPriorityWarnDev(
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Warning: Text content did not match.',
);
}
}).toWarnDev('Warning: Text content did not match.');
});
it('should throw on full document render w/ no markup', () => {
@ -239,7 +228,6 @@ describe('rendering React components at document', () => {
});
it('supports findDOMNode on full-page components', () => {
spyOnDev(console, 'warn');
const tree = (
<html>
<head>
@ -251,10 +239,12 @@ describe('rendering React components at document', () => {
const markup = ReactDOMServer.renderToString(tree);
const testDocument = getTestDocument(markup);
const component = ReactDOM.render(tree, testDocument);
let component;
expectDeprecationWarningWithFiber(() => {
component = ReactDOM.render(tree, testDocument);
});
expect(testDocument.body.innerHTML).toBe('Hello world');
expect(ReactDOM.findDOMNode(component).tagName).toBe('HTML');
expectDeprecationWarningWithFiber();
});
});
@ -375,17 +365,12 @@ describe('rendering React components at document', () => {
});
it('renders over an existing text child without throwing', () => {
spyOnDev(console, 'error');
const container = document.createElement('div');
container.textContent = 'potato';
ReactDOM.hydrate(<div>parsnip</div>, container);
expect(() => ReactDOM.hydrate(<div>parsnip</div>, container)).toWarnDev(
'Expected server HTML to contain a matching <div> in <div>.',
);
expect(container.textContent).toBe('parsnip');
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Expected server HTML to contain a matching <div> in <div>.',
);
}
});
it('should give helpful errors on state desync', () => {
@ -407,19 +392,13 @@ describe('rendering React components at document', () => {
);
const testDocument = getTestDocument(markup);
spyOnDev(console, 'error');
ReactDOM.hydrate(<Component text="Hello world" />, testDocument);
expect(() =>
ReactDOM.hydrate(<Component text="Hello world" />, testDocument),
).toWarnDev('Warning: Text content did not match.');
expect(testDocument.body.innerHTML).toBe('Hello world');
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Warning: Text content did not match.',
);
}
});
it('should render w/ no markup to full document', () => {
spyOnDev(console, 'error');
const testDocument = getTestDocument();
class Component extends React.Component {
@ -435,15 +414,11 @@ describe('rendering React components at document', () => {
}
}
ReactDOM.hydrate(<Component text="Hello world" />, testDocument);
// getTestDocument() has an extra <meta> that we didn't render.
expect(() =>
ReactDOM.hydrate(<Component text="Hello world" />, testDocument),
).toWarnDev('Did not expect server HTML to contain a <meta> in <head>.');
expect(testDocument.body.innerHTML).toBe('Hello world');
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
// getTestDocument() has an extra <meta> that we didn't render.
'Did not expect server HTML to contain a <meta> in <head>.',
);
}
});
it('supports findDOMNode on full-page components', () => {

View File

@ -431,24 +431,17 @@ describe('ReactDOMServer', () => {
}
}
spyOnDev(console, 'error');
ReactDOMServer.renderToString(<Foo />);
jest.runOnlyPendingTimers();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.mostRecent().args[0]).toBe(
'Warning: setState(...): Can only update a mounting component.' +
' This usually means you called setState() outside componentWillMount() on the server.' +
' This is a no-op.\n\nPlease check the code for the Foo component.',
);
}
expect(() => jest.runOnlyPendingTimers()).toWarnDev(
'Warning: setState(...): Can only update a mounting component.' +
' This usually means you called setState() outside componentWillMount() on the server.' +
' This is a no-op.\n\nPlease check the code for the Foo component.',
);
const markup = ReactDOMServer.renderToStaticMarkup(<Foo />);
expect(markup).toBe('<div>hello</div>');
// No additional warnings are expected
jest.runOnlyPendingTimers();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
}
});
it('warns with a no-op when an async forceUpdate is triggered', () => {
@ -465,23 +458,17 @@ describe('ReactDOMServer', () => {
}
}
spyOnDev(console, 'error');
ReactDOMServer.renderToString(<Baz />);
jest.runOnlyPendingTimers();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.mostRecent().args[0]).toBe(
'Warning: forceUpdate(...): Can only update a mounting component. ' +
'This usually means you called forceUpdate() outside componentWillMount() on the server. ' +
'This is a no-op.\n\nPlease check the code for the Baz component.',
);
}
expect(() => jest.runOnlyPendingTimers()).toWarnDev(
'Warning: forceUpdate(...): Can only update a mounting component. ' +
'This usually means you called forceUpdate() outside componentWillMount() on the server. ' +
'This is a no-op.\n\nPlease check the code for the Baz component.',
);
const markup = ReactDOMServer.renderToStaticMarkup(<Baz />);
expect(markup).toBe('<div></div>');
});
it('should throw (in dev) when children are mutated during render', () => {
spyOnDev(console, 'error');
function Wrapper(props) {
props.children[1] = <p key={1} />; // Mutation is illegal
return <div>{props.children}</div>;
@ -510,51 +497,43 @@ describe('ReactDOMServer', () => {
});
it('warns about lowercase html but not in svg tags', () => {
spyOnDev(console, 'error');
function CompositeG(props) {
// Make sure namespace passes through composites
return <g>{props.children}</g>;
}
ReactDOMServer.renderToStaticMarkup(
<div>
<inPUT />
<svg>
<CompositeG>
<linearGradient />
<foreignObject>
{/* back to HTML */}
<iFrame />
</foreignObject>
</CompositeG>
</svg>
</div>,
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(2);
expect(console.error.calls.argsFor(0)[0]).toBe(
'Warning: <inPUT /> is using uppercase HTML. Always use lowercase ' +
'HTML tags in React.',
);
expect(() =>
ReactDOMServer.renderToStaticMarkup(
<div>
<inPUT />
<svg>
<CompositeG>
<linearGradient />
<foreignObject>
{/* back to HTML */}
<iFrame />
</foreignObject>
</CompositeG>
</svg>
</div>,
),
).toWarnDev([
'Warning: <inPUT /> is using uppercase HTML. Always use lowercase ' +
'HTML tags in React.',
// linearGradient doesn't warn
expect(console.error.calls.argsFor(1)[0]).toBe(
'Warning: <iFrame /> is using uppercase HTML. Always use lowercase ' +
'HTML tags in React.',
);
}
'Warning: <iFrame /> is using uppercase HTML. Always use lowercase ' +
'HTML tags in React.',
]);
});
it('should warn about contentEditable and children', () => {
spyOnDev(console, 'error');
ReactDOMServer.renderToString(<div contentEditable={true} children="" />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: A component is `contentEditable` and contains `children` ' +
'managed by React. It is now your responsibility to guarantee that ' +
'none of those nodes are unexpectedly modified or duplicated. This ' +
'is probably not intentional.\n in div (at **)',
);
}
expect(() =>
ReactDOMServer.renderToString(<div contentEditable={true} children="" />),
).toWarnDev(
'Warning: A component is `contentEditable` and contains `children` ' +
'managed by React. It is now your responsibility to guarantee that ' +
'none of those nodes are unexpectedly modified or duplicated. This ' +
'is probably not intentional.\n in div (at **)',
);
});
it('should throw rendering call/return on the server', () => {

View File

@ -26,9 +26,6 @@ describe('ReactDOMServerHydration', () => {
});
it('should have the correct mounting behavior (old hydrate API)', () => {
spyOnDev(console, 'warn');
spyOnDev(console, 'error');
let mountCount = 0;
let numClicks = 0;
@ -78,17 +75,16 @@ describe('ReactDOMServerHydration', () => {
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
element.innerHTML = lastMarkup;
let instance = ReactDOM.render(<TestComponent name="x" />, element);
let instance;
expect(() => {
instance = ReactDOM.render(<TestComponent name="x" />, element);
}).toLowPriorityWarnDev(
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
expect(mountCount).toEqual(3);
if (__DEV__) {
expect(console.warn.calls.count()).toBe(1);
expect(console.warn.calls.argsFor(0)[0]).toContain(
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
console.warn.calls.reset();
}
expect(element.innerHTML).toBe(lastMarkup);
// Ensure the events system works after mount into server markup
@ -102,15 +98,10 @@ describe('ReactDOMServerHydration', () => {
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
element.innerHTML = lastMarkup;
instance = ReactDOM.render(<TestComponent name="y" />, element);
expect(() => {
instance = ReactDOM.render(<TestComponent name="y" />, element);
}).toWarnDev('Text content did not match. Server: "x" Client: "y"');
expect(mountCount).toEqual(4);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Text content did not match. Server: "x" Client: "y"',
);
console.error.calls.reset();
}
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
@ -118,15 +109,9 @@ describe('ReactDOMServerHydration', () => {
expect(numClicks).toEqual(1);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(2);
if (__DEV__) {
expect(console.warn.calls.count()).toBe(0);
expect(console.error.calls.count()).toBe(0);
}
});
it('should have the correct mounting behavior (new hydrate API)', () => {
spyOnDev(console, 'error');
let mountCount = 0;
let numClicks = 0;
@ -191,11 +176,10 @@ describe('ReactDOMServerHydration', () => {
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
element.innerHTML = lastMarkup;
instance = ReactDOM.hydrate(<TestComponent name="y" />, element);
expect(() => {
instance = ReactDOM.hydrate(<TestComponent name="y" />, element);
}).toWarnDev('Text content did not match. Server: "x" Client: "y"');
expect(mountCount).toEqual(4);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
}
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
@ -242,8 +226,6 @@ describe('ReactDOMServerHydration', () => {
// Regression test for https://github.com/facebook/react/issues/11726
it('should not focus on either server or client with autofocus={false} even if there is a markup mismatch', () => {
spyOnDev(console, 'error');
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(
<button autoFocus={false}>server</button>,
@ -251,15 +233,14 @@ describe('ReactDOMServerHydration', () => {
expect(element.firstChild.autofocus).toBe(false);
element.firstChild.focus = jest.fn();
ReactDOM.hydrate(<button autoFocus={false}>client</button>, element);
expect(() =>
ReactDOM.hydrate(<button autoFocus={false}>client</button>, element),
).toWarnDev(
'Warning: Text content did not match. Server: "server" Client: "client"',
);
expect(element.firstChild.focus).not.toHaveBeenCalled();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
'Warning: Text content did not match. Server: "server" Client: "client"',
);
}
});
it('should throw rendering portals on the server', () => {

View File

@ -19,10 +19,6 @@ function StatelessComponent(props) {
}
describe('ReactStatelessComponent', () => {
function normalizeCodeLocInfo(str) {
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
}
beforeEach(() => {
jest.resetModuleRegistry();
PropTypes = require('prop-types');
@ -103,7 +99,6 @@ describe('ReactStatelessComponent', () => {
});
it('should warn for childContextTypes on a functional component', () => {
spyOnDev(console, 'error');
function StatelessComponentWithChildContext(props) {
return <div>{props.name}</div>;
}
@ -114,15 +109,15 @@ describe('ReactStatelessComponent', () => {
const container = document.createElement('div');
ReactDOM.render(<StatelessComponentWithChildContext name="A" />, container);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'StatelessComponentWithChildContext(...): childContextTypes cannot ' +
'be defined on a functional component.',
);
}
expect(() =>
ReactDOM.render(
<StatelessComponentWithChildContext name="A" />,
container,
),
).toWarnDev(
'StatelessComponentWithChildContext(...): childContextTypes cannot ' +
'be defined on a functional component.',
);
});
it('should throw when stateless component returns undefined', () => {
@ -157,8 +152,6 @@ describe('ReactStatelessComponent', () => {
});
it('should warn when given a string ref', () => {
spyOnDev(console, 'error');
function Indirection(props) {
return <div>{props.children}</div>;
}
@ -173,29 +166,23 @@ describe('ReactStatelessComponent', () => {
}
}
ReactTestUtils.renderIntoDocument(<ParentUsingStringRef />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: Stateless function components cannot be given refs. ' +
'Attempts to access this ref will fail.\n\nCheck the render method ' +
'of `ParentUsingStringRef`.\n' +
' in StatelessComponent (at **)\n' +
' in div (at **)\n' +
' in Indirection (at **)\n' +
' in ParentUsingStringRef (at **)',
);
}
expect(() =>
ReactTestUtils.renderIntoDocument(<ParentUsingStringRef />),
).toWarnDev(
'Warning: Stateless function components cannot be given refs. ' +
'Attempts to access this ref will fail.\n\nCheck the render method ' +
'of `ParentUsingStringRef`.\n' +
' in StatelessComponent (at **)\n' +
' in div (at **)\n' +
' in Indirection (at **)\n' +
' in ParentUsingStringRef (at **)',
);
// No additional warnings should be logged
ReactTestUtils.renderIntoDocument(<ParentUsingStringRef />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
}
});
it('should warn when given a function ref', () => {
spyOnDev(console, 'error');
function Indirection(props) {
return <div>{props.children}</div>;
}
@ -215,29 +202,23 @@ describe('ReactStatelessComponent', () => {
}
}
ReactTestUtils.renderIntoDocument(<ParentUsingFunctionRef />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: Stateless function components cannot be given refs. ' +
'Attempts to access this ref will fail.\n\nCheck the render method ' +
'of `ParentUsingFunctionRef`.\n' +
' in StatelessComponent (at **)\n' +
' in div (at **)\n' +
' in Indirection (at **)\n' +
' in ParentUsingFunctionRef (at **)',
);
}
expect(() =>
ReactTestUtils.renderIntoDocument(<ParentUsingFunctionRef />),
).toWarnDev(
'Warning: Stateless function components cannot be given refs. ' +
'Attempts to access this ref will fail.\n\nCheck the render method ' +
'of `ParentUsingFunctionRef`.\n' +
' in StatelessComponent (at **)\n' +
' in div (at **)\n' +
' in Indirection (at **)\n' +
' in ParentUsingFunctionRef (at **)',
);
// No additional warnings should be logged
ReactTestUtils.renderIntoDocument(<ParentUsingFunctionRef />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
}
});
it('deduplicates ref warnings based on element or owner', () => {
spyOnDev(console, 'error');
// When owner uses JSX, we can use exact line location to dedupe warnings
class AnonymousParentUsingJSX extends React.Component {
render() {
@ -246,23 +227,19 @@ describe('ReactStatelessComponent', () => {
}
Object.defineProperty(AnonymousParentUsingJSX, 'name', {value: undefined});
const instance1 = ReactTestUtils.renderIntoDocument(
<AnonymousParentUsingJSX />,
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Warning: Stateless function components cannot be given refs.',
let instance1;
expect(() => {
instance1 = ReactTestUtils.renderIntoDocument(
<AnonymousParentUsingJSX />,
);
}
}).toWarnDev(
'Warning: Stateless function components cannot be given refs.',
);
// Should be deduped (offending element is on the same line):
instance1.forceUpdate();
// Should also be deduped (offending element is on the same line):
ReactTestUtils.renderIntoDocument(<AnonymousParentUsingJSX />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
console.error.calls.reset();
}
// When owner doesn't use JSX, and is anonymous, we warn once per internal instance.
class AnonymousParentNotUsingJSX extends React.Component {
@ -277,29 +254,20 @@ describe('ReactStatelessComponent', () => {
value: undefined,
});
const instance2 = ReactTestUtils.renderIntoDocument(
<AnonymousParentNotUsingJSX />,
let instance2;
expect(() => {
instance2 = ReactTestUtils.renderIntoDocument(
<AnonymousParentNotUsingJSX />,
);
}).toWarnDev(
'Warning: Stateless function components cannot be given refs.',
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Warning: Stateless function components cannot be given refs.',
);
}
// Should be deduped (same internal instance):
// Should be deduped (same internal instance, no additional warnings)
instance2.forceUpdate();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
}
// Could not be deduped (different internal instance):
ReactTestUtils.renderIntoDocument(<AnonymousParentNotUsingJSX />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(2);
expect(console.error.calls.argsFor(1)[0]).toContain(
'Warning: Stateless function components cannot be given refs.',
);
console.error.calls.reset();
}
expect(() =>
ReactTestUtils.renderIntoDocument(<AnonymousParentNotUsingJSX />),
).toWarnDev('Warning: Stateless function components cannot be given refs.');
// When owner doesn't use JSX, but is named, we warn once per owner name
class NamedParentNotUsingJSX extends React.Component {
@ -310,33 +278,21 @@ describe('ReactStatelessComponent', () => {
});
}
}
const instance3 = ReactTestUtils.renderIntoDocument(
<NamedParentNotUsingJSX />,
let instance3;
expect(() => {
instance3 = ReactTestUtils.renderIntoDocument(<NamedParentNotUsingJSX />);
}).toWarnDev(
'Warning: Stateless function components cannot be given refs.',
);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Warning: Stateless function components cannot be given refs.',
);
}
// Should be deduped (same owner name):
// Should be deduped (same owner name, no additional warnings):
instance3.forceUpdate();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
}
// Should also be deduped (same owner name):
// Should also be deduped (same owner name, no additional warnings):
ReactTestUtils.renderIntoDocument(<NamedParentNotUsingJSX />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
console.error.calls.reset();
}
});
// This guards against a regression caused by clearing the current debug fiber.
// https://github.com/facebook/react/issues/10831
it('should warn when giving a function ref with context', () => {
spyOnDev(console, 'error');
function Child() {
return null;
}
@ -358,17 +314,13 @@ describe('ReactStatelessComponent', () => {
}
}
ReactTestUtils.renderIntoDocument(<Parent />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: Stateless function components cannot be given refs. ' +
'Attempts to access this ref will fail.\n\nCheck the render method ' +
'of `Parent`.\n' +
' in Child (at **)\n' +
' in Parent (at **)',
);
}
expect(() => ReactTestUtils.renderIntoDocument(<Parent />)).toWarnDev(
'Warning: Stateless function components cannot be given refs. ' +
'Attempts to access this ref will fail.\n\nCheck the render method ' +
'of `Parent`.\n' +
' in Child (at **)\n' +
' in Parent (at **)',
);
});
it('should provide a null ref', () => {
@ -385,15 +337,10 @@ describe('ReactStatelessComponent', () => {
return <div>{[<span />]}</div>;
}
spyOnDev(console, 'error');
ReactTestUtils.renderIntoDocument(<Child />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'a unique "key" prop',
);
expect(console.error.calls.argsFor(0)[0]).toContain('Child');
}
expect(() => ReactTestUtils.renderIntoDocument(<Child />)).toWarnDev(
'Each child in an array or iterator should have a unique "key" prop.\n\n' +
'Check the render method of `Child`.',
);
});
it('should support default props and prop types', () => {
@ -403,18 +350,11 @@ describe('ReactStatelessComponent', () => {
Child.defaultProps = {test: 2};
Child.propTypes = {test: PropTypes.string};
spyOnDev(console, 'error');
ReactTestUtils.renderIntoDocument(<Child />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
).toBe(
'Warning: Failed prop type: Invalid prop `test` of type `number` ' +
'supplied to `Child`, expected `string`.\n' +
' in Child (at **)',
);
}
expect(() => ReactTestUtils.renderIntoDocument(<Child />)).toWarnDev(
'Warning: Failed prop type: Invalid prop `test` of type `number` ' +
'supplied to `Child`, expected `string`.\n' +
' in Child (at **)',
);
});
it('should receive context', () => {

View File

@ -842,8 +842,6 @@ describe('ReactUpdates', () => {
});
it('throws in setState if the update callback is not a function', () => {
spyOnDev(console, 'error');
function Foo() {
this.a = 1;
this.b = 2;
@ -859,44 +857,38 @@ describe('ReactUpdates', () => {
let component = ReactTestUtils.renderIntoDocument(<A />);
expect(() => component.setState({}, 'no')).toThrowError(
'Invalid argument passed as callback. Expected a function. Instead ' +
'received: no',
);
if (__DEV__) {
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() => {
expect(() => component.setState({}, 'no')).toWarnDev(
'setState(...): Expected the last optional `callback` argument to be ' +
'a function. Instead received: no.',
);
}
component = ReactTestUtils.renderIntoDocument(<A />);
expect(() => component.setState({}, {foo: 'bar'})).toThrowError(
}).toThrowError(
'Invalid argument passed as callback. Expected a function. Instead ' +
'received: [object Object]',
'received: no',
);
if (__DEV__) {
expect(console.error.calls.argsFor(1)[0]).toContain(
component = ReactTestUtils.renderIntoDocument(<A />);
expect(() => {
expect(() => component.setState({}, {foo: 'bar'})).toWarnDev(
'setState(...): Expected the last optional `callback` argument to be ' +
'a function. Instead received: [object Object].',
);
}
component = ReactTestUtils.renderIntoDocument(<A />);
expect(() => component.setState({}, new Foo())).toThrowError(
}).toThrowError(
'Invalid argument passed as callback. Expected a function. Instead ' +
'received: [object Object]',
);
if (__DEV__) {
expect(console.error.calls.argsFor(2)[0]).toContain(
component = ReactTestUtils.renderIntoDocument(<A />);
expect(() => {
expect(() => component.setState({}, new Foo())).toWarnDev(
'setState(...): Expected the last optional `callback` argument to be ' +
'a function. Instead received: [object Object].',
);
expect(console.error.calls.count()).toBe(3);
}
}).toThrowError(
'Invalid argument passed as callback. Expected a function. Instead ' +
'received: [object Object]',
);
});
it('throws in forceUpdate if the update callback is not a function', () => {
spyOnDev(console, 'error');
function Foo() {
this.a = 1;
this.b = 2;
@ -912,39 +904,35 @@ describe('ReactUpdates', () => {
let component = ReactTestUtils.renderIntoDocument(<A />);
expect(() => component.forceUpdate('no')).toThrowError(
'Invalid argument passed as callback. Expected a function. Instead ' +
'received: no',
);
if (__DEV__) {
expect(console.error.calls.argsFor(0)[0]).toContain(
expect(() => {
expect(() => component.forceUpdate('no')).toWarnDev(
'forceUpdate(...): Expected the last optional `callback` argument to be ' +
'a function. Instead received: no.',
);
}
component = ReactTestUtils.renderIntoDocument(<A />);
expect(() => component.forceUpdate({foo: 'bar'})).toThrowError(
}).toThrowError(
'Invalid argument passed as callback. Expected a function. Instead ' +
'received: [object Object]',
'received: no',
);
if (__DEV__) {
expect(console.error.calls.argsFor(1)[0]).toContain(
component = ReactTestUtils.renderIntoDocument(<A />);
expect(() => {
expect(() => component.forceUpdate({foo: 'bar'})).toWarnDev(
'forceUpdate(...): Expected the last optional `callback` argument to be ' +
'a function. Instead received: [object Object].',
);
}
component = ReactTestUtils.renderIntoDocument(<A />);
expect(() => component.forceUpdate(new Foo())).toThrowError(
}).toThrowError(
'Invalid argument passed as callback. Expected a function. Instead ' +
'received: [object Object]',
);
if (__DEV__) {
expect(console.error.calls.argsFor(2)[0]).toContain(
component = ReactTestUtils.renderIntoDocument(<A />);
expect(() => {
expect(() => component.forceUpdate(new Foo())).toWarnDev(
'forceUpdate(...): Expected the last optional `callback` argument to be ' +
'a function. Instead received: [object Object].',
);
expect(console.error.calls.count()).toBe(3);
}
}).toThrowError(
'Invalid argument passed as callback. Expected a function. Instead ' +
'received: [object Object]',
);
});
it('does not update one component twice in a batch (#2410)', () => {
@ -1228,8 +1216,6 @@ describe('ReactUpdates', () => {
);
it('uses correct base state for setState inside render phase', () => {
spyOnDev(console, 'error');
let ops = [];
class Foo extends React.Component {
@ -1246,14 +1232,10 @@ describe('ReactUpdates', () => {
}
const container = document.createElement('div');
ReactDOM.render(<Foo />, container);
expect(() => ReactDOM.render(<Foo />, container)).toWarnDev(
'Cannot update during an existing state transition',
);
expect(ops).toEqual(['base: 0, memoized: 0', 'base: 1, memoized: 1']);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Cannot update during an existing state transition',
);
}
});
it('does not re-render if state update is null', () => {

View File

@ -86,19 +86,13 @@ describe('TapEventPlugin', () => {
idCallOrder = [];
tapMoveThreshold = TapEventPlugin.tapMoveThreshold;
spyOnDev(console, 'warn');
EventPluginHub.injection.injectEventPluginsByName({
TapEventPlugin: TapEventPlugin,
});
});
afterEach(() => {
if (__DEV__) {
expect(console.warn.calls.count()).toBe(1);
expect(console.warn.calls.argsFor(0)[0]).toContain(
'Injecting custom event plugins (TapEventPlugin) is deprecated',
);
}
expect(() =>
EventPluginHub.injection.injectEventPluginsByName({
TapEventPlugin: TapEventPlugin,
}),
).toLowPriorityWarnDev(
'Injecting custom event plugins (TapEventPlugin) is deprecated',
);
});
/**

View File

@ -12,40 +12,22 @@
const React = require('react');
const ReactDOM = require('react-dom');
function normalizeCodeLocInfo(str) {
return str && str.replace(/at .+?:\d+/g, 'at **');
}
function expectWarnings(tags, warnings = []) {
tags = [...tags];
warnings = [...warnings];
let element = null;
if (__DEV__) {
console.error.calls.reset();
}
const container = document.createElement(tags.splice(0, 1));
while (tags.length) {
const Tag = tags.pop();
element = <Tag>{element}</Tag>;
}
ReactDOM.render(element, container);
if (__DEV__) {
expect(console.error.calls.count()).toEqual(warnings.length);
while (warnings.length) {
expect(
normalizeCodeLocInfo(
console.error.calls.argsFor(warnings.length - 1)[0],
),
).toContain(warnings.pop());
}
}
expect(() => ReactDOM.render(element, container)).toWarnDev(warnings);
}
describe('validateDOMNesting', () => {
it('allows valid nestings', () => {
spyOnDev(console, 'error');
expectWarnings(['table', 'tbody', 'tr', 'td', 'b']);
expectWarnings(
['body', 'datalist', 'option'],
@ -66,7 +48,6 @@ describe('validateDOMNesting', () => {
});
it('prevents problematic nestings', () => {
spyOnDev(console, 'error');
expectWarnings(
['a', 'a'],
[

View File

@ -157,7 +157,6 @@ describe('SyntheticEvent', () => {
});
it('should be nullified and log warnings if the synthetic event has not been persisted', () => {
spyOnDev(console, 'error');
let node;
let expectedCount = 0;
let syntheticEvent;
@ -173,26 +172,30 @@ describe('SyntheticEvent', () => {
event.initEvent('click', true, true);
node.dispatchEvent(event);
expect(syntheticEvent.type).toBe(null);
expect(syntheticEvent.nativeEvent).toBe(null);
expect(syntheticEvent.target).toBe(null);
if (__DEV__) {
// once for each property accessed
expect(console.error.calls.count()).toBe(3);
// assert the first warning for accessing `type`
expect(console.error.calls.argsFor(0)[0]).toBe(
'Warning: This synthetic event is reused for performance reasons. If ' +
"you're seeing this, you're accessing the property `type` on a " +
'released/nullified synthetic event. This is set to null. If you must ' +
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
}
const getExpectedWarning = property =>
'Warning: This synthetic event is reused for performance reasons. If ' +
`you're seeing this, you're accessing the property \`${
property
}\` on a ` +
'released/nullified synthetic event. This is set to null. If you must ' +
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.';
// once for each property accessed
expect(() => expect(syntheticEvent.type).toBe(null)).toWarnDev(
getExpectedWarning('type'),
);
expect(() => expect(syntheticEvent.nativeEvent).toBe(null)).toWarnDev(
getExpectedWarning('nativeEvent'),
);
expect(() => expect(syntheticEvent.target).toBe(null)).toWarnDev(
getExpectedWarning('target'),
);
expect(expectedCount).toBe(1);
});
it('should warn when setting properties of a synthetic event that has not been persisted', () => {
spyOnDev(console, 'error');
let node;
let expectedCount = 0;
let syntheticEvent;
@ -208,22 +211,19 @@ describe('SyntheticEvent', () => {
event.initEvent('click', true, true);
node.dispatchEvent(event);
syntheticEvent.type = 'MouseEvent';
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
'Warning: This synthetic event is reused for performance reasons. If ' +
"you're seeing this, you're setting the property `type` on a " +
'released/nullified synthetic event. This is effectively a no-op. If you must ' +
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
}
expect(() => {
syntheticEvent.type = 'MouseEvent';
}).toWarnDev(
'Warning: This synthetic event is reused for performance reasons. If ' +
"you're seeing this, you're setting the property `type` on a " +
'released/nullified synthetic event. This is effectively a no-op. If you must ' +
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
expect(expectedCount).toBe(1);
});
it('should warn when calling `preventDefault` if the synthetic event has not been persisted', () => {
spyOnDev(console, 'error');
let node;
let expectedCount = 0;
let syntheticEvent;
@ -238,22 +238,17 @@ describe('SyntheticEvent', () => {
event.initEvent('click', true, true);
node.dispatchEvent(event);
syntheticEvent.preventDefault();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
'Warning: This synthetic event is reused for performance reasons. If ' +
"you're seeing this, you're accessing the method `preventDefault` on a " +
'released/nullified synthetic event. This is a no-op function. If you must ' +
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
}
expect(() => syntheticEvent.preventDefault()).toWarnDev(
'Warning: This synthetic event is reused for performance reasons. If ' +
"you're seeing this, you're accessing the method `preventDefault` on a " +
'released/nullified synthetic event. This is a no-op function. If you must ' +
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
expect(expectedCount).toBe(1);
});
it('should warn when calling `stopPropagation` if the synthetic event has not been persisted', () => {
spyOnDev(console, 'error');
let node;
let expectedCount = 0;
let syntheticEvent;
@ -269,17 +264,13 @@ describe('SyntheticEvent', () => {
node.dispatchEvent(event);
syntheticEvent.stopPropagation();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
'Warning: This synthetic event is reused for performance reasons. If ' +
"you're seeing this, you're accessing the method `stopPropagation` on a " +
'released/nullified synthetic event. This is a no-op function. If you must ' +
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
}
expect(() => syntheticEvent.stopPropagation()).toWarnDev(
'Warning: This synthetic event is reused for performance reasons. If ' +
"you're seeing this, you're accessing the method `stopPropagation` on a " +
'released/nullified synthetic event. This is a no-op function. If you must ' +
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
expect(expectedCount).toBe(1);
});
@ -287,7 +278,6 @@ describe('SyntheticEvent', () => {
// using TestUtils.Simulate to avoid spurious warnings that result from the
// way we simulate events.
xit('should properly log warnings when events simulated with rendered components', () => {
spyOnDev(console, 'error');
let event;
const element = document.createElement('div');
function assignEvent(e) {
@ -295,33 +285,37 @@ describe('SyntheticEvent', () => {
}
const node = ReactDOM.render(<div onClick={assignEvent} />, element);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(node));
if (__DEV__) {
expect(console.error.calls.count()).toBe(0);
}
// access a property to cause the warning
event.nativeEvent; // eslint-disable-line no-unused-expressions
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
'Warning: This synthetic event is reused for performance reasons. If ' +
"you're seeing this, you're accessing the property `nativeEvent` on a " +
'released/nullified synthetic event. This is set to null. If you must ' +
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
}
expect(() => {
event.nativeEvent; // eslint-disable-line no-unused-expressions
}).toWarnDev(
'Warning: This synthetic event is reused for performance reasons. If ' +
"you're seeing this, you're accessing the property `nativeEvent` on a " +
'released/nullified synthetic event. This is set to null. If you must ' +
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
});
it('should warn if Proxy is supported and the synthetic event is added a property', () => {
spyOnDev(console, 'error');
let node;
let expectedCount = 0;
let syntheticEvent;
const eventHandler = e => {
e.foo = 'bar';
if (typeof Proxy === 'function') {
expect(() => {
e.foo = 'bar';
}).toWarnDev(
'Warning: This synthetic event is reused for performance reasons. If ' +
"you're seeing this, you're adding a new property in the synthetic " +
'event object. The property is never released. ' +
'See https://fb.me/react-event-pooling for more information.',
);
} else {
e.foo = 'bar';
}
syntheticEvent = e;
expectedCount++;
};
@ -333,19 +327,6 @@ describe('SyntheticEvent', () => {
node.dispatchEvent(event);
expect(syntheticEvent.foo).toBe('bar');
if (__DEV__) {
if (typeof Proxy === 'function') {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
'Warning: This synthetic event is reused for performance reasons. If ' +
"you're seeing this, you're adding a new property in the synthetic " +
'event object. The property is never released. ' +
'See https://fb.me/react-event-pooling for more information.',
);
} else {
expect(console.error.calls.count()).toBe(0);
}
}
expect(expectedCount).toBe(1);
});
});

View File

@ -672,7 +672,6 @@ describe('ReactFragment', () => {
});
it('should not preserve state when switching to a keyed fragment to an array', function() {
spyOnDev(console, 'error');
const ops = [];
class Stateful extends React.Component {
@ -707,7 +706,9 @@ describe('ReactFragment', () => {
ReactNoop.flush();
ReactNoop.render(<Foo condition={false} />);
ReactNoop.flush();
expect(ReactNoop.flush).toWarnDev(
'Each child in an array or iterator should have a unique "key" prop.',
);
expect(ops).toEqual([]);
expect(ReactNoop.getChildren()).toEqual([div(div(), span())]);
@ -717,16 +718,9 @@ describe('ReactFragment', () => {
expect(ops).toEqual([]);
expect(ReactNoop.getChildren()).toEqual([div(div(), span())]);
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('should preserve state when it does not change positions', function() {
spyOnDev(console, 'error');
const ops = [];
class Stateful extends React.Component {
@ -756,26 +750,24 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
ReactNoop.flush();
expect(ReactNoop.flush).toWarnDev(
'Each child in an array or iterator should have a unique "key" prop.',
);
ReactNoop.render(<Foo condition={false} />);
ReactNoop.flush();
expect(ReactNoop.flush).toWarnDev(
'Each child in an array or iterator should have a unique "key" prop.',
);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop.getChildren()).toEqual([span(), div()]);
ReactNoop.render(<Foo condition={true} />);
ReactNoop.flush();
expect(ReactNoop.flush).toWarnDev(
'Each child in an array or iterator should have a unique "key" prop.',
);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop.getChildren()).toEqual([span(), div()]);
if (__DEV__) {
expect(console.error.calls.count()).toBe(3);
for (let errorIndex = 0; errorIndex < 3; ++errorIndex) {
expect(console.error.calls.argsFor(errorIndex)[0]).toContain(
'Each child in an array or iterator should have a unique "key" prop.',
);
}
}
});
});

View File

@ -720,8 +720,6 @@ describe('ReactIncrementalErrorHandling', () => {
});
it('catches reconciler errors in a boundary during mounting', () => {
spyOnDev(console, 'error');
class ErrorBoundary extends React.Component {
state = {error: null};
componentDidCatch(error) {
@ -744,7 +742,9 @@ describe('ReactIncrementalErrorHandling', () => {
<BrokenRender />
</ErrorBoundary>,
);
ReactNoop.flush();
expect(ReactNoop.flush).toWarnDev(
'Warning: React.createElement: type is invalid -- expected a string',
);
expect(ReactNoop.getChildren()).toEqual([
span(
'Element type is invalid: expected a string (for built-in components) or ' +
@ -756,17 +756,9 @@ describe('ReactIncrementalErrorHandling', () => {
: ''),
),
]);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Warning: React.createElement: type is invalid -- expected a string',
);
}
});
it('catches reconciler errors in a boundary during update', () => {
spyOnDev(console, 'error');
class ErrorBoundary extends React.Component {
state = {error: null};
componentDidCatch(error) {
@ -797,7 +789,9 @@ describe('ReactIncrementalErrorHandling', () => {
<BrokenRender fail={true} />
</ErrorBoundary>,
);
ReactNoop.flush();
expect(ReactNoop.flush).toWarnDev(
'Warning: React.createElement: type is invalid -- expected a string',
);
expect(ReactNoop.getChildren()).toEqual([
span(
'Element type is invalid: expected a string (for built-in components) or ' +
@ -809,21 +803,14 @@ describe('ReactIncrementalErrorHandling', () => {
: ''),
),
]);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Warning: React.createElement: type is invalid -- expected a string',
);
}
});
it('recovers from uncaught reconciler errors', () => {
spyOnDev(console, 'error');
const InvalidType = undefined;
ReactNoop.render(<InvalidType />);
expect(() => {
ReactNoop.flush();
}).toThrowError(
expect(() => ReactNoop.render(<InvalidType />)).toWarnDev(
'Warning: React.createElement: type is invalid -- expected a string',
);
expect(ReactNoop.flush).toThrowError(
'Element type is invalid: expected a string (for built-in components) or ' +
'a class/function (for composite components) but got: undefined.' +
(__DEV__
@ -835,12 +822,6 @@ describe('ReactIncrementalErrorHandling', () => {
ReactNoop.render(<span prop="hi" />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span('hi')]);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Warning: React.createElement: type is invalid -- expected a string',
);
}
});
it('unmounts components with uncaught errors', () => {

View File

@ -20,10 +20,6 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop = require('react-noop-renderer');
});
function normalizeCodeLocInfo(str) {
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
}
function div(...children) {
children = children.map(c => (typeof c === 'string' ? {text: c} : c));
return {type: 'div', children, prop: undefined};
@ -987,7 +983,6 @@ describe('ReactIncrementalSideEffects', () => {
});
it('invokes ref callbacks after insertion/update/unmount', () => {
spyOnDev(console, 'error');
let classInstance = null;
let ops = [];
@ -1014,7 +1009,14 @@ describe('ReactIncrementalSideEffects', () => {
}
ReactNoop.render(<Foo show={true} />);
ReactNoop.flush();
expect(ReactNoop.flush).toWarnDev(
'Warning: Stateless function components cannot be given refs. ' +
'Attempts to access this ref will fail.\n\nCheck the render method ' +
'of `Foo`.\n' +
' in FunctionalComponent (at **)\n' +
' in div (at **)\n' +
' in Foo (at **)',
);
expect(ops).toEqual([
classInstance,
// no call for functional components
@ -1044,17 +1046,6 @@ describe('ReactIncrementalSideEffects', () => {
null,
null,
]);
if (__DEV__) {
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: Stateless function components cannot be given refs. ' +
'Attempts to access this ref will fail.\n\nCheck the render method ' +
'of `Foo`.\n' +
' in FunctionalComponent (at **)\n' +
' in div (at **)\n' +
' in Foo (at **)',
);
}
});
// TODO: Test that mounts, updates, refs, unmounts and deletions happen in the

View File

@ -321,7 +321,6 @@ describe('ReactIncrementalUpdates', () => {
});
it('enqueues setState inside an updater function as if the in-progress update is progressed (and warns)', () => {
spyOnDev(console, 'error');
let instance;
let ops = [];
class Foo extends React.Component {
@ -342,7 +341,12 @@ describe('ReactIncrementalUpdates', () => {
return {a: 'a'};
});
ReactNoop.flush();
expect(ReactNoop.flush).toWarnDev(
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
'from inside an update function. Update functions should be pure, ' +
'with zero side-effects. Consider using componentDidUpdate or a ' +
'callback.',
);
expect(ops).toEqual([
// Initial render
'render',
@ -353,24 +357,11 @@ describe('ReactIncrementalUpdates', () => {
]);
expect(instance.state).toEqual({a: 'a', b: 'b'});
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
'from inside an update function. Update functions should be pure, ' +
'with zero side-effects. Consider using componentDidUpdate or a ' +
'callback.',
);
}
// Test deduplication
// Test deduplication (no additional warnings expected)
instance.setState(function a() {
this.setState({a: 'a'});
return {b: 'b'};
});
ReactNoop.flush();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
}
});
});

View File

@ -771,8 +771,6 @@ describe('ReactShallowRenderer', () => {
});
it('can fail context when shallowly rendering', () => {
spyOnDev(console, 'error');
class SimpleComponent extends React.Component {
static contextTypes = {
name: PropTypes.string.isRequired,
@ -784,22 +782,14 @@ describe('ReactShallowRenderer', () => {
}
const shallowRenderer = createRenderer();
shallowRenderer.render(<SimpleComponent />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
).toBe(
'Warning: Failed context type: The context `name` is marked as ' +
'required in `SimpleComponent`, but its value is `undefined`.\n' +
' in SimpleComponent (at **)',
);
}
expect(() => shallowRenderer.render(<SimpleComponent />)).toWarnDev(
'Warning: Failed context type: The context `name` is marked as ' +
'required in `SimpleComponent`, but its value is `undefined`.\n' +
' in SimpleComponent (at **)',
);
});
it('should warn about propTypes (but only once)', () => {
spyOnDev(console, 'error');
class SimpleComponent extends React.Component {
render() {
return React.createElement('div', null, this.props.name);
@ -811,18 +801,13 @@ describe('ReactShallowRenderer', () => {
};
const shallowRenderer = createRenderer();
shallowRenderer.render(React.createElement(SimpleComponent, {name: 123}));
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
).toBe(
'Warning: Failed prop type: Invalid prop `name` of type `number` ' +
'supplied to `SimpleComponent`, expected `string`.\n' +
' in SimpleComponent',
);
}
expect(() =>
shallowRenderer.render(React.createElement(SimpleComponent, {name: 123})),
).toWarnDev(
'Warning: Failed prop type: Invalid prop `name` of type `number` ' +
'supplied to `SimpleComponent`, expected `string`.\n' +
' in SimpleComponent',
);
});
it('should enable rendering of cloned element', () => {
@ -942,36 +927,24 @@ describe('ReactShallowRenderer', () => {
});
it('throws usefully when rendering badly-typed elements', () => {
spyOnDev(console, 'error');
const shallowRenderer = createRenderer();
const Undef = undefined;
expect(() => shallowRenderer.render(<Undef />)).toThrowError(
'ReactShallowRenderer render(): Shallow rendering works only with custom ' +
'components, but the provided element type was `undefined`.',
);
const renderAndVerifyWarningAndError = (Component, typeString) => {
expect(() => {
expect(() => shallowRenderer.render(<Component />)).toWarnDev(
'React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite components) ' +
`but got: ${typeString}.`,
);
}).toThrowError(
'ReactShallowRenderer render(): Shallow rendering works only with custom ' +
`components, but the provided element type was \`${typeString}\`.`,
);
};
const Null = null;
expect(() => shallowRenderer.render(<Null />)).toThrowError(
'ReactShallowRenderer render(): Shallow rendering works only with custom ' +
'components, but the provided element type was `null`.',
);
const Arr = [];
expect(() => shallowRenderer.render(<Arr />)).toThrowError(
'ReactShallowRenderer render(): Shallow rendering works only with custom ' +
'components, but the provided element type was `array`.',
);
const Obj = {};
expect(() => shallowRenderer.render(<Obj />)).toThrowError(
'ReactShallowRenderer render(): Shallow rendering works only with custom ' +
'components, but the provided element type was `object`.',
);
if (__DEV__) {
// One warning for each element creation
expect(console.error.calls.count()).toBe(4);
}
renderAndVerifyWarningAndError(undefined, 'undefined');
renderAndVerifyWarningAndError(null, 'null');
renderAndVerifyWarningAndError([], 'array');
renderAndVerifyWarningAndError({}, 'object');
});
});

View File

@ -42,10 +42,6 @@ function cleanNodeOrArray(node) {
}
describe('ReactTestRenderer', () => {
function normalizeCodeLocInfo(str) {
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
}
it('renders a simple component', () => {
function Link() {
return <a role="link" />;
@ -264,7 +260,6 @@ describe('ReactTestRenderer', () => {
});
it('warns correctly for refs on SFCs', () => {
spyOnDev(console, 'error');
function Bar() {
return <div>Hello, world</div>;
}
@ -279,16 +274,12 @@ describe('ReactTestRenderer', () => {
}
}
ReactTestRenderer.create(<Baz />);
ReactTestRenderer.create(<Foo />);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
'Warning: Stateless function components cannot be given refs. Attempts ' +
'to access this ref will fail.\n\nCheck the render method of `Foo`.\n' +
' in Bar (at **)\n' +
' in Foo (at **)',
);
}
expect(() => ReactTestRenderer.create(<Foo />)).toWarnDev(
'Warning: Stateless function components cannot be given refs. Attempts ' +
'to access this ref will fail.\n\nCheck the render method of `Foo`.\n' +
' in Bar (at **)\n' +
' in Foo (at **)',
);
});
it('allows an optional createNodeMock function', () => {

View File

@ -308,12 +308,21 @@ export function createElementWithValidation(type, props, children) {
info += getStackAddendum() || '';
let typeString;
if (type === null) {
typeString = 'null';
} else if (Array.isArray(type)) {
typeString = 'array';
} else {
typeString = typeof type;
}
warning(
false,
'React.createElement: type is invalid -- expected a string (for ' +
'built-in components) or a class/function (for composite ' +
'components) but got: %s.%s',
type == null ? type : typeof type,
typeString,
info,
);
}

View File

@ -350,8 +350,6 @@ describe('ReactElementValidator', () => {
});
it('should warn if a PropType creator is used as a PropType', () => {
spyOnDev(console, 'error');
class Component extends React.Component {
static propTypes = {
myProp: PropTypes.shape,

View File

@ -15,14 +15,9 @@ describe('ReactDOMFrameScheduling', () => {
try {
global.requestAnimationFrame = undefined;
jest.resetModules();
spyOnDev(console, 'error');
require('react-dom');
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'React depends on requestAnimationFrame.',
);
}
expect(() => require('react-dom')).toWarnDev(
'React depends on requestAnimationFrame.',
);
} finally {
global.requestAnimationFrame = previousRAF;
}