/** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails react-core */ 'use strict'; var React; var ReactDOM; var ReactDOMServer; var ReactTestUtils; describe('ReactComponent', () => { function normalizeCodeLocInfo(str) { return str && str.replace(/\(at .+?:\d+\)/g, '(at **)'); } beforeEach(() => { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); ReactTestUtils = require('react-dom/test-utils'); }); it('should throw on invalid render targets', () => { var container = document.createElement('div'); // jQuery objects are basically arrays; people often pass them in by mistake expect(function() { ReactDOM.render(
, [container]); }).toThrowError(/Target container is not a DOM element./); expect(function() { ReactDOM.render(
, null); }).toThrowError(/Target container is not a DOM element./); }); it('should throw when supplying a ref outside of render method', () => { var instance =
; expect(function() { instance = ReactTestUtils.renderIntoDocument(instance); }).toThrow(); }); it('should warn when children are mutated during render', () => { spyOn(console, 'error'); function Wrapper(props) { props.children[1] =

; // Mutation is illegal return

{props.children}
; } expect(() => { ReactTestUtils.renderIntoDocument( , ); }).toThrowError(/Cannot assign to read only property.*/); }); it('should warn when children are mutated during update', () => { spyOn(console, 'error'); class Wrapper extends React.Component { componentDidMount() { this.props.children[1] =

; // Mutation is illegal this.forceUpdate(); } render() { return

{this.props.children}
; } } expect(() => { ReactTestUtils.renderIntoDocument( , ); }).toThrowError(/Cannot assign to read only property.*/); }); it('should support refs on owned components', () => { var innerObj = {}; var outerObj = {}; class Wrapper extends React.Component { getObject = () => { return this.props.object; }; render() { return
{this.props.children}
; } } class Component extends React.Component { render() { var inner = ; var outer = {inner}; return outer; } componentDidMount() { expect(this.refs.inner.getObject()).toEqual(innerObj); expect(this.refs.outer.getObject()).toEqual(outerObj); } } ReactTestUtils.renderIntoDocument(); }); it('should not have refs on unmounted components', () => { class Parent extends React.Component { render() { return
; } componentDidMount() { expect(this.refs && this.refs.test).toEqual(undefined); } } class Child extends React.Component { render() { return
; } } ReactTestUtils.renderIntoDocument(} />); }); it('should support new-style refs', () => { var innerObj = {}; var outerObj = {}; class Wrapper extends React.Component { getObject = () => { return this.props.object; }; render() { return
{this.props.children}
; } } var mounted = false; class Component extends React.Component { render() { var inner = ( (this.innerRef = c)} /> ); var outer = ( (this.outerRef = c)}> {inner} ); return outer; } componentDidMount() { expect(this.innerRef.getObject()).toEqual(innerObj); expect(this.outerRef.getObject()).toEqual(outerObj); mounted = true; } } ReactTestUtils.renderIntoDocument(); expect(mounted).toBe(true); }); it('should support new-style refs with mixed-up owners', () => { class Wrapper extends React.Component { getTitle = () => { return this.props.title; }; render() { return this.props.getContent(); } } var mounted = false; class Component extends React.Component { getInner = () => { // (With old-style refs, it's impossible to get a ref to this div // because Wrapper is the current owner when this function is called.) return
(this.innerRef = c)} />; }; render() { return ( (this.wrapperRef = c)} getContent={this.getInner} /> ); } componentDidMount() { // Check .props.title to make sure we got the right elements back expect(this.wrapperRef.getTitle()).toBe('wrapper'); expect(ReactDOM.findDOMNode(this.innerRef).className).toBe('inner'); mounted = true; } } ReactTestUtils.renderIntoDocument(); expect(mounted).toBe(true); }); it('should call refs at the correct time', () => { var log = []; class Inner extends React.Component { render() { log.push(`inner ${this.props.id} render`); return
; } componentDidMount() { log.push(`inner ${this.props.id} componentDidMount`); } componentDidUpdate() { log.push(`inner ${this.props.id} componentDidUpdate`); } componentWillUnmount() { log.push(`inner ${this.props.id} componentWillUnmount`); } } class Outer extends React.Component { render() { return (
{ log.push(`ref 1 got ${c ? `instance ${c.props.id}` : 'null'}`); }} /> { log.push(`ref 2 got ${c ? `instance ${c.props.id}` : 'null'}`); }} />
); } componentDidMount() { log.push('outer componentDidMount'); } componentDidUpdate() { log.push('outer componentDidUpdate'); } componentWillUnmount() { log.push('outer componentWillUnmount'); } } // mount, update, unmount var el = document.createElement('div'); log.push('start mount'); ReactDOM.render(, el); log.push('start update'); ReactDOM.render(, el); log.push('start unmount'); ReactDOM.unmountComponentAtNode(el); /* eslint-disable indent */ expect(log).toEqual([ 'start mount', 'inner 1 render', 'inner 2 render', 'inner 1 componentDidMount', 'ref 1 got instance 1', 'inner 2 componentDidMount', 'ref 2 got instance 2', 'outer componentDidMount', 'start update', // Previous (equivalent) refs get cleared // Fiber renders first, resets refs later 'inner 1 render', 'inner 2 render', 'ref 1 got null', 'ref 2 got null', 'inner 1 componentDidUpdate', 'ref 1 got instance 1', 'inner 2 componentDidUpdate', 'ref 2 got instance 2', 'outer componentDidUpdate', 'start unmount', 'outer componentWillUnmount', 'ref 1 got null', 'inner 1 componentWillUnmount', 'ref 2 got null', 'inner 2 componentWillUnmount', ]); /* eslint-enable indent */ }); it('fires the callback after a component is rendered', () => { var callback = jest.fn(); var container = document.createElement('div'); ReactDOM.render(
, container, callback); expect(callback.mock.calls.length).toBe(1); ReactDOM.render(
, container, callback); expect(callback.mock.calls.length).toBe(2); ReactDOM.render(, container, callback); expect(callback.mock.calls.length).toBe(3); }); it('throws usefully when rendering badly-typed elements', () => { spyOn(console, 'error'); var X = undefined; expect(() => ReactTestUtils.renderIntoDocument()).toThrowError( 'Element type is invalid: expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: undefined. ' + "You likely forgot to export your component from the file it's " + 'defined in.', ); var Y = null; expect(() => ReactTestUtils.renderIntoDocument()).toThrowError( 'Element type is invalid: expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: null.', ); // One warning for each element creation expectDev(console.error.calls.count()).toBe(2); }); it('includes owner name in the error about badly-typed elements', () => { spyOn(console, 'error'); var X = undefined; function Indirection(props) { return
{props.children}
; } function Bar() { return ; } function Foo() { return ; } expect(() => ReactTestUtils.renderIntoDocument()).toThrowError( 'Element type is invalid: expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: undefined. ' + "You likely forgot to export your component from the file it's " + 'defined in.\n\nCheck the render method of `Bar`.', ); // One warning for each element creation expectDev(console.error.calls.count()).toBe(1); }); it('throws if a plain object is used as a child', () => { var children = { x: , y: , z: , }; var element =
{[children]}
; var container = document.createElement('div'); var ex; try { ReactDOM.render(element, container); } catch (e) { ex = e; } expect(ex).toBeDefined(); expect(normalizeCodeLocInfo(ex.message)).toBe( 'Objects are not valid as a React child (found: object with keys ' + '{x, y, z}). If you meant to render a collection of children, use ' + 'an array instead.' + '\n in div (at **)', ); }); it('throws if a plain object even if it is in an owner', () => { class Foo extends React.Component { render() { var children = { a: , b: , c: , }; return
{[children]}
; } } var container = document.createElement('div'); var ex; try { ReactDOM.render(, container); } catch (e) { ex = e; } expect(ex).toBeDefined(); expect(normalizeCodeLocInfo(ex.message)).toBe( 'Objects are not valid as a React child (found: object with keys ' + '{a, b, c}). If you meant to render a collection of children, use ' + 'an array instead.\n' + ' in div (at **)\n' + ' in Foo (at **)', ); }); it('throws if a plain object is used as a child when using SSR', async () => { var children = { x: , y: , z: , }; var element =
{[children]}
; var ex; try { ReactDOMServer.renderToString(element); } catch (e) { ex = e; } expect(ex).toBeDefined(); expect(normalizeCodeLocInfo(ex.message)).toBe( 'Objects are not valid as a React child (found: object with keys ' + '{x, y, z}). If you meant to render a collection of children, use ' + 'an array instead.' + '\n in div (at **)', ); }); it('throws if a plain object even if it is in an owner when using SSR', async () => { class Foo extends React.Component { render() { var children = { a: , b: , c: , }; return
{[children]}
; } } var container = document.createElement('div'); var ex; try { ReactDOMServer.renderToString(, container); } catch (e) { ex = e; } expect(ex).toBeDefined(); expect(normalizeCodeLocInfo(ex.message)).toBe( 'Objects are not valid as a React child (found: object with keys ' + '{a, b, c}). If you meant to render a collection of children, use ' + 'an array instead.\n' + ' in div (at **)\n' + ' in Foo (at **)', ); }); describe('with new features', () => { it('warns on function as a return value from a function', () => { function Foo() { return Foo; } spyOn(console, 'error'); var container = document.createElement('div'); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: Functions are not valid as a React child. This may happen if ' + 'you return a Component instead of from render. ' + 'Or maybe you meant to call this function rather than return it.\n' + ' in Foo (at **)', ); }); it('warns on function as a return value from a class', () => { class Foo extends React.Component { render() { return Foo; } } spyOn(console, 'error'); var container = document.createElement('div'); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: Functions are not valid as a React child. This may happen if ' + 'you return a Component instead of from render. ' + 'Or maybe you meant to call this function rather than return it.\n' + ' in Foo (at **)', ); }); it('warns on function as a child to host component', () => { function Foo() { return
{Foo}
; } spyOn(console, 'error'); var container = document.createElement('div'); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: Functions are not valid as a React child. This may happen if ' + 'you return a Component instead of from render. ' + 'Or maybe you meant to call this function rather than return it.\n' + ' in span (at **)\n' + ' in div (at **)\n' + ' in Foo (at **)', ); }); it('does not warn for function-as-a-child that gets resolved', () => { function Bar(props) { return props.children(); } function Foo() { return {() => 'Hello'}; } spyOn(console, 'error'); var container = document.createElement('div'); ReactDOM.render(, container); expect(container.innerHTML).toBe('Hello'); expectDev(console.error.calls.count()).toBe(0); }); it('deduplicates function type warnings based on component type', () => { spyOn(console, 'error'); class Foo extends React.PureComponent { constructor() { super(); this.state = {type: 'mushrooms'}; } render() { return (
{Foo}{Foo} {Foo}{Foo}
); } } var container = document.createElement('div'); var component = ReactDOM.render(, container); component.setState({type: 'portobello mushrooms'}); expectDev(console.error.calls.count()).toBe(2); expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: Functions are not valid as a React child. This may happen if ' + 'you return a Component instead of from render. ' + 'Or maybe you meant to call this function rather than return it.\n' + ' in div (at **)\n' + ' in Foo (at **)', ); expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( 'Warning: Functions are not valid as a React child. This may happen if ' + 'you return a Component instead of from render. ' + 'Or maybe you meant to call this function rather than return it.\n' + ' in span (at **)\n' + ' in div (at **)\n' + ' in Foo (at **)', ); }); }); });