diff --git a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js index edc11927a7612..09337a0caea6d 100644 --- a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -12,7 +12,6 @@ 'use strict'; let createRenderer; -let PropTypes; let React; let ReactDOM; let ReactDOMServer; @@ -21,625 +20,12 @@ let ReactTestUtils; describe('ReactTestUtils', () => { beforeEach(() => { createRenderer = require('react-test-renderer/shallow').createRenderer; - PropTypes = require('prop-types'); React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); ReactTestUtils = require('react-dom/test-utils'); }); - it('should call all of the lifecycle hooks', () => { - const logs = []; - const logger = message => () => logs.push(message) || true; - - class SomeComponent extends React.Component { - componentWillMount = logger('componentWillMount'); - componentDidMount = logger('componentDidMount'); - componentWillReceiveProps = logger('componentWillReceiveProps'); - shouldComponentUpdate = logger('shouldComponentUpdate'); - componentWillUpdate = logger('componentWillUpdate'); - componentDidUpdate = logger('componentDidUpdate'); - componentWillUnmount = logger('componentWillUnmount'); - render() { - return
; - } - } - - const shallowRenderer = createRenderer(); - shallowRenderer.render(); - - // Calling cDU might lead to problems with host component references. - // Since our components aren't really mounted, refs won't be available. - expect(logs).toEqual(['componentWillMount']); - - logs.splice(0); - - const instance = shallowRenderer.getMountedInstance(); - instance.setState({}); - - // The previous shallow renderer triggered cDU for setState() calls. - expect(logs).toEqual([ - 'shouldComponentUpdate', - 'componentWillUpdate', - 'componentDidUpdate', - ]); - - logs.splice(0); - - shallowRenderer.render(); - - // The previous shallow renderer did not trigger cDU for props changes. - expect(logs).toEqual([ - 'componentWillReceiveProps', - 'shouldComponentUpdate', - 'componentWillUpdate', - ]); - }); - - it('should only render 1 level deep', () => { - function Parent() { - return
; - } - function Child() { - throw Error('This component should not render'); - } - - const shallowRenderer = createRenderer(); - shallowRenderer.render(React.createElement(Parent)); - }); - - it('should have shallow rendering', () => { - class SomeComponent extends React.Component { - render() { - return ( -
- - -
- ); - } - } - - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(); - - expect(result.type).toBe('div'); - expect(result.props.children).toEqual([ - , - , - ]); - }); - - it('should enable shouldComponentUpdate to prevent a re-render', () => { - let renderCounter = 0; - class SimpleComponent extends React.Component { - state = {update: false}; - shouldComponentUpdate(nextProps, nextState) { - return this.state.update !== nextState.update; - } - render() { - renderCounter++; - return
{`${renderCounter}`}
; - } - } - - const shallowRenderer = createRenderer(); - shallowRenderer.render(); - expect(shallowRenderer.getRenderOutput()).toEqual(
1
); - - const instance = shallowRenderer.getMountedInstance(); - instance.setState({update: false}); - expect(shallowRenderer.getRenderOutput()).toEqual(
1
); - - instance.setState({update: true}); - expect(shallowRenderer.getRenderOutput()).toEqual(
2
); - }); - - it('should shallow render a functional component', () => { - function SomeComponent(props, context) { - return ( -
-
{props.foo}
-
{context.bar}
- - -
- ); - } - - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(, { - bar: 'BAR', - }); - - expect(result.type).toBe('div'); - expect(result.props.children).toEqual([ -
FOO
, -
BAR
, - , - , - ]); - }); - - it('should shallow render a component returning strings directly from render', () => { - const Text = ({value}) => value; - - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(); - expect(result).toEqual('foo'); - }); - - it('should shallow render a component returning numbers directly from render', () => { - const Text = ({value}) => value; - - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(); - expect(result).toEqual(10); - }); - - it('should shallow render a fragment', () => { - class SomeComponent extends React.Component { - render() { - return
; - } - } - class Fragment extends React.Component { - render() { - return [
, , ]; - } - } - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(); - expect(result).toEqual([ -
, - , - , - ]); - }); - - it('should throw for invalid elements', () => { - class SomeComponent extends React.Component { - render() { - return
; - } - } - - const shallowRenderer = createRenderer(); - expect(() => shallowRenderer.render(SomeComponent)).toThrowError( - 'ReactShallowRenderer render(): Invalid component element. Instead of ' + - 'passing a component class, make sure to instantiate it by passing it ' + - 'to React.createElement.', - ); - expect(() => shallowRenderer.render(
)).toThrowError( - 'ReactShallowRenderer render(): Shallow rendering works only with ' + - 'custom components, not primitives (div). Instead of calling ' + - '`.render(el)` and inspecting the rendered output, look at `el.props` ' + - 'directly instead.', - ); - }); - - it('should have shallow unmounting', () => { - const componentWillUnmount = jest.fn(); - - class SomeComponent extends React.Component { - componentWillUnmount = componentWillUnmount; - render() { - return
; - } - } - - const shallowRenderer = createRenderer(); - shallowRenderer.render(); - shallowRenderer.unmount(); - - expect(componentWillUnmount).toBeCalled(); - }); - - it('can shallow render to null', () => { - class SomeComponent extends React.Component { - render() { - return null; - } - } - - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(); - - expect(result).toBe(null); - }); - - it('can shallow render with a ref', () => { - class SomeComponent extends React.Component { - render() { - return
; - } - } - - const shallowRenderer = createRenderer(); - // Shouldn't crash. - shallowRenderer.render(); - }); - - it('lets you update shallowly rendered components', () => { - class SomeComponent extends React.Component { - state = {clicked: false}; - - onClick = () => { - this.setState({clicked: true}); - }; - - render() { - const className = this.state.clicked ? 'was-clicked' : ''; - - if (this.props.aNew === 'prop') { - return ( - - Test link - - ); - } else { - return ( -
- - -
- ); - } - } - } - - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(); - expect(result.type).toBe('div'); - expect(result.props.children).toEqual([ - , - , - ]); - - const updatedResult = shallowRenderer.render(); - expect(updatedResult.type).toBe('a'); - - const mockEvent = {}; - updatedResult.props.onClick(mockEvent); - - const updatedResultCausedByClick = shallowRenderer.getRenderOutput(); - expect(updatedResultCausedByClick.type).toBe('a'); - expect(updatedResultCausedByClick.props.className).toBe('was-clicked'); - }); - - it('can access the mounted component instance', () => { - class SimpleComponent extends React.Component { - someMethod = () => { - return this.props.n; - }; - - render() { - return
{this.props.n}
; - } - } - - const shallowRenderer = createRenderer(); - shallowRenderer.render(); - expect(shallowRenderer.getMountedInstance().someMethod()).toEqual(5); - }); - - it('can shallowly render components with contextTypes', () => { - class SimpleComponent extends React.Component { - static contextTypes = { - name: PropTypes.string, - }; - - render() { - return
; - } - } - - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(); - expect(result).toEqual(
); - }); - - it('can shallowly render components with ref as function', () => { - class SimpleComponent extends React.Component { - state = {clicked: false}; - - handleUserClick = () => { - this.setState({clicked: true}); - }; - - render() { - return ( -
{}} - onClick={this.handleUserClick} - className={this.state.clicked ? 'clicked' : ''} - /> - ); - } - } - - const shallowRenderer = createRenderer(); - shallowRenderer.render(); - let result = shallowRenderer.getRenderOutput(); - expect(result.type).toEqual('div'); - expect(result.props.className).toEqual(''); - result.props.onClick(); - - result = shallowRenderer.getRenderOutput(); - expect(result.type).toEqual('div'); - expect(result.props.className).toEqual('clicked'); - }); - - it('can setState in componentWillMount when shallow rendering', () => { - class SimpleComponent extends React.Component { - componentWillMount() { - this.setState({groovy: 'doovy'}); - } - - render() { - return
{this.state.groovy}
; - } - } - - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(); - expect(result).toEqual(
doovy
); - }); - - it('can setState in componentWillReceiveProps when shallow rendering', () => { - class SimpleComponent extends React.Component { - state = {count: 0}; - - componentWillReceiveProps(nextProps) { - if (nextProps.updateState) { - this.setState({count: 1}); - } - } - - render() { - return
{this.state.count}
; - } - } - - const shallowRenderer = createRenderer(); - let result = shallowRenderer.render( - , - ); - expect(result.props.children).toEqual(0); - - result = shallowRenderer.render(); - expect(result.props.children).toEqual(1); - }); - - it('can setState with an updater function', () => { - let instance; - - class SimpleComponent extends React.Component { - state = { - counter: 0, - }; - - render() { - instance = this; - return ( - - ); - } - } - - const shallowRenderer = createRenderer(); - let result = shallowRenderer.render(); - expect(result.props.children).toEqual(0); - - instance.setState((state, props) => { - return {counter: props.defaultCount + 1}; - }); - - result = shallowRenderer.getRenderOutput(); - expect(result.props.children).toEqual(2); - }); - - it('can setState with a callback', () => { - let instance; - - class SimpleComponent extends React.Component { - state = { - counter: 0, - }; - render() { - instance = this; - return ( -

- {this.state.counter} -

- ); - } - } - - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(); - expect(result.props.children).toBe(0); - - const callback = jest.fn(function() { - expect(this).toBe(instance); - }); - - instance.setState({counter: 1}, callback); - - const updated = shallowRenderer.getRenderOutput(); - expect(updated.props.children).toBe(1); - expect(callback).toHaveBeenCalled(); - }); - - it('can replaceState with a callback', () => { - let instance; - - class SimpleComponent extends React.Component { - state = { - counter: 0, - }; - render() { - instance = this; - return ( -

- {this.state.counter} -

- ); - } - } - - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(); - expect(result.props.children).toBe(0); - - const callback = jest.fn(function() { - expect(this).toBe(instance); - }); - - // No longer a public API, but we can test that it works internally by - // reaching into the updater. - shallowRenderer._updater.enqueueReplaceState( - instance, - {counter: 1}, - callback, - ); - - const updated = shallowRenderer.getRenderOutput(); - expect(updated.props.children).toBe(1); - expect(callback).toHaveBeenCalled(); - }); - - it('can forceUpdate with a callback', () => { - let instance; - - class SimpleComponent extends React.Component { - state = { - counter: 0, - }; - render() { - instance = this; - return ( -

- {this.state.counter} -

- ); - } - } - - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(); - expect(result.props.children).toBe(0); - - const callback = jest.fn(function() { - expect(this).toBe(instance); - }); - - instance.forceUpdate(callback); - - const updated = shallowRenderer.getRenderOutput(); - expect(updated.props.children).toBe(0); - expect(callback).toHaveBeenCalled(); - }); - - it('can pass context when shallowly rendering', () => { - class SimpleComponent extends React.Component { - static contextTypes = { - name: PropTypes.string, - }; - - render() { - return
{this.context.name}
; - } - } - - const shallowRenderer = createRenderer(); - const result = shallowRenderer.render(, { - name: 'foo', - }); - expect(result).toEqual(
foo
); - }); - - it('should track context across updates', () => { - class SimpleComponent extends React.Component { - static contextTypes = { - foo: PropTypes.string, - }; - - state = { - bar: 'bar', - }; - - render() { - return
{`${this.context.foo}:${this.state.bar}`}
; - } - } - - const shallowRenderer = createRenderer(); - let result = shallowRenderer.render(, { - foo: 'foo', - }); - expect(result).toEqual(
foo:bar
); - - const instance = shallowRenderer.getMountedInstance(); - instance.setState({bar: 'baz'}); - - result = shallowRenderer.getRenderOutput(); - expect(result).toEqual(
foo:baz
); - }); - - it('can fail context when shallowly rendering', () => { - spyOn(console, 'error'); - - class SimpleComponent extends React.Component { - static contextTypes = { - name: PropTypes.string.isRequired, - }; - - render() { - return
{this.context.name}
; - } - } - - const shallowRenderer = createRenderer(); - shallowRenderer.render(); - expectDev(console.error.calls.count()).toBe(1); - expect( - console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'), - ).toBe( - 'Warning: Failed context type: The context `name` is marked as ' + - 'required in `SimpleComponent`, but its value is `undefined`.\n' + - ' in SimpleComponent (at **)', - ); - }); - - it('should warn about propTypes (but only once)', () => { - spyOn(console, 'error'); - - class SimpleComponent extends React.Component { - render() { - return React.createElement('div', null, this.props.name); - } - } - - SimpleComponent.propTypes = { - name: PropTypes.string.isRequired, - }; - - const shallowRenderer = createRenderer(); - shallowRenderer.render(React.createElement(SimpleComponent, {name: 123})); - - expect(console.error.calls.count()).toBe(1); - expect( - console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'), - ).toBe( - 'Warning: Failed prop type: Invalid prop `name` of type `number` ' + - 'supplied to `SimpleComponent`, expected `string`.\n' + - ' in SimpleComponent', - ); - }); - it('can scryRenderedDOMComponentsWithClass with TextComponent', () => { class Wrapper extends React.Component { render() { @@ -917,31 +303,6 @@ describe('ReactTestUtils', () => { expect(hrs.length).toBe(2); }); - it('should enable rendering of cloned element', () => { - class SimpleComponent extends React.Component { - constructor(props) { - super(props); - - this.state = { - bar: 'bar', - }; - } - - render() { - return
{`${this.props.foo}:${this.state.bar}`}
; - } - } - - const shallowRenderer = createRenderer(); - let result = shallowRenderer.render(); - expect(result).toEqual(
foo:bar
); - - const instance = shallowRenderer.getMountedInstance(); - const cloned = React.cloneElement(instance, {foo: 'baz'}); - result = shallowRenderer.render(cloned); - expect(result).toEqual(
baz:bar
); - }); - describe('Simulate', () => { it('should set the type of the event', () => { let event; diff --git a/src/renderers/testing/__tests__/ReactShallowRenderer-test.js b/src/renderers/testing/__tests__/ReactShallowRenderer-test.js new file mode 100644 index 0000000000000..40535673b2cc5 --- /dev/null +++ b/src/renderers/testing/__tests__/ReactShallowRenderer-test.js @@ -0,0 +1,661 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +'use strict'; + +let createRenderer; +let PropTypes; +let React; + +describe('ReactTestUtils', () => { + beforeEach(() => { + createRenderer = require('react-test-renderer/shallow').createRenderer; + PropTypes = require('prop-types'); + React = require('react'); + }); + + it('should call all of the lifecycle hooks', () => { + const logs = []; + const logger = message => () => logs.push(message) || true; + + class SomeComponent extends React.Component { + componentWillMount = logger('componentWillMount'); + componentDidMount = logger('componentDidMount'); + componentWillReceiveProps = logger('componentWillReceiveProps'); + shouldComponentUpdate = logger('shouldComponentUpdate'); + componentWillUpdate = logger('componentWillUpdate'); + componentDidUpdate = logger('componentDidUpdate'); + componentWillUnmount = logger('componentWillUnmount'); + render() { + return
; + } + } + + const shallowRenderer = createRenderer(); + shallowRenderer.render(); + + // Calling cDU might lead to problems with host component references. + // Since our components aren't really mounted, refs won't be available. + expect(logs).toEqual(['componentWillMount']); + + logs.splice(0); + + const instance = shallowRenderer.getMountedInstance(); + instance.setState({}); + + // The previous shallow renderer triggered cDU for setState() calls. + expect(logs).toEqual([ + 'shouldComponentUpdate', + 'componentWillUpdate', + 'componentDidUpdate', + ]); + + logs.splice(0); + + shallowRenderer.render(); + + // The previous shallow renderer did not trigger cDU for props changes. + expect(logs).toEqual([ + 'componentWillReceiveProps', + 'shouldComponentUpdate', + 'componentWillUpdate', + ]); + }); + + it('should only render 1 level deep', () => { + function Parent() { + return
; + } + function Child() { + throw Error('This component should not render'); + } + + const shallowRenderer = createRenderer(); + shallowRenderer.render(React.createElement(Parent)); + }); + + it('should have shallow rendering', () => { + class SomeComponent extends React.Component { + render() { + return ( +
+ + +
+ ); + } + } + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); + + expect(result.type).toBe('div'); + expect(result.props.children).toEqual([ + , + , + ]); + }); + + it('should enable shouldComponentUpdate to prevent a re-render', () => { + let renderCounter = 0; + class SimpleComponent extends React.Component { + state = {update: false}; + shouldComponentUpdate(nextProps, nextState) { + return this.state.update !== nextState.update; + } + render() { + renderCounter++; + return
{`${renderCounter}`}
; + } + } + + const shallowRenderer = createRenderer(); + shallowRenderer.render(); + expect(shallowRenderer.getRenderOutput()).toEqual(
1
); + + const instance = shallowRenderer.getMountedInstance(); + instance.setState({update: false}); + expect(shallowRenderer.getRenderOutput()).toEqual(
1
); + + instance.setState({update: true}); + expect(shallowRenderer.getRenderOutput()).toEqual(
2
); + }); + + it('should shallow render a functional component', () => { + function SomeComponent(props, context) { + return ( +
+
{props.foo}
+
{context.bar}
+ + +
+ ); + } + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(, { + bar: 'BAR', + }); + + expect(result.type).toBe('div'); + expect(result.props.children).toEqual([ +
FOO
, +
BAR
, + , + , + ]); + }); + + it('should shallow render a component returning strings directly from render', () => { + const Text = ({value}) => value; + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); + expect(result).toEqual('foo'); + }); + + it('should shallow render a component returning numbers directly from render', () => { + const Text = ({value}) => value; + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); + expect(result).toEqual(10); + }); + + it('should shallow render a fragment', () => { + class SomeComponent extends React.Component { + render() { + return
; + } + } + class Fragment extends React.Component { + render() { + return [
, , ]; + } + } + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); + expect(result).toEqual([ +
, + , + , + ]); + }); + + it('should throw for invalid elements', () => { + class SomeComponent extends React.Component { + render() { + return
; + } + } + + const shallowRenderer = createRenderer(); + expect(() => shallowRenderer.render(SomeComponent)).toThrowError( + 'ReactShallowRenderer render(): Invalid component element. Instead of ' + + 'passing a component class, make sure to instantiate it by passing it ' + + 'to React.createElement.', + ); + expect(() => shallowRenderer.render(
)).toThrowError( + 'ReactShallowRenderer render(): Shallow rendering works only with ' + + 'custom components, not primitives (div). Instead of calling ' + + '`.render(el)` and inspecting the rendered output, look at `el.props` ' + + 'directly instead.', + ); + }); + + it('should have shallow unmounting', () => { + const componentWillUnmount = jest.fn(); + + class SomeComponent extends React.Component { + componentWillUnmount = componentWillUnmount; + render() { + return
; + } + } + + const shallowRenderer = createRenderer(); + shallowRenderer.render(); + shallowRenderer.unmount(); + + expect(componentWillUnmount).toBeCalled(); + }); + + it('can shallow render to null', () => { + class SomeComponent extends React.Component { + render() { + return null; + } + } + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); + + expect(result).toBe(null); + }); + + it('can shallow render with a ref', () => { + class SomeComponent extends React.Component { + render() { + return
; + } + } + + const shallowRenderer = createRenderer(); + // Shouldn't crash. + shallowRenderer.render(); + }); + + it('lets you update shallowly rendered components', () => { + class SomeComponent extends React.Component { + state = {clicked: false}; + + onClick = () => { + this.setState({clicked: true}); + }; + + render() { + const className = this.state.clicked ? 'was-clicked' : ''; + + if (this.props.aNew === 'prop') { + return ( + + Test link + + ); + } else { + return ( +
+ + +
+ ); + } + } + } + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); + expect(result.type).toBe('div'); + expect(result.props.children).toEqual([ + , + , + ]); + + const updatedResult = shallowRenderer.render(); + expect(updatedResult.type).toBe('a'); + + const mockEvent = {}; + updatedResult.props.onClick(mockEvent); + + const updatedResultCausedByClick = shallowRenderer.getRenderOutput(); + expect(updatedResultCausedByClick.type).toBe('a'); + expect(updatedResultCausedByClick.props.className).toBe('was-clicked'); + }); + + it('can access the mounted component instance', () => { + class SimpleComponent extends React.Component { + someMethod = () => { + return this.props.n; + }; + + render() { + return
{this.props.n}
; + } + } + + const shallowRenderer = createRenderer(); + shallowRenderer.render(); + expect(shallowRenderer.getMountedInstance().someMethod()).toEqual(5); + }); + + it('can shallowly render components with contextTypes', () => { + class SimpleComponent extends React.Component { + static contextTypes = { + name: PropTypes.string, + }; + + render() { + return
; + } + } + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); + expect(result).toEqual(
); + }); + + it('can shallowly render components with ref as function', () => { + class SimpleComponent extends React.Component { + state = {clicked: false}; + + handleUserClick = () => { + this.setState({clicked: true}); + }; + + render() { + return ( +
{}} + onClick={this.handleUserClick} + className={this.state.clicked ? 'clicked' : ''} + /> + ); + } + } + + const shallowRenderer = createRenderer(); + shallowRenderer.render(); + let result = shallowRenderer.getRenderOutput(); + expect(result.type).toEqual('div'); + expect(result.props.className).toEqual(''); + result.props.onClick(); + + result = shallowRenderer.getRenderOutput(); + expect(result.type).toEqual('div'); + expect(result.props.className).toEqual('clicked'); + }); + + it('can setState in componentWillMount when shallow rendering', () => { + class SimpleComponent extends React.Component { + componentWillMount() { + this.setState({groovy: 'doovy'}); + } + + render() { + return
{this.state.groovy}
; + } + } + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); + expect(result).toEqual(
doovy
); + }); + + it('can setState in componentWillReceiveProps when shallow rendering', () => { + class SimpleComponent extends React.Component { + state = {count: 0}; + + componentWillReceiveProps(nextProps) { + if (nextProps.updateState) { + this.setState({count: 1}); + } + } + + render() { + return
{this.state.count}
; + } + } + + const shallowRenderer = createRenderer(); + let result = shallowRenderer.render( + , + ); + expect(result.props.children).toEqual(0); + + result = shallowRenderer.render(); + expect(result.props.children).toEqual(1); + }); + + it('can setState with an updater function', () => { + let instance; + + class SimpleComponent extends React.Component { + state = { + counter: 0, + }; + + render() { + instance = this; + return ( + + ); + } + } + + const shallowRenderer = createRenderer(); + let result = shallowRenderer.render(); + expect(result.props.children).toEqual(0); + + instance.setState((state, props) => { + return {counter: props.defaultCount + 1}; + }); + + result = shallowRenderer.getRenderOutput(); + expect(result.props.children).toEqual(2); + }); + + it('can setState with a callback', () => { + let instance; + + class SimpleComponent extends React.Component { + state = { + counter: 0, + }; + render() { + instance = this; + return ( +

+ {this.state.counter} +

+ ); + } + } + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); + expect(result.props.children).toBe(0); + + const callback = jest.fn(function() { + expect(this).toBe(instance); + }); + + instance.setState({counter: 1}, callback); + + const updated = shallowRenderer.getRenderOutput(); + expect(updated.props.children).toBe(1); + expect(callback).toHaveBeenCalled(); + }); + + it('can replaceState with a callback', () => { + let instance; + + class SimpleComponent extends React.Component { + state = { + counter: 0, + }; + render() { + instance = this; + return ( +

+ {this.state.counter} +

+ ); + } + } + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); + expect(result.props.children).toBe(0); + + const callback = jest.fn(function() { + expect(this).toBe(instance); + }); + + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + shallowRenderer._updater.enqueueReplaceState( + instance, + {counter: 1}, + callback, + ); + + const updated = shallowRenderer.getRenderOutput(); + expect(updated.props.children).toBe(1); + expect(callback).toHaveBeenCalled(); + }); + + it('can forceUpdate with a callback', () => { + let instance; + + class SimpleComponent extends React.Component { + state = { + counter: 0, + }; + render() { + instance = this; + return ( +

+ {this.state.counter} +

+ ); + } + } + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); + expect(result.props.children).toBe(0); + + const callback = jest.fn(function() { + expect(this).toBe(instance); + }); + + instance.forceUpdate(callback); + + const updated = shallowRenderer.getRenderOutput(); + expect(updated.props.children).toBe(0); + expect(callback).toHaveBeenCalled(); + }); + + it('can pass context when shallowly rendering', () => { + class SimpleComponent extends React.Component { + static contextTypes = { + name: PropTypes.string, + }; + + render() { + return
{this.context.name}
; + } + } + + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(, { + name: 'foo', + }); + expect(result).toEqual(
foo
); + }); + + it('should track context across updates', () => { + class SimpleComponent extends React.Component { + static contextTypes = { + foo: PropTypes.string, + }; + + state = { + bar: 'bar', + }; + + render() { + return
{`${this.context.foo}:${this.state.bar}`}
; + } + } + + const shallowRenderer = createRenderer(); + let result = shallowRenderer.render(, { + foo: 'foo', + }); + expect(result).toEqual(
foo:bar
); + + const instance = shallowRenderer.getMountedInstance(); + instance.setState({bar: 'baz'}); + + result = shallowRenderer.getRenderOutput(); + expect(result).toEqual(
foo:baz
); + }); + + it('can fail context when shallowly rendering', () => { + spyOn(console, 'error'); + + class SimpleComponent extends React.Component { + static contextTypes = { + name: PropTypes.string.isRequired, + }; + + render() { + return
{this.context.name}
; + } + } + + const shallowRenderer = createRenderer(); + shallowRenderer.render(); + expectDev(console.error.calls.count()).toBe(1); + expect( + console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'), + ).toBe( + 'Warning: Failed context type: The context `name` is marked as ' + + 'required in `SimpleComponent`, but its value is `undefined`.\n' + + ' in SimpleComponent (at **)', + ); + }); + + it('should warn about propTypes (but only once)', () => { + spyOn(console, 'error'); + + class SimpleComponent extends React.Component { + render() { + return React.createElement('div', null, this.props.name); + } + } + + SimpleComponent.propTypes = { + name: PropTypes.string.isRequired, + }; + + const shallowRenderer = createRenderer(); + shallowRenderer.render(React.createElement(SimpleComponent, {name: 123})); + + expect(console.error.calls.count()).toBe(1); + expect( + console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'), + ).toBe( + 'Warning: Failed prop type: Invalid prop `name` of type `number` ' + + 'supplied to `SimpleComponent`, expected `string`.\n' + + ' in SimpleComponent', + ); + }); + + it('should enable rendering of cloned element', () => { + class SimpleComponent extends React.Component { + constructor(props) { + super(props); + + this.state = { + bar: 'bar', + }; + } + + render() { + return
{`${this.props.foo}:${this.state.bar}`}
; + } + } + + const shallowRenderer = createRenderer(); + let result = shallowRenderer.render(); + expect(result).toEqual(
foo:bar
); + + const instance = shallowRenderer.getMountedInstance(); + const cloned = React.cloneElement(instance, {foo: 'baz'}); + result = shallowRenderer.render(cloned); + expect(result).toEqual(
baz:bar
); + }); +});