@samitouri / QOS-React / commits / a442d9bc08

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

* 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()

Brian Vaughn committed Jan 3, 2018 at 10:08 UTC a442d9bc082878ccf66872b1eeed3465927801b6
19 files changed +1410 -2243
packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.internal.js
-4
@@ -277,15 +277,11 @@ describe('ReactBrowserEventEmitter', () => {
277 putListener(CHILD, ON_CLICK_KEY, recordIDAndReturnFalse.bind(null, CHILD));
278 putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
279 putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
280 - spyOnDev(console, 'error');
280 ReactTestUtils.Simulate.click(CHILD);
281 expect(idCallOrder.length).toBe(3);
282 expect(idCallOrder[0]).toBe(CHILD);
283 expect(idCallOrder[1]).toBe(PARENT);
284 expect(idCallOrder[2]).toBe(GRANDPARENT);
286 - if (__DEV__) {
287 - expect(console.error.calls.count()).toEqual(0);
288 - }
285 });
286
287 /**
packages/react-dom/src/__tests__/ReactComponent-test.js
+54 -75
@@ -46,7 +46,6 @@ describe('ReactComponent', () => {
46 });
47
48 it('should throw (in dev) when children are mutated during render', () => {
49 - spyOnDev(console, 'error');
49 function Wrapper(props) {
50 props.children[1] = <p key={1} />; // Mutation is illegal
51 return <div>{props.children}</div>;
@@ -73,8 +72,6 @@ describe('ReactComponent', () => {
72 });
73
74 it('should throw (in dev) when children are mutated during update', () => {
76 - spyOnDev(console, 'error');
77 -
75 class Wrapper extends React.Component {
76 componentDidMount() {
77 this.props.children[1] = <p key={1} />; // Mutation is illegal
@@ -355,10 +352,13 @@ describe('ReactComponent', () => {
352 });
353
354 it('throws usefully when rendering badly-typed elements', () => {
358 - spyOnDev(console, 'error');
359 -
355 const X = undefined;
361 - expect(() => ReactTestUtils.renderIntoDocument(<X />)).toThrowError(
356 + expect(() => {
357 + expect(() => ReactTestUtils.renderIntoDocument(<X />)).toWarnDev(
358 + 'React.createElement: type is invalid -- expected a string (for built-in components) ' +
359 + 'or a class/function (for composite components) but got: undefined.',
360 + );
361 + }).toThrowError(
362 'Element type is invalid: expected a string (for built-in components) ' +
363 'or a class/function (for composite components) but got: undefined.' +
364 (__DEV__
@@ -368,20 +368,18 @@ describe('ReactComponent', () => {
368 );
369
370 const Y = null;
371 - expect(() => ReactTestUtils.renderIntoDocument(<Y />)).toThrowError(
371 + expect(() => {
372 + expect(() => ReactTestUtils.renderIntoDocument(<Y />)).toWarnDev(
373 + 'React.createElement: type is invalid -- expected a string (for built-in components) ' +
374 + 'or a class/function (for composite components) but got: null.',
375 + );
376 + }).toThrowError(
377 'Element type is invalid: expected a string (for built-in components) ' +
378 'or a class/function (for composite components) but got: null.',
379 );
375 -
376 - if (__DEV__) {
377 - // One warning for each element creation
378 - expect(console.error.calls.count()).toBe(2);
379 - }
380 });
381
382 it('includes owner name in the error about badly-typed elements', () => {
383 - spyOnDev(console, 'error');
384 -
383 const X = undefined;
384
385 function Indirection(props) {
@@ -400,7 +398,12 @@ describe('ReactComponent', () => {
398 return <Bar />;
399 }
400
403 - expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toThrowError(
401 + expect(() => {
402 + expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toWarnDev(
403 + 'React.createElement: type is invalid -- expected a string (for built-in components) ' +
404 + 'or a class/function (for composite components) but got: undefined.',
405 + );
406 + }).toThrowError(
407 'Element type is invalid: expected a string (for built-in components) ' +
408 'or a class/function (for composite components) but got: undefined.' +
409 (__DEV__
@@ -409,11 +412,6 @@ describe('ReactComponent', () => {
412 '\n\nCheck the render method of `Bar`.'
413 : ''),
414 );
412 -
413 - if (__DEV__) {
414 - // One warning for each element creation
415 - expect(console.error.calls.count()).toBe(1);
416 - }
415 });
416
417 it('throws if a plain object is used as a child', () => {
@@ -530,18 +528,13 @@ describe('ReactComponent', () => {
528 function Foo() {
529 return Foo;
530 }
533 - spyOnDev(console, 'error');
531 const container = document.createElement('div');
535 - ReactDOM.render(<Foo />, container);
536 - if (__DEV__) {
537 - expect(console.error.calls.count()).toBe(1);
538 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
539 - 'Warning: Functions are not valid as a React child. This may happen if ' +
540 - 'you return a Component instead of <Component /> from render. ' +
541 - 'Or maybe you meant to call this function rather than return it.\n' +
542 - ' in Foo (at **)',
543 - );
544 - }
532 + expect(() => ReactDOM.render(<Foo />, container)).toWarnDev(
533 + 'Warning: Functions are not valid as a React child. This may happen if ' +
534 + 'you return a Component instead of <Component /> from render. ' +
535 + 'Or maybe you meant to call this function rather than return it.\n' +
536 + ' in Foo (at **)',
537 + );
538 });
539
540 it('warns on function as a return value from a class', () => {
@@ -550,18 +543,13 @@ describe('ReactComponent', () => {
543 return Foo;
544 }
545 }
553 - spyOnDev(console, 'error');
546 const container = document.createElement('div');
555 - ReactDOM.render(<Foo />, container);
556 - if (__DEV__) {
557 - expect(console.error.calls.count()).toBe(1);
558 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
559 - 'Warning: Functions are not valid as a React child. This may happen if ' +
560 - 'you return a Component instead of <Component /> from render. ' +
561 - 'Or maybe you meant to call this function rather than return it.\n' +
562 - ' in Foo (at **)',
563 - );
564 - }
547 + expect(() => ReactDOM.render(<Foo />, container)).toWarnDev(
548 + 'Warning: Functions are not valid as a React child. This may happen if ' +
549 + 'you return a Component instead of <Component /> from render. ' +
550 + 'Or maybe you meant to call this function rather than return it.\n' +
551 + ' in Foo (at **)',
552 + );
553 });
554
555 it('warns on function as a child to host component', () => {
@@ -572,20 +560,15 @@ describe('ReactComponent', () => {
560 </div>
561 );
562 }
575 - spyOnDev(console, 'error');
563 const container = document.createElement('div');
577 - ReactDOM.render(<Foo />, container);
578 - if (__DEV__) {
579 - expect(console.error.calls.count()).toBe(1);
580 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
581 - 'Warning: Functions are not valid as a React child. This may happen if ' +
582 - 'you return a Component instead of <Component /> from render. ' +
583 - 'Or maybe you meant to call this function rather than return it.\n' +
584 - ' in span (at **)\n' +
585 - ' in div (at **)\n' +
586 - ' in Foo (at **)',
587 - );
588 - }
564 + expect(() => ReactDOM.render(<Foo />, container)).toWarnDev(
565 + 'Warning: Functions are not valid as a React child. This may happen if ' +
566 + 'you return a Component instead of <Component /> from render. ' +
567 + 'Or maybe you meant to call this function rather than return it.\n' +
568 + ' in span (at **)\n' +
569 + ' in div (at **)\n' +
570 + ' in Foo (at **)',
571 + );
572 });
573
574 it('does not warn for function-as-a-child that gets resolved', () => {
@@ -601,7 +584,6 @@ describe('ReactComponent', () => {
584 });
585
586 it('deduplicates function type warnings based on component type', () => {
604 - spyOnDev(console, 'error');
587 class Foo extends React.PureComponent {
588 constructor() {
589 super();
@@ -621,26 +603,23 @@ describe('ReactComponent', () => {
603 }
604 }
605 const container = document.createElement('div');
624 - const component = ReactDOM.render(<Foo />, container);
606 + let component;
607 + expect(() => {
608 + component = ReactDOM.render(<Foo />, container);
609 + }).toWarnDev([
610 + 'Warning: Functions are not valid as a React child. This may happen if ' +
611 + 'you return a Component instead of <Component /> from render. ' +
612 + 'Or maybe you meant to call this function rather than return it.\n' +
613 + ' in div (at **)\n' +
614 + ' in Foo (at **)',
615 + 'Warning: Functions are not valid as a React child. This may happen if ' +
616 + 'you return a Component instead of <Component /> from render. ' +
617 + 'Or maybe you meant to call this function rather than return it.\n' +
618 + ' in span (at **)\n' +
619 + ' in div (at **)\n' +
620 + ' in Foo (at **)',
621 + ]);
622 component.setState({type: 'portobello mushrooms'});
626 - if (__DEV__) {
627 - expect(console.error.calls.count()).toBe(2);
628 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
629 - 'Warning: Functions are not valid as a React child. This may happen if ' +
630 - 'you return a Component instead of <Component /> from render. ' +
631 - 'Or maybe you meant to call this function rather than return it.\n' +
632 - ' in div (at **)\n' +
633 - ' in Foo (at **)',
634 - );
635 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
636 - 'Warning: Functions are not valid as a React child. This may happen if ' +
637 - 'you return a Component instead of <Component /> from render. ' +
638 - 'Or maybe you meant to call this function rather than return it.\n' +
639 - ' in span (at **)\n' +
640 - ' in div (at **)\n' +
641 - ' in Foo (at **)',
642 - );
643 - }
623 });
624 });
625 });
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
+27 -54
@@ -198,8 +198,6 @@ describe('ReactComponentLifeCycle', () => {
198 });
199
200 it('should not allow update state inside of getInitialState', () => {
201 - spyOnDev(console, 'error');
202 -
201 class StatefulComponent extends React.Component {
202 constructor(props, context) {
203 super(props, context);
@@ -213,26 +211,20 @@ describe('ReactComponentLifeCycle', () => {
211 }
212 }
213
216 - ReactTestUtils.renderIntoDocument(<StatefulComponent />);
217 - if (__DEV__) {
218 - expect(console.error.calls.count()).toBe(1);
219 - expect(console.error.calls.argsFor(0)[0]).toBe(
220 - 'Warning: setState(...): Can only update a mounted or ' +
221 - 'mounting component. This usually means you called setState() on an ' +
222 - 'unmounted component. This is a no-op.\n\nPlease check the code for the ' +
223 - 'StatefulComponent component.',
224 - );
225 - }
214 + expect(() => {
215 + ReactTestUtils.renderIntoDocument(<StatefulComponent />);
216 + }).toWarnDev(
217 + 'Warning: setState(...): Can only update a mounted or ' +
218 + 'mounting component. This usually means you called setState() on an ' +
219 + 'unmounted component. This is a no-op.\n\nPlease check the code for the ' +
220 + 'StatefulComponent component.',
221 + );
222
227 - // Check deduplication
223 + // Check deduplication; (no extra warnings should be logged).
224 ReactTestUtils.renderIntoDocument(<StatefulComponent />);
229 - if (__DEV__) {
230 - expect(console.error.calls.count()).toBe(1);
231 - }
225 });
226
227 it('should correctly determine if a component is mounted', () => {
235 - spyOnDev(console, 'error');
228 class Component extends React.Component {
229 _isMounted() {
230 // No longer a public API, but we can test that it works internally by
@@ -253,19 +245,13 @@ describe('ReactComponentLifeCycle', () => {
245
246 const element = <Component />;
247
256 - const instance = ReactTestUtils.renderIntoDocument(element);
257 - expect(instance._isMounted()).toBeTruthy();
258 -
259 - if (__DEV__) {
260 - expect(console.error.calls.count()).toBe(1);
261 - expect(console.error.calls.argsFor(0)[0]).toContain(
262 - 'Component is accessing isMounted inside its render()',
263 - );
264 - }
248 + expect(() => {
249 + const instance = ReactTestUtils.renderIntoDocument(element);
250 + expect(instance._isMounted()).toBeTruthy();
251 + }).toWarnDev('Component is accessing isMounted inside its render()');
252 });
253
254 it('should correctly determine if a null component is mounted', () => {
268 - spyOnDev(console, 'error');
255 class Component extends React.Component {
256 _isMounted() {
257 // No longer a public API, but we can test that it works internally by
@@ -286,15 +272,10 @@ describe('ReactComponentLifeCycle', () => {
272
273 const element = <Component />;
274
289 - const instance = ReactTestUtils.renderIntoDocument(element);
290 - expect(instance._isMounted()).toBeTruthy();
291 -
292 - if (__DEV__) {
293 - expect(console.error.calls.count()).toBe(1);
294 - expect(console.error.calls.argsFor(0)[0]).toContain(
295 - 'Component is accessing isMounted inside its render()',
296 - );
297 - }
275 + expect(() => {
276 + const instance = ReactTestUtils.renderIntoDocument(element);
277 + expect(instance._isMounted()).toBeTruthy();
278 + }).toWarnDev('Component is accessing isMounted inside its render()');
279 });
280
281 it('isMounted should return false when unmounted', () => {
@@ -317,7 +298,6 @@ describe('ReactComponentLifeCycle', () => {
298 });
299
300 it('warns if findDOMNode is used inside render', () => {
320 - spyOnDev(console, 'error');
301 class Component extends React.Component {
302 state = {isMounted: false};
303 componentDidMount() {
@@ -331,18 +311,12 @@ describe('ReactComponentLifeCycle', () => {
311 }
312 }
313
334 - ReactTestUtils.renderIntoDocument(<Component />);
335 - if (__DEV__) {
336 - expect(console.error.calls.count()).toBe(1);
337 - expect(console.error.calls.argsFor(0)[0]).toContain(
338 - 'Component is accessing findDOMNode inside its render()',
339 - );
340 - }
314 + expect(() => {
315 + ReactTestUtils.renderIntoDocument(<Component />);
316 + }).toWarnDev('Component is accessing findDOMNode inside its render()');
317 });
318
319 it('should carry through each of the phases of setup', () => {
344 - spyOnDev(console, 'error');
345 -
320 class LifeCycleComponent extends React.Component {
321 constructor(props, context) {
322 super(props, context);
@@ -399,7 +373,13 @@ describe('ReactComponentLifeCycle', () => {
373 // yet initialized, or rendered.
374 //
375 const container = document.createElement('div');
402 - const instance = ReactDOM.render(<LifeCycleComponent />, container);
376 +
377 + let instance;
378 + expect(() => {
379 + instance = ReactDOM.render(<LifeCycleComponent />, container);
380 + }).toWarnDev(
381 + 'LifeCycleComponent is accessing isMounted inside its render() function',
382 + );
383
384 // getInitialState
385 expect(instance._testJournal.returnedFromGetInitialState).toEqual(
@@ -449,13 +429,6 @@ describe('ReactComponentLifeCycle', () => {
429 // But the current lifecycle of the component is unmounted.
430 expect(getLifeCycleState(instance)).toBe('UNMOUNTED');
431 expect(instance.state).toEqual(POST_WILL_UNMOUNT_STATE);
452 -
453 - if (__DEV__) {
454 - expect(console.error.calls.count()).toBe(1);
455 - expect(console.error.calls.argsFor(0)[0]).toContain(
456 - 'LifeCycleComponent is accessing isMounted inside its render() function',
457 - );
458 - }
432 });
433
434 it('should not throw when updating an auxiliary component', () => {
packages/react-dom/src/__tests__/ReactCompositeComponent-test.js
+87 -190
@@ -122,29 +122,21 @@ describe('ReactCompositeComponent', () => {
122 }
123 }
124
125 - spyOnDev(console, 'warn');
125 const markup = ReactDOMServer.renderToString(<Parent />);
126
127 // Old API based on heuristic
128 let container = document.createElement('div');
129 container.innerHTML = markup;
131 - ReactDOM.render(<Parent />, container);
132 - if (__DEV__) {
133 - expect(console.warn.calls.count()).toBe(1);
134 - expect(console.warn.calls.argsFor(0)[0]).toContain(
135 - 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
136 - 'will stop working in React v17. Replace the ReactDOM.render() call ' +
137 - 'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
138 - );
139 - console.warn.calls.reset();
140 - }
130 + expect(() => ReactDOM.render(<Parent />, container)).toLowPriorityWarnDev(
131 + 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
132 + 'will stop working in React v17. Replace the ReactDOM.render() call ' +
133 + 'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
134 + );
135 +
136 // New explicit API
137 container = document.createElement('div');
138 container.innerHTML = markup;
139 ReactDOM.hydrate(<Parent />, container);
145 - if (__DEV__) {
146 - expect(console.warn.calls.count()).toBe(0);
147 - }
140 });
141
142 it('should react to state changes from callbacks', () => {
@@ -234,8 +226,6 @@ describe('ReactCompositeComponent', () => {
226 });
227
228 it('should warn about `forceUpdate` on unmounted components', () => {
237 - spyOnDev(console, 'error');
238 -
229 const container = document.createElement('div');
230 document.body.appendChild(container);
231
@@ -251,32 +241,20 @@ describe('ReactCompositeComponent', () => {
241 instance = ReactDOM.render(instance, container);
242 instance.forceUpdate();
243
254 - if (__DEV__) {
255 - expect(console.error.calls.count()).toBe(0);
256 - }
257 -
244 ReactDOM.unmountComponentAtNode(container);
245
260 - instance.forceUpdate();
261 - if (__DEV__) {
262 - expect(console.error.calls.count()).toBe(1);
263 - expect(console.error.calls.argsFor(0)[0]).toContain(
264 - 'Can only update a mounted or mounting component. This usually means ' +
265 - 'you called setState, replaceState, or forceUpdate on an unmounted ' +
266 - 'component. This is a no-op.\n\nPlease check the code for the ' +
267 - 'Component component.',
268 - );
269 - }
246 + expect(() => instance.forceUpdate()).toWarnDev(
247 + 'Can only update a mounted or mounting component. This usually means ' +
248 + 'you called setState, replaceState, or forceUpdate on an unmounted ' +
249 + 'component. This is a no-op.\n\nPlease check the code for the ' +
250 + 'Component component.',
251 + );
252
253 + // No additional warning should be recorded
254 instance.forceUpdate();
272 - if (__DEV__) {
273 - expect(console.error.calls.count()).toBe(1);
274 - }
255 });
256
257 it('should warn about `setState` on unmounted components', () => {
278 - spyOnDev(console, 'error');
279 -
258 const container = document.createElement('div');
259 document.body.appendChild(container);
260
@@ -300,26 +278,20 @@ describe('ReactCompositeComponent', () => {
278
279 instance.setState({value: 1});
280
303 - if (__DEV__) {
304 - expect(console.error.calls.count()).toBe(0);
305 - }
306 -
281 expect(renders).toBe(2);
282
283 ReactDOM.unmountComponentAtNode(container);
310 - instance.setState({value: 2});
284
312 - expect(renders).toBe(2);
285 + expect(() => {
286 + instance.setState({value: 2});
287 + }).toWarnDev(
288 + 'Can only update a mounted or mounting component. This usually means ' +
289 + 'you called setState, replaceState, or forceUpdate on an unmounted ' +
290 + 'component. This is a no-op.\n\nPlease check the code for the ' +
291 + 'Component component.',
292 + );
293
314 - if (__DEV__) {
315 - expect(console.error.calls.count()).toBe(1);
316 - expect(console.error.calls.argsFor(0)[0]).toContain(
317 - 'Can only update a mounted or mounting component. This usually means ' +
318 - 'you called setState, replaceState, or forceUpdate on an unmounted ' +
319 - 'component. This is a no-op.\n\nPlease check the code for the ' +
320 - 'Component component.',
321 - );
322 - }
294 + expect(renders).toBe(2);
295 });
296
297 it('should silently allow `setState`, not call cb on unmounting components', () => {
@@ -351,32 +323,24 @@ describe('ReactCompositeComponent', () => {
323 });
324
325 it('should warn when rendering a class with a render method that does not extend React.Component', () => {
354 - spyOnDev(console, 'error');
326 const container = document.createElement('div');
327 class ClassWithRenderNotExtended {
328 render() {
329 return <div />;
330 }
331 }
361 - if (__DEV__) {
362 - expect(console.error.calls.count()).toBe(0);
363 - }
332 expect(() => {
365 - ReactDOM.render(<ClassWithRenderNotExtended />, container);
366 - }).toThrow(TypeError);
367 - if (__DEV__) {
368 - expect(console.error.calls.count()).toBe(1);
369 - expect(console.error.calls.argsFor(0)[0]).toContain(
333 + expect(() => {
334 + ReactDOM.render(<ClassWithRenderNotExtended />, container);
335 + }).toWarnDev(
336 'Warning: The <ClassWithRenderNotExtended /> component appears to have a render method, ' +
337 "but doesn't extend React.Component. This is likely to cause errors. " +
338 'Change ClassWithRenderNotExtended to extend React.Component instead.',
339 );
374 - }
340 + }).toThrow(TypeError);
341 });
342
343 it('should warn about `setState` in render', () => {
378 - spyOnDev(console, 'error');
379 -
344 const container = document.createElement('div');
345
346 let renderedState = -1;
@@ -395,21 +359,16 @@ describe('ReactCompositeComponent', () => {
359 }
360 }
361
398 - if (__DEV__) {
399 - expect(console.error.calls.count()).toBe(0);
400 - }
401 -
402 - const instance = ReactDOM.render(<Component />, container);
362 + let instance;
363
404 - if (__DEV__) {
405 - expect(console.error.calls.count()).toBe(1);
406 - expect(console.error.calls.argsFor(0)[0]).toContain(
407 - 'Cannot update during an existing state transition (such as within ' +
408 - "`render` or another component's constructor). Render methods should " +
409 - 'be a pure function of props and state; constructor side-effects are ' +
410 - 'an anti-pattern, but can be moved to `componentWillMount`.',
411 - );
412 - }
364 + expect(() => {
365 + instance = ReactDOM.render(<Component />, container);
366 + }).toWarnDev(
367 + 'Cannot update during an existing state transition (such as within ' +
368 + "`render` or another component's constructor). Render methods should " +
369 + 'be a pure function of props and state; constructor side-effects are ' +
370 + 'an anti-pattern, but can be moved to `componentWillMount`.',
371 + );
372
373 // The setState call is queued and then executed as a second pass. This
374 // behavior is undefined though so we're free to change it to suit the
@@ -424,17 +383,12 @@ describe('ReactCompositeComponent', () => {
383 expect(renderedState).toBe(1);
384 expect(instance2.state.value).toBe(1);
385
427 - // Test deduplication
386 + // Test deduplication; (no additional warnings are expected).
387 ReactDOM.unmountComponentAtNode(container);
388 ReactDOM.render(<Component prop={123} />, container);
430 - if (__DEV__) {
431 - expect(console.error.calls.count()).toBe(1);
432 - }
389 });
390
391 it('should warn about `setState` in getChildContext', () => {
436 - spyOnDev(console, 'error');
437 -
392 const container = document.createElement('div');
393
394 let renderPasses = 0;
@@ -455,25 +409,20 @@ describe('ReactCompositeComponent', () => {
409 }
410 Component.childContextTypes = {};
411
458 - if (__DEV__) {
459 - expect(console.error.calls.count()).toBe(0);
460 - }
461 - const instance = ReactDOM.render(<Component />, container);
412 + let instance;
413 +
414 + expect(() => {
415 + instance = ReactDOM.render(<Component />, container);
416 + }).toWarnDev(
417 + 'Warning: setState(...): Cannot call setState() inside getChildContext()',
418 + );
419 +
420 expect(renderPasses).toBe(2);
421 expect(instance.state.value).toBe(1);
464 - if (__DEV__) {
465 - expect(console.error.calls.count()).toBe(1);
466 - expect(console.error.calls.argsFor(0)[0]).toBe(
467 - 'Warning: setState(...): Cannot call setState() inside getChildContext()',
468 - );
469 - }
422
471 - // Test deduplication
423 + // Test deduplication; (no additional warnings are expected).
424 ReactDOM.unmountComponentAtNode(container);
425 ReactDOM.render(<Component />, container);
474 - if (__DEV__) {
475 - expect(console.error.calls.count()).toBe(1);
476 - }
426 });
427
428 it('should cleanup even if render() fatals', () => {
@@ -487,7 +436,7 @@ describe('ReactCompositeComponent', () => {
436
437 expect(ReactCurrentOwner.current).toBe(null);
438
490 - expect(function() {
439 + expect(() => {
440 instance = ReactTestUtils.renderIntoDocument(instance);
441 }).toThrow();
442
@@ -525,8 +474,6 @@ describe('ReactCompositeComponent', () => {
474 });
475
476 it('should warn when shouldComponentUpdate() returns undefined', () => {
528 - spyOnDev(console, 'error');
529 -
477 class Component extends React.Component {
478 state = {bogus: false};
479
@@ -540,20 +487,14 @@ describe('ReactCompositeComponent', () => {
487 }
488
489 const instance = ReactTestUtils.renderIntoDocument(<Component />);
543 - instance.setState({bogus: true});
490
545 - if (__DEV__) {
546 - expect(console.error.calls.count()).toBe(1);
547 - expect(console.error.calls.argsFor(0)[0]).toBe(
548 - 'Warning: Component.shouldComponentUpdate(): Returned undefined instead of a ' +
549 - 'boolean value. Make sure to return true or false.',
550 - );
551 - }
491 + expect(() => instance.setState({bogus: true})).toWarnDev(
492 + 'Warning: Component.shouldComponentUpdate(): Returned undefined instead of a ' +
493 + 'boolean value. Make sure to return true or false.',
494 + );
495 });
496
497 it('should warn when componentDidUnmount method is defined', () => {
555 - spyOnDev(console, 'error');
556 -
498 class Component extends React.Component {
499 componentDidUnmount = () => {};
500
@@ -562,21 +503,14 @@ describe('ReactCompositeComponent', () => {
503 }
504 }
505
565 - ReactTestUtils.renderIntoDocument(<Component />);
566 -
567 - if (__DEV__) {
568 - expect(console.error.calls.count()).toBe(1);
569 - expect(console.error.calls.argsFor(0)[0]).toBe(
570 - 'Warning: Component has a method called ' +
571 - 'componentDidUnmount(). But there is no such lifecycle method. ' +
572 - 'Did you mean componentWillUnmount()?',
573 - );
574 - }
506 + expect(() => ReactTestUtils.renderIntoDocument(<Component />)).toWarnDev(
507 + 'Warning: Component has a method called ' +
508 + 'componentDidUnmount(). But there is no such lifecycle method. ' +
509 + 'Did you mean componentWillUnmount()?',
510 + );
511 });
512
513 it('should warn when componentDidReceiveProps method is defined', () => {
578 - spyOnDev(console, 'error');
579 -
514 class Component extends React.Component {
515 componentDidReceiveProps = () => {};
516
@@ -585,23 +519,16 @@ describe('ReactCompositeComponent', () => {
519 }
520 }
521
588 - ReactTestUtils.renderIntoDocument(<Component />);
589 -
590 - if (__DEV__) {
591 - expect(console.error.calls.count()).toBe(1);
592 - expect(console.error.calls.argsFor(0)[0]).toBe(
593 - 'Warning: Component has a method called ' +
594 - 'componentDidReceiveProps(). But there is no such lifecycle method. ' +
595 - 'If you meant to update the state in response to changing props, ' +
596 - 'use componentWillReceiveProps(). If you meant to fetch data or ' +
597 - 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
598 - );
599 - }
522 + expect(() => ReactTestUtils.renderIntoDocument(<Component />)).toWarnDev(
523 + 'Warning: Component has a method called ' +
524 + 'componentDidReceiveProps(). But there is no such lifecycle method. ' +
525 + 'If you meant to update the state in response to changing props, ' +
526 + 'use componentWillReceiveProps(). If you meant to fetch data or ' +
527 + 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
528 + );
529 });
530
531 it('should warn when defaultProps was defined as an instance property', () => {
603 - spyOnDev(console, 'error');
604 -
532 class Component extends React.Component {
533 constructor(props) {
534 super(props);
@@ -613,15 +540,10 @@ describe('ReactCompositeComponent', () => {
540 }
541 }
542
616 - ReactTestUtils.renderIntoDocument(<Component />);
617 -
618 - if (__DEV__) {
619 - expect(console.error.calls.count()).toBe(1);
620 - expect(console.error.calls.argsFor(0)[0]).toBe(
621 - 'Warning: Setting defaultProps as an instance property on Component is not supported ' +
622 - 'and will be ignored. Instead, define defaultProps as a static property on Component.',
623 - );
624 - }
543 + expect(() => ReactTestUtils.renderIntoDocument(<Component />)).toWarnDev(
544 + 'Warning: Setting defaultProps as an instance property on Component is not supported ' +
545 + 'and will be ignored. Instead, define defaultProps as a static property on Component.',
546 + );
547 });
548
549 it('should pass context to children when not owner', () => {
@@ -1093,8 +1015,6 @@ describe('ReactCompositeComponent', () => {
1015 });
1016
1017 it('should disallow nested render calls', () => {
1096 - spyOnDev(console, 'error');
1097 -
1018 class Inner extends React.Component {
1019 render() {
1020 return <div />;
@@ -1108,16 +1028,12 @@ describe('ReactCompositeComponent', () => {
1028 }
1029 }
1030
1111 - ReactTestUtils.renderIntoDocument(<Outer />);
1112 - if (__DEV__) {
1113 - expect(console.error.calls.count()).toBe(1);
1114 - expect(console.error.calls.argsFor(0)[0]).toMatch(
1115 - 'Render methods should be a pure function of props and state; ' +
1116 - 'triggering nested component updates from render is not allowed. If ' +
1117 - 'necessary, trigger nested updates in componentDidUpdate.\n\nCheck the ' +
1118 - 'render method of Outer.',
1119 - );
1120 - }
1031 + expect(() => ReactTestUtils.renderIntoDocument(<Outer />)).toWarnDev(
1032 + 'Render methods should be a pure function of props and state; ' +
1033 + 'triggering nested component updates from render is not allowed. If ' +
1034 + 'necessary, trigger nested updates in componentDidUpdate.\n\nCheck the ' +
1035 + 'render method of Outer.',
1036 + );
1037 });
1038
1039 it('only renders once if updated in componentWillReceiveProps', () => {
@@ -1392,8 +1308,6 @@ describe('ReactCompositeComponent', () => {
1308 });
1309
1310 it('should warn when mutated props are passed', () => {
1395 - spyOnDev(console, 'error');
1396 -
1311 const container = document.createElement('div');
1312
1313 class Foo extends React.Component {
@@ -1407,19 +1321,10 @@ describe('ReactCompositeComponent', () => {
1321 }
1322 }
1323
1410 - if (__DEV__) {
1411 - expect(console.error.calls.count()).toBe(0);
1412 - }
1413 -
1414 - ReactDOM.render(<Foo idx="qwe" />, container);
1415 -
1416 - if (__DEV__) {
1417 - expect(console.error.calls.count()).toBe(1);
1418 - expect(console.error.calls.argsFor(0)[0]).toContain(
1419 - 'Foo(...): When calling super() in `Foo`, make sure to pass ' +
1420 - "up the same props that your component's constructor was passed.",
1421 - );
1422 - }
1324 + expect(() => ReactDOM.render(<Foo idx="qwe" />, container)).toWarnDev(
1325 + 'Foo(...): When calling super() in `Foo`, make sure to pass ' +
1326 + "up the same props that your component's constructor was passed.",
1327 + );
1328 });
1329
1330 it('should only call componentWillUnmount once', () => {
@@ -1456,7 +1361,7 @@ describe('ReactCompositeComponent', () => {
1361 }
1362 };
1363
1459 - expect(function() {
1364 + expect(() => {
1365 ReactDOM.render(<App ref={setRef} stage={1} />, container);
1366 ReactDOM.render(<App ref={setRef} stage={2} />, container);
1367 }).toThrow();
@@ -1702,7 +1607,6 @@ describe('ReactCompositeComponent', () => {
1607 });
1608
1609 it('should return a meaningful warning when constructor is returned', () => {
1705 - spyOnDev(console, 'error');
1610 class RenderTextInvalidConstructor extends React.Component {
1611 constructor(props) {
1612 super(props);
@@ -1714,33 +1618,26 @@ describe('ReactCompositeComponent', () => {
1618 }
1619 }
1620
1717 - expect(function() {
1718 - ReactTestUtils.renderIntoDocument(<RenderTextInvalidConstructor />);
1719 - }).toThrow();
1720 -
1721 - if (__DEV__) {
1722 - expect(console.error.calls.count()).toBe(1);
1723 - expect(console.error.calls.mostRecent().args[0]).toBe(
1621 + expect(() => {
1622 + expect(() => {
1623 + ReactTestUtils.renderIntoDocument(<RenderTextInvalidConstructor />);
1624 + }).toWarnDev(
1625 'Warning: RenderTextInvalidConstructor(...): No `render` method found on the returned component instance: ' +
1626 'did you accidentally return an object from the constructor?',
1627 );
1727 - }
1628 + }).toThrow();
1629 });
1630
1631 it('should return error if render is not defined', () => {
1731 - spyOnDev(console, 'error');
1632 class RenderTestUndefinedRender extends React.Component {}
1633
1734 - expect(function() {
1735 - ReactTestUtils.renderIntoDocument(<RenderTestUndefinedRender />);
1736 - }).toThrow();
1737 -
1738 - if (__DEV__) {
1739 - expect(console.error.calls.count()).toBe(1);
1740 - expect(console.error.calls.mostRecent().args[0]).toBe(
1634 + expect(() => {
1635 + expect(() => {
1636 + ReactTestUtils.renderIntoDocument(<RenderTestUndefinedRender />);
1637 + }).toWarnDev(
1638 'Warning: RenderTestUndefinedRender(...): No `render` method found on the returned ' +
1639 'component instance: you may have forgotten to define `render`.',
1640 );
1744 - }
1641 + }).toThrow();
1642 });
1643 });
packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js
+11 -25
@@ -17,7 +17,6 @@ let TestComponent;
17 describe('ReactCompositeComponent-state', () => {
18 beforeEach(() => {
19 React = require('react');
20 -
20 ReactDOM = require('react-dom');
21
22 TestComponent = class extends React.Component {
@@ -381,8 +380,6 @@ describe('ReactCompositeComponent-state', () => {
380 });
381
382 it('should treat assigning to this.state inside cWRP as a replaceState, with a warning', () => {
384 - spyOnDev(console, 'error');
385 -
383 let ops = [];
384 class Test extends React.Component {
385 state = {step: 1, extra: true};
@@ -409,27 +406,20 @@ describe('ReactCompositeComponent-state', () => {
406 const container = document.createElement('div');
407 ReactDOM.render(<Test />, container);
408 // Update
412 - ReactDOM.render(<Test />, container);
409 + expect(() => ReactDOM.render(<Test />, container)).toWarnDev(
410 + 'Warning: Test.componentWillReceiveProps(): Assigning directly to ' +
411 + "this.state is deprecated (except inside a component's constructor). " +
412 + 'Use setState instead.',
413 + );
414
415 expect(ops).toEqual([
416 'render -- step: 1, extra: true',
417 'render -- step: 3, extra: false',
418 'callback -- step: 3, extra: false',
419 ]);
419 - if (__DEV__) {
420 - expect(console.error.calls.count()).toEqual(1);
421 - expect(console.error.calls.argsFor(0)[0]).toEqual(
422 - 'Warning: Test.componentWillReceiveProps(): Assigning directly to ' +
423 - "this.state is deprecated (except inside a component's constructor). " +
424 - 'Use setState instead.',
425 - );
426 - }
420
428 - // Check deduplication
421 + // Check deduplication; (no additional warnings are expected)
422 ReactDOM.render(<Test />, container);
430 - if (__DEV__) {
431 - expect(console.error.calls.count()).toEqual(1);
432 - }
423 });
424
425 it('should treat assigning to this.state inside cWM as a replaceState, with a warning', () => {
@@ -459,19 +449,15 @@ describe('ReactCompositeComponent-state', () => {
449
450 // Mount
451 const container = document.createElement('div');
462 - ReactDOM.render(<Test />, container);
452 + expect(() => ReactDOM.render(<Test />, container)).toWarnDev(
453 + 'Warning: Test.componentWillMount(): Assigning directly to ' +
454 + "this.state is deprecated (except inside a component's constructor). " +
455 + 'Use setState instead.',
456 + );
457
458 expect(ops).toEqual([
459 'render -- step: 3, extra: false',
460 'callback -- step: 3, extra: false',
461 ]);
468 - if (__DEV__) {
469 - expect(console.error.calls.count()).toEqual(1);
470 - expect(console.error.calls.argsFor(0)[0]).toEqual(
471 - 'Warning: Test.componentWillMount(): Assigning directly to ' +
472 - "this.state is deprecated (except inside a component's constructor). " +
473 - 'Use setState instead.',
474 - );
475 - }
462 });
463 });
packages/react-dom/src/__tests__/ReactDOM-test.js
+57 -57
@@ -109,8 +109,6 @@ describe('ReactDOM', () => {
109 });
110
111 it('throws in render() if the mount callback is not a function', () => {
112 - spyOnDev(console, 'error');
113 -
112 function Foo() {
113 this.a = 1;
114 this.b = 2;
@@ -125,42 +123,44 @@ describe('ReactDOM', () => {
123 }
124
125 const myDiv = document.createElement('div');
128 - expect(() => ReactDOM.render(<A />, myDiv, 'no')).toThrowError(
129 - 'Invalid argument passed as callback. Expected a function. Instead ' +
130 - 'received: no',
131 - );
132 - if (__DEV__) {
133 - expect(console.error.calls.argsFor(0)[0]).toContain(
126 + expect(() => {
127 + expect(() => {
128 + ReactDOM.render(<A />, myDiv, 'no');
129 + }).toWarnDev(
130 'render(...): Expected the last optional `callback` argument to be ' +
131 'a function. Instead received: no.',
132 );
137 - }
138 - expect(() => ReactDOM.render(<A />, myDiv, {foo: 'bar'})).toThrowError(
133 + }).toThrowError(
134 'Invalid argument passed as callback. Expected a function. Instead ' +
140 - 'received: [object Object]',
135 + 'received: no',
136 );
142 - if (__DEV__) {
143 - expect(console.error.calls.argsFor(1)[0]).toContain(
137 +
138 + expect(() => {
139 + expect(() => {
140 + ReactDOM.render(<A />, myDiv, {foo: 'bar'});
141 + }).toWarnDev(
142 'render(...): Expected the last optional `callback` argument to be ' +
143 'a function. Instead received: [object Object].',
144 );
147 - }
148 - expect(() => ReactDOM.render(<A />, myDiv, new Foo())).toThrowError(
145 + }).toThrowError(
146 'Invalid argument passed as callback. Expected a function. Instead ' +
147 'received: [object Object]',
148 );
152 - if (__DEV__) {
153 - expect(console.error.calls.argsFor(2)[0]).toContain(
149 +
150 + expect(() => {
151 + expect(() => {
152 + ReactDOM.render(<A />, myDiv, new Foo());
153 + }).toWarnDev(
154 'render(...): Expected the last optional `callback` argument to be ' +
155 'a function. Instead received: [object Object].',
156 );
157 - expect(console.error.calls.count()).toBe(3);
158 - }
157 + }).toThrowError(
158 + 'Invalid argument passed as callback. Expected a function. Instead ' +
159 + 'received: [object Object]',
160 + );
161 });
162
163 it('throws in render() if the update callback is not a function', () => {
162 - spyOnDev(console, 'error');
163 -
164 function Foo() {
165 this.a = 1;
166 this.b = 2;
@@ -176,39 +176,43 @@ describe('ReactDOM', () => {
176
177 const myDiv = document.createElement('div');
178 ReactDOM.render(<A />, myDiv);
179 - expect(() => ReactDOM.render(<A />, myDiv, 'no')).toThrowError(
180 - 'Invalid argument passed as callback. Expected a function. Instead ' +
181 - 'received: no',
182 - );
183 - if (__DEV__) {
184 - expect(console.error.calls.argsFor(0)[0]).toContain(
179 + expect(() => {
180 + expect(() => {
181 + ReactDOM.render(<A />, myDiv, 'no');
182 + }).toWarnDev(
183 'render(...): Expected the last optional `callback` argument to be ' +
184 'a function. Instead received: no.',
185 );
188 - }
189 - ReactDOM.render(<A />, myDiv); // Re-mount
190 - expect(() => ReactDOM.render(<A />, myDiv, {foo: 'bar'})).toThrowError(
186 + }).toThrowError(
187 'Invalid argument passed as callback. Expected a function. Instead ' +
192 - 'received: [object Object]',
188 + 'received: no',
189 );
194 - if (__DEV__) {
195 - expect(console.error.calls.argsFor(1)[0]).toContain(
190 +
191 + ReactDOM.render(<A />, myDiv); // Re-mount
192 + expect(() => {
193 + expect(() => {
194 + ReactDOM.render(<A />, myDiv, {foo: 'bar'});
195 + }).toWarnDev(
196 'render(...): Expected the last optional `callback` argument to be ' +
197 'a function. Instead received: [object Object].',
198 );
199 - }
200 - ReactDOM.render(<A />, myDiv); // Re-mount
201 - expect(() => ReactDOM.render(<A />, myDiv, new Foo())).toThrowError(
199 + }).toThrowError(
200 'Invalid argument passed as callback. Expected a function. Instead ' +
201 'received: [object Object]',
202 );
205 - if (__DEV__) {
206 - expect(console.error.calls.argsFor(2)[0]).toContain(
203 +
204 + ReactDOM.render(<A />, myDiv); // Re-mount
205 + expect(() => {
206 + expect(() => {
207 + ReactDOM.render(<A />, myDiv, new Foo());
208 + }).toWarnDev(
209 'render(...): Expected the last optional `callback` argument to be ' +
210 'a function. Instead received: [object Object].',
211 );
210 - expect(console.error.calls.count()).toBe(3);
211 - }
212 + }).toThrowError(
213 + 'Invalid argument passed as callback. Expected a function. Instead ' +
214 + 'received: [object Object]',
215 + );
216 });
217
218 it('preserves focus', () => {
@@ -375,28 +379,24 @@ describe('ReactDOM', () => {
379
380 // https://github.com/facebook/react/issues/11689
381 it('should warn when attempting to inject an event plugin', () => {
378 - spyOnDev(console, 'warn');
379 - ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.EventPluginHub.injection.injectEventPluginsByName(
380 - {
381 - TapEventPlugin: {
382 - extractEvents() {},
382 + expect(() => {
383 + ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.EventPluginHub.injection.injectEventPluginsByName(
384 + {
385 + TapEventPlugin: {
386 + extractEvents() {},
387 + },
388 },
384 - },
385 - );
386 - if (__DEV__) {
387 - expect(console.warn.calls.count()).toBe(1);
388 - expect(console.warn.calls.argsFor(0)[0]).toContain(
389 - 'Injecting custom event plugins (TapEventPlugin) is deprecated ' +
390 - 'and will not work in React 17+. Please update your code ' +
391 - 'to not depend on React internals. The stack trace for this ' +
392 - 'warning should reveal the library that is using them. ' +
393 - 'See https://github.com/facebook/react/issues/11689 for a discussion.',
389 );
395 - }
390 + }).toLowPriorityWarnDev(
391 + 'Injecting custom event plugins (TapEventPlugin) is deprecated ' +
392 + 'and will not work in React 17+. Please update your code ' +
393 + 'to not depend on React internals. The stack trace for this ' +
394 + 'warning should reveal the library that is using them. ' +
395 + 'See https://github.com/facebook/react/issues/11689 for a discussion.',
396 + );
397 });
398
399 it('throws in DEV if jsdom is destroyed by the time setState() is called', () => {
399 - spyOnDev(console, 'error');
400 class App extends React.Component {
401 state = {x: 1};
402 render() {
packages/react-dom/src/__tests__/ReactDOMAttribute-test.js
+38 -68
@@ -19,10 +19,6 @@ describe('ReactDOM unknown attribute', () => {
19 ReactDOM = require('react-dom');
20 });
21
22 - function normalizeCodeLocInfo(str) {
23 - return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
24 - }
25 -
22 function testUnknownAttributeRemoval(givenValue) {
23 const el = document.createElement('div');
24 ReactDOM.render(<div unknown="something" />, el);
@@ -46,20 +42,13 @@ describe('ReactDOM unknown attribute', () => {
42 });
43
44 it('changes values true, false to null, and also warns once', () => {
49 - spyOnDev(console, 'error');
50 -
51 - testUnknownAttributeAssignment(true, null);
45 + expect(() => testUnknownAttributeAssignment(true, null)).toWarnDev(
46 + 'Received `true` for a non-boolean attribute `unknown`.\n\n' +
47 + 'If you want to write it to the DOM, pass a string instead: ' +
48 + 'unknown="true" or unknown={value.toString()}.\n' +
49 + ' in div (at **)',
50 + );
51 testUnknownAttributeAssignment(false, null);
53 -
54 - if (__DEV__) {
55 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toMatch(
56 - 'Received `true` for a non-boolean attribute `unknown`.\n\n' +
57 - 'If you want to write it to the DOM, pass a string instead: ' +
58 - 'unknown="true" or unknown={value.toString()}.\n' +
59 - ' in div (at **)',
60 - );
61 - expect(console.error.calls.count()).toBe(1);
62 - }
52 });
53
54 it('removes unknown attributes that were rendered but are now missing', () => {
@@ -82,17 +71,11 @@ describe('ReactDOM unknown attribute', () => {
71 });
72
73 it('coerces NaN to strings and warns', () => {
85 - spyOnDev(console, 'error');
86 -
87 - testUnknownAttributeAssignment(NaN, 'NaN');
88 - if (__DEV__) {
89 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toMatch(
90 - 'Warning: Received NaN for the `unknown` attribute. ' +
91 - 'If this is expected, cast the value to a string.\n' +
92 - ' in div (at **)',
93 - );
94 - expect(console.error.calls.count()).toBe(1);
95 - }
74 + expect(() => testUnknownAttributeAssignment(NaN, 'NaN')).toWarnDev(
75 + 'Warning: Received NaN for the `unknown` attribute. ' +
76 + 'If this is expected, cast the value to a string.\n' +
77 + ' in div (at **)',
78 + );
79 });
80
81 it('coerces objects to strings and warns', () => {
@@ -107,54 +90,41 @@ describe('ReactDOM unknown attribute', () => {
90 });
91
92 it('removes symbols and warns', () => {
110 - spyOnDev(console, 'error');
111 -
112 - testUnknownAttributeRemoval(Symbol('foo'));
113 - if (__DEV__) {
114 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
115 - 'Warning: Invalid value for prop `unknown` on <div> tag. Either remove it ' +
116 - 'from the element, or pass a string or number value to keep it ' +
117 - 'in the DOM. For details, see https://fb.me/react-attribute-behavior\n' +
118 - ' in div (at **)',
119 - );
120 - expect(console.error.calls.count()).toBe(1);
121 - }
93 + expect(() => testUnknownAttributeRemoval(Symbol('foo'))).toWarnDev(
94 + 'Warning: Invalid value for prop `unknown` on <div> tag. Either remove it ' +
95 + 'from the element, or pass a string or number value to keep it ' +
96 + 'in the DOM. For details, see https://fb.me/react-attribute-behavior\n' +
97 + ' in div (at **)',
98 + );
99 });
100
101 it('removes functions and warns', () => {
125 - spyOnDev(console, 'error');
126 -
127 - testUnknownAttributeRemoval(function someFunction() {});
128 - if (__DEV__) {
129 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
130 - 'Warning: Invalid value for prop `unknown` on <div> tag. Either remove ' +
131 - 'it from the element, or pass a string or number value to ' +
132 - 'keep it in the DOM. For details, see ' +
133 - 'https://fb.me/react-attribute-behavior\n' +
134 - ' in div (at **)',
135 - );
136 - expect(console.error.calls.count()).toBe(1);
137 - }
102 + expect(() =>
103 + testUnknownAttributeRemoval(function someFunction() {}),
104 + ).toWarnDev(
105 + 'Warning: Invalid value for prop `unknown` on <div> tag. Either remove ' +
106 + 'it from the element, or pass a string or number value to ' +
107 + 'keep it in the DOM. For details, see ' +
108 + 'https://fb.me/react-attribute-behavior\n' +
109 + ' in div (at **)',
110 + );
111 });
112
113 it('allows camelCase unknown attributes and warns', () => {
141 - spyOnDev(console, 'error');
142 -
114 const el = document.createElement('div');
144 - ReactDOM.render(<div helloWorld="something" />, el);
145 - expect(el.firstChild.getAttribute('helloworld')).toBe('something');
115
147 - if (__DEV__) {
148 - expect(console.error.calls.count()).toBe(1);
149 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toMatch(
150 - 'React does not recognize the `helloWorld` prop on a DOM element. ' +
151 - 'If you intentionally want it to appear in the DOM as a custom ' +
152 - 'attribute, spell it as lowercase `helloworld` instead. ' +
153 - 'If you accidentally passed it from a parent component, remove ' +
154 - 'it from the DOM element.\n' +
155 - ' in div (at **)',
156 - );
157 - }
116 + expect(() =>
117 + ReactDOM.render(<div helloWorld="something" />, el),
118 + ).toWarnDev(
119 + 'React does not recognize the `helloWorld` prop on a DOM element. ' +
120 + 'If you intentionally want it to appear in the DOM as a custom ' +
121 + 'attribute, spell it as lowercase `helloworld` instead. ' +
122 + 'If you accidentally passed it from a parent component, remove ' +
123 + 'it from the DOM element.\n' +
124 + ' in div (at **)',
125 + );
126 +
127 + expect(el.firstChild.getAttribute('helloworld')).toBe('something');
128 });
129 });
130 });
packages/react-dom/src/__tests__/ReactDOMComponent-test.js
+568 -867
@@ -147,110 +147,97 @@ describe('ReactDOMComponent', () => {
147 });
148
149 it('should warn for unknown prop', () => {
150 - spyOnDev(console, 'error');
150 const container = document.createElement('div');
152 - ReactDOM.render(<div foo={() => {}} />, container);
153 - if (__DEV__) {
154 - expect(console.error.calls.count(0)).toBe(1);
155 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
156 - 'Warning: Invalid value for prop `foo` on <div> tag. Either remove it ' +
157 - 'from the element, or pass a string or number value to keep ' +
158 - 'it in the DOM. For details, see https://fb.me/react-attribute-behavior' +
159 - '\n in div (at **)',
160 - );
161 - }
151 + expect(() =>
152 + ReactDOM.render(<div foo={() => {}} />, container),
153 + ).toWarnDev(
154 + 'Warning: Invalid value for prop `foo` on <div> tag. Either remove it ' +
155 + 'from the element, or pass a string or number value to keep ' +
156 + 'it in the DOM. For details, see https://fb.me/react-attribute-behavior' +
157 + '\n in div (at **)',
158 + );
159 });
160
161 it('should group multiple unknown prop warnings together', () => {
165 - spyOnDev(console, 'error');
162 const container = document.createElement('div');
167 - ReactDOM.render(<div foo={() => {}} baz={() => {}} />, container);
168 - if (__DEV__) {
169 - expect(console.error.calls.count(0)).toBe(1);
170 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
171 - 'Warning: Invalid values for props `foo`, `baz` on <div> tag. Either remove ' +
172 - 'them from the element, or pass a string or number value to keep ' +
173 - 'them in the DOM. For details, see https://fb.me/react-attribute-behavior' +
174 - '\n in div (at **)',
175 - );
176 - }
163 + expect(() =>
164 + ReactDOM.render(<div foo={() => {}} baz={() => {}} />, container),
165 + ).toWarnDev(
166 + 'Warning: Invalid values for props `foo`, `baz` on <div> tag. Either remove ' +
167 + 'them from the element, or pass a string or number value to keep ' +
168 + 'them in the DOM. For details, see https://fb.me/react-attribute-behavior' +
169 + '\n in div (at **)',
170 + );
171 });
172
173 it('should warn for onDblClick prop', () => {
180 - spyOnDev(console, 'error');
174 const container = document.createElement('div');
182 - ReactDOM.render(<div onDblClick={() => {}} />, container);
183 - if (__DEV__) {
184 - expect(console.error.calls.count(0)).toBe(1);
185 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
186 - 'Warning: Invalid event handler property `onDblClick`. Did you mean `onDoubleClick`?\n in div (at **)',
187 - );
188 - }
175 + expect(() =>
176 + ReactDOM.render(<div onDblClick={() => {}} />, container),
177 + ).toWarnDev(
178 + 'Warning: Invalid event handler property `onDblClick`. Did you mean `onDoubleClick`?\n in div (at **)',
179 + );
180 });
181
182 it('should warn for unknown string event handlers', () => {
192 - spyOnDev(console, 'error');
183 const container = document.createElement('div');
194 - ReactDOM.render(<div onUnknown="alert(&quot;hack&quot;)" />, container);
184 + expect(() =>
185 + ReactDOM.render(<div onUnknown="alert(&quot;hack&quot;)" />, container),
186 + ).toWarnDev(
187 + 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)',
188 + );
189 expect(container.firstChild.hasAttribute('onUnknown')).toBe(false);
190 expect(container.firstChild.onUnknown).toBe(undefined);
197 - ReactDOM.render(<div onunknown="alert(&quot;hack&quot;)" />, container);
191 + expect(() =>
192 + ReactDOM.render(<div onunknown="alert(&quot;hack&quot;)" />, container),
193 + ).toWarnDev(
194 + 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)',
195 + );
196 expect(container.firstChild.hasAttribute('onunknown')).toBe(false);
197 expect(container.firstChild.onunknown).toBe(undefined);
200 - ReactDOM.render(<div on-unknown="alert(&quot;hack&quot;)" />, container);
198 + expect(() =>
199 + ReactDOM.render(
200 + <div on-unknown="alert(&quot;hack&quot;)" />,
201 + container,
202 + ),
203 + ).toWarnDev(
204 + 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)',
205 + );
206 expect(container.firstChild.hasAttribute('on-unknown')).toBe(false);
207 expect(container.firstChild['on-unknown']).toBe(undefined);
203 - if (__DEV__) {
204 - expect(console.error.calls.count(0)).toBe(3);
205 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
206 - 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)',
207 - );
208 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
209 - 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)',
210 - );
211 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe(
212 - 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)',
213 - );
214 - }
208 });
209
210 it('should warn for unknown function event handlers', () => {
218 - spyOnDev(console, 'error');
211 const container = document.createElement('div');
220 - ReactDOM.render(<div onUnknown={function() {}} />, container);
212 + expect(() =>
213 + ReactDOM.render(<div onUnknown={function() {}} />, container),
214 + ).toWarnDev(
215 + 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)',
216 + );
217 expect(container.firstChild.hasAttribute('onUnknown')).toBe(false);
218 expect(container.firstChild.onUnknown).toBe(undefined);
223 - ReactDOM.render(<div onunknown={function() {}} />, container);
219 + expect(() =>
220 + ReactDOM.render(<div onunknown={function() {}} />, container),
221 + ).toWarnDev(
222 + 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)',
223 + );
224 expect(container.firstChild.hasAttribute('onunknown')).toBe(false);
225 expect(container.firstChild.onunknown).toBe(undefined);
226 - ReactDOM.render(<div on-unknown={function() {}} />, container);
226 + expect(() =>
227 + ReactDOM.render(<div on-unknown={function() {}} />, container),
228 + ).toWarnDev(
229 + 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)',
230 + );
231 expect(container.firstChild.hasAttribute('on-unknown')).toBe(false);
232 expect(container.firstChild['on-unknown']).toBe(undefined);
229 - if (__DEV__) {
230 - expect(console.error.calls.count(0)).toBe(3);
231 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
232 - 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)',
233 - );
234 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
235 - 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)',
236 - );
237 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe(
238 - 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)',
239 - );
240 - }
233 });
234
235 it('should warn for badly cased React attributes', () => {
244 - spyOnDev(console, 'error');
236 const container = document.createElement('div');
246 - ReactDOM.render(<div CHILDREN="5" />, container);
237 + expect(() => ReactDOM.render(<div CHILDREN="5" />, container)).toWarnDev(
238 + 'Warning: Invalid DOM property `CHILDREN`. Did you mean `children`?\n in div (at **)',
239 + );
240 expect(container.firstChild.getAttribute('CHILDREN')).toBe('5');
248 - if (__DEV__) {
249 - expect(console.error.calls.count(0)).toBe(1);
250 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
251 - 'Warning: Invalid DOM property `CHILDREN`. Did you mean `children`?\n in div (at **)',
252 - );
253 - }
241 });
242
243 it('should not warn for "0" as a unitless style value', () => {
@@ -264,20 +251,13 @@ describe('ReactDOMComponent', () => {
251 });
252
253 it('should warn nicely about NaN in style', () => {
267 - spyOnDev(console, 'error');
268 -
254 const style = {fontSize: NaN};
255 const div = document.createElement('div');
256 + expect(() => ReactDOM.render(<span style={style} />, div)).toWarnDev(
257 + 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' +
258 + '\n in span (at **)',
259 + );
260 ReactDOM.render(<span style={style} />, div);
272 - ReactDOM.render(<span style={style} />, div);
273 -
274 - if (__DEV__) {
275 - expect(console.error.calls.count()).toBe(1);
276 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
277 - 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' +
278 - '\n in span (at **)',
279 - );
280 - }
261 });
262
263 it('should update styles if initially null', () => {
@@ -575,44 +555,38 @@ describe('ReactDOMComponent', () => {
555 });
556
557 it('should reject attribute key injection attack on markup', () => {
578 - spyOnDev(console, 'error');
579 - for (let i = 0; i < 3; i++) {
580 - const container = document.createElement('div');
581 - const element = React.createElement(
582 - 'x-foo-component',
583 - {'blah" onclick="beevil" noise="hi': 'selected'},
584 - null,
585 - );
586 - ReactDOM.render(element, container);
587 - }
588 - if (__DEV__) {
589 - expect(console.error.calls.count()).toBe(1);
590 - expect(console.error.calls.argsFor(0)[0]).toEqual(
591 - 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
592 - );
593 - }
558 + expect(() => {
559 + for (let i = 0; i < 3; i++) {
560 + const container = document.createElement('div');
561 + const element = React.createElement(
562 + 'x-foo-component',
563 + {'blah" onclick="beevil" noise="hi': 'selected'},
564 + null,
565 + );
566 + ReactDOM.render(element, container);
567 + }
568 + }).toWarnDev(
569 + 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
570 + );
571 });
572
573 it('should reject attribute key injection attack on update', () => {
597 - spyOnDev(console, 'error');
598 - for (let i = 0; i < 3; i++) {
599 - const container = document.createElement('div');
600 - const beforeUpdate = React.createElement('x-foo-component', {}, null);
601 - ReactDOM.render(beforeUpdate, container);
602 -
603 - const afterUpdate = React.createElement(
604 - 'x-foo-component',
605 - {'blah" onclick="beevil" noise="hi': 'selected'},
606 - null,
607 - );
608 - ReactDOM.render(afterUpdate, container);
609 - }
610 - if (__DEV__) {
611 - expect(console.error.calls.count()).toBe(1);
612 - expect(console.error.calls.argsFor(0)[0]).toEqual(
613 - 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
614 - );
615 - }
574 + expect(() => {
575 + for (let i = 0; i < 3; i++) {
576 + const container = document.createElement('div');
577 + const beforeUpdate = React.createElement('x-foo-component', {}, null);
578 + ReactDOM.render(beforeUpdate, container);
579 +
580 + const afterUpdate = React.createElement(
581 + 'x-foo-component',
582 + {'blah" onclick="beevil" noise="hi': 'selected'},
583 + null,
584 + );
585 + ReactDOM.render(afterUpdate, container);
586 + }
587 + }).toWarnDev(
588 + 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`',
589 + );
590 });
591
592 it('should update arbitrary attributes for tags containing dashes', () => {
@@ -857,17 +831,13 @@ describe('ReactDOMComponent', () => {
831 });
832
833 it('should warn about non-string "is" attribute', () => {
860 - spyOnDev(console, 'error');
834 const container = document.createElement('div');
862 - ReactDOM.render(<button is={function() {}} />, container);
863 -
864 - if (__DEV__) {
865 - expect(console.error.calls.count()).toBe(1);
866 - expect(console.error.calls.argsFor(0)[0]).toContain(
867 - 'Received a `function` for a string attribute `is`. If this is expected, cast ' +
868 - 'the value to a string.',
869 - );
870 - }
835 + expect(() =>
836 + ReactDOM.render(<button is={function() {}} />, container),
837 + ).toWarnDev(
838 + 'Received a `function` for a string attribute `is`. If this is expected, cast ' +
839 + 'the value to a string.',
840 + );
841 });
842
843 it('should not update when switching between null/undefined', () => {
@@ -1020,59 +990,41 @@ describe('ReactDOMComponent', () => {
990 });
991
992 it('should not duplicate uppercased selfclosing tags', () => {
1023 - spyOnDev(console, 'error');
993 class Container extends React.Component {
994 render() {
995 return React.createElement('BR', null);
996 }
997 }
998
1030 - const returnedValue = ReactDOMServer.renderToString(<Container />);
999 + let returnedValue;
1000 +
1001 + expect(() => {
1002 + returnedValue = ReactDOMServer.renderToString(<Container />);
1003 + }).toWarnDev('<BR /> is using uppercase HTML.');
1004 expect(returnedValue).not.toContain('</BR>');
1032 - if (__DEV__) {
1033 - expect(console.error.calls.count()).toBe(1);
1034 - expect(console.error.calls.argsFor(0)[0]).toContain(
1035 - '<BR /> is using uppercase HTML.',
1036 - );
1037 - }
1005 });
1006
1007 it('should warn on upper case HTML tags, not SVG nor custom tags', () => {
1041 - spyOnDev(console, 'error');
1008 ReactTestUtils.renderIntoDocument(
1009 React.createElement('svg', null, React.createElement('PATH')),
1010 );
1045 - if (__DEV__) {
1046 - expect(console.error.calls.count()).toBe(0);
1047 - }
1011 ReactTestUtils.renderIntoDocument(React.createElement('CUSTOM-TAG'));
1049 - if (__DEV__) {
1050 - expect(console.error.calls.count()).toBe(0);
1051 - }
1052 - ReactTestUtils.renderIntoDocument(React.createElement('IMG'));
1053 - if (__DEV__) {
1054 - expect(console.error.calls.count()).toBe(1);
1055 - expect(console.error.calls.argsFor(0)[0]).toContain(
1056 - '<IMG /> is using uppercase HTML.',
1057 - );
1058 - }
1012 +
1013 + expect(() =>
1014 + ReactTestUtils.renderIntoDocument(React.createElement('IMG')),
1015 + ).toWarnDev('<IMG /> is using uppercase HTML.');
1016 });
1017
1018 it('should warn on props reserved for future use', () => {
1062 - spyOnDev(console, 'error');
1063 - ReactTestUtils.renderIntoDocument(<div aria="hello" />);
1064 - if (__DEV__) {
1065 - expect(console.error.calls.count()).toBe(1);
1066 - expect(console.error.calls.argsFor(0)[0]).toContain(
1067 - 'The `aria` attribute is reserved for future use in React. ' +
1068 - 'Pass individual `aria-` attributes instead.',
1069 - );
1070 - }
1019 + expect(() =>
1020 + ReactTestUtils.renderIntoDocument(<div aria="hello" />),
1021 + ).toWarnDev(
1022 + 'The `aria` attribute is reserved for future use in React. ' +
1023 + 'Pass individual `aria-` attributes instead.',
1024 + );
1025 });
1026
1027 it('should warn if the tag is unrecognized', () => {
1074 - spyOnDev(console, 'error');
1075 -
1028 let realToString;
1029 try {
1030 realToString = Object.prototype.toString;
@@ -1089,9 +1041,13 @@ describe('ReactDOMComponent', () => {
1041 };
1042 Object.prototype.toString = wrappedToString; // eslint-disable-line no-extend-native
1043
1092 - ReactTestUtils.renderIntoDocument(<bar />);
1044 + expect(() => ReactTestUtils.renderIntoDocument(<bar />)).toWarnDev(
1045 + 'The tag <bar> is unrecognized in this browser',
1046 + );
1047 // Test deduplication
1094 - ReactTestUtils.renderIntoDocument(<foo />);
1048 + expect(() => ReactTestUtils.renderIntoDocument(<foo />)).toWarnDev(
1049 + 'The tag <foo> is unrecognized in this browser',
1050 + );
1051 ReactTestUtils.renderIntoDocument(<foo />);
1052 // This is a funny case.
1053 // Chrome is the only major browser not shipping <time>. But as of July
@@ -1100,25 +1056,14 @@ describe('ReactDOMComponent', () => {
1056 // it soon will be, and many apps have been using it anyway.
1057 ReactTestUtils.renderIntoDocument(<time />);
1058 // Corner case. Make sure out deduplication logic doesn't break with weird tag.
1103 - ReactTestUtils.renderIntoDocument(<hasOwnProperty />);
1104 - } finally {
1105 - Object.prototype.toString = realToString; // eslint-disable-line no-extend-native
1106 - }
1107 -
1108 - if (__DEV__) {
1109 - expect(console.error.calls.count()).toBe(4);
1110 - expect(console.error.calls.argsFor(0)[0]).toContain(
1111 - 'The tag <bar> is unrecognized in this browser',
1112 - );
1113 - expect(console.error.calls.argsFor(1)[0]).toContain(
1114 - 'The tag <foo> is unrecognized in this browser',
1115 - );
1116 - expect(console.error.calls.argsFor(2)[0]).toContain(
1059 + expect(() =>
1060 + ReactTestUtils.renderIntoDocument(<hasOwnProperty />),
1061 + ).toWarnDev([
1062 '<hasOwnProperty /> is using uppercase HTML',
1118 - );
1119 - expect(console.error.calls.argsFor(3)[0]).toContain(
1063 'The tag <hasOwnProperty> is unrecognized in this browser',
1121 - );
1064 + ]);
1065 + } finally {
1066 + Object.prototype.toString = realToString; // eslint-disable-line no-extend-native
1067 }
1068 });
1069
@@ -1158,8 +1103,6 @@ describe('ReactDOMComponent', () => {
1103 });
1104
1105 it('should emit a warning once for a named custom component using shady DOM', () => {
1161 - spyOnDev(console, 'error');
1162 -
1106 const defaultCreateElement = document.createElement.bind(document);
1107
1108 try {
@@ -1174,26 +1117,17 @@ describe('ReactDOMComponent', () => {
1117 }
1118 }
1119 const node = document.createElement('div');
1177 - ReactDOM.render(<ShadyComponent />, node);
1178 - if (__DEV__) {
1179 - expect(console.error.calls.count()).toBe(1);
1180 - expect(console.error.calls.argsFor(0)[0]).toContain(
1181 - 'ShadyComponent is using shady DOM. Using shady DOM with React can ' +
1182 - 'cause things to break subtly.',
1183 - );
1184 - }
1120 + expect(() => ReactDOM.render(<ShadyComponent />, node)).toWarnDev(
1121 + 'ShadyComponent is using shady DOM. Using shady DOM with React can ' +
1122 + 'cause things to break subtly.',
1123 + );
1124 mountComponent({is: 'custom-shady-div2'});
1186 - if (__DEV__) {
1187 - expect(console.error.calls.count()).toBe(1);
1188 - }
1125 } finally {
1126 document.createElement = defaultCreateElement;
1127 }
1128 });
1129
1130 it('should emit a warning once for an unnamed custom component using shady DOM', () => {
1195 - spyOnDev(console, 'error');
1196 -
1131 const defaultCreateElement = document.createElement.bind(document);
1132
1133 try {
@@ -1203,19 +1137,13 @@ describe('ReactDOMComponent', () => {
1137 return container;
1138 };
1139
1206 - mountComponent({is: 'custom-shady-div'});
1207 - if (__DEV__) {
1208 - expect(console.error.calls.count()).toBe(1);
1209 - expect(console.error.calls.argsFor(0)[0]).toContain(
1210 - 'A component is using shady DOM. Using shady DOM with React can ' +
1211 - 'cause things to break subtly.',
1212 - );
1213 - }
1140 + expect(() => mountComponent({is: 'custom-shady-div'})).toWarnDev(
1141 + 'A component is using shady DOM. Using shady DOM with React can ' +
1142 + 'cause things to break subtly.',
1143 + );
1144
1145 + // No additional warnings are expected
1146 mountComponent({is: 'custom-shady-div2'});
1216 - if (__DEV__) {
1217 - expect(console.error.calls.count()).toBe(1);
1218 - }
1147 } finally {
1148 document.createElement = defaultCreateElement;
1149 }
@@ -1223,7 +1151,6 @@ describe('ReactDOMComponent', () => {
1151
1152 it('should treat menuitem as a void element but still create the closing tag', () => {
1153 // menuitem is not implemented in jsdom, so this triggers the unknown warning error
1226 - spyOnDev(console, 'error');
1154 const container = document.createElement('div');
1155
1156 const returnedValue = ReactDOMServer.renderToString(
@@ -1256,25 +1183,15 @@ describe('ReactDOMComponent', () => {
1183 });
1184
1185 it('should validate against use of innerHTML', () => {
1259 - spyOnDev(console, 'error');
1260 - mountComponent({innerHTML: '<span>Hi Jim!</span>'});
1261 - if (__DEV__) {
1262 - expect(console.error.calls.count()).toBe(1);
1263 - expect(console.error.calls.argsFor(0)[0]).toContain(
1264 - 'Directly setting property `innerHTML` is not permitted. ',
1265 - );
1266 - }
1186 + expect(() =>
1187 + mountComponent({innerHTML: '<span>Hi Jim!</span>'}),
1188 + ).toWarnDev('Directly setting property `innerHTML` is not permitted. ');
1189 });
1190
1191 it('should validate against use of innerHTML without case sensitivity', () => {
1270 - spyOnDev(console, 'error');
1271 - mountComponent({innerhtml: '<span>Hi Jim!</span>'});
1272 - if (__DEV__) {
1273 - expect(console.error.calls.count()).toBe(1);
1274 - expect(console.error.calls.argsFor(0)[0]).toContain(
1275 - 'Directly setting property `innerHTML` is not permitted. ',
1276 - );
1277 - }
1192 + expect(() =>
1193 + mountComponent({innerhtml: '<span>Hi Jim!</span>'}),
1194 + ).toWarnDev('Directly setting property `innerHTML` is not permitted. ');
1195 });
1196
1197 it('should validate use of dangerouslySetInnerHTML', () => {
@@ -1302,17 +1219,14 @@ describe('ReactDOMComponent', () => {
1219 });
1220
1221 it('should warn about contentEditable and children', () => {
1305 - spyOnDev(console, 'error');
1306 - mountComponent({contentEditable: true, children: ''});
1307 - if (__DEV__) {
1308 - expect(console.error.calls.count()).toBe(1);
1309 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1310 - 'Warning: A component is `contentEditable` and contains `children` ' +
1311 - 'managed by React. It is now your responsibility to guarantee that ' +
1312 - 'none of those nodes are unexpectedly modified or duplicated. This ' +
1313 - 'is probably not intentional.\n in div (at **)',
1314 - );
1315 - }
1222 + expect(() =>
1223 + mountComponent({contentEditable: true, children: ''}),
1224 + ).toWarnDev(
1225 + 'Warning: A component is `contentEditable` and contains `children` ' +
1226 + 'managed by React. It is now your responsibility to guarantee that ' +
1227 + 'none of those nodes are unexpectedly modified or duplicated. This ' +
1228 + 'is probably not intentional.\n in div (at **)',
1229 + );
1230 });
1231
1232 it('should respect suppressContentEditableWarning', () => {
@@ -1442,17 +1356,14 @@ describe('ReactDOMComponent', () => {
1356 });
1357
1358 it('should warn about contentEditable and children', () => {
1445 - spyOnDev(console, 'error');
1446 - ReactDOM.render(
1447 - <div contentEditable={true}>
1448 - <div />
1449 - </div>,
1450 - container,
1451 - );
1452 - if (__DEV__) {
1453 - expect(console.error.calls.count()).toBe(1);
1454 - expect(console.error.calls.argsFor(0)[0]).toContain('contentEditable');
1455 - }
1359 + expect(() => {
1360 + ReactDOM.render(
1361 + <div contentEditable={true}>
1362 + <div />
1363 + </div>,
1364 + container,
1365 + );
1366 + }).toWarnDev('contentEditable');
1367 });
1368
1369 it('should validate against invalid styles', () => {
@@ -1564,56 +1475,45 @@ describe('ReactDOMComponent', () => {
1475
1476 describe('nesting validation', () => {
1477 it('warns on invalid nesting', () => {
1567 - spyOnDev(console, 'error');
1568 - ReactTestUtils.renderIntoDocument(
1569 - <div>
1570 - <tr />
1571 - <tr />
1572 - </div>,
1573 - );
1574 -
1575 - if (__DEV__) {
1576 - expect(console.error.calls.count()).toBe(2);
1577 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1578 - 'Warning: validateDOMNesting(...): <tr> cannot appear as a child of ' +
1579 - '<div>.' +
1580 - '\n in tr (at **)' +
1581 - '\n in div (at **)',
1582 - );
1583 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
1584 - 'Warning: validateDOMNesting(...): <tr> cannot appear as a child of ' +
1585 - '<div>.' +
1586 - '\n in tr (at **)' +
1587 - '\n in div (at **)',
1478 + expect(() => {
1479 + ReactTestUtils.renderIntoDocument(
1480 + <div>
1481 + <tr />
1482 + <tr />
1483 + </div>,
1484 );
1589 - }
1485 + }).toWarnDev([
1486 + 'Warning: validateDOMNesting(...): <tr> cannot appear as a child of ' +
1487 + '<div>.' +
1488 + '\n in tr (at **)' +
1489 + '\n in div (at **)',
1490 + 'Warning: validateDOMNesting(...): <tr> cannot appear as a child of ' +
1491 + '<div>.' +
1492 + '\n in tr (at **)' +
1493 + '\n in div (at **)',
1494 + ]);
1495 });
1496
1497 it('warns on invalid nesting at root', () => {
1593 - spyOnDev(console, 'error');
1498 const p = document.createElement('p');
1595 - ReactDOM.render(
1596 - <span>
1597 - <p />
1598 - </span>,
1599 - p,
1600 - );
1499
1602 - if (__DEV__) {
1603 - expect(console.error.calls.count()).toBe(1);
1604 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1605 - 'Warning: validateDOMNesting(...): <p> cannot appear as a descendant ' +
1606 - 'of <p>.' +
1607 - // There is no outer `p` here because root container is not part of the stack.
1608 - '\n in p (at **)' +
1609 - '\n in span (at **)',
1500 + expect(() => {
1501 + ReactDOM.render(
1502 + <span>
1503 + <p />
1504 + </span>,
1505 + p,
1506 );
1611 - }
1507 + }).toWarnDev(
1508 + 'Warning: validateDOMNesting(...): <p> cannot appear as a descendant ' +
1509 + 'of <p>.' +
1510 + // There is no outer `p` here because root container is not part of the stack.
1511 + '\n in p (at **)' +
1512 + '\n in span (at **)',
1513 + );
1514 });
1515
1516 it('warns nicely for table rows', () => {
1615 - spyOnDev(console, 'error');
1616 -
1517 class Row extends React.Component {
1518 render() {
1519 return <tr>x</tr>;
@@ -1630,41 +1530,29 @@ describe('ReactDOMComponent', () => {
1530 }
1531 }
1532
1633 - ReactTestUtils.renderIntoDocument(<Foo />);
1634 - if (__DEV__) {
1635 - expect(console.error.calls.count()).toBe(3);
1636 -
1637 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1638 - 'Warning: validateDOMNesting(...): <tr> cannot appear as a child of ' +
1639 - '<table>. Add a <tbody> to your code to match the DOM tree generated ' +
1640 - 'by the browser.' +
1641 - '\n in tr (at **)' +
1642 - '\n in Row (at **)' +
1643 - '\n in table (at **)' +
1644 - '\n in Foo (at **)',
1645 - );
1646 -
1647 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
1648 - 'Warning: validateDOMNesting(...): Text nodes cannot appear as a ' +
1649 - 'child of <tr>.' +
1650 - '\n in tr (at **)' +
1651 - '\n in Row (at **)' +
1652 - '\n in table (at **)' +
1653 - '\n in Foo (at **)',
1654 - );
1655 -
1656 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe(
1657 - 'Warning: validateDOMNesting(...): Whitespace text nodes cannot ' +
1658 - "appear as a child of <table>. Make sure you don't have any extra " +
1659 - 'whitespace between tags on each line of your source code.' +
1660 - '\n in table (at **)' +
1661 - '\n in Foo (at **)',
1662 - );
1663 - }
1533 + expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toWarnDev([
1534 + 'Warning: validateDOMNesting(...): <tr> cannot appear as a child of ' +
1535 + '<table>. Add a <tbody> to your code to match the DOM tree generated ' +
1536 + 'by the browser.' +
1537 + '\n in tr (at **)' +
1538 + '\n in Row (at **)' +
1539 + '\n in table (at **)' +
1540 + '\n in Foo (at **)',
1541 + 'Warning: validateDOMNesting(...): Text nodes cannot appear as a ' +
1542 + 'child of <tr>.' +
1543 + '\n in tr (at **)' +
1544 + '\n in Row (at **)' +
1545 + '\n in table (at **)' +
1546 + '\n in Foo (at **)',
1547 + 'Warning: validateDOMNesting(...): Whitespace text nodes cannot ' +
1548 + "appear as a child of <table>. Make sure you don't have any extra " +
1549 + 'whitespace between tags on each line of your source code.' +
1550 + '\n in table (at **)' +
1551 + '\n in Foo (at **)',
1552 + ]);
1553 });
1554
1555 it('gives useful context in warnings', () => {
1667 - spyOnDev(console, 'error');
1556 function Row() {
1557 return <tr />;
1558 }
@@ -1694,19 +1582,13 @@ describe('ReactDOMComponent', () => {
1582 function App1() {
1583 return <Viz1 />;
1584 }
1697 - ReactTestUtils.renderIntoDocument(<App1 />);
1698 - if (__DEV__) {
1699 - expect(console.error.calls.count()).toBe(1);
1700 - expect(
1701 - normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
1702 - ).toContain(
1703 - '\n in tr (at **)' +
1704 - '\n in Row (at **)' +
1705 - '\n in FancyRow (at **)' +
1706 - '\n in table (at **)' +
1707 - '\n in Viz1 (at **)',
1708 - );
1709 - }
1585 + expect(() => ReactTestUtils.renderIntoDocument(<App1 />)).toWarnDev(
1586 + '\n in tr (at **)' +
1587 + '\n in Row (at **)' +
1588 + '\n in FancyRow (at **)' +
1589 + '\n in table (at **)' +
1590 + '\n in Viz1 (at **)',
1591 + );
1592
1593 function Viz2() {
1594 return (
@@ -1718,74 +1600,56 @@ describe('ReactDOMComponent', () => {
1600 function App2() {
1601 return <Viz2 />;
1602 }
1721 - ReactTestUtils.renderIntoDocument(<App2 />);
1722 - if (__DEV__) {
1723 - expect(console.error.calls.count()).toBe(2);
1724 - expect(
1725 - normalizeCodeLocInfo(console.error.calls.argsFor(1)[0]),
1726 - ).toContain(
1727 - '\n in tr (at **)' +
1728 - '\n in Row (at **)' +
1729 - '\n in FancyRow (at **)' +
1730 - '\n in table (at **)' +
1731 - '\n in Table (at **)' +
1732 - '\n in FancyTable (at **)' +
1733 - '\n in Viz2 (at **)',
1734 - );
1735 - }
1736 -
1737 - ReactTestUtils.renderIntoDocument(
1738 - <FancyTable>
1739 - <FancyRow />
1740 - </FancyTable>,
1603 + expect(() => ReactTestUtils.renderIntoDocument(<App2 />)).toWarnDev(
1604 + '\n in tr (at **)' +
1605 + '\n in Row (at **)' +
1606 + '\n in FancyRow (at **)' +
1607 + '\n in table (at **)' +
1608 + '\n in Table (at **)' +
1609 + '\n in FancyTable (at **)' +
1610 + '\n in Viz2 (at **)',
1611 );
1742 - if (__DEV__) {
1743 - expect(console.error.calls.count()).toBe(3);
1744 - expect(
1745 - normalizeCodeLocInfo(console.error.calls.argsFor(2)[0]),
1746 - ).toContain(
1747 - '\n in tr (at **)' +
1748 - '\n in Row (at **)' +
1749 - '\n in FancyRow (at **)' +
1750 - '\n in table (at **)' +
1751 - '\n in Table (at **)' +
1752 - '\n in FancyTable (at **)',
1753 - );
1754 - }
1612
1756 - ReactTestUtils.renderIntoDocument(
1757 - <table>
1758 - <FancyRow />
1759 - </table>,
1760 - );
1761 - if (__DEV__) {
1762 - expect(console.error.calls.count()).toBe(4);
1763 - expect(
1764 - normalizeCodeLocInfo(console.error.calls.argsFor(3)[0]),
1765 - ).toContain(
1766 - '\n in tr (at **)' +
1767 - '\n in Row (at **)' +
1768 - '\n in FancyRow (at **)' +
1769 - '\n in table (at **)',
1613 + expect(() => {
1614 + ReactTestUtils.renderIntoDocument(
1615 + <FancyTable>
1616 + <FancyRow />
1617 + </FancyTable>,
1618 );
1771 - }
1619 + }).toWarnDev(
1620 + '\n in tr (at **)' +
1621 + '\n in Row (at **)' +
1622 + '\n in FancyRow (at **)' +
1623 + '\n in table (at **)' +
1624 + '\n in Table (at **)' +
1625 + '\n in FancyTable (at **)',
1626 + );
1627
1773 - ReactTestUtils.renderIntoDocument(
1774 - <FancyTable>
1775 - <tr />
1776 - </FancyTable>,
1628 + expect(() => {
1629 + ReactTestUtils.renderIntoDocument(
1630 + <table>
1631 + <FancyRow />
1632 + </table>,
1633 + );
1634 + }).toWarnDev(
1635 + '\n in tr (at **)' +
1636 + '\n in Row (at **)' +
1637 + '\n in FancyRow (at **)' +
1638 + '\n in table (at **)',
1639 );
1778 - if (__DEV__) {
1779 - expect(console.error.calls.count()).toBe(5);
1780 - expect(
1781 - normalizeCodeLocInfo(console.error.calls.argsFor(4)[0]),
1782 - ).toContain(
1783 - '\n in tr (at **)' +
1784 - '\n in table (at **)' +
1785 - '\n in Table (at **)' +
1786 - '\n in FancyTable (at **)',
1640 +
1641 + expect(() => {
1642 + ReactTestUtils.renderIntoDocument(
1643 + <FancyTable>
1644 + <tr />
1645 + </FancyTable>,
1646 );
1788 - }
1647 + }).toWarnDev(
1648 + '\n in tr (at **)' +
1649 + '\n in table (at **)' +
1650 + '\n in Table (at **)' +
1651 + '\n in FancyTable (at **)',
1652 + );
1653
1654 class Link extends React.Component {
1655 render() {
@@ -1793,305 +1657,228 @@ describe('ReactDOMComponent', () => {
1657 }
1658 }
1659
1796 - ReactTestUtils.renderIntoDocument(
1797 - <Link>
1798 - <div>
1799 - <Link />
1800 - </div>
1801 - </Link>,
1802 - );
1803 - if (__DEV__) {
1804 - expect(console.error.calls.count()).toBe(6);
1805 - expect(
1806 - normalizeCodeLocInfo(console.error.calls.argsFor(5)[0]),
1807 - ).toContain(
1808 - '\n in a (at **)' +
1809 - '\n in Link (at **)' +
1810 - '\n in div (at **)' +
1811 - '\n in a (at **)' +
1812 - '\n in Link (at **)',
1660 + expect(() => {
1661 + ReactTestUtils.renderIntoDocument(
1662 + <Link>
1663 + <div>
1664 + <Link />
1665 + </div>
1666 + </Link>,
1667 );
1814 - }
1668 + }).toWarnDev(
1669 + '\n in a (at **)' +
1670 + '\n in Link (at **)' +
1671 + '\n in div (at **)' +
1672 + '\n in a (at **)' +
1673 + '\n in Link (at **)',
1674 + );
1675 });
1676
1677 it('should warn about incorrect casing on properties (ssr)', () => {
1818 - spyOnDev(console, 'error');
1819 - ReactDOMServer.renderToString(
1820 - React.createElement('input', {type: 'text', tabindex: '1'}),
1821 - );
1822 - if (__DEV__) {
1823 - expect(console.error.calls.count()).toBe(1);
1824 - expect(console.error.calls.argsFor(0)[0]).toContain('tabIndex');
1825 - }
1678 + expect(() => {
1679 + ReactDOMServer.renderToString(
1680 + React.createElement('input', {type: 'text', tabindex: '1'}),
1681 + );
1682 + }).toWarnDev('tabIndex');
1683 });
1684
1685 it('should warn about incorrect casing on event handlers (ssr)', () => {
1829 - spyOnDev(console, 'error');
1830 - ReactDOMServer.renderToString(
1831 - React.createElement('input', {type: 'text', oninput: '1'}),
1686 + expect(() => {
1687 + ReactDOMServer.renderToString(
1688 + React.createElement('input', {type: 'text', oninput: '1'}),
1689 + );
1690 + }).toWarnDev(
1691 + 'Invalid event handler property `oninput`. ' +
1692 + 'React events use the camelCase naming convention, ' +
1693 + // Note: we don't know the right event name so we
1694 + // use a generic one (onClick) as a suggestion.
1695 + // This is because we don't bundle the event system
1696 + // on the server.
1697 + 'for example `onClick`.',
1698 );
1699 ReactDOMServer.renderToString(
1700 React.createElement('input', {type: 'text', onKeydown: '1'}),
1701 );
1836 - if (__DEV__) {
1837 - expect(console.error.calls.count()).toBe(1);
1838 - expect(console.error.calls.argsFor(0)[0]).toContain(
1839 - 'Invalid event handler property `oninput`. ' +
1840 - 'React events use the camelCase naming convention, ' +
1841 - // Note: we don't know the right event name so we
1842 - // use a generic one (onClick) as a suggestion.
1843 - // This is because we don't bundle the event system
1844 - // on the server.
1845 - 'for example `onClick`.',
1846 - );
1847 - // We can't warn for `onKeydown` on the server because
1848 - // there is no way tell if this is a valid event or not
1849 - // without access to the event system (which we don't bundle).
1850 - }
1702 + // We can't warn for `onKeydown` on the server because
1703 + // there is no way tell if this is a valid event or not
1704 + // without access to the event system (which we don't bundle).
1705 });
1706
1707 it('should warn about incorrect casing on properties', () => {
1854 - spyOnDev(console, 'error');
1855 - ReactTestUtils.renderIntoDocument(
1856 - React.createElement('input', {type: 'text', tabindex: '1'}),
1857 - );
1858 - if (__DEV__) {
1859 - expect(console.error.calls.count()).toBe(1);
1860 - expect(console.error.calls.argsFor(0)[0]).toContain('tabIndex');
1861 - }
1708 + expect(() => {
1709 + ReactTestUtils.renderIntoDocument(
1710 + React.createElement('input', {type: 'text', tabindex: '1'}),
1711 + );
1712 + }).toWarnDev('tabIndex');
1713 });
1714
1715 it('should warn about incorrect casing on event handlers', () => {
1865 - spyOnDev(console, 'error');
1866 - ReactTestUtils.renderIntoDocument(
1867 - React.createElement('input', {type: 'text', oninput: '1'}),
1868 - );
1869 - ReactTestUtils.renderIntoDocument(
1870 - React.createElement('input', {type: 'text', onKeydown: '1'}),
1871 - );
1872 - if (__DEV__) {
1873 - expect(console.error.calls.count()).toBe(2);
1874 - expect(console.error.calls.argsFor(0)[0]).toContain('onInput');
1875 - expect(console.error.calls.argsFor(1)[0]).toContain('onKeyDown');
1876 - }
1716 + expect(() => {
1717 + ReactTestUtils.renderIntoDocument(
1718 + React.createElement('input', {type: 'text', oninput: '1'}),
1719 + );
1720 + }).toWarnDev('onInput');
1721 + expect(() => {
1722 + ReactTestUtils.renderIntoDocument(
1723 + React.createElement('input', {type: 'text', onKeydown: '1'}),
1724 + );
1725 + }).toWarnDev('onKeyDown');
1726 });
1727
1728 it('should warn about class', () => {
1880 - spyOnDev(console, 'error');
1881 - ReactTestUtils.renderIntoDocument(
1882 - React.createElement('div', {class: 'muffins'}),
1883 - );
1884 - if (__DEV__) {
1885 - expect(console.error.calls.count()).toBe(1);
1886 - expect(console.error.calls.argsFor(0)[0]).toContain('className');
1887 - }
1729 + expect(() => {
1730 + ReactTestUtils.renderIntoDocument(
1731 + React.createElement('div', {class: 'muffins'}),
1732 + );
1733 + }).toWarnDev('className');
1734 });
1735
1736 it('should warn about class (ssr)', () => {
1891 - spyOnDev(console, 'error');
1892 - ReactDOMServer.renderToString(
1893 - React.createElement('div', {class: 'muffins'}),
1894 - );
1895 - if (__DEV__) {
1896 - expect(console.error.calls.count()).toBe(1);
1897 - expect(console.error.calls.argsFor(0)[0]).toContain('className');
1898 - }
1737 + expect(() => {
1738 + ReactDOMServer.renderToString(
1739 + React.createElement('div', {class: 'muffins'}),
1740 + );
1741 + }).toWarnDev('className');
1742 });
1743
1744 it('should warn about props that are no longer supported', () => {
1902 - spyOnDev(console, 'error');
1745 ReactTestUtils.renderIntoDocument(<div />);
1904 - if (__DEV__) {
1905 - expect(console.error.calls.count()).toBe(0);
1906 - }
1746
1908 - ReactTestUtils.renderIntoDocument(<div onFocusIn={() => {}} />);
1909 - if (__DEV__) {
1910 - expect(console.error.calls.count()).toBe(1);
1911 - }
1912 -
1913 - ReactTestUtils.renderIntoDocument(<div onFocusOut={() => {}} />);
1914 - if (__DEV__) {
1915 - expect(console.error.calls.count()).toBe(2);
1916 - }
1747 + expect(() =>
1748 + ReactTestUtils.renderIntoDocument(<div onFocusIn={() => {}} />),
1749 + ).toWarnDev(
1750 + 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
1751 + );
1752 + expect(() =>
1753 + ReactTestUtils.renderIntoDocument(<div onFocusOut={() => {}} />),
1754 + ).toWarnDev(
1755 + 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
1756 + );
1757 });
1758
1759 it('should warn about props that are no longer supported without case sensitivity', () => {
1920 - spyOnDev(console, 'error');
1760 ReactTestUtils.renderIntoDocument(<div />);
1922 - if (__DEV__) {
1923 - expect(console.error.calls.count()).toBe(0);
1924 - }
1925 -
1926 - ReactTestUtils.renderIntoDocument(<div onfocusin={() => {}} />);
1927 - if (__DEV__) {
1928 - expect(console.error.calls.count()).toBe(1);
1929 - }
1930 -
1931 - ReactTestUtils.renderIntoDocument(<div onfocusout={() => {}} />);
1932 - if (__DEV__) {
1933 - expect(console.error.calls.count()).toBe(2);
1934 - }
1761 + expect(() =>
1762 + ReactTestUtils.renderIntoDocument(<div onfocusin={() => {}} />),
1763 + ).toWarnDev(
1764 + 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
1765 + );
1766 + expect(() =>
1767 + ReactTestUtils.renderIntoDocument(<div onfocusout={() => {}} />),
1768 + ).toWarnDev(
1769 + 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
1770 + );
1771 });
1772
1773 it('should warn about props that are no longer supported (ssr)', () => {
1938 - spyOnDev(console, 'error');
1774 ReactDOMServer.renderToString(<div />);
1940 - if (__DEV__) {
1941 - expect(console.error.calls.count()).toBe(0);
1942 - }
1943 -
1944 - ReactDOMServer.renderToString(<div onFocusIn={() => {}} />);
1945 - if (__DEV__) {
1946 - expect(console.error.calls.count()).toBe(1);
1947 - }
1948 -
1949 - ReactDOMServer.renderToString(<div onFocusOut={() => {}} />);
1950 - if (__DEV__) {
1951 - expect(console.error.calls.count()).toBe(2);
1952 - }
1775 + expect(() =>
1776 + ReactDOMServer.renderToString(<div onFocusIn={() => {}} />),
1777 + ).toWarnDev(
1778 + 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
1779 + );
1780 + expect(() =>
1781 + ReactDOMServer.renderToString(<div onFocusOut={() => {}} />),
1782 + ).toWarnDev(
1783 + 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
1784 + );
1785 });
1786
1787 it('should warn about props that are no longer supported without case sensitivity (ssr)', () => {
1956 - spyOnDev(console, 'error');
1788 ReactDOMServer.renderToString(<div />);
1958 - if (__DEV__) {
1959 - expect(console.error.calls.count()).toBe(0);
1960 - }
1961 -
1962 - ReactDOMServer.renderToString(<div onfocusin={() => {}} />);
1963 - if (__DEV__) {
1964 - expect(console.error.calls.count()).toBe(1);
1965 - }
1966 -
1967 - ReactDOMServer.renderToString(<div onfocusout={() => {}} />);
1968 - if (__DEV__) {
1969 - expect(console.error.calls.count()).toBe(2);
1970 - }
1789 + expect(() =>
1790 + ReactDOMServer.renderToString(<div onfocusin={() => {}} />),
1791 + ).toWarnDev(
1792 + 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
1793 + );
1794 + expect(() =>
1795 + ReactDOMServer.renderToString(<div onfocusout={() => {}} />),
1796 + ).toWarnDev(
1797 + 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut.',
1798 + );
1799 });
1800
1801 it('gives source code refs for unknown prop warning', () => {
1974 - spyOnDev(console, 'error');
1975 - ReactTestUtils.renderIntoDocument(<div class="paladin" />);
1976 - ReactTestUtils.renderIntoDocument(<input type="text" onclick="1" />);
1977 - if (__DEV__) {
1978 - expect(console.error.calls.count()).toBe(2);
1979 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1980 - 'Warning: Invalid DOM property `class`. Did you mean `className`?\n in div (at **)',
1981 - );
1982 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
1983 - 'Warning: Invalid event handler property `onclick`. Did you mean ' +
1984 - '`onClick`?\n in input (at **)',
1985 - );
1986 - }
1802 + expect(() =>
1803 + ReactTestUtils.renderIntoDocument(<div class="paladin" />),
1804 + ).toWarnDev(
1805 + 'Warning: Invalid DOM property `class`. Did you mean `className`?\n in div (at **)',
1806 + );
1807 + expect(() =>
1808 + ReactTestUtils.renderIntoDocument(<input type="text" onclick="1" />),
1809 + ).toWarnDev(
1810 + 'Warning: Invalid event handler property `onclick`. Did you mean ' +
1811 + '`onClick`?\n in input (at **)',
1812 + );
1813 });
1814
1815 it('gives source code refs for unknown prop warning (ssr)', () => {
1990 - spyOnDev(console, 'error');
1991 - ReactDOMServer.renderToString(<div class="paladin" />);
1992 - ReactDOMServer.renderToString(<input type="text" oninput="1" />);
1993 - if (__DEV__) {
1994 - expect(console.error.calls.count()).toBe(2);
1995 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1996 - 'Warning: Invalid DOM property `class`. Did you mean `className`?\n in div (at **)',
1997 - );
1998 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
1999 - 'Warning: Invalid event handler property `oninput`. ' +
2000 - // Note: we don't know the right event name so we
2001 - // use a generic one (onClick) as a suggestion.
2002 - // This is because we don't bundle the event system
2003 - // on the server.
2004 - 'React events use the camelCase naming convention, for example `onClick`.' +
2005 - '\n in input (at **)',
2006 - );
2007 - }
1816 + expect(() =>
1817 + ReactDOMServer.renderToString(<div class="paladin" />),
1818 + ).toWarnDev(
1819 + 'Warning: Invalid DOM property `class`. Did you mean `className`?\n in div (at **)',
1820 + );
1821 + expect(() =>
1822 + ReactDOMServer.renderToString(<input type="text" oninput="1" />),
1823 + ).toWarnDev(
1824 + 'Warning: Invalid event handler property `oninput`. ' +
1825 + // Note: we don't know the right event name so we
1826 + // use a generic one (onClick) as a suggestion.
1827 + // This is because we don't bundle the event system
1828 + // on the server.
1829 + 'React events use the camelCase naming convention, for example `onClick`.' +
1830 + '\n in input (at **)',
1831 + );
1832 });
1833
1834 it('gives source code refs for unknown prop warning for update render', () => {
2011 - spyOnDev(console, 'error');
1835 const container = document.createElement('div');
1836
1837 ReactTestUtils.renderIntoDocument(<div className="paladin" />, container);
2015 - if (__DEV__) {
2016 - expect(console.error.calls.count()).toBe(0);
2017 - }
2018 -
2019 - ReactTestUtils.renderIntoDocument(<div class="paladin" />, container);
2020 - if (__DEV__) {
2021 - expect(console.error.calls.count()).toBe(1);
2022 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
2023 - 'Warning: Invalid DOM property `class`. Did you mean `className`?\n in div (at **)',
2024 - );
2025 - }
1838 + expect(() =>
1839 + ReactTestUtils.renderIntoDocument(<div class="paladin" />, container),
1840 + ).toWarnDev(
1841 + 'Warning: Invalid DOM property `class`. Did you mean `className`?\n in div (at **)',
1842 + );
1843 });
1844
1845 it('gives source code refs for unknown prop warning for exact elements', () => {
2029 - spyOnDev(console, 'error');
2030 -
2031 - ReactTestUtils.renderIntoDocument(
2032 - <div className="foo1">
2033 - <div class="foo2" />
2034 - <div onClick={() => {}} />
2035 - <div onclick={() => {}} />
2036 - <div className="foo5" />
2037 - <div className="foo6" />
2038 - </div>,
2039 - );
2040 -
2041 - if (__DEV__) {
2042 - expect(console.error.calls.count()).toBe(2);
2043 -
2044 - expect(console.error.calls.argsFor(0)[0]).toContain('className');
2045 - let matches = console.error.calls
2046 - .argsFor(0)[0]
2047 - .match(/.*\(.*:(\d+)\).*/);
2048 - const previousLine = matches[1];
2049 -
2050 - expect(console.error.calls.argsFor(1)[0]).toContain('onClick');
2051 - matches = console.error.calls.argsFor(1)[0].match(/.*\(.*:(\d+)\).*/);
2052 - const currentLine = matches[1];
2053 -
2054 - //verify line number has a proper relative difference,
2055 - //since hard coding the line number would make test too brittle
2056 - expect(parseInt(previousLine, 10) + 2).toBe(parseInt(currentLine, 10));
2057 - }
1846 + expect(() =>
1847 + ReactTestUtils.renderIntoDocument(
1848 + <div className="foo1">
1849 + <span class="foo2" />
1850 + <div onClick={() => {}} />
1851 + <strong onclick={() => {}} />
1852 + <div className="foo5" />
1853 + <div className="foo6" />
1854 + </div>,
1855 + ),
1856 + ).toWarnDev([
1857 + 'Invalid DOM property `class`. Did you mean `className`?\n in span (at **)',
1858 + 'Invalid event handler property `onclick`. Did you mean `onClick`?\n in strong (at **)',
1859 + ]);
1860 });
1861
1862 it('gives source code refs for unknown prop warning for exact elements (ssr)', () => {
2061 - spyOnDev(console, 'error');
2062 -
2063 - ReactDOMServer.renderToString(
2064 - <div className="foo1">
2065 - <div class="foo2" />
2066 - <div onClick="foo3" />
2067 - <div onclick="foo4" />
2068 - <div className="foo5" />
2069 - <div className="foo6" />
2070 - </div>,
2071 - );
2072 -
2073 - if (__DEV__) {
2074 - expect(console.error.calls.count()).toBe(2);
2075 -
2076 - expect(console.error.calls.argsFor(0)[0]).toContain('className');
2077 - let matches = console.error.calls
2078 - .argsFor(0)[0]
2079 - .match(/.*\(.*:(\d+)\).*/);
2080 - const previousLine = (matches || [])[1];
2081 -
2082 - expect(console.error.calls.argsFor(1)[0]).toContain('onClick');
2083 - matches =
2084 - console.error.calls.argsFor(1)[0].match(/.*\(.*:(\d+)\).*/) || {};
2085 - const currentLine = (matches || [])[1];
2086 -
2087 - //verify line number has a proper relative difference,
2088 - //since hard coding the line number would make test too brittle
2089 - expect(parseInt(previousLine, 10) + 2).toBe(parseInt(currentLine, 10));
2090 - }
1863 + expect(() =>
1864 + ReactDOMServer.renderToString(
1865 + <div className="foo1">
1866 + <span class="foo2" />
1867 + <div onClick="foo3" />
1868 + <strong onclick="foo4" />
1869 + <div className="foo5" />
1870 + <div className="foo6" />
1871 + </div>,
1872 + ),
1873 + ).toWarnDev([
1874 + 'Invalid DOM property `class`. Did you mean `className`?\n in span (at **)',
1875 + 'Invalid event handler property `onclick`. ' +
1876 + 'React events use the camelCase naming convention, for example `onClick`.' +
1877 + '\n in strong (at **)',
1878 + ]);
1879 });
1880
1881 it('gives source code refs for unknown prop warning for exact elements in composition', () => {
2094 - spyOnDev(console, 'error');
1882 const container = document.createElement('div');
1883
1884 class Parent extends React.Component {
@@ -2109,7 +1896,7 @@ describe('ReactDOMComponent', () => {
1896
1897 class Child1 extends React.Component {
1898 render() {
2112 - return <div class="paladin">Child1</div>;
1899 + return <span class="paladin">Child1</span>;
1900 }
1901 }
1902
@@ -2121,7 +1908,7 @@ describe('ReactDOMComponent', () => {
1908
1909 class Child3 extends React.Component {
1910 render() {
2124 - return <div onclick="1">Child3</div>;
1911 + return <strong onclick="1">Child3</strong>;
1912 }
1913 }
1914
@@ -2131,29 +1918,15 @@ describe('ReactDOMComponent', () => {
1918 }
1919 }
1920
2134 - ReactTestUtils.renderIntoDocument(<Parent />, container);
2135 -
2136 - if (__DEV__) {
2137 - expect(console.error.calls.count()).toBe(2);
2138 -
2139 - expect(console.error.calls.argsFor(0)[0]).toContain('className');
2140 - let matches = console.error.calls
2141 - .argsFor(0)[0]
2142 - .match(/.*\(.*:(\d+)\).*/);
2143 - const previousLine = (matches || [])[1];
2144 -
2145 - expect(console.error.calls.argsFor(1)[0]).toContain('onClick');
2146 - matches = console.error.calls.argsFor(1)[0].match(/.*\(.*:(\d+)\).*/);
2147 - const currentLine = (matches || [])[1];
2148 -
2149 - //verify line number has a proper relative difference,
2150 - //since hard coding the line number would make test too brittle
2151 - expect(parseInt(previousLine, 10) + 12).toBe(parseInt(currentLine, 10));
2152 - }
1921 + expect(() =>
1922 + ReactTestUtils.renderIntoDocument(<Parent />, container),
1923 + ).toWarnDev([
1924 + 'Invalid DOM property `class`. Did you mean `className`?\n in span (at **)',
1925 + 'Invalid event handler property `onclick`. Did you mean `onClick`?\n in strong (at **)',
1926 + ]);
1927 });
1928
1929 it('gives source code refs for unknown prop warning for exact elements in composition (ssr)', () => {
2156 - spyOnDev(console, 'error');
1930 const container = document.createElement('div');
1931
1932 class Parent extends React.Component {
@@ -2171,7 +1944,7 @@ describe('ReactDOMComponent', () => {
1944
1945 class Child1 extends React.Component {
1946 render() {
2174 - return <div class="paladin">Child1</div>;
1947 + return <span class="paladin">Child1</span>;
1948 }
1949 }
1950
@@ -2183,7 +1956,7 @@ describe('ReactDOMComponent', () => {
1956
1957 class Child3 extends React.Component {
1958 render() {
2186 - return <div onclick="1">Child3</div>;
1959 + return <strong onclick="1">Child3</strong>;
1960 }
1961 }
1962
@@ -2193,71 +1966,49 @@ describe('ReactDOMComponent', () => {
1966 }
1967 }
1968
2196 - ReactDOMServer.renderToString(<Parent />, container);
2197 -
2198 - if (__DEV__) {
2199 - expect(console.error.calls.count()).toBe(2);
2200 -
2201 - expect(console.error.calls.argsFor(0)[0]).toContain('className');
2202 - let matches = console.error.calls
2203 - .argsFor(0)[0]
2204 - .match(/.*\(.*:(\d+)\).*/);
2205 - const previousLine = (matches || [])[1];
2206 -
2207 - expect(console.error.calls.argsFor(1)[0]).toContain('onClick');
2208 - matches = console.error.calls.argsFor(1)[0].match(/.*\(.*:(\d+)\).*/);
2209 - const currentLine = (matches || [])[1];
2210 -
2211 - //verify line number has a proper relative difference,
2212 - //since hard coding the line number would make test too brittle
2213 - expect(parseInt(previousLine, 10) + 12).toBe(parseInt(currentLine, 10));
2214 - }
1969 + expect(() =>
1970 + ReactDOMServer.renderToString(<Parent />, container),
1971 + ).toWarnDev([
1972 + 'Invalid DOM property `class`. Did you mean `className`?\n in span (at **)',
1973 + 'Invalid event handler property `onclick`. ' +
1974 + 'React events use the camelCase naming convention, for example `onClick`.' +
1975 + '\n in strong (at **)',
1976 + ]);
1977 });
1978
1979 it('should suggest property name if available', () => {
2218 - spyOnDev(console, 'error');
2219 -
2220 - ReactTestUtils.renderIntoDocument(
2221 - React.createElement('label', {for: 'test'}),
2222 - );
2223 - ReactTestUtils.renderIntoDocument(
2224 - React.createElement('input', {type: 'text', autofocus: true}),
1980 + expect(() =>
1981 + ReactTestUtils.renderIntoDocument(
1982 + React.createElement('label', {for: 'test'}),
1983 + ),
1984 + ).toWarnDev(
1985 + 'Warning: Invalid DOM property `for`. Did you mean `htmlFor`?\n in label',
1986 );
1987
2227 - if (__DEV__) {
2228 - expect(console.error.calls.count()).toBe(2);
2229 -
2230 - expect(console.error.calls.argsFor(0)[0]).toBe(
2231 - 'Warning: Invalid DOM property `for`. Did you mean `htmlFor`?\n in label',
2232 - );
2233 -
2234 - expect(console.error.calls.argsFor(1)[0]).toBe(
2235 - 'Warning: Invalid DOM property `autofocus`. Did you mean `autoFocus`?\n in input',
2236 - );
2237 - }
1988 + expect(() =>
1989 + ReactTestUtils.renderIntoDocument(
1990 + React.createElement('input', {type: 'text', autofocus: true}),
1991 + ),
1992 + ).toWarnDev(
1993 + 'Warning: Invalid DOM property `autofocus`. Did you mean `autoFocus`?\n in input',
1994 + );
1995 });
1996
1997 it('should suggest property name if available (ssr)', () => {
2241 - spyOnDev(console, 'error');
2242 -
2243 - ReactDOMServer.renderToString(
2244 - React.createElement('label', {for: 'test'}),
1998 + expect(() =>
1999 + ReactDOMServer.renderToString(
2000 + React.createElement('label', {for: 'test'}),
2001 + ),
2002 + ).toWarnDev(
2003 + 'Warning: Invalid DOM property `for`. Did you mean `htmlFor`?\n in label',
2004 );
2246 - ReactDOMServer.renderToString(
2247 - React.createElement('input', {type: 'text', autofocus: true}),
2005 + expect(() =>
2006 + ReactDOMServer.renderToString(
2007 + React.createElement('input', {type: 'text', autofocus: true}),
2008 + ),
2009 + ).toWarnDev(
2010 + 'Warning: Invalid DOM property `autofocus`. Did you mean `autoFocus`?\n in input',
2011 );
2249 -
2250 - if (__DEV__) {
2251 - expect(console.error.calls.count()).toBe(2);
2252 -
2253 - expect(console.error.calls.argsFor(0)[0]).toBe(
2254 - 'Warning: Invalid DOM property `for`. Did you mean `htmlFor`?\n in label',
2255 - );
2256 -
2257 - expect(console.error.calls.argsFor(1)[0]).toBe(
2258 - 'Warning: Invalid DOM property `autofocus`. Did you mean `autoFocus`?\n in input',
2259 - );
2260 - }
2012 });
2013 });
2014
@@ -2287,50 +2038,41 @@ describe('ReactDOMComponent', () => {
2038
2039 describe('Attributes with aliases', function() {
2040 it('sets aliased attributes on HTML attributes', function() {
2290 - spyOnDev(console, 'error');
2291 -
2292 - const el = ReactTestUtils.renderIntoDocument(<div class="test" />);
2041 + let el;
2042 + expect(() => {
2043 + el = ReactTestUtils.renderIntoDocument(<div class="test" />);
2044 + }).toWarnDev(
2045 + 'Warning: Invalid DOM property `class`. Did you mean `className`?',
2046 + );
2047
2048 expect(el.className).toBe('test');
2295 -
2296 - if (__DEV__) {
2297 - expect(console.error.calls.argsFor(0)[0]).toContain(
2298 - 'Warning: Invalid DOM property `class`. Did you mean `className`?',
2299 - );
2300 - }
2049 });
2050
2051 it('sets incorrectly cased aliased attributes on HTML attributes with a warning', function() {
2304 - spyOnDev(console, 'error');
2305 -
2306 - const el = ReactTestUtils.renderIntoDocument(<div cLASS="test" />);
2052 + let el;
2053 + expect(() => {
2054 + el = ReactTestUtils.renderIntoDocument(<div cLASS="test" />);
2055 + }).toWarnDev(
2056 + 'Warning: Invalid DOM property `cLASS`. Did you mean `className`?',
2057 + );
2058
2059 expect(el.className).toBe('test');
2309 -
2310 - if (__DEV__) {
2311 - expect(console.error.calls.argsFor(0)[0]).toContain(
2312 - 'Warning: Invalid DOM property `cLASS`. Did you mean `className`?',
2313 - );
2314 - }
2060 });
2061
2062 it('sets aliased attributes on SVG elements with a warning', function() {
2318 - spyOnDev(console, 'error');
2319 -
2320 - const el = ReactTestUtils.renderIntoDocument(
2321 - <svg>
2322 - <text arabic-form="initial" />
2323 - </svg>,
2063 + let el;
2064 + expect(() => {
2065 + el = ReactTestUtils.renderIntoDocument(
2066 + <svg>
2067 + <text arabic-form="initial" />
2068 + </svg>,
2069 + );
2070 + }).toWarnDev(
2071 + 'Warning: Invalid DOM property `arabic-form`. Did you mean `arabicForm`?',
2072 );
2073 const text = el.querySelector('text');
2074
2075 expect(text.hasAttribute('arabic-form')).toBe(true);
2328 -
2329 - if (__DEV__) {
2330 - expect(console.error.calls.argsFor(0)[0]).toContain(
2331 - 'Warning: Invalid DOM property `arabic-form`. Did you mean `arabicForm`?',
2332 - );
2333 - }
2076 });
2077
2078 it('sets aliased attributes on custom elements', function() {
@@ -2377,36 +2119,30 @@ describe('ReactDOMComponent', () => {
2119 });
2120
2121 it('does not assign a boolean custom attributes as a string', function() {
2380 - spyOnDev(console, 'error');
2381 -
2382 - const el = ReactTestUtils.renderIntoDocument(<div whatever={true} />);
2122 + let el;
2123 + expect(() => {
2124 + el = ReactTestUtils.renderIntoDocument(<div whatever={true} />);
2125 + }).toWarnDev(
2126 + 'Received `true` for a non-boolean attribute `whatever`.\n\n' +
2127 + 'If you want to write it to the DOM, pass a string instead: ' +
2128 + 'whatever="true" or whatever={value.toString()}.',
2129 + );
2130
2131 expect(el.hasAttribute('whatever')).toBe(false);
2385 -
2386 - if (__DEV__) {
2387 - expect(console.error.calls.argsFor(0)[0]).toContain(
2388 - 'Received `true` for a non-boolean attribute `whatever`.\n\n' +
2389 - 'If you want to write it to the DOM, pass a string instead: ' +
2390 - 'whatever="true" or whatever={value.toString()}.',
2391 - );
2392 - }
2132 });
2133
2134 it('does not assign an implicit boolean custom attributes', function() {
2396 - spyOnDev(console, 'error');
2397 -
2398 - // eslint-disable-next-line react/jsx-boolean-value
2399 - const el = ReactTestUtils.renderIntoDocument(<div whatever />);
2135 + let el;
2136 + expect(() => {
2137 + // eslint-disable-next-line react/jsx-boolean-value
2138 + el = ReactTestUtils.renderIntoDocument(<div whatever />);
2139 + }).toWarnDev(
2140 + 'Received `true` for a non-boolean attribute `whatever`.\n\n' +
2141 + 'If you want to write it to the DOM, pass a string instead: ' +
2142 + 'whatever="true" or whatever={value.toString()}.',
2143 + );
2144
2145 expect(el.hasAttribute('whatever')).toBe(false);
2402 -
2403 - if (__DEV__) {
2404 - expect(console.error.calls.argsFor(0)[0]).toContain(
2405 - 'Received `true` for a non-boolean attribute `whatever`.\n\n' +
2406 - 'If you want to write it to the DOM, pass a string instead: ' +
2407 - 'whatever="true" or whatever={value.toString()}.',
2408 - );
2409 - }
2146 });
2147
2148 it('assigns a numeric custom attributes as a string', function() {
@@ -2416,17 +2152,12 @@ describe('ReactDOMComponent', () => {
2152 });
2153
2154 it('will not assign a function custom attributes', function() {
2419 - spyOnDev(console, 'error');
2420 -
2421 - const el = ReactTestUtils.renderIntoDocument(<div whatever={() => {}} />);
2155 + let el;
2156 + expect(() => {
2157 + el = ReactTestUtils.renderIntoDocument(<div whatever={() => {}} />);
2158 + }).toWarnDev('Warning: Invalid value for prop `whatever` on <div> tag');
2159
2160 expect(el.hasAttribute('whatever')).toBe(false);
2424 -
2425 - if (__DEV__) {
2426 - expect(console.error.calls.argsFor(0)[0]).toContain(
2427 - 'Warning: Invalid value for prop `whatever` on <div> tag',
2428 - );
2429 - }
2161 });
2162
2163 it('will assign an object custom attributes', function() {
@@ -2435,87 +2166,66 @@ describe('ReactDOMComponent', () => {
2166 });
2167
2168 it('allows cased data attributes', function() {
2438 - spyOnDev(console, 'error');
2439 -
2440 - const el = ReactTestUtils.renderIntoDocument(<div data-fooBar="true" />);
2169 + let el;
2170 + expect(() => {
2171 + el = ReactTestUtils.renderIntoDocument(<div data-fooBar="true" />);
2172 + }).toWarnDev(
2173 + 'React does not recognize the `data-fooBar` prop on a DOM element. ' +
2174 + 'If you intentionally want it to appear in the DOM as a custom ' +
2175 + 'attribute, spell it as lowercase `data-foobar` instead. ' +
2176 + 'If you accidentally passed it from a parent component, remove ' +
2177 + 'it from the DOM element.\n' +
2178 + ' in div (at **)',
2179 + );
2180 expect(el.getAttribute('data-foobar')).toBe('true');
2442 -
2443 - if (__DEV__) {
2444 - expect(console.error.calls.count()).toBe(1);
2445 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toMatch(
2446 - 'React does not recognize the `data-fooBar` prop on a DOM element. ' +
2447 - 'If you intentionally want it to appear in the DOM as a custom ' +
2448 - 'attribute, spell it as lowercase `data-foobar` instead. ' +
2449 - 'If you accidentally passed it from a parent component, remove ' +
2450 - 'it from the DOM element.\n' +
2451 - ' in div (at **)',
2452 - );
2453 - }
2181 });
2182
2183 it('allows cased custom attributes', function() {
2457 - spyOnDev(console, 'error');
2458 -
2459 - const el = ReactTestUtils.renderIntoDocument(<div fooBar="true" />);
2184 + let el;
2185 + expect(() => {
2186 + el = ReactTestUtils.renderIntoDocument(<div fooBar="true" />);
2187 + }).toWarnDev(
2188 + 'React does not recognize the `fooBar` prop on a DOM element. ' +
2189 + 'If you intentionally want it to appear in the DOM as a custom ' +
2190 + 'attribute, spell it as lowercase `foobar` instead. ' +
2191 + 'If you accidentally passed it from a parent component, remove ' +
2192 + 'it from the DOM element.\n' +
2193 + ' in div (at **)',
2194 + );
2195 expect(el.getAttribute('foobar')).toBe('true');
2461 -
2462 - if (__DEV__) {
2463 - expect(console.error.calls.count()).toBe(1);
2464 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toMatch(
2465 - 'React does not recognize the `fooBar` prop on a DOM element. ' +
2466 - 'If you intentionally want it to appear in the DOM as a custom ' +
2467 - 'attribute, spell it as lowercase `foobar` instead. ' +
2468 - 'If you accidentally passed it from a parent component, remove ' +
2469 - 'it from the DOM element.\n' +
2470 - ' in div (at **)',
2471 - );
2472 - }
2196 });
2197
2198 it('warns on NaN attributes', function() {
2476 - spyOnDev(console, 'error');
2477 -
2478 - const el = ReactTestUtils.renderIntoDocument(<div whatever={NaN} />);
2199 + let el;
2200 + expect(() => {
2201 + el = ReactTestUtils.renderIntoDocument(<div whatever={NaN} />);
2202 + }).toWarnDev(
2203 + 'Warning: Received NaN for the `whatever` attribute. If this is ' +
2204 + 'expected, cast the value to a string.\n in div',
2205 + );
2206
2207 expect(el.getAttribute('whatever')).toBe('NaN');
2481 -
2482 - if (__DEV__) {
2483 - expect(console.error.calls.argsFor(0)[0]).toContain(
2484 - 'Warning: Received NaN for the `whatever` attribute. If this is ' +
2485 - 'expected, cast the value to a string.\n in div',
2486 - );
2487 - }
2208 });
2209
2210 it('removes a property when it becomes invalid', function() {
2491 - spyOnDev(console, 'error');
2492 -
2211 const container = document.createElement('div');
2212 ReactDOM.render(<div whatever={0} />, container);
2495 - ReactDOM.render(<div whatever={() => {}} />, container);
2213 + expect(() =>
2214 + ReactDOM.render(<div whatever={() => {}} />, container),
2215 + ).toWarnDev('Warning: Invalid value for prop `whatever` on <div> tag.');
2216 const el = container.firstChild;
2497 -
2217 expect(el.hasAttribute('whatever')).toBe(false);
2499 -
2500 - if (__DEV__) {
2501 - expect(console.error.calls.argsFor(0)[0]).toContain(
2502 - 'Warning: Invalid value for prop `whatever` on <div> tag.',
2503 - );
2504 - }
2218 });
2219
2220 it('warns on bad casing of known HTML attributes', function() {
2508 - spyOnDev(console, 'error');
2509 -
2510 - const el = ReactTestUtils.renderIntoDocument(<div SiZe="30" />);
2221 + let el;
2222 + expect(() => {
2223 + el = ReactTestUtils.renderIntoDocument(<div SiZe="30" />);
2224 + }).toWarnDev(
2225 + 'Warning: Invalid DOM property `SiZe`. Did you mean `size`?',
2226 + );
2227
2228 expect(el.getAttribute('size')).toBe('30');
2513 -
2514 - if (__DEV__) {
2515 - expect(console.error.calls.argsFor(0)[0]).toContain(
2516 - 'Warning: Invalid DOM property `SiZe`. Did you mean `size`?',
2517 - );
2518 - }
2229 });
2230 });
2231
@@ -2581,19 +2291,16 @@ describe('ReactDOMComponent', () => {
2291
2292 describe('String boolean attributes', function() {
2293 it('does not assign string boolean attributes for custom attributes', function() {
2584 - spyOnDev(console, 'error');
2585 -
2586 - const el = ReactTestUtils.renderIntoDocument(<div whatever={true} />);
2294 + let el;
2295 + expect(() => {
2296 + el = ReactTestUtils.renderIntoDocument(<div whatever={true} />);
2297 + }).toWarnDev(
2298 + 'Received `true` for a non-boolean attribute `whatever`.\n\n' +
2299 + 'If you want to write it to the DOM, pass a string instead: ' +
2300 + 'whatever="true" or whatever={value.toString()}.',
2301 + );
2302
2303 expect(el.hasAttribute('whatever')).toBe(false);
2589 -
2590 - if (__DEV__) {
2591 - expect(console.error.calls.argsFor(0)[0]).toContain(
2592 - 'Received `true` for a non-boolean attribute `whatever`.\n\n' +
2593 - 'If you want to write it to the DOM, pass a string instead: ' +
2594 - 'whatever="true" or whatever={value.toString()}.',
2595 - );
2596 - }
2304 });
2305
2306 it('stringifies the boolean true for allowed attributes', function() {
@@ -2618,47 +2325,41 @@ describe('ReactDOMComponent', () => {
2325
2326 describe('Hyphenated SVG elements', function() {
2327 it('the font-face element is not a custom element', function() {
2621 - spyOnDev(console, 'error');
2622 - const el = ReactTestUtils.renderIntoDocument(
2623 - <svg>
2624 - <font-face x-height={false} />
2625 - </svg>,
2328 + let el;
2329 + expect(() => {
2330 + el = ReactTestUtils.renderIntoDocument(
2331 + <svg>
2332 + <font-face x-height={false} />
2333 + </svg>,
2334 + );
2335 + }).toWarnDev(
2336 + 'Warning: Invalid DOM property `x-height`. Did you mean `xHeight`',
2337 );
2338
2339 expect(el.querySelector('font-face').hasAttribute('x-height')).toBe(
2340 false,
2341 );
2631 -
2632 - if (__DEV__) {
2633 - expect(console.error.calls.count()).toBe(1);
2634 - expect(console.error.calls.argsFor(0)[0]).toContain(
2635 - 'Warning: Invalid DOM property `x-height`. Did you mean `xHeight`',
2636 - );
2637 - }
2342 });
2343
2344 it('the font-face element does not allow unknown boolean values', function() {
2641 - spyOnDev(console, 'error');
2642 - const el = ReactTestUtils.renderIntoDocument(
2643 - <svg>
2644 - <font-face whatever={false} />
2645 - </svg>,
2345 + let el;
2346 + expect(() => {
2347 + el = ReactTestUtils.renderIntoDocument(
2348 + <svg>
2349 + <font-face whatever={false} />
2350 + </svg>,
2351 + );
2352 + }).toWarnDev(
2353 + 'Received `false` for a non-boolean attribute `whatever`.\n\n' +
2354 + 'If you want to write it to the DOM, pass a string instead: ' +
2355 + 'whatever="false" or whatever={value.toString()}.\n\n' +
2356 + 'If you used to conditionally omit it with whatever={condition && value}, ' +
2357 + 'pass whatever={condition ? value : undefined} instead.',
2358 );
2359
2360 expect(el.querySelector('font-face').hasAttribute('whatever')).toBe(
2361 false,
2362 );
2651 -
2652 - if (__DEV__) {
2653 - expect(console.error.calls.count()).toBe(1);
2654 - expect(console.error.calls.argsFor(0)[0]).toContain(
2655 - 'Received `false` for a non-boolean attribute `whatever`.\n\n' +
2656 - 'If you want to write it to the DOM, pass a string instead: ' +
2657 - 'whatever="false" or whatever={value.toString()}.\n\n' +
2658 - 'If you used to conditionally omit it with whatever={condition && value}, ' +
2659 - 'pass whatever={condition ? value : undefined} instead.',
2660 - );
2661 - }
2363 });
2364 });
2365
packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js
+20 -38
@@ -177,42 +177,28 @@ describe('ReactDOMComponentTree', () => {
177
178 const component = <Controlled />;
179 const instance = ReactDOM.render(component, container);
180 - spyOnDev(console, 'error');
181 - if (__DEV__) {
182 - expect(console.error.calls.count()).toBe(0);
183 - }
184 - simulateInput(instance.a, finishValue);
185 - if (__DEV__) {
186 - expect(console.error.calls.count()).toBe(1);
187 - expect(console.error.calls.argsFor(0)[0]).toContain(
188 - 'Warning: A component is changing an uncontrolled input of ' +
189 - 'type text to be controlled. Input elements should not ' +
190 - 'switch from uncontrolled to controlled (or vice versa). ' +
191 - 'Decide between using a controlled or uncontrolled input ' +
192 - 'element for the lifetime of the component. More info: ' +
193 - 'https://fb.me/react-controlled-components',
194 - );
195 - }
180 + expect(() => simulateInput(instance.a, finishValue)).toWarnDev(
181 + 'Warning: A component is changing an uncontrolled input of ' +
182 + 'type text to be controlled. Input elements should not ' +
183 + 'switch from uncontrolled to controlled (or vice versa). ' +
184 + 'Decide between using a controlled or uncontrolled input ' +
185 + 'element for the lifetime of the component. More info: ' +
186 + 'https://fb.me/react-controlled-components',
187 + );
188 });
189
190 it('finds instance of node that is attempted to be unmounted', () => {
199 - spyOnDev(console, 'error');
191 const component = <div />;
192 const node = ReactDOM.render(<div>{component}</div>, container);
202 - ReactDOM.unmountComponentAtNode(node);
203 - if (__DEV__) {
204 - expect(console.error.calls.count()).toBe(1);
205 - expect(console.error.calls.argsFor(0)[0]).toContain(
206 - "unmountComponentAtNode(): The node you're attempting to unmount " +
207 - 'was rendered by React and is not a top-level container. You may ' +
208 - 'have accidentally passed in a React root node instead of its ' +
209 - 'container.',
210 - );
211 - }
193 + expect(() => ReactDOM.unmountComponentAtNode(node)).toWarnDev(
194 + "unmountComponentAtNode(): The node you're attempting to unmount " +
195 + 'was rendered by React and is not a top-level container. You may ' +
196 + 'have accidentally passed in a React root node instead of its ' +
197 + 'container.',
198 + );
199 });
200
201 it('finds instance from node to stop rendering over other react rendered components', () => {
215 - spyOnDev(console, 'error');
202 const component = (
203 <div>
204 <span>Hello</span>
@@ -220,15 +206,11 @@ describe('ReactDOMComponentTree', () => {
206 );
207 const anotherComponent = <div />;
208 const instance = ReactDOM.render(component, container);
223 - ReactDOM.render(anotherComponent, instance);
224 - if (__DEV__) {
225 - expect(console.error.calls.count()).toBe(1);
226 - expect(console.error.calls.argsFor(0)[0]).toContain(
227 - 'render(...): Replacing React-rendered children with a new root ' +
228 - 'component. If you intended to update the children of this node, ' +
229 - 'you should instead have the existing children update their state ' +
230 - 'and render the new components instead of calling ReactDOM.render.',
231 - );
232 - }
209 + expect(() => ReactDOM.render(anotherComponent, instance)).toWarnDev(
210 + 'render(...): Replacing React-rendered children with a new root ' +
211 + 'component. If you intended to update the children of this node, ' +
212 + 'you should instead have the existing children update their state ' +
213 + 'and render the new components instead of calling ReactDOM.render.',
214 + );
215 });
216 });
packages/react-dom/src/__tests__/ReactDOMFiber-test.js
+43 -82
@@ -15,10 +15,6 @@ const ReactTestUtils = require('react-dom/test-utils');
15 const PropTypes = require('prop-types');
16
17 describe('ReactDOMFiber', () => {
18 - function normalizeCodeLocInfo(str) {
19 - return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
20 - }
21 -
18 let container;
19
20 beforeEach(() => {
@@ -222,28 +218,24 @@ describe('ReactDOMFiber', () => {
218
219 // TODO: remove in React 17
220 it('should support unstable_createPortal alias', () => {
225 - spyOnDev(console, 'warn');
221 const portalContainer = document.createElement('div');
222
228 - ReactDOM.render(
229 - <div>
230 - {ReactDOM.unstable_createPortal(<div>portal</div>, portalContainer)}
231 - </div>,
232 - container,
223 + expect(() =>
224 + ReactDOM.render(
225 + <div>
226 + {ReactDOM.unstable_createPortal(<div>portal</div>, portalContainer)}
227 + </div>,
228 + container,
229 + ),
230 + ).toLowPriorityWarnDev(
231 + 'The ReactDOM.unstable_createPortal() alias has been deprecated, ' +
232 + 'and will be removed in React 17+. Update your code to use ' +
233 + 'ReactDOM.createPortal() instead. It has the exact same API, ' +
234 + 'but without the "unstable_" prefix.',
235 );
236 expect(portalContainer.innerHTML).toBe('<div>portal</div>');
237 expect(container.innerHTML).toBe('<div></div>');
238
237 - if (__DEV__) {
238 - expect(console.warn.calls.count()).toBe(1);
239 - expect(console.warn.calls.argsFor(0)[0]).toContain(
240 - 'The ReactDOM.unstable_createPortal() alias has been deprecated, ' +
241 - 'and will be removed in React 17+. Update your code to use ' +
242 - 'ReactDOM.createPortal() instead. It has the exact same API, ' +
243 - 'but without the "unstable_" prefix.',
244 - );
245 - }
246 -
239 ReactDOM.unmountComponentAtNode(container);
240 expect(portalContainer.innerHTML).toBe('');
241 expect(container.innerHTML).toBe('');
@@ -930,40 +922,30 @@ describe('ReactDOMFiber', () => {
922 });
923
924 it('should warn for non-functional event listeners', () => {
933 - spyOnDev(console, 'error');
925 class Example extends React.Component {
926 render() {
927 return <div onClick="woops" />;
928 }
929 }
939 - ReactDOM.render(<Example />, container);
940 - if (__DEV__) {
941 - expect(console.error.calls.count()).toBe(1);
942 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
943 - 'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
944 - ' in div (at **)\n' +
945 - ' in Example (at **)',
946 - );
947 - }
930 + expect(() => ReactDOM.render(<Example />, container)).toWarnDev(
931 + 'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
932 + ' in div (at **)\n' +
933 + ' in Example (at **)',
934 + );
935 });
936
937 it('should warn with a special message for `false` event listeners', () => {
951 - spyOnDev(console, 'error');
938 class Example extends React.Component {
939 render() {
940 return <div onClick={false} />;
941 }
942 }
957 - ReactDOM.render(<Example />, container);
958 - if (__DEV__) {
959 - expect(console.error.calls.count()).toBe(1);
960 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
961 - 'Expected `onClick` listener to be a function, instead got `false`.\n\n' +
962 - 'If you used to conditionally omit it with onClick={condition && value}, ' +
963 - 'pass onClick={condition ? value : undefined} instead.\n',
964 - ' in div (at **)\n' + ' in Example (at **)',
965 - );
966 - }
943 + expect(() => ReactDOM.render(<Example />, container)).toWarnDev(
944 + 'Expected `onClick` listener to be a function, instead got `false`.\n\n' +
945 + 'If you used to conditionally omit it with onClick={condition && value}, ' +
946 + 'pass onClick={condition ? value : undefined} instead.\n',
947 + ' in div (at **)\n' + ' in Example (at **)',
948 + );
949 });
950
951 it('should not update event handlers until commit', () => {
@@ -1059,23 +1041,15 @@ describe('ReactDOMFiber', () => {
1041 });
1042
1043 it('should not warn when rendering into an empty container', () => {
1062 - spyOnDev(console, 'error');
1044 ReactDOM.render(<div>foo</div>, container);
1045 expect(container.innerHTML).toBe('<div>foo</div>');
1046 ReactDOM.render(null, container);
1047 expect(container.innerHTML).toBe('');
1067 - if (__DEV__) {
1068 - expect(console.error.calls.count()).toBe(0);
1069 - }
1048 ReactDOM.render(<div>bar</div>, container);
1049 expect(container.innerHTML).toBe('<div>bar</div>');
1072 - if (__DEV__) {
1073 - expect(console.error.calls.count()).toBe(0);
1074 - }
1050 });
1051
1052 it('should warn when replacing a container which was manually updated outside of React', () => {
1078 - spyOnDev(console, 'error');
1053 // when not messing with the DOM outside of React
1054 ReactDOM.render(<div key="1">foo</div>, container);
1055 ReactDOM.render(<div key="1">bar</div>, container);
@@ -1083,64 +1057,51 @@ describe('ReactDOMFiber', () => {
1057 // then we mess with the DOM before an update
1058 // we know this will error - that is expected right now
1059 // It's an error of type 'NotFoundError' with no message
1060 + container.innerHTML = '<div>MEOW.</div>';
1061 +
1062 expect(() => {
1087 - container.innerHTML = '<div>MEOW.</div>';
1088 - ReactDOM.render(<div key="2">baz</div>, container);
1089 - }).toThrowError();
1090 - if (__DEV__) {
1091 - expect(console.error.calls.count()).toBe(1);
1092 - expect(console.error.calls.argsFor(0)[0]).toContain(
1063 + expect(() =>
1064 + ReactDOM.render(<div key="2">baz</div>, container),
1065 + ).toWarnDev(
1066 'render(...): ' +
1067 'It looks like the React-rendered content of this container was ' +
1068 'removed without using React. This is not supported and will ' +
1069 'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
1070 'to empty a container.',
1071 );
1099 - }
1072 + }).toThrowError();
1073 });
1074
1075 it('should warn when doing an update to a container manually updated outside of React', () => {
1103 - spyOnDev(console, 'error');
1076 // when not messing with the DOM outside of React
1077 ReactDOM.render(<div>foo</div>, container);
1078 ReactDOM.render(<div>bar</div>, container);
1079 expect(container.innerHTML).toBe('<div>bar</div>');
1080 // then we mess with the DOM before an update
1081 container.innerHTML = '<div>MEOW.</div>';
1110 - ReactDOM.render(<div>baz</div>, container);
1111 - // silently fails to update
1112 - if (__DEV__) {
1113 - expect(console.error.calls.count()).toBe(1);
1114 - expect(console.error.calls.argsFor(0)[0]).toContain(
1115 - 'render(...): ' +
1116 - 'It looks like the React-rendered content of this container was ' +
1117 - 'removed without using React. This is not supported and will ' +
1118 - 'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
1119 - 'to empty a container.',
1120 - );
1121 - }
1082 + expect(() => ReactDOM.render(<div>baz</div>, container)).toWarnDev(
1083 + 'render(...): ' +
1084 + 'It looks like the React-rendered content of this container was ' +
1085 + 'removed without using React. This is not supported and will ' +
1086 + 'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
1087 + 'to empty a container.',
1088 + );
1089 });
1090
1091 it('should warn when doing an update to a container manually cleared outside of React', () => {
1125 - spyOnDev(console, 'error');
1092 // when not messing with the DOM outside of React
1093 ReactDOM.render(<div>foo</div>, container);
1094 ReactDOM.render(<div>bar</div>, container);
1095 expect(container.innerHTML).toBe('<div>bar</div>');
1096 // then we mess with the DOM before an update
1097 container.innerHTML = '';
1132 - ReactDOM.render(<div>baz</div>, container);
1133 - // silently fails to update
1134 - if (__DEV__) {
1135 - expect(console.error.calls.count()).toBe(1);
1136 - expect(console.error.calls.argsFor(0)[0]).toContain(
1137 - 'render(...): ' +
1138 - 'It looks like the React-rendered content of this container was ' +
1139 - 'removed without using React. This is not supported and will ' +
1140 - 'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
1141 - 'to empty a container.',
1142 - );
1143 - }
1098 + expect(() => ReactDOM.render(<div>baz</div>, container)).toWarnDev(
1099 + 'render(...): ' +
1100 + 'It looks like the React-rendered content of this container was ' +
1101 + 'removed without using React. This is not supported and will ' +
1102 + 'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
1103 + 'to empty a container.',
1104 + );
1105 });
1106
1107 it('should render a text component with a text DOM node on the same document as the container', () => {
packages/react-dom/src/__tests__/ReactDOMInput-test.js
+277 -399
@@ -18,10 +18,6 @@ describe('ReactDOMInput', () => {
18 let ReactTestUtils;
19 let setUntrackedValue;
20
21 - function normalizeCodeLocInfo(str) {
22 - return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
23 - }
24 -
21 function dispatchEventOnNode(node, type) {
22 node.dispatchEvent(new Event(type, {bubbles: true, cancelable: true}));
23 }
@@ -41,16 +37,18 @@ describe('ReactDOMInput', () => {
37 });
38
39 it('should properly control a value even if no event listener exists', () => {
44 - spyOnDev(console, 'error');
40 const container = document.createElement('div');
46 - const stub = ReactDOM.render(<input type="text" value="lion" />, container);
41 + let stub;
42 +
43 + expect(() => {
44 + stub = ReactDOM.render(<input type="text" value="lion" />, container);
45 + }).toWarnDev(
46 + 'Failed prop type: You provided a `value` prop to a form field without an `onChange` handler.',
47 + );
48
49 document.body.appendChild(container);
50
51 const node = ReactDOM.findDOMNode(stub);
51 - if (__DEV__) {
52 - expect(console.error.calls.count()).toBe(1);
53 - }
52
53 setUntrackedValue.call(node, 'giraffe');
54
@@ -224,7 +222,6 @@ describe('ReactDOMInput', () => {
222 });
223
224 it('does change the string ".98" to "0.98" with no change handler', () => {
227 - spyOnDev(console, 'error');
225 class Stub extends React.Component {
226 state = {
227 value: '.98',
@@ -234,18 +231,17 @@ describe('ReactDOMInput', () => {
231 }
232 }
233
237 - const stub = ReactTestUtils.renderIntoDocument(<Stub />);
234 + let stub;
235 + expect(() => {
236 + stub = ReactTestUtils.renderIntoDocument(<Stub />);
237 + }).toWarnDev(
238 + 'You provided a `value` prop to a form field ' +
239 + 'without an `onChange` handler.',
240 + );
241 const node = ReactDOM.findDOMNode(stub);
242 stub.setState({value: '0.98'});
243
244 expect(node.value).toEqual('0.98');
242 - if (__DEV__) {
243 - expect(console.error.calls.count()).toBe(1);
244 - expect(console.error.calls.argsFor(0)[0]).toContain(
245 - 'You provided a `value` prop to a form field ' +
246 - 'without an `onChange` handler.',
247 - );
248 - }
245 });
246
247 it('performs a state change from "" to 0', () => {
@@ -266,7 +262,6 @@ describe('ReactDOMInput', () => {
262 });
263
264 it('distinguishes precision for extra zeroes in string number values', () => {
269 - spyOnDev(console, 'error');
265 class Stub extends React.Component {
266 state = {
267 value: '3.0000',
@@ -276,18 +271,18 @@ describe('ReactDOMInput', () => {
271 }
272 }
273
279 - const stub = ReactTestUtils.renderIntoDocument(<Stub />);
274 + let stub;
275 +
276 + expect(() => {
277 + stub = ReactTestUtils.renderIntoDocument(<Stub />);
278 + }).toWarnDev(
279 + 'You provided a `value` prop to a form field ' +
280 + 'without an `onChange` handler.',
281 + );
282 const node = ReactDOM.findDOMNode(stub);
283 stub.setState({value: '3'});
284
285 expect(node.value).toEqual('3');
284 - if (__DEV__) {
285 - expect(console.error.calls.count()).toBe(1);
286 - expect(console.error.calls.argsFor(0)[0]).toContain(
287 - 'You provided a `value` prop to a form field ' +
288 - 'without an `onChange` handler.',
289 - );
290 - }
286 });
287
288 it('should display `defaultValue` of number 0', () => {
@@ -374,23 +369,19 @@ describe('ReactDOMInput', () => {
369 });
370
371 it('should take `defaultValue` when changing to uncontrolled input', () => {
377 - spyOnDev(console, 'error');
372 const container = document.createElement('div');
373 const node = ReactDOM.render(
374 <input type="text" value="0" readOnly="true" />,
375 container,
376 );
377 expect(node.value).toBe('0');
384 - ReactDOM.render(<input type="text" defaultValue="1" />, container);
378 + expect(() =>
379 + ReactDOM.render(<input type="text" defaultValue="1" />, container),
380 + ).toWarnDev(
381 + 'A component is changing a controlled input of type ' +
382 + 'text to be uncontrolled.',
383 + );
384 expect(node.value).toBe('0');
386 -
387 - if (__DEV__) {
388 - expect(console.error.calls.count()).toBe(1);
389 - expect(console.error.calls.argsFor(0)[0]).toContain(
390 - 'A component is changing a controlled input of type ' +
391 - 'text to be uncontrolled.',
392 - );
393 - }
385 });
386
387 it('should render defaultValue for SSR', () => {
@@ -865,27 +856,21 @@ describe('ReactDOMInput', () => {
856 });
857
858 it('should warn with value and no onChange handler and readOnly specified', () => {
868 - spyOnDev(console, 'error');
859 ReactTestUtils.renderIntoDocument(
860 <input type="text" value="zoink" readOnly={true} />,
861 );
872 - if (__DEV__) {
873 - expect(console.error.calls.count()).toBe(0);
874 - }
862
876 - ReactTestUtils.renderIntoDocument(
877 - <input type="text" value="zoink" readOnly={false} />,
863 + expect(() =>
864 + ReactTestUtils.renderIntoDocument(
865 + <input type="text" value="zoink" readOnly={false} />,
866 + ),
867 + ).toWarnDev(
868 + 'Warning: Failed prop type: You provided a `value` prop to a form ' +
869 + 'field without an `onChange` handler. This will render a read-only ' +
870 + 'field. If the field should be mutable use `defaultValue`. ' +
871 + 'Otherwise, set either `onChange` or `readOnly`.\n' +
872 + ' in input (at **)',
873 );
879 - if (__DEV__) {
880 - expect(console.error.calls.count()).toBe(1);
881 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
882 - 'Warning: Failed prop type: You provided a `value` prop to a form ' +
883 - 'field without an `onChange` handler. This will render a read-only ' +
884 - 'field. If the field should be mutable use `defaultValue`. ' +
885 - 'Otherwise, set either `onChange` or `readOnly`.\n' +
886 - ' in input (at **)',
887 - );
888 - }
874 });
875
876 it('should have a this value of undefined if bind is not used', () => {
@@ -900,20 +885,19 @@ describe('ReactDOMInput', () => {
885 });
886
887 it('should warn with checked and no onChange handler with readOnly specified', () => {
903 - spyOnDev(console, 'error');
888 ReactTestUtils.renderIntoDocument(
889 <input type="checkbox" checked="false" readOnly={true} />,
890 );
907 - if (__DEV__) {
908 - expect(console.error.calls.count()).toBe(0);
909 - }
891
911 - ReactTestUtils.renderIntoDocument(
912 - <input type="checkbox" checked="false" readOnly={false} />,
892 + expect(() =>
893 + ReactTestUtils.renderIntoDocument(
894 + <input type="checkbox" checked="false" readOnly={false} />,
895 + ),
896 + ).toWarnDev(
897 + 'Failed prop type: You provided a `checked` prop to a form field without an `onChange` handler. ' +
898 + 'This will render a read-only field. If the field should be mutable use `defaultChecked`. ' +
899 + 'Otherwise, set either `onChange` or `readOnly`.',
900 );
914 - if (__DEV__) {
915 - expect(console.error.calls.count()).toBe(1);
916 - }
901 });
902
903 it('should update defaultValue to empty string', () => {
@@ -924,42 +908,35 @@ describe('ReactDOMInput', () => {
908 });
909
910 it('should warn if value is null', () => {
927 - spyOnDev(console, 'error');
928 - ReactTestUtils.renderIntoDocument(<input type="text" value={null} />);
929 - if (__DEV__) {
930 - expect(console.error.calls.argsFor(0)[0]).toContain(
931 - '`value` prop on `input` should not be null. ' +
932 - 'Consider using an empty string to clear the component or `undefined` ' +
933 - 'for uncontrolled components.',
934 - );
935 - }
911 + expect(() =>
912 + ReactTestUtils.renderIntoDocument(<input type="text" value={null} />),
913 + ).toWarnDev(
914 + '`value` prop on `input` should not be null. ' +
915 + 'Consider using an empty string to clear the component or `undefined` ' +
916 + 'for uncontrolled components.',
917 + );
918
919 ReactTestUtils.renderIntoDocument(<input type="text" value={null} />);
938 - if (__DEV__) {
939 - expect(console.error.calls.count()).toBe(1);
940 - }
920 });
921
922 it('should warn if checked and defaultChecked props are specified', () => {
944 - spyOnDev(console, 'error');
945 - ReactTestUtils.renderIntoDocument(
946 - <input
947 - type="radio"
948 - checked={true}
949 - defaultChecked={true}
950 - readOnly={true}
951 - />,
923 + expect(() =>
924 + ReactTestUtils.renderIntoDocument(
925 + <input
926 + type="radio"
927 + checked={true}
928 + defaultChecked={true}
929 + readOnly={true}
930 + />,
931 + ),
932 + ).toWarnDev(
933 + 'A component contains an input of type radio with both checked and defaultChecked props. ' +
934 + 'Input elements must be either controlled or uncontrolled ' +
935 + '(specify either the checked prop, or the defaultChecked prop, but not ' +
936 + 'both). Decide between using a controlled or uncontrolled input ' +
937 + 'element and remove one of these props. More info: ' +
938 + 'https://fb.me/react-controlled-components',
939 );
953 - if (__DEV__) {
954 - expect(console.error.calls.argsFor(0)[0]).toContain(
955 - 'A component contains an input of type radio with both checked and defaultChecked props. ' +
956 - 'Input elements must be either controlled or uncontrolled ' +
957 - '(specify either the checked prop, or the defaultChecked prop, but not ' +
958 - 'both). Decide between using a controlled or uncontrolled input ' +
959 - 'element and remove one of these props. More info: ' +
960 - 'https://fb.me/react-controlled-components',
961 - );
962 - }
940
941 ReactTestUtils.renderIntoDocument(
942 <input
@@ -969,318 +946,269 @@ describe('ReactDOMInput', () => {
946 readOnly={true}
947 />,
948 );
972 - if (__DEV__) {
973 - expect(console.error.calls.count()).toBe(1);
974 - }
949 });
950
951 it('should warn if value and defaultValue props are specified', () => {
978 - spyOnDev(console, 'error');
979 - ReactTestUtils.renderIntoDocument(
980 - <input type="text" value="foo" defaultValue="bar" readOnly={true} />,
952 + expect(() =>
953 + ReactTestUtils.renderIntoDocument(
954 + <input type="text" value="foo" defaultValue="bar" readOnly={true} />,
955 + ),
956 + ).toWarnDev(
957 + 'A component contains an input of type text with both value and defaultValue props. ' +
958 + 'Input elements must be either controlled or uncontrolled ' +
959 + '(specify either the value prop, or the defaultValue prop, but not ' +
960 + 'both). Decide between using a controlled or uncontrolled input ' +
961 + 'element and remove one of these props. More info: ' +
962 + 'https://fb.me/react-controlled-components',
963 );
982 - if (__DEV__) {
983 - expect(console.error.calls.argsFor(0)[0]).toContain(
984 - 'A component contains an input of type text with both value and defaultValue props. ' +
985 - 'Input elements must be either controlled or uncontrolled ' +
986 - '(specify either the value prop, or the defaultValue prop, but not ' +
987 - 'both). Decide between using a controlled or uncontrolled input ' +
988 - 'element and remove one of these props. More info: ' +
989 - 'https://fb.me/react-controlled-components',
990 - );
991 - }
964
965 ReactTestUtils.renderIntoDocument(
966 <input type="text" value="foo" defaultValue="bar" readOnly={true} />,
967 );
996 - if (__DEV__) {
997 - expect(console.error.calls.count()).toBe(1);
998 - }
968 });
969
970 it('should warn if controlled input switches to uncontrolled (value is undefined)', () => {
1002 - spyOnDev(console, 'error');
971 const stub = (
972 <input type="text" value="controlled" onChange={emptyFunction} />
973 );
974 const container = document.createElement('div');
975 ReactDOM.render(stub, container);
1008 - ReactDOM.render(<input type="text" />, container);
1009 - if (__DEV__) {
1010 - expect(console.error.calls.count()).toBe(1);
1011 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1012 - 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
1013 - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1014 - 'Decide between using a controlled or uncontrolled input ' +
1015 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1016 - ' in input (at **)',
1017 - );
1018 - }
976 + expect(() => ReactDOM.render(<input type="text" />, container)).toWarnDev(
977 + 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
978 + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
979 + 'Decide between using a controlled or uncontrolled input ' +
980 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
981 + ' in input (at **)',
982 + );
983 });
984
985 it('should warn if controlled input switches to uncontrolled (value is null)', () => {
1022 - spyOnDev(console, 'error');
986 const stub = (
987 <input type="text" value="controlled" onChange={emptyFunction} />
988 );
989 const container = document.createElement('div');
990 ReactDOM.render(stub, container);
1028 - ReactDOM.render(<input type="text" value={null} />, container);
1029 - if (__DEV__) {
1030 - expect(console.error.calls.count()).toBeGreaterThan(0);
1031 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
1032 - 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
1033 - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1034 - 'Decide between using a controlled or uncontrolled input ' +
1035 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1036 - ' in input (at **)',
1037 - );
1038 - }
991 + expect(() =>
992 + ReactDOM.render(<input type="text" value={null} />, container),
993 + ).toWarnDev([
994 + '`value` prop on `input` should not be null. ' +
995 + 'Consider using an empty string to clear the component or `undefined` for uncontrolled components',
996 + 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
997 + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
998 + 'Decide between using a controlled or uncontrolled input ' +
999 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1000 + ' in input (at **)',
1001 + ]);
1002 });
1003
1004 it('should warn if controlled input switches to uncontrolled with defaultValue', () => {
1042 - spyOnDev(console, 'error');
1005 const stub = (
1006 <input type="text" value="controlled" onChange={emptyFunction} />
1007 );
1008 const container = document.createElement('div');
1009 ReactDOM.render(stub, container);
1048 - ReactDOM.render(
1049 - <input type="text" defaultValue="uncontrolled" />,
1050 - container,
1010 + expect(() =>
1011 + ReactDOM.render(
1012 + <input type="text" defaultValue="uncontrolled" />,
1013 + container,
1014 + ),
1015 + ).toWarnDev(
1016 + 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
1017 + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1018 + 'Decide between using a controlled or uncontrolled input ' +
1019 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1020 + ' in input (at **)',
1021 );
1052 - if (__DEV__) {
1053 - expect(console.error.calls.count()).toBe(1);
1054 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1055 - 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
1056 - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1057 - 'Decide between using a controlled or uncontrolled input ' +
1058 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1059 - ' in input (at **)',
1060 - );
1061 - }
1022 });
1023
1024 it('should warn if uncontrolled input (value is undefined) switches to controlled', () => {
1065 - spyOnDev(console, 'error');
1025 const stub = <input type="text" />;
1026 const container = document.createElement('div');
1027 ReactDOM.render(stub, container);
1069 - ReactDOM.render(<input type="text" value="controlled" />, container);
1070 - if (__DEV__) {
1071 - expect(console.error.calls.count()).toBe(1);
1072 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1073 - 'Warning: A component is changing an uncontrolled input of type text to be controlled. ' +
1074 - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
1075 - 'Decide between using a controlled or uncontrolled input ' +
1076 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1077 - ' in input (at **)',
1078 - );
1079 - }
1028 + expect(() =>
1029 + ReactDOM.render(<input type="text" value="controlled" />, container),
1030 + ).toWarnDev(
1031 + 'Warning: A component is changing an uncontrolled input of type text to be controlled. ' +
1032 + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
1033 + 'Decide between using a controlled or uncontrolled input ' +
1034 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1035 + ' in input (at **)',
1036 + );
1037 });
1038
1039 it('should warn if uncontrolled input (value is null) switches to controlled', () => {
1083 - spyOnDev(console, 'error');
1040 const stub = <input type="text" value={null} />;
1041 const container = document.createElement('div');
1086 - ReactDOM.render(stub, container);
1087 - ReactDOM.render(<input type="text" value="controlled" />, container);
1088 - if (__DEV__) {
1089 - expect(console.error.calls.count()).toBeGreaterThan(0);
1090 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
1091 - 'Warning: A component is changing an uncontrolled input of type text to be controlled. ' +
1092 - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
1093 - 'Decide between using a controlled or uncontrolled input ' +
1094 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1095 - ' in input (at **)',
1096 - );
1097 - }
1042 + expect(() => ReactDOM.render(stub, container)).toWarnDev(
1043 + '`value` prop on `input` should not be null. ' +
1044 + 'Consider using an empty string to clear the component or `undefined` for uncontrolled components.',
1045 + );
1046 + expect(() =>
1047 + ReactDOM.render(<input type="text" value="controlled" />, container),
1048 + ).toWarnDev(
1049 + 'Warning: A component is changing an uncontrolled input of type text to be controlled. ' +
1050 + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
1051 + 'Decide between using a controlled or uncontrolled input ' +
1052 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1053 + ' in input (at **)',
1054 + );
1055 });
1056
1057 it('should warn if controlled checkbox switches to uncontrolled (checked is undefined)', () => {
1101 - spyOnDev(console, 'error');
1058 const stub = (
1059 <input type="checkbox" checked={true} onChange={emptyFunction} />
1060 );
1061 const container = document.createElement('div');
1062 ReactDOM.render(stub, container);
1107 - ReactDOM.render(<input type="checkbox" />, container);
1108 - if (__DEV__) {
1109 - expect(console.error.calls.count()).toBe(1);
1110 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1111 - 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
1112 - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1113 - 'Decide between using a controlled or uncontrolled input ' +
1114 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1115 - ' in input (at **)',
1116 - );
1117 - }
1063 + expect(() =>
1064 + ReactDOM.render(<input type="checkbox" />, container),
1065 + ).toWarnDev(
1066 + 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
1067 + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1068 + 'Decide between using a controlled or uncontrolled input ' +
1069 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1070 + ' in input (at **)',
1071 + );
1072 });
1073
1074 it('should warn if controlled checkbox switches to uncontrolled (checked is null)', () => {
1121 - spyOnDev(console, 'error');
1075 const stub = (
1076 <input type="checkbox" checked={true} onChange={emptyFunction} />
1077 );
1078 const container = document.createElement('div');
1079 ReactDOM.render(stub, container);
1127 - ReactDOM.render(<input type="checkbox" checked={null} />, container);
1128 - if (__DEV__) {
1129 - expect(console.error.calls.count()).toBe(1);
1130 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1131 - 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
1132 - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1133 - 'Decide between using a controlled or uncontrolled input ' +
1134 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1135 - ' in input (at **)',
1136 - );
1137 - }
1080 + expect(() =>
1081 + ReactDOM.render(<input type="checkbox" checked={null} />, container),
1082 + ).toWarnDev(
1083 + 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
1084 + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1085 + 'Decide between using a controlled or uncontrolled input ' +
1086 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1087 + ' in input (at **)',
1088 + );
1089 });
1090
1091 it('should warn if controlled checkbox switches to uncontrolled with defaultChecked', () => {
1141 - spyOnDev(console, 'error');
1092 const stub = (
1093 <input type="checkbox" checked={true} onChange={emptyFunction} />
1094 );
1095 const container = document.createElement('div');
1096 ReactDOM.render(stub, container);
1147 - ReactDOM.render(<input type="checkbox" defaultChecked={true} />, container);
1148 - if (__DEV__) {
1149 - expect(console.error.calls.count()).toBe(1);
1150 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1151 - 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
1152 - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1153 - 'Decide between using a controlled or uncontrolled input ' +
1154 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1155 - ' in input (at **)',
1156 - );
1157 - }
1097 + expect(() =>
1098 + ReactDOM.render(
1099 + <input type="checkbox" defaultChecked={true} />,
1100 + container,
1101 + ),
1102 + ).toWarnDev(
1103 + 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
1104 + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1105 + 'Decide between using a controlled or uncontrolled input ' +
1106 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1107 + ' in input (at **)',
1108 + );
1109 });
1110
1111 it('should warn if uncontrolled checkbox (checked is undefined) switches to controlled', () => {
1161 - spyOnDev(console, 'error');
1112 const stub = <input type="checkbox" />;
1113 const container = document.createElement('div');
1114 ReactDOM.render(stub, container);
1165 - ReactDOM.render(<input type="checkbox" checked={true} />, container);
1166 - if (__DEV__) {
1167 - expect(console.error.calls.count()).toBe(1);
1168 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1169 - 'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' +
1170 - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
1171 - 'Decide between using a controlled or uncontrolled input ' +
1172 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1173 - ' in input (at **)',
1174 - );
1175 - }
1115 + expect(() =>
1116 + ReactDOM.render(<input type="checkbox" checked={true} />, container),
1117 + ).toWarnDev(
1118 + 'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' +
1119 + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
1120 + 'Decide between using a controlled or uncontrolled input ' +
1121 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1122 + ' in input (at **)',
1123 + );
1124 });
1125
1126 it('should warn if uncontrolled checkbox (checked is null) switches to controlled', () => {
1179 - spyOnDev(console, 'error');
1127 const stub = <input type="checkbox" checked={null} />;
1128 const container = document.createElement('div');
1129 ReactDOM.render(stub, container);
1183 - ReactDOM.render(<input type="checkbox" checked={true} />, container);
1184 - if (__DEV__) {
1185 - expect(console.error.calls.count()).toBe(1);
1186 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1187 - 'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' +
1188 - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
1189 - 'Decide between using a controlled or uncontrolled input ' +
1190 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1191 - ' in input (at **)',
1192 - );
1193 - }
1130 + expect(() =>
1131 + ReactDOM.render(<input type="checkbox" checked={true} />, container),
1132 + ).toWarnDev(
1133 + 'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' +
1134 + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
1135 + 'Decide between using a controlled or uncontrolled input ' +
1136 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1137 + ' in input (at **)',
1138 + );
1139 });
1140
1141 it('should warn if controlled radio switches to uncontrolled (checked is undefined)', () => {
1197 - spyOnDev(console, 'error');
1142 const stub = <input type="radio" checked={true} onChange={emptyFunction} />;
1143 const container = document.createElement('div');
1144 ReactDOM.render(stub, container);
1201 - ReactDOM.render(<input type="radio" />, container);
1202 - if (__DEV__) {
1203 - expect(console.error.calls.count()).toBe(1);
1204 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1205 - 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
1206 - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1207 - 'Decide between using a controlled or uncontrolled input ' +
1208 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1209 - ' in input (at **)',
1210 - );
1211 - }
1145 + expect(() => ReactDOM.render(<input type="radio" />, container)).toWarnDev(
1146 + 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
1147 + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1148 + 'Decide between using a controlled or uncontrolled input ' +
1149 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1150 + ' in input (at **)',
1151 + );
1152 });
1153
1154 it('should warn if controlled radio switches to uncontrolled (checked is null)', () => {
1215 - spyOnDev(console, 'error');
1155 const stub = <input type="radio" checked={true} onChange={emptyFunction} />;
1156 const container = document.createElement('div');
1157 ReactDOM.render(stub, container);
1219 - ReactDOM.render(<input type="radio" checked={null} />, container);
1220 - if (__DEV__) {
1221 - expect(console.error.calls.count()).toBe(1);
1222 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1223 - 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
1224 - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1225 - 'Decide between using a controlled or uncontrolled input ' +
1226 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1227 - ' in input (at **)',
1228 - );
1229 - }
1158 + expect(() =>
1159 + ReactDOM.render(<input type="radio" checked={null} />, container),
1160 + ).toWarnDev(
1161 + 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
1162 + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1163 + 'Decide between using a controlled or uncontrolled input ' +
1164 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1165 + ' in input (at **)',
1166 + );
1167 });
1168
1169 it('should warn if controlled radio switches to uncontrolled with defaultChecked', () => {
1233 - spyOnDev(console, 'error');
1170 const stub = <input type="radio" checked={true} onChange={emptyFunction} />;
1171 const container = document.createElement('div');
1172 ReactDOM.render(stub, container);
1237 - ReactDOM.render(<input type="radio" defaultChecked={true} />, container);
1238 - if (__DEV__) {
1239 - expect(console.error.calls.count()).toBe(1);
1240 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1241 - 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
1242 - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1243 - 'Decide between using a controlled or uncontrolled input ' +
1244 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1245 - ' in input (at **)',
1246 - );
1247 - }
1173 + expect(() =>
1174 + ReactDOM.render(<input type="radio" defaultChecked={true} />, container),
1175 + ).toWarnDev(
1176 + 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
1177 + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1178 + 'Decide between using a controlled or uncontrolled input ' +
1179 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1180 + ' in input (at **)',
1181 + );
1182 });
1183
1184 it('should warn if uncontrolled radio (checked is undefined) switches to controlled', () => {
1251 - spyOnDev(console, 'error');
1185 const stub = <input type="radio" />;
1186 const container = document.createElement('div');
1187 ReactDOM.render(stub, container);
1255 - ReactDOM.render(<input type="radio" checked={true} />, container);
1256 - if (__DEV__) {
1257 - expect(console.error.calls.count()).toBe(1);
1258 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1259 - 'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' +
1260 - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
1261 - 'Decide between using a controlled or uncontrolled input ' +
1262 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1263 - ' in input (at **)',
1264 - );
1265 - }
1188 + expect(() =>
1189 + ReactDOM.render(<input type="radio" checked={true} />, container),
1190 + ).toWarnDev(
1191 + 'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' +
1192 + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
1193 + 'Decide between using a controlled or uncontrolled input ' +
1194 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1195 + ' in input (at **)',
1196 + );
1197 });
1198
1199 it('should warn if uncontrolled radio (checked is null) switches to controlled', () => {
1269 - spyOnDev(console, 'error');
1200 const stub = <input type="radio" checked={null} />;
1201 const container = document.createElement('div');
1202 ReactDOM.render(stub, container);
1273 - ReactDOM.render(<input type="radio" checked={true} />, container);
1274 - if (__DEV__) {
1275 - expect(console.error.calls.count()).toBe(1);
1276 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1277 - 'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' +
1278 - 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
1279 - 'Decide between using a controlled or uncontrolled input ' +
1280 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1281 - ' in input (at **)',
1282 - );
1283 - }
1203 + expect(() =>
1204 + ReactDOM.render(<input type="radio" checked={true} />, container),
1205 + ).toWarnDev(
1206 + 'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' +
1207 + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
1208 + 'Decide between using a controlled or uncontrolled input ' +
1209 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1210 + ' in input (at **)',
1211 + );
1212 });
1213
1214 it('should not warn if radio value changes but never becomes controlled', () => {
@@ -1317,7 +1245,6 @@ describe('ReactDOMInput', () => {
1245 });
1246
1247 it('should warn if radio checked false changes to become uncontrolled', () => {
1320 - spyOnDev(console, 'error');
1248 const container = document.createElement('div');
1249 ReactDOM.render(
1250 <input
@@ -1328,16 +1255,15 @@ describe('ReactDOMInput', () => {
1255 />,
1256 container,
1257 );
1331 - ReactDOM.render(<input type="radio" value="value" />, container);
1332 - if (__DEV__) {
1333 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1334 - 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
1335 - 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1336 - 'Decide between using a controlled or uncontrolled input ' +
1337 - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1338 - ' in input (at **)',
1339 - );
1340 - }
1258 + expect(() =>
1259 + ReactDOM.render(<input type="radio" value="value" />, container),
1260 + ).toWarnDev(
1261 + 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
1262 + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
1263 + 'Decide between using a controlled or uncontrolled input ' +
1264 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
1265 + ' in input (at **)',
1266 + );
1267 });
1268
1269 it('sets type, step, min, max before value always', () => {
@@ -1556,29 +1482,19 @@ describe('ReactDOMInput', () => {
1482 }
1483
1484 it('reverts the value attribute to the initial value', () => {
1559 - spyOnDev(console, 'error');
1560 - renderInputWithStringThenWithUndefined();
1485 + expect(renderInputWithStringThenWithUndefined).toWarnDev(
1486 + 'Input elements should not switch from controlled to ' +
1487 + 'uncontrolled (or vice versa).',
1488 + );
1489 expect(input.getAttribute('value')).toBe('first');
1562 - if (__DEV__) {
1563 - expect(console.error.calls.count()).toBe(1);
1564 - expect(console.error.calls.argsFor(0)[0]).toContain(
1565 - 'Input elements should not switch from controlled to ' +
1566 - 'uncontrolled (or vice versa).',
1567 - );
1568 - }
1490 });
1491
1492 it('preserves the value property', () => {
1572 - spyOnDev(console, 'error');
1573 - renderInputWithStringThenWithUndefined();
1493 + expect(renderInputWithStringThenWithUndefined).toWarnDev(
1494 + 'Input elements should not switch from controlled to ' +
1495 + 'uncontrolled (or vice versa).',
1496 + );
1497 expect(input.value).toBe('latest');
1575 - if (__DEV__) {
1576 - expect(console.error.calls.count()).toBe(1);
1577 - expect(console.error.calls.argsFor(0)[0]).toContain(
1578 - 'Input elements should not switch from controlled to ' +
1579 - 'uncontrolled (or vice versa).',
1580 - );
1581 - }
1498 });
1499 });
1500
@@ -1605,82 +1521,56 @@ describe('ReactDOMInput', () => {
1521 }
1522
1523 it('reverts the value attribute to the initial value', () => {
1608 - spyOnDev(console, 'error');
1609 - renderInputWithStringThenWithNull();
1524 + expect(renderInputWithStringThenWithNull).toWarnDev([
1525 + '`value` prop on `input` should not be null. ' +
1526 + 'Consider using an empty string to clear the component ' +
1527 + 'or `undefined` for uncontrolled components.',
1528 + 'Input elements should not switch from controlled ' +
1529 + 'to uncontrolled (or vice versa).',
1530 + ]);
1531 expect(input.getAttribute('value')).toBe('first');
1611 - if (__DEV__) {
1612 - expect(console.error.calls.count()).toBe(2);
1613 - expect(console.error.calls.argsFor(0)[0]).toContain(
1614 - '`value` prop on `input` should not be null. ' +
1615 - 'Consider using an empty string to clear the component ' +
1616 - 'or `undefined` for uncontrolled components.',
1617 - );
1618 - expect(console.error.calls.argsFor(1)[0]).toContain(
1619 - 'Input elements should not switch from controlled ' +
1620 - 'to uncontrolled (or vice versa).',
1621 - );
1622 - }
1532 });
1533
1534 it('preserves the value property', () => {
1626 - spyOnDev(console, 'error');
1627 - renderInputWithStringThenWithNull();
1535 + expect(renderInputWithStringThenWithNull).toWarnDev([
1536 + '`value` prop on `input` should not be null. ' +
1537 + 'Consider using an empty string to clear the component ' +
1538 + 'or `undefined` for uncontrolled components.',
1539 + 'Input elements should not switch from controlled ' +
1540 + 'to uncontrolled (or vice versa).',
1541 + ]);
1542 expect(input.value).toBe('latest');
1629 - if (__DEV__) {
1630 - expect(console.error.calls.count()).toBe(2);
1631 - expect(console.error.calls.argsFor(0)[0]).toContain(
1632 - '`value` prop on `input` should not be null. ' +
1633 - 'Consider using an empty string to clear the component ' +
1634 - 'or `undefined` for uncontrolled components.',
1635 - );
1636 - expect(console.error.calls.argsFor(1)[0]).toContain(
1637 - 'Input elements should not switch from controlled ' +
1638 - 'to uncontrolled (or vice versa).',
1639 - );
1640 - }
1543 });
1544 });
1545
1546 describe('When given a Symbol value', function() {
1547 it('treats initial Symbol value as an empty string', function() {
1646 - spyOnDev(console, 'error');
1548 const container = document.createElement('div');
1648 - ReactDOM.render(
1649 - <input value={Symbol('foobar')} onChange={() => {}} />,
1650 - container,
1651 - );
1549 + expect(() =>
1550 + ReactDOM.render(
1551 + <input value={Symbol('foobar')} onChange={() => {}} />,
1552 + container,
1553 + ),
1554 + ).toWarnDev('Invalid value for prop `value`');
1555 const node = container.firstChild;
1556
1557 expect(node.value).toBe('');
1558 expect(node.getAttribute('value')).toBe('');
1656 -
1657 - if (__DEV__) {
1658 - expect(console.error.calls.count()).toBe(1);
1659 - expect(console.error.calls.argsFor(0)[0]).toContain(
1660 - 'Invalid value for prop `value`',
1661 - );
1662 - }
1559 });
1560
1561 it('treats updated Symbol value as an empty string', function() {
1666 - spyOnDev(console, 'error');
1562 const container = document.createElement('div');
1563 ReactDOM.render(<input value="foo" onChange={() => {}} />, container);
1669 - ReactDOM.render(
1670 - <input value={Symbol('foobar')} onChange={() => {}} />,
1671 - container,
1672 - );
1564 + expect(() =>
1565 + ReactDOM.render(
1566 + <input value={Symbol('foobar')} onChange={() => {}} />,
1567 + container,
1568 + ),
1569 + ).toWarnDev('Invalid value for prop `value`');
1570 const node = container.firstChild;
1571
1572 expect(node.value).toBe('');
1573 expect(node.getAttribute('value')).toBe('');
1677 -
1678 - if (__DEV__) {
1679 - expect(console.error.calls.count()).toBe(1);
1680 - expect(console.error.calls.argsFor(0)[0]).toContain(
1681 - 'Invalid value for prop `value`',
1682 - );
1683 - }
1574 });
1575
1576 it('treats initial Symbol defaultValue as an empty string', function() {
@@ -1707,44 +1597,32 @@ describe('ReactDOMInput', () => {
1597
1598 describe('When given a function value', function() {
1599 it('treats initial function value as an empty string', function() {
1710 - spyOnDev(console, 'error');
1600 const container = document.createElement('div');
1712 - ReactDOM.render(
1713 - <input value={() => {}} onChange={() => {}} />,
1714 - container,
1715 - );
1601 + expect(() =>
1602 + ReactDOM.render(
1603 + <input value={() => {}} onChange={() => {}} />,
1604 + container,
1605 + ),
1606 + ).toWarnDev('Invalid value for prop `value`');
1607 const node = container.firstChild;
1608
1609 expect(node.value).toBe('');
1610 expect(node.getAttribute('value')).toBe('');
1720 -
1721 - if (__DEV__) {
1722 - expect(console.error.calls.count()).toBe(1);
1723 - expect(console.error.calls.argsFor(0)[0]).toContain(
1724 - 'Invalid value for prop `value`',
1725 - );
1726 - }
1611 });
1612
1613 it('treats updated function value as an empty string', function() {
1730 - spyOnDev(console, 'error');
1614 const container = document.createElement('div');
1615 ReactDOM.render(<input value="foo" onChange={() => {}} />, container);
1733 - ReactDOM.render(
1734 - <input value={() => {}} onChange={() => {}} />,
1735 - container,
1736 - );
1616 + expect(() =>
1617 + ReactDOM.render(
1618 + <input value={() => {}} onChange={() => {}} />,
1619 + container,
1620 + ),
1621 + ).toWarnDev('Invalid value for prop `value`');
1622 const node = container.firstChild;
1623
1624 expect(node.value).toBe('');
1625 expect(node.getAttribute('value')).toBe('');
1741 -
1742 - if (__DEV__) {
1743 - expect(console.error.calls.count()).toBe(1);
1744 - expect(console.error.calls.argsFor(0)[0]).toContain(
1745 - 'Invalid value for prop `value`',
1746 - );
1747 - }
1626 });
1627
1628 it('treats initial function defaultValue as an empty string', function() {
packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js
+25 -52
@@ -26,74 +26,47 @@ describe('ReactDOMInvalidARIAHook', () => {
26
27 describe('aria-* props', () => {
28 it('should allow valid aria-* props', () => {
29 - spyOnDev(console, 'error');
29 mountComponent({'aria-label': 'Bumble bees'});
31 - if (__DEV__) {
32 - expect(console.error.calls.count()).toBe(0);
33 - }
30 });
31 it('should warn for one invalid aria-* prop', () => {
36 - spyOnDev(console, 'error');
37 - mountComponent({'aria-badprop': 'maybe'});
38 - if (__DEV__) {
39 - expect(console.error.calls.count()).toBe(1);
40 - expect(console.error.calls.argsFor(0)[0]).toContain(
41 - 'Warning: Invalid aria prop `aria-badprop` on <div> tag. ' +
42 - 'For details, see https://fb.me/invalid-aria-prop',
43 - );
44 - }
32 + expect(() => mountComponent({'aria-badprop': 'maybe'})).toWarnDev(
33 + 'Warning: Invalid aria prop `aria-badprop` on <div> tag. ' +
34 + 'For details, see https://fb.me/invalid-aria-prop',
35 + );
36 });
37 it('should warn for many invalid aria-* props', () => {
47 - spyOnDev(console, 'error');
48 - mountComponent({
49 - 'aria-badprop': 'Very tall trees',
50 - 'aria-malprop': 'Turbulent seas',
51 - });
52 - if (__DEV__) {
53 - expect(console.error.calls.count()).toBe(1);
54 - expect(console.error.calls.argsFor(0)[0]).toContain(
55 - 'Warning: Invalid aria props `aria-badprop`, `aria-malprop` on <div> ' +
56 - 'tag. For details, see https://fb.me/invalid-aria-prop',
57 - );
58 - }
38 + expect(() =>
39 + mountComponent({
40 + 'aria-badprop': 'Very tall trees',
41 + 'aria-malprop': 'Turbulent seas',
42 + }),
43 + ).toWarnDev(
44 + 'Warning: Invalid aria props `aria-badprop`, `aria-malprop` on <div> ' +
45 + 'tag. For details, see https://fb.me/invalid-aria-prop',
46 + );
47 });
48 it('should warn for an improperly cased aria-* prop', () => {
61 - spyOnDev(console, 'error');
49 // The valid attribute name is aria-haspopup.
63 - mountComponent({'aria-hasPopup': 'true'});
64 - if (__DEV__) {
65 - expect(console.error.calls.count()).toBe(1);
66 - expect(console.error.calls.argsFor(0)[0]).toContain(
67 - 'Warning: Unknown ARIA attribute `aria-hasPopup`. ' +
68 - 'Did you mean `aria-haspopup`?',
69 - );
70 - }
50 + expect(() => mountComponent({'aria-hasPopup': 'true'})).toWarnDev(
51 + 'Warning: Unknown ARIA attribute `aria-hasPopup`. ' +
52 + 'Did you mean `aria-haspopup`?',
53 + );
54 });
55
56 it('should warn for use of recognized camel case aria attributes', () => {
74 - spyOnDev(console, 'error');
57 // The valid attribute name is aria-haspopup.
76 - mountComponent({ariaHasPopup: 'true'});
77 - if (__DEV__) {
78 - expect(console.error.calls.count()).toBe(1);
79 - expect(console.error.calls.argsFor(0)[0]).toContain(
80 - 'Warning: Invalid ARIA attribute `ariaHasPopup`. ' +
81 - 'Did you mean `aria-haspopup`?',
82 - );
83 - }
58 + expect(() => mountComponent({ariaHasPopup: 'true'})).toWarnDev(
59 + 'Warning: Invalid ARIA attribute `ariaHasPopup`. ' +
60 + 'Did you mean `aria-haspopup`?',
61 + );
62 });
63
64 it('should warn for use of unrecognized camel case aria attributes', () => {
87 - spyOnDev(console, 'error');
65 // The valid attribute name is aria-haspopup.
89 - mountComponent({ariaSomethingInvalid: 'true'});
90 - if (__DEV__) {
91 - expect(console.error.calls.count()).toBe(1);
92 - expect(console.error.calls.argsFor(0)[0]).toContain(
93 - 'Warning: Invalid ARIA attribute `ariaSomethingInvalid`. ARIA ' +
94 - 'attributes follow the pattern aria-* and must be lowercase.',
95 - );
96 - }
66 + expect(() => mountComponent({ariaSomethingInvalid: 'true'})).toWarnDev(
67 + 'Warning: Invalid ARIA attribute `ariaSomethingInvalid`. ARIA ' +
68 + 'attributes follow the pattern aria-* and must be lowercase.',
69 + );
70 });
71 });
72 });
packages/react-dom/src/__tests__/ReactDOMOption-test.js
+8 -20
@@ -10,10 +10,6 @@
10 'use strict';
11
12 describe('ReactDOMOption', () => {
13 - function normalizeCodeLocInfo(str) {
14 - return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
15 - }
16 -
13 let React;
14 let ReactDOM;
15 let ReactTestUtils;
@@ -37,24 +33,21 @@ describe('ReactDOMOption', () => {
33 });
34
35 it('should ignore and warn invalid children types', () => {
40 - spyOnDev(console, 'error');
36 const el = (
37 <option>
38 {1} <div /> {2}
39 </option>
40 );
46 - const node = ReactTestUtils.renderIntoDocument(el);
41 + let node;
42 + expect(() => {
43 + node = ReactTestUtils.renderIntoDocument(el);
44 + }).toWarnDev(
45 + '<div> cannot appear as a child of <option>.\n' +
46 + ' in div (at **)\n' +
47 + ' in option (at **)',
48 + );
49 expect(node.innerHTML).toBe('1 2');
50 ReactTestUtils.renderIntoDocument(el);
49 - if (__DEV__) {
50 - // only warn once
51 - expect(console.error.calls.count()).toBe(1);
52 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
53 - '<div> cannot appear as a child of <option>.\n' +
54 - ' in div (at **)\n' +
55 - ' in option (at **)',
56 - );
57 - }
51 });
52
53 it('should ignore null/undefined/false children without warning', () => {
@@ -66,14 +59,9 @@ describe('ReactDOMOption', () => {
59 {undefined} {2}
60 </option>
61 );
69 - spyOnDev(console, 'error');
62 stub = ReactTestUtils.renderIntoDocument(stub);
63
64 const node = ReactDOM.findDOMNode(stub);
73 -
74 - if (__DEV__) {
75 - expect(console.error.calls.count()).toBe(0);
76 - }
65 expect(node.innerHTML).toBe('1 2');
66 });
67
packages/react-dom/src/__tests__/ReactDOMRoot-test.internal.js
+1 -11
@@ -130,8 +130,6 @@ describe('ReactDOMRoot', () => {
130 ),
131 );
132
133 - spyOnDev(console, 'error');
134 -
133 // Does not hydrate by default
134 const container1 = document.createElement('div');
135 container1.innerHTML = markup;
@@ -142,9 +140,6 @@ describe('ReactDOMRoot', () => {
140 </div>,
141 );
142 flush();
145 - if (__DEV__) {
146 - expect(console.error.calls.count()).toBe(0);
147 - }
143
144 // Accepts `hydrate` option
145 const container2 = document.createElement('div');
@@ -155,15 +150,10 @@ describe('ReactDOMRoot', () => {
150 <span />
151 </div>,
152 );
158 - flush();
159 - if (__DEV__) {
160 - expect(console.error.calls.count()).toBe(1);
161 - expect(console.error.calls.argsFor(0)[0]).toMatch('Extra attributes');
162 - }
153 + expect(flush).toWarnDev('Extra attributes');
154 });
155
156 it('does not clear existing children', async () => {
166 - spyOnDev(console, 'error');
157 container.innerHTML = '<div>a</div><div>b</div>';
158 const root = ReactDOM.createRoot(container);
159 root.render(
packages/react-dom/src/__tests__/ReactDOMSelect-test.js
+45 -71
@@ -532,44 +532,37 @@ describe('ReactDOMSelect', () => {
532 });
533
534 it('should warn if value is null', () => {
535 - spyOnDev(console, 'error');
536 -
537 - ReactTestUtils.renderIntoDocument(
538 - <select value={null}>
539 - <option value="test" />
540 - </select>,
535 + expect(() =>
536 + ReactTestUtils.renderIntoDocument(
537 + <select value={null}>
538 + <option value="test" />
539 + </select>,
540 + ),
541 + ).toWarnDev(
542 + '`value` prop on `select` should not be null. ' +
543 + 'Consider using an empty string to clear the component or `undefined` ' +
544 + 'for uncontrolled components.',
545 );
542 - if (__DEV__) {
543 - expect(console.error.calls.count()).toBe(1);
544 - expect(console.error.calls.argsFor(0)[0]).toContain(
545 - '`value` prop on `select` should not be null. ' +
546 - 'Consider using an empty string to clear the component or `undefined` ' +
547 - 'for uncontrolled components.',
548 - );
549 - }
546
547 ReactTestUtils.renderIntoDocument(
548 <select value={null}>
549 <option value="test" />
550 </select>,
551 );
556 - if (__DEV__) {
557 - expect(console.error.calls.count()).toBe(1);
558 - }
552 });
553
554 it('should warn if selected is set on <option>', () => {
562 - spyOnDev(console, 'error');
563 -
564 - ReactTestUtils.renderIntoDocument(
565 - <select>
566 - <option selected={true} />
567 - <option selected={true} />
568 - </select>,
555 + expect(() =>
556 + ReactTestUtils.renderIntoDocument(
557 + <select>
558 + <option selected={true} />
559 + <option selected={true} />
560 + </select>,
561 + ),
562 + ).toWarnDev(
563 + 'Use the `defaultValue` or `value` props on <select> instead of ' +
564 + 'setting `selected` on <option>.',
565 );
570 - if (__DEV__) {
571 - expect(console.error.calls.count()).toBe(1);
572 - }
566
567 ReactTestUtils.renderIntoDocument(
568 <select>
@@ -577,41 +570,27 @@ describe('ReactDOMSelect', () => {
570 <option selected={true} />
571 </select>,
572 );
580 - if (__DEV__) {
581 - expect(console.error.calls.count()).toBe(1);
582 - expect(console.error.calls.argsFor(0)[0]).toContain(
583 - 'Use the `defaultValue` or `value` props on <select> instead of ' +
584 - 'setting `selected` on <option>.',
585 - );
586 - }
573 });
574
575 it('should warn if value is null and multiple is true', () => {
590 - spyOnDev(console, 'error');
591 - ReactTestUtils.renderIntoDocument(
592 - <select value={null} multiple={true}>
593 - <option value="test" />
594 - </select>,
576 + expect(() =>
577 + ReactTestUtils.renderIntoDocument(
578 + <select value={null} multiple={true}>
579 + <option value="test" />
580 + </select>,
581 + ),
582 + ).toWarnDev(
583 + '`value` prop on `select` should not be null. ' +
584 + 'Consider using an empty array when `multiple` is ' +
585 + 'set to `true` to clear the component or `undefined` ' +
586 + 'for uncontrolled components.',
587 );
588
597 - if (__DEV__) {
598 - expect(console.error.calls.count()).toBe(1);
599 - expect(console.error.calls.argsFor(0)[0]).toContain(
600 - '`value` prop on `select` should not be null. ' +
601 - 'Consider using an empty array when `multiple` is ' +
602 - 'set to `true` to clear the component or `undefined` ' +
603 - 'for uncontrolled components.',
604 - );
605 - }
606 -
589 ReactTestUtils.renderIntoDocument(
590 <select value={null} multiple={true}>
591 <option value="test" />
592 </select>,
593 );
612 - if (__DEV__) {
613 - expect(console.error.calls.count()).toBe(1);
614 - }
594 });
595
596 it('should refresh state on change', () => {
@@ -631,23 +610,21 @@ describe('ReactDOMSelect', () => {
610 });
611
612 it('should warn if value and defaultValue props are specified', () => {
634 - spyOnDev(console, 'error');
635 - ReactTestUtils.renderIntoDocument(
636 - <select value="giraffe" defaultValue="giraffe" readOnly={true}>
637 - <option value="monkey">A monkey!</option>
638 - <option value="giraffe">A giraffe!</option>
639 - <option value="gorilla">A gorilla!</option>
640 - </select>,
613 + expect(() =>
614 + ReactTestUtils.renderIntoDocument(
615 + <select value="giraffe" defaultValue="giraffe" readOnly={true}>
616 + <option value="monkey">A monkey!</option>
617 + <option value="giraffe">A giraffe!</option>
618 + <option value="gorilla">A gorilla!</option>
619 + </select>,
620 + ),
621 + ).toWarnDev(
622 + 'Select elements must be either controlled or uncontrolled ' +
623 + '(specify either the value prop, or the defaultValue prop, but not ' +
624 + 'both). Decide between using a controlled or uncontrolled select ' +
625 + 'element and remove one of these props. More info: ' +
626 + 'https://fb.me/react-controlled-components',
627 );
642 - if (__DEV__) {
643 - expect(console.error.calls.argsFor(0)[0]).toContain(
644 - 'Select elements must be either controlled or uncontrolled ' +
645 - '(specify either the value prop, or the defaultValue prop, but not ' +
646 - 'both). Decide between using a controlled or uncontrolled select ' +
647 - 'element and remove one of these props. More info: ' +
648 - 'https://fb.me/react-controlled-components',
649 - );
650 - }
628
629 ReactTestUtils.renderIntoDocument(
630 <select value="giraffe" defaultValue="giraffe" readOnly={true}>
@@ -656,9 +633,6 @@ describe('ReactDOMSelect', () => {
633 <option value="gorilla">A gorilla!</option>
634 </select>,
635 );
659 - if (__DEV__) {
660 - expect(console.error.calls.count()).toBe(1);
661 - }
636 });
637
638 it('should be able to safely remove select onChange', () => {
packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
+64 -72
@@ -207,8 +207,6 @@ describe('ReactDOMTextarea', () => {
207
208 expect(node.value).toBe('0');
209
210 - spyOnDev(console, 'error'); // deprecation warning for `children` content
211 -
210 ReactDOM.render(<textarea>1</textarea>, container);
211
212 expect(node.value).toBe('0');
@@ -247,15 +245,16 @@ describe('ReactDOMTextarea', () => {
245 });
246
247 it('should treat children like `defaultValue`', () => {
250 - spyOnDev(console, 'error');
251 -
248 const container = document.createElement('div');
249 let stub = <textarea>giraffe</textarea>;
254 - const node = renderTextarea(stub, container);
250 + let node;
251 +
252 + expect(() => {
253 + node = renderTextarea(stub, container);
254 + }).toWarnDev(
255 + 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
256 + );
257
256 - if (__DEV__) {
257 - expect(console.error.calls.count()).toBe(1);
258 - }
258 expect(node.value).toBe('giraffe');
259
260 // Changing children should do nothing, it functions like `defaultValue`.
@@ -301,67 +300,69 @@ describe('ReactDOMTextarea', () => {
300 });
301
302 it('should allow numbers as children', () => {
304 - spyOnDev(console, 'error');
305 - const node = renderTextarea(<textarea>{17}</textarea>);
306 - if (__DEV__) {
307 - expect(console.error.calls.count()).toBe(1);
308 - }
303 + let node;
304 + expect(() => {
305 + node = renderTextarea(<textarea>{17}</textarea>);
306 + }).toWarnDev(
307 + 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
308 + );
309 expect(node.value).toBe('17');
310 });
311
312 it('should allow booleans as children', () => {
313 - spyOnDev(console, 'error');
314 - const node = renderTextarea(<textarea>{false}</textarea>);
315 - if (__DEV__) {
316 - expect(console.error.calls.count()).toBe(1);
317 - }
313 + let node;
314 + expect(() => {
315 + node = renderTextarea(<textarea>{false}</textarea>);
316 + }).toWarnDev(
317 + 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
318 + );
319 expect(node.value).toBe('false');
320 });
321
322 it('should allow objects as children', () => {
322 - spyOnDev(console, 'error');
323 const obj = {
324 toString: function() {
325 return 'sharkswithlasers';
326 },
327 };
328 - const node = renderTextarea(<textarea>{obj}</textarea>);
329 - if (__DEV__) {
330 - expect(console.error.calls.count()).toBe(1);
331 - }
328 + let node;
329 + expect(() => {
330 + node = renderTextarea(<textarea>{obj}</textarea>);
331 + }).toWarnDev(
332 + 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
333 + );
334 expect(node.value).toBe('sharkswithlasers');
335 });
336
337 it('should throw with multiple or invalid children', () => {
336 - spyOnDev(console, 'error');
337 -
338 - expect(function() {
339 - ReactTestUtils.renderIntoDocument(
340 - <textarea>
341 - {'hello'}
342 - {'there'}
343 - </textarea>,
338 + expect(() => {
339 + expect(() =>
340 + ReactTestUtils.renderIntoDocument(
341 + <textarea>
342 + {'hello'}
343 + {'there'}
344 + </textarea>,
345 + ),
346 + ).toWarnDev(
347 + 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
348 );
349 }).toThrow();
350
347 - if (__DEV__) {
348 - expect(console.error.calls.count()).toBe(1);
349 - }
350 -
351 let node;
352 - expect(function() {
353 - node = renderTextarea(
354 - <textarea>
355 - <strong />
356 - </textarea>,
352 + expect(() => {
353 + expect(
354 + () =>
355 + (node = renderTextarea(
356 + <textarea>
357 + <strong />
358 + </textarea>,
359 + )),
360 + ).toWarnDev(
361 + 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
362 );
363 }).not.toThrow();
364
365 expect(node.value).toBe('[object Object]');
361 -
362 - if (__DEV__) {
363 - expect(console.error.calls.count()).toBe(2);
364 - }
366 });
367
368 it('should unmount', () => {
@@ -371,43 +372,34 @@ describe('ReactDOMTextarea', () => {
372 });
373
374 it('should warn if value is null', () => {
374 - spyOnDev(console, 'error');
375 -
376 - ReactTestUtils.renderIntoDocument(<textarea value={null} />);
377 - if (__DEV__) {
378 - expect(console.error.calls.argsFor(0)[0]).toContain(
379 - '`value` prop on `textarea` should not be null. ' +
380 - 'Consider using an empty string to clear the component or `undefined` ' +
381 - 'for uncontrolled components.',
382 - );
383 - }
375 + expect(() =>
376 + ReactTestUtils.renderIntoDocument(<textarea value={null} />),
377 + ).toWarnDev(
378 + '`value` prop on `textarea` should not be null. ' +
379 + 'Consider using an empty string to clear the component or `undefined` ' +
380 + 'for uncontrolled components.',
381 + );
382
383 + // No additional warnings are expected
384 ReactTestUtils.renderIntoDocument(<textarea value={null} />);
386 - if (__DEV__) {
387 - expect(console.error.calls.count()).toBe(1);
388 - }
385 });
386
387 it('should warn if value and defaultValue are specified', () => {
392 - spyOnDev(console, 'error');
393 - ReactTestUtils.renderIntoDocument(
394 - <textarea value="foo" defaultValue="bar" readOnly={true} />,
388 + expect(() =>
389 + ReactTestUtils.renderIntoDocument(
390 + <textarea value="foo" defaultValue="bar" readOnly={true} />,
391 + ),
392 + ).toWarnDev(
393 + 'Textarea elements must be either controlled or uncontrolled ' +
394 + '(specify either the value prop, or the defaultValue prop, but not ' +
395 + 'both). Decide between using a controlled or uncontrolled textarea ' +
396 + 'and remove one of these props. More info: ' +
397 + 'https://fb.me/react-controlled-components',
398 );
396 - if (__DEV__) {
397 - expect(console.error.calls.argsFor(0)[0]).toContain(
398 - 'Textarea elements must be either controlled or uncontrolled ' +
399 - '(specify either the value prop, or the defaultValue prop, but not ' +
400 - 'both). Decide between using a controlled or uncontrolled textarea ' +
401 - 'and remove one of these props. More info: ' +
402 - 'https://fb.me/react-controlled-components',
403 - );
404 - }
399
400 + // No additional warnings are expected
401 ReactTestUtils.renderIntoDocument(
402 <textarea value="foo" defaultValue="bar" readOnly={true} />,
403 );
409 - if (__DEV__) {
410 - expect(console.error.calls.count()).toBe(1);
411 - }
404 });
405 });
packages/react-dom/src/__tests__/ReactMount-test.js
+36 -74
@@ -29,7 +29,7 @@ describe('ReactMount', () => {
29 describe('unmountComponentAtNode', () => {
30 it('throws when given a non-node', () => {
31 const nodeArray = document.getElementsByTagName('div');
32 - expect(function() {
32 + expect(() => {
33 ReactDOM.unmountComponentAtNode(nodeArray);
34 }).toThrowError(
35 'unmountComponentAtNode(...): Target container is not a DOM element.',
@@ -53,22 +53,17 @@ describe('ReactMount', () => {
53 });
54
55 it('warns when given a factory', () => {
56 - spyOnDev(console, 'error');
56 class Component extends React.Component {
57 render() {
58 return <div />;
59 }
60 }
61
63 - ReactTestUtils.renderIntoDocument(Component);
64 - if (__DEV__) {
65 - expect(console.error.calls.count()).toBe(1);
66 - expect(console.error.calls.argsFor(0)[0]).toContain(
67 - 'Functions are not valid as a React child. ' +
68 - 'This may happen if you return a Component instead of <Component /> from render. ' +
69 - 'Or maybe you meant to call this function rather than return it.',
70 - );
71 - }
62 + expect(() => ReactTestUtils.renderIntoDocument(Component)).toWarnDev(
63 + 'Functions are not valid as a React child. ' +
64 + 'This may happen if you return a Component instead of <Component /> from render. ' +
65 + 'Or maybe you meant to call this function rather than return it.',
66 + );
67 });
68
69 it('should render different components in same root', () => {
@@ -129,54 +124,36 @@ describe('ReactMount', () => {
124 const container = document.createElement('container');
125 container.innerHTML = ReactDOMServer.renderToString(<div />) + ' ';
126
132 - spyOnDev(console, 'error');
133 - ReactDOM.hydrate(<div />, container);
134 - if (__DEV__) {
135 - expect(console.error.calls.count()).toBe(1);
136 - expect(console.error.calls.argsFor(0)[0]).toContain(
137 - 'Did not expect server HTML to contain the text node " " in <container>.',
138 - );
139 - }
127 + expect(() => ReactDOM.hydrate(<div />, container)).toWarnDev(
128 + 'Did not expect server HTML to contain the text node " " in <container>.',
129 + );
130 });
131
132 it('should warn if mounting into right padded rendered markup', () => {
133 const container = document.createElement('container');
134 container.innerHTML = ' ' + ReactDOMServer.renderToString(<div />);
135
146 - spyOnDev(console, 'error');
147 - ReactDOM.hydrate(<div />, container);
148 - if (__DEV__) {
149 - expect(console.error.calls.count()).toBe(1);
150 - expect(console.error.calls.argsFor(0)[0]).toContain(
151 - 'Did not expect server HTML to contain the text node " " in <container>.',
152 - );
153 - }
136 + expect(() => ReactDOM.hydrate(<div />, container)).toWarnDev(
137 + 'Did not expect server HTML to contain the text node " " in <container>.',
138 + );
139 });
140
141 it('should not warn if mounting into non-empty node', () => {
142 const container = document.createElement('container');
143 container.innerHTML = '<div></div>';
144
160 - spyOnDev(console, 'error');
145 ReactDOM.render(<div />, container);
162 - if (__DEV__) {
163 - expect(console.error.calls.count()).toBe(0);
164 - }
146 });
147
148 it('should warn when mounting into document.body', () => {
149 const iFrame = document.createElement('iframe');
150 document.body.appendChild(iFrame);
170 - spyOnDev(console, 'error');
171 -
172 - ReactDOM.render(<div />, iFrame.contentDocument.body);
151
174 - if (__DEV__) {
175 - expect(console.error.calls.count()).toBe(1);
176 - expect(console.error.calls.argsFor(0)[0]).toContain(
177 - 'Rendering components directly into document.body is discouraged',
178 - );
179 - }
152 + expect(() =>
153 + ReactDOM.render(<div />, iFrame.contentDocument.body),
154 + ).toWarnDev(
155 + 'Rendering components directly into document.body is discouraged',
156 + );
157 });
158
159 it('should account for escaping on a checksum mismatch', () => {
@@ -186,18 +163,15 @@ describe('ReactMount', () => {
163 );
164 div.innerHTML = markup;
165
189 - spyOnDev(console, 'error');
190 - ReactDOM.hydrate(
191 - <div>This markup contains an nbsp entity: &nbsp; client text</div>,
192 - div,
166 + expect(() =>
167 + ReactDOM.hydrate(
168 + <div>This markup contains an nbsp entity: &nbsp; client text</div>,
169 + div,
170 + ),
171 + ).toWarnDev(
172 + 'Server: "This markup contains an nbsp entity:   server text" ' +
173 + 'Client: "This markup contains an nbsp entity:   client text"',
174 );
194 - if (__DEV__) {
195 - expect(console.error.calls.count()).toBe(1);
196 - expect(console.error.calls.argsFor(0)[0]).toContain(
197 - 'Server: "This markup contains an nbsp entity:   server text" ' +
198 - 'Client: "This markup contains an nbsp entity:   client text"',
199 - );
200 - }
175 });
176
177 it('should warn if render removes React-rendered children', () => {
@@ -216,18 +190,14 @@ describe('ReactMount', () => {
190 ReactDOM.render(<Component />, container);
191
192 // Test that blasting away children throws a warning
219 - spyOnDev(console, 'error');
193 const rootNode = container.firstChild;
221 - ReactDOM.render(<span />, rootNode);
222 - if (__DEV__) {
223 - expect(console.error.calls.count()).toBe(1);
224 - expect(console.error.calls.argsFor(0)[0]).toBe(
225 - 'Warning: render(...): Replacing React-rendered children with a new ' +
226 - 'root component. If you intended to update the children of this node, ' +
227 - 'you should instead have the existing children update their state and ' +
228 - 'render the new components instead of calling ReactDOM.render.',
229 - );
230 - }
194 +
195 + expect(() => ReactDOM.render(<span />, rootNode)).toWarnDev(
196 + 'Warning: render(...): Replacing React-rendered children with a new ' +
197 + 'root component. If you intended to update the children of this node, ' +
198 + 'you should instead have the existing children update their state and ' +
199 + 'render the new components instead of calling ReactDOM.render.',
200 + );
201 });
202
203 it('should warn if the unmounted node was rendered by another copy of React', () => {
@@ -249,21 +219,13 @@ describe('ReactMount', () => {
219 // Make sure ReactDOM and ReactDOMOther are different copies
220 expect(ReactDOM).not.toEqual(ReactDOMOther);
221
252 - spyOnDev(console, 'error');
253 - ReactDOMOther.unmountComponentAtNode(container);
254 - if (__DEV__) {
255 - expect(console.error.calls.count()).toBe(1);
256 - expect(console.error.calls.argsFor(0)[0]).toBe(
257 - "Warning: unmountComponentAtNode(): The node you're attempting to unmount " +
258 - 'was rendered by another copy of React.',
259 - );
260 - }
222 + expect(() => ReactDOMOther.unmountComponentAtNode(container)).toWarnDev(
223 + "Warning: unmountComponentAtNode(): The node you're attempting to unmount " +
224 + 'was rendered by another copy of React.',
225 + );
226
227 // Don't throw a warning if the correct React copy unmounts the node
228 ReactDOM.unmountComponentAtNode(container);
264 - if (__DEV__) {
265 - expect(console.error.calls.count()).toBe(1);
266 - }
229 });
230
231 it('passes the correct callback context', () => {
packages/react-dom/src/__tests__/ReactMountDestruction-test.js
+12 -22
@@ -50,17 +50,12 @@ describe('ReactMount', () => {
50
51 // Test that unmounting at a root node gives a helpful warning
52 const rootDiv = mainContainerDiv.firstChild;
53 - spyOnDev(console, 'error');
54 - ReactDOM.unmountComponentAtNode(rootDiv);
55 - if (__DEV__) {
56 - expect(console.error.calls.count()).toBe(1);
57 - expect(console.error.calls.argsFor(0)[0]).toBe(
58 - "Warning: unmountComponentAtNode(): The node you're attempting to " +
59 - 'unmount was rendered by React and is not a top-level container. You ' +
60 - 'may have accidentally passed in a React root node instead of its ' +
61 - 'container.',
62 - );
63 - }
53 + expect(() => ReactDOM.unmountComponentAtNode(rootDiv)).toWarnDev(
54 + "Warning: unmountComponentAtNode(): The node you're attempting to " +
55 + 'unmount was rendered by React and is not a top-level container. You ' +
56 + 'may have accidentally passed in a React root node instead of its ' +
57 + 'container.',
58 + );
59 });
60
61 it('should warn when unmounting a non-container, non-root node', () => {
@@ -77,16 +72,11 @@ describe('ReactMount', () => {
72
73 // Test that unmounting at a non-root node gives a different warning
74 const nonRootDiv = mainContainerDiv.firstChild.firstChild;
80 - spyOnDev(console, 'error');
81 - ReactDOM.unmountComponentAtNode(nonRootDiv);
82 - if (__DEV__) {
83 - expect(console.error.calls.count()).toBe(1);
84 - expect(console.error.calls.argsFor(0)[0]).toBe(
85 - "Warning: unmountComponentAtNode(): The node you're attempting to " +
86 - 'unmount was rendered by React and is not a top-level container. ' +
87 - 'Instead, have the parent component update its state and rerender in ' +
88 - 'order to remove this component.',
89 - );
90 - }
75 + expect(() => ReactDOM.unmountComponentAtNode(nonRootDiv)).toWarnDev(
76 + "Warning: unmountComponentAtNode(): The node you're attempting to " +
77 + 'unmount was rendered by React and is not a top-level container. ' +
78 + 'Instead, have the parent component update its state and rerender in ' +
79 + 'order to remove this component.',
80 + );
81 });
82 });
packages/react-dom/src/__tests__/ReactMultiChild-test.js
+37 -62
@@ -10,10 +10,6 @@
10 'use strict';
11
12 describe('ReactMultiChild', () => {
13 - function normalizeCodeLocInfo(str) {
14 - return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
15 - }
16 -
13 let React;
14 let ReactDOM;
15
@@ -183,8 +179,6 @@ describe('ReactMultiChild', () => {
179 });
180
181 it('should warn for duplicated array keys with component stack info', () => {
186 - spyOnDev(console, 'error');
187 -
182 const container = document.createElement('div');
183
184 class WrapperComponent extends React.Component {
@@ -205,32 +199,25 @@ describe('ReactMultiChild', () => {
199
200 ReactDOM.render(<Parent>{[<div key="1" />]}</Parent>, container);
201
208 - ReactDOM.render(
209 - <Parent>{[<div key="1" />, <div key="1" />]}</Parent>,
210 - container,
211 - );
212 -
213 - if (__DEV__) {
214 - expect(console.error.calls.count()).toBe(1);
215 - expect(
216 - normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
217 - ).toContain(
218 - 'Encountered two children with the same key, `1`. ' +
219 - 'Keys should be unique so that components maintain their identity ' +
220 - 'across updates. Non-unique keys may cause children to be ' +
221 - 'duplicated and/or omitted — the behavior is unsupported and ' +
222 - 'could change in a future version.',
202 + expect(() =>
203 + ReactDOM.render(
204 + <Parent>{[<div key="1" />, <div key="1" />]}</Parent>,
205 + container,
206 + ),
207 + ).toWarnDev(
208 + 'Encountered two children with the same key, `1`. ' +
209 + 'Keys should be unique so that components maintain their identity ' +
210 + 'across updates. Non-unique keys may cause children to be ' +
211 + 'duplicated and/or omitted — the behavior is unsupported and ' +
212 + 'could change in a future version.',
213 + ' in div (at **)\n' +
214 + ' in WrapperComponent (at **)\n' +
215 ' in div (at **)\n' +
224 - ' in WrapperComponent (at **)\n' +
225 - ' in div (at **)\n' +
226 - ' in Parent (at **)',
227 - );
228 - }
216 + ' in Parent (at **)',
217 + );
218 });
219
220 it('should warn for duplicated iterable keys with component stack info', () => {
232 - spyOnDev(console, 'error');
233 -
221 const container = document.createElement('div');
222
223 class WrapperComponent extends React.Component {
@@ -272,54 +259,42 @@ describe('ReactMultiChild', () => {
259 container,
260 );
261
275 - ReactDOM.render(
276 - <Parent>{createIterable([<div key="1" />, <div key="1" />])}</Parent>,
277 - container,
278 - );
279 -
280 - if (__DEV__) {
281 - expect(console.error.calls.count()).toBe(1);
282 - expect(
283 - normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
284 - ).toContain(
285 - 'Encountered two children with the same key, `1`. ' +
286 - 'Keys should be unique so that components maintain their identity ' +
287 - 'across updates. Non-unique keys may cause children to be ' +
288 - 'duplicated and/or omitted — the behavior is unsupported and ' +
289 - 'could change in a future version.',
262 + expect(() =>
263 + ReactDOM.render(
264 + <Parent>{createIterable([<div key="1" />, <div key="1" />])}</Parent>,
265 + container,
266 + ),
267 + ).toWarnDev(
268 + 'Encountered two children with the same key, `1`. ' +
269 + 'Keys should be unique so that components maintain their identity ' +
270 + 'across updates. Non-unique keys may cause children to be ' +
271 + 'duplicated and/or omitted — the behavior is unsupported and ' +
272 + 'could change in a future version.',
273 + ' in div (at **)\n' +
274 + ' in WrapperComponent (at **)\n' +
275 ' in div (at **)\n' +
291 - ' in WrapperComponent (at **)\n' +
292 - ' in div (at **)\n' +
293 - ' in Parent (at **)',
294 - );
295 - }
276 + ' in Parent (at **)',
277 + );
278 });
279 });
280
281 it('should warn for using maps as children with owner info', () => {
300 - spyOnDev(console, 'error');
282 class Parent extends React.Component {
283 render() {
284 return <div>{new Map([['foo', 0], ['bar', 1]])}</div>;
285 }
286 }
287 const container = document.createElement('div');
307 - ReactDOM.render(<Parent />, container);
308 - if (__DEV__) {
309 - expect(console.error.calls.count()).toBe(1);
310 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
311 - 'Warning: Using Maps as children is unsupported and will likely yield ' +
312 - 'unexpected results. Convert it to a sequence/iterable of keyed ' +
313 - 'ReactElements instead.\n' +
314 - ' in div (at **)\n' +
315 - ' in Parent (at **)',
316 - );
317 - }
288 + expect(() => ReactDOM.render(<Parent />, container)).toWarnDev(
289 + 'Warning: Using Maps as children is unsupported and will likely yield ' +
290 + 'unexpected results. Convert it to a sequence/iterable of keyed ' +
291 + 'ReactElements instead.\n' +
292 + ' in div (at **)\n' +
293 + ' in Parent (at **)',
294 + );
295 });
296
297 it('should reorder bailed-out children', () => {
321 - spyOnDev(console, 'error');
322 -
298 class LetterInner extends React.Component {
299 render() {
300 return <div>{this.props.char}</div>;