Back to Repositories

Testing React JSX Element Creation and Props Workflow in React

This test suite validates core React JSX functionality in React 14, covering element creation, prop handling, and ref management. It ensures proper JSX transformation and runtime behavior through comprehensive unit tests.

Test Coverage Overview

The test suite provides extensive coverage of React JSX element creation and manipulation. It verifies key functionality like element immutability, prop handling, children merging, and ref management. Edge cases tested include null/undefined handling, key validation, and PropType checking.

  • Element creation and validation
  • Props merging and transformation
  • Children prop handling
  • Ref and key management
  • PropTypes validation

Implementation Analysis

The testing approach uses Jest’s unit testing framework with ReactTestUtils for component rendering and validation. Tests follow a pattern of creating elements, rendering them, and asserting on their properties and behavior.

Key patterns include:
  • Direct element property validation
  • Component rendering with ReactTestUtils
  • Mock implementations for jsx-runtime
  • Error case validation with toErrorDev

Technical Details

Testing tools and setup:

  • Jest as the test runner
  • ReactTestUtils for component testing
  • ReactDOM for rendering
  • Mock implementations for jsx-runtime and jsx-dev-runtime
  • PropTypes for type validation

Best Practices Demonstrated

The test suite exemplifies high-quality React testing practices through comprehensive coverage and proper isolation. It demonstrates proper testing of component rendering, prop handling, and error cases.

  • Isolated component testing
  • Comprehensive prop validation
  • Error boundary testing
  • Mock implementation usage
  • Clear test case organization

facebook/react

fixtures/legacy-jsx-runtimes/react-14/react-14.test.js

            
/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @emails react-core
 */

// These tests are based on ReactJSXElement-test,
// ReactJSXElementValidator-test, ReactComponent-test,
// and ReactElementJSX-test.

jest.mock('react/jsx-runtime', () => require('./jsx-runtime'), {virtual: true});
jest.mock('react/jsx-dev-runtime', () => require('./jsx-dev-runtime'), {
  virtual: true,
});

let React = require('react');
let ReactDOM = require('react-dom');
let ReactTestUtils = {
  renderIntoDocument(el) {
    const container = document.createElement('div');
    return ReactDOM.render(el, container);
  },
};
let PropTypes = React.PropTypes;
let Component = class Component extends React.Component {
  render() {
    return <div />;
  }
};
let RequiredPropComponent = class extends React.Component {
  render() {
    return <span>{this.props.prop}</span>;
  }
};
RequiredPropComponent.displayName = 'RequiredPropComponent';
RequiredPropComponent.propTypes = {prop: PropTypes.string.isRequired};

it('works', () => {
  const container = document.createElement('div');
  ReactDOM.render(<h1>hello</h1>, container);
  expect(container.textContent).toBe('hello');
});

it('returns a complete element according to spec', () => {
  const element = <Component />;
  expect(element.type).toBe(Component);
  expect(element.key).toBe(null);
  expect(element.ref).toBe(null);
  const expectation = {};
  Object.freeze(expectation);
  expect(element.props).toEqual(expectation);
});

it('allows a lower-case to be passed as the string type', () => {
  const element = <div />;
  expect(element.type).toBe('div');
  expect(element.key).toBe(null);
  expect(element.ref).toBe(null);
  const expectation = {};
  Object.freeze(expectation);
  expect(element.props).toEqual(expectation);
});

it('allows a string to be passed as the type', () => {
  const TagName = 'div';
  const element = <TagName />;
  expect(element.type).toBe('div');
  expect(element.key).toBe(null);
  expect(element.ref).toBe(null);
  const expectation = {};
  Object.freeze(expectation);
  expect(element.props).toEqual(expectation);
});

it('returns an immutable element', () => {
  const element = <Component />;
  if (process.env.NODE_ENV === 'development') {
    expect(() => (element.type = 'div')).toThrow();
  } else {
    expect(() => (element.type = 'div')).not.toThrow();
  }
});

it('does not reuse the object that is spread into props', () => {
  const config = {foo: 1};
  const element = <Component {...config} />;
  expect(element.props.foo).toBe(1);
  config.foo = 2;
  expect(element.props.foo).toBe(1);
});

it('extracts key and ref from the rest of the props', () => {
  const element = <Component key="12" ref="34" foo="56" />;
  expect(element.type).toBe(Component);
  expect(element.key).toBe('12');
  expect(element.ref).toBe('34');
  const expectation = {foo: '56'};
  Object.freeze(expectation);
  expect(element.props).toEqual(expectation);
});

it('coerces the key to a string', () => {
  const element = <Component key={12} foo="56" />;
  expect(element.type).toBe(Component);
  expect(element.key).toBe('12');
  expect(element.ref).toBe(null);
  const expectation = {foo: '56'};
  Object.freeze(expectation);
  expect(element.props).toEqual(expectation);
});

it('merges JSX children onto the children prop', () => {
  const a = 1;
  const element = <Component children="text">{a}</Component>;
  expect(element.props.children).toBe(a);
});

it('does not override children if no JSX children are provided', () => {
  const element = <Component children="text" />;
  expect(element.props.children).toBe('text');
});

it('overrides children if null is provided as a JSX child', () => {
  const element = <Component children="text">{null}</Component>;
  expect(element.props.children).toBe(null);
});

it('overrides children if undefined is provided as an argument', () => {
  const element = <Component children="text">{undefined}</Component>;
  expect(element.props.children).toBe(undefined);

  const element2 = React.cloneElement(
    <Component children="text" />,
    {},
    undefined
  );
  expect(element2.props.children).toBe(undefined);
});

it('merges JSX children onto the children prop in an array', () => {
  const a = 1;
  const b = 2;
  const c = 3;
  const element = (
    <Component>
      {a}
      {b}
      {c}
    </Component>
  );
  expect(element.props.children).toEqual([1, 2, 3]);
});

it('allows static methods to be called using the type property', () => {
  class StaticMethodComponent {
    static someStaticMethod() {
      return 'someReturnValue';
    }
    render() {
      return <div />;
    }
  }

  const element = <StaticMethodComponent />;
  expect(element.type.someStaticMethod()).toBe('someReturnValue');
});

it('identifies valid elements', () => {
  expect(React.isValidElement(<div />)).toEqual(true);
  expect(React.isValidElement(<Component />)).toEqual(true);

  expect(React.isValidElement(null)).toEqual(false);
  expect(React.isValidElement(true)).toEqual(false);
  expect(React.isValidElement({})).toEqual(false);
  expect(React.isValidElement('string')).toEqual(false);
  expect(React.isValidElement(Component)).toEqual(false);
  expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
});

it('is indistinguishable from a plain object', () => {
  const element = <div className="foo" />;
  const object = {};
  expect(element.constructor).toBe(object.constructor);
});

it('should use default prop value when removing a prop', () => {
  Component.defaultProps = {fruit: 'persimmon'};

  const container = document.createElement('div');
  const instance = ReactDOM.render(<Component fruit="mango" />, container);
  expect(instance.props.fruit).toBe('mango');

  ReactDOM.render(<Component />, container);
  expect(instance.props.fruit).toBe('persimmon');
});

it('should normalize props with default values', () => {
  class NormalizingComponent extends React.Component {
    render() {
      return <span>{this.props.prop}</span>;
    }
  }
  NormalizingComponent.defaultProps = {prop: 'testKey'};

  const container = document.createElement('div');
  const instance = ReactDOM.render(<NormalizingComponent />, container);
  expect(instance.props.prop).toBe('testKey');

  const inst2 = ReactDOM.render(
    <NormalizingComponent prop={null} />,
    container
  );
  expect(inst2.props.prop).toBe(null);
});

it('warns for keys for arrays of elements in children position', () => {
  expect(() =>
    ReactTestUtils.renderIntoDocument(
      <Component>{[<Component />, <Component />]}</Component>
    )
  ).toErrorDev('Each child in a list should have a unique "key" prop.', {
    withoutStack: true,
  });
});

it('warns for keys for arrays of elements with owner info', () => {
  class InnerComponent extends React.Component {
    render() {
      return <Component>{this.props.childSet}</Component>;
    }
  }

  class ComponentWrapper extends React.Component {
    render() {
      return <InnerComponent childSet={[<Component />, <Component />]} />;
    }
  }

  expect(() =>
    ReactTestUtils.renderIntoDocument(<ComponentWrapper />)
  ).toErrorDev(
    'Each child in a list should have a unique "key" prop.' +
      '\n\nCheck the render method of `InnerComponent`. ' +
      'It was passed a child from ComponentWrapper. ',
    {withoutStack: true}
  );
});

it('does not warn for arrays of elements with keys', () => {
  ReactTestUtils.renderIntoDocument(
    <Component>{[<Component key="#1" />, <Component key="#2" />]}</Component>
  );
});

it('does not warn for iterable elements with keys', () => {
  const iterable = {
    '@@iterator': function () {
      let i = 0;
      return {
        next: function () {
          const done = ++i > 2;
          return {
            value: done ? undefined : <Component key={'#' + i} />,
            done: done,
          };
        },
      };
    },
  };

  ReactTestUtils.renderIntoDocument(<Component>{iterable}</Component>);
});

it('does not warn for numeric keys in entry iterable as a child', () => {
  const iterable = {
    '@@iterator': function () {
      let i = 0;
      return {
        next: function () {
          const done = ++i > 2;
          return {value: done ? undefined : [i, <Component />], done: done};
        },
      };
    },
  };
  iterable.entries = iterable['@@iterator'];

  ReactTestUtils.renderIntoDocument(<Component>{iterable}</Component>);
});

it('does not warn when the element is directly as children', () => {
  ReactTestUtils.renderIntoDocument(
    <Component>
      <Component />
      <Component />
    </Component>
  );
});

it('does not warn when the child array contains non-elements', () => {
  void (<Component>{[{}, {}]}</Component>);
});

it('should give context for PropType errors in nested components.', () => {
  // In this test, we're making sure that if a proptype error is found in a
  // component, we give a small hint as to which parent instantiated that
  // component as per warnings about key usage in ReactElementValidator.
  function MyComp({color}) {
    return <div>My color is {color}</div>;
  }
  MyComp.propTypes = {
    color: PropTypes.string,
  };
  class ParentComp extends React.Component {
    render() {
      return <MyComp color={123} />;
    }
  }
  expect(() => ReactTestUtils.renderIntoDocument(<ParentComp />)).toErrorDev(
    'Warning: Failed prop type: ' +
      'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
      'expected `string`.',
    {withoutStack: true}
  );
});

it('gives a helpful error when passing null, undefined, or boolean', () => {
  const Undefined = undefined;
  const Null = null;
  const True = true;
  const Div = 'div';
  expect(() => void (<Undefined />)).toErrorDev(
    'Warning: React.jsx: type is invalid -- expected a string ' +
      '(for built-in components) or a class/function (for composite ' +
      'components) but got: undefined. You likely forgot to export your ' +
      "component from the file it's defined in, or you might have mixed up " +
      'default and named imports.' +
      (process.env.BABEL_ENV === 'development'
        ? '\n\nCheck your code at **.'
        : ''),
    {withoutStack: true}
  );
  expect(() => void (<Null />)).toErrorDev(
    'Warning: React.jsx: type is invalid -- expected a string ' +
      '(for built-in components) or a class/function (for composite ' +
      'components) but got: null.' +
      (process.env.BABEL_ENV === 'development'
        ? '\n\nCheck your code at **.'
        : ''),
    {withoutStack: true}
  );
  expect(() => void (<True />)).toErrorDev(
    'Warning: React.jsx: type is invalid -- expected a string ' +
      '(for built-in components) or a class/function (for composite ' +
      'components) but got: boolean.' +
      (process.env.BABEL_ENV === 'development'
        ? '\n\nCheck your code at **.'
        : ''),
    {withoutStack: true}
  );
  // No error expected
  void (<Div />);
});

it('should check default prop values', () => {
  RequiredPropComponent.defaultProps = {prop: null};

  expect(() =>
    ReactTestUtils.renderIntoDocument(<RequiredPropComponent />)
  ).toErrorDev(
    'Warning: Failed prop type: Required prop `prop` was not specified in ' +
      '`RequiredPropComponent`.',
    {withoutStack: true}
  );
});

it('should warn on invalid prop types', () => {
  // Since there is no prevalidation step for ES6 classes, there is no hook
  // for us to issue a warning earlier than element creation when the error
  // actually occurs. Since this step is skipped in production, we should just
  // warn instead of throwing for this case.
  class NullPropTypeComponent extends React.Component {
    render() {
      return <span>{this.props.prop}</span>;
    }
  }
  NullPropTypeComponent.propTypes = {
    prop: null,
  };
  expect(() =>
    ReactTestUtils.renderIntoDocument(<NullPropTypeComponent />)
  ).toErrorDev(
    'NullPropTypeComponent: prop type `prop` is invalid; it must be a ' +
      'function, usually from the `prop-types` package,',
    {withoutStack: true}
  );
});

xit('should warn on invalid context types', () => {
  class NullContextTypeComponent extends React.Component {
    render() {
      return <span>{this.props.prop}</span>;
    }
  }
  NullContextTypeComponent.contextTypes = {
    prop: null,
  };
  expect(() =>
    ReactTestUtils.renderIntoDocument(<NullContextTypeComponent />)
  ).toErrorDev(
    'NullContextTypeComponent: type `prop` is invalid; it must ' +
      'be a function, usually from the `prop-types` package,'
  );
});

it('should warn if getDefaultProps is specified on the class', () => {
  class GetDefaultPropsComponent extends React.Component {
    render() {
      return <span>{this.props.prop}</span>;
    }
  }
  GetDefaultPropsComponent.getDefaultProps = () => ({
    prop: 'foo',
  });
  expect(() =>
    ReactTestUtils.renderIntoDocument(<GetDefaultPropsComponent />)
  ).toErrorDev(
    'getDefaultProps is only used on classic React.createClass definitions.' +
      ' Use a static property named `defaultProps` instead.',
    {withoutStack: true}
  );
});

it('should warn if component declares PropTypes instead of propTypes', () => {
  class MisspelledPropTypesComponent extends React.Component {
    render() {
      return <span>{this.props.prop}</span>;
    }
  }
  MisspelledPropTypesComponent.PropTypes = {
    prop: PropTypes.string,
  };
  expect(() =>
    ReactTestUtils.renderIntoDocument(
      <MisspelledPropTypesComponent prop="hi" />
    )
  ).toErrorDev(
    'Warning: Component MisspelledPropTypesComponent declared `PropTypes` ' +
      'instead of `propTypes`. Did you misspell the property assignment?',
    {withoutStack: true}
  );
});

// Not supported.
xit('warns for fragments with illegal attributes', () => {
  class Foo extends React.Component {
    render() {
      return <React.Fragment a={1}>hello</React.Fragment>;
    }
  }

  expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toErrorDev(
    'Invalid prop `a` supplied to `React.Fragment`. React.Fragment ' +
      'can only have `key` and `children` props.'
  );
});

// Not supported.
xit('warns for fragments with refs', () => {
  class Foo extends React.Component {
    render() {
      return (
        <React.Fragment
          ref={bar => {
            this.foo = bar;
          }}>
          hello
        </React.Fragment>
      );
    }
  }

  expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toErrorDev(
    'Invalid attribute `ref` supplied to `React.Fragment`.'
  );
});

// Not supported.
xit('does not warn for fragments of multiple elements without keys', () => {
  ReactTestUtils.renderIntoDocument(
    <>
      <span>1</span>
      <span>2</span>
    </>
  );
});

// Not supported.
xit('warns for fragments of multiple elements with same key', () => {
  expect(() =>
    ReactTestUtils.renderIntoDocument(
      <>
        <span key="a">1</span>
        <span key="a">2</span>
        <span key="b">3</span>
      </>
    )
  ).toErrorDev('Encountered two children with the same key, `a`.', {
    withoutStack: true,
  });
});

// Not supported.
xit('does not call lazy initializers eagerly', () => {
  let didCall = false;
  const Lazy = React.lazy(() => {
    didCall = true;
    return {then() {}};
  });
  <Lazy />;
  expect(didCall).toBe(false);
});

it('supports classic refs', () => {
  class Foo extends React.Component {
    render() {
      return <div className="foo" ref="inner" />;
    }
  }
  const container = document.createElement('div');
  const instance = ReactDOM.render(<Foo />, container);
  expect(instance.refs.inner.className).toBe('foo');
});

it('should support refs on owned components', () => {
  const innerObj = {};
  const outerObj = {};

  class Wrapper extends React.Component {
    getObject = () => {
      return this.props.object;
    };

    render() {
      return <div>{this.props.children}</div>;
    }
  }

  class Component extends React.Component {
    render() {
      const inner = <Wrapper object={innerObj} ref="inner" />;
      const outer = (
        <Wrapper object={outerObj} ref="outer">
          {inner}
        </Wrapper>
      );
      return outer;
    }

    componentDidMount() {
      expect(this.refs.inner.getObject()).toEqual(innerObj);
      expect(this.refs.outer.getObject()).toEqual(outerObj);
    }
  }

  ReactTestUtils.renderIntoDocument(<Component />);
});

it('should support callback-style refs', () => {
  const innerObj = {};
  const outerObj = {};

  class Wrapper extends React.Component {
    getObject = () => {
      return this.props.object;
    };

    render() {
      return <div>{this.props.children}</div>;
    }
  }

  let mounted = false;

  class Component extends React.Component {
    render() {
      const inner = (
        <Wrapper object={innerObj} ref={c => (this.innerRef = c)} />
      );
      const outer = (
        <Wrapper object={outerObj} ref={c => (this.outerRef = c)}>
          {inner}
        </Wrapper>
      );
      return outer;
    }

    componentDidMount() {
      expect(this.innerRef.getObject()).toEqual(innerObj);
      expect(this.outerRef.getObject()).toEqual(outerObj);
      mounted = true;
    }
  }

  ReactTestUtils.renderIntoDocument(<Component />);
  expect(mounted).toBe(true);
});

// Not supported.
xit('should support object-style refs', () => {
  const innerObj = {};
  const outerObj = {};

  class Wrapper extends React.Component {
    getObject = () => {
      return this.props.object;
    };

    render() {
      return <div>{this.props.children}</div>;
    }
  }

  let mounted = false;

  class Component extends React.Component {
    constructor() {
      super();
      this.innerRef = React.createRef();
      this.outerRef = React.createRef();
    }
    render() {
      const inner = <Wrapper object={innerObj} ref={this.innerRef} />;
      const outer = (
        <Wrapper object={outerObj} ref={this.outerRef}>
          {inner}
        </Wrapper>
      );
      return outer;
    }

    componentDidMount() {
      expect(this.innerRef.current.getObject()).toEqual(innerObj);
      expect(this.outerRef.current.getObject()).toEqual(outerObj);
      mounted = true;
    }
  }

  ReactTestUtils.renderIntoDocument(<Component />);
  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();
    }
  }

  let 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 <div className="inner" ref={c => (this.innerRef = c)} />;
    };

    render() {
      return (
        <Wrapper
          title="wrapper"
          ref={c => (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(this.innerRef.className).toBe('inner');
      mounted = true;
    }
  }

  ReactTestUtils.renderIntoDocument(<Component />);
  expect(mounted).toBe(true);
});

it('should warn when `key` is being accessed on composite element', () => {
  const container = document.createElement('div');
  class Child extends React.Component {
    render() {
      return <div> {this.props.key} </div>;
    }
  }
  class Parent extends React.Component {
    render() {
      return (
        <div>
          <Child key="0" />
          <Child key="1" />
          <Child key="2" />
        </div>
      );
    }
  }
  expect(() => ReactDOM.render(<Parent />, container)).toErrorDev(
    'Child: `key` is not a prop. Trying to access it will result ' +
      'in `undefined` being returned. If you need to access the same ' +
      'value within the child component, you should pass it as a different ' +
      'prop. (https://reactjs.org/link/special-props)',
    {withoutStack: true}
  );
});

it('should warn when `ref` is being accessed', () => {
  const container = document.createElement('div');
  class Child extends React.Component {
    render() {
      return <div> {this.props.ref} </div>;
    }
  }
  class Parent extends React.Component {
    render() {
      return (
        <div>
          <Child ref="childElement" />
        </div>
      );
    }
  }
  expect(() => ReactDOM.render(<Parent />, container)).toErrorDev(
    'Child: `ref` is not a prop. Trying to access it will result ' +
      'in `undefined` being returned. If you need to access the same ' +
      'value within the child component, you should pass it as a different ' +
      'prop. (https://reactjs.org/link/special-props)',
    {withoutStack: true}
  );
});

// Note: no warning before 16.
it('should NOT warn when owner and self are different for string refs', () => {
  class ClassWithRenderProp extends React.Component {
    render() {
      return this.props.children();
    }
  }

  class ClassParent extends React.Component {
    render() {
      return (
        <ClassWithRenderProp>{() => <div ref="myRef" />}</ClassWithRenderProp>
      );
    }
  }

  const container = document.createElement('div');
  ReactDOM.render(<ClassParent />, container);
});