Files
react/src/renderers/testing/__tests__/ReactTestRenderer-test.js
T
Brandon Dail f3569a2c31 Implement createNodeMock for ReactTestRenderer (#7649)
* Implement optional mockConfig and getMockRef

* default mockConfig, walk render tree

* Pass mockConfig to transaction

* Attach mockConfig to transaction

* type mockConfig in ReactRef

* Expect object in native component ref test

* Fix argument name for attachRefs

* Add mockConfig support to legacy refs

* Pass transaction to getPublicInstance

* Implement getMockConfig on ReactTestReconcileTransaction

* Merge defaultMockConfig and mockConfig options

* Rename mockConfig to testOptions

* Break getPublicInstnce into three lines

* createMockRef -> createNodeMock
2016-09-13 09:09:20 -05:00

368 lines
8.7 KiB
JavaScript

/**
* 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';
var React = require('React');
var ReactTestRenderer = require('ReactTestRenderer');
describe('ReactTestRenderer', () => {
it('renders a simple component', () => {
function Link() {
return <a role="link" />;
}
var renderer = ReactTestRenderer.create(<Link />);
expect(renderer.toJSON()).toEqual({
type: 'a',
props: { role: 'link' },
children: null,
});
});
it('renders a top-level empty component', () => {
function Empty() {
return null;
}
var renderer = ReactTestRenderer.create(<Empty />);
expect(renderer.toJSON()).toEqual(null);
});
it('exposes a type flag', () => {
function Link() {
return <a role="link" />;
}
var renderer = ReactTestRenderer.create(<Link />);
var object = renderer.toJSON();
expect(object.$$typeof).toBe(Symbol.for('react.test.json'));
// $$typeof should not be enumerable.
for (var key in object) {
if (Object.prototype.hasOwnProperty.call(object, key)) {
expect(key).not.toBe('$$typeof');
}
}
});
it('renders some basics with an update', () => {
var renders = 0;
class Component extends React.Component {
state = {x: 3};
render() {
renders++;
return (
<div className="purple">
{this.state.x}
<Child />
<Null />
</div>
);
}
componentDidMount() {
this.setState({x: 7});
}
}
var Child = () => (renders++, <moo />);
var Null = () => (renders++, null);
var renderer = ReactTestRenderer.create(<Component />);
expect(renderer.toJSON()).toEqual({
type: 'div',
props: { className: 'purple' },
children: [
7,
{ type: 'moo', props: {}, children: null },
],
});
expect(renders).toBe(6);
});
it('exposes the instance', () => {
class Mouse extends React.Component {
constructor() {
super();
this.state = {mouse: 'mouse'};
}
handleMoose() {
this.setState({mouse: 'moose'});
}
render() {
return <div>{this.state.mouse}</div>;
}
}
var renderer = ReactTestRenderer.create(<Mouse />);
expect(renderer.toJSON()).toEqual({
type: 'div',
props: {},
children: ['mouse'],
});
var mouse = renderer.getInstance();
mouse.handleMoose();
expect(renderer.toJSON()).toEqual({
type: 'div',
props: {},
children: ['moose'],
});
});
it('updates types', () => {
var renderer = ReactTestRenderer.create(<div>mouse</div>);
expect(renderer.toJSON()).toEqual({
type: 'div',
props: {},
children: ['mouse'],
});
renderer.update(<span>mice</span>);
expect(renderer.toJSON()).toEqual({
type: 'span',
props: {},
children: ['mice'],
});
});
it('updates children', () => {
var renderer = ReactTestRenderer.create(
<div>
<span key="a">A</span>
<span key="b">B</span>
<span key="c">C</span>
</div>
);
expect(renderer.toJSON()).toEqual({
type: 'div',
props: {},
children: [
{type: 'span', props: {}, children: ['A']},
{type: 'span', props: {}, children: ['B']},
{type: 'span', props: {}, children: ['C']},
],
});
renderer.update(
<div>
<span key="d">D</span>
<span key="c">C</span>
<span key="b">B</span>
</div>
);
expect(renderer.toJSON()).toEqual({
type: 'div',
props: {},
children: [
{type: 'span', props: {}, children: ['D']},
{type: 'span', props: {}, children: ['C']},
{type: 'span', props: {}, children: ['B']},
],
});
});
it('does the full lifecycle', () => {
var log = [];
class Log extends React.Component {
render() {
log.push('render ' + this.props.name);
return <div />;
}
componentDidMount() {
log.push('mount ' + this.props.name);
}
componentWillUnmount() {
log.push('unmount ' + this.props.name);
}
}
var renderer = ReactTestRenderer.create(<Log key="foo" name="Foo" />);
renderer.update(<Log key="bar" name="Bar" />);
renderer.unmount();
expect(log).toEqual([
'render Foo',
'mount Foo',
'unmount Foo',
'render Bar',
'mount Bar',
'unmount Bar',
]);
});
it('gives a ref to native components', () => {
var log = [];
ReactTestRenderer.create(<div ref={(r) => log.push(r)} />);
expect(log).toEqual([null]);
});
it('warns correctly for refs on SFCs', () => {
spyOn(console, 'error');
function Bar() {
return <div>Hello, world</div>;
}
class Foo extends React.Component {
render() {
return <Bar ref="foo" />;
}
}
class Baz extends React.Component {
render() {
return <div ref="baz" />;
}
}
ReactTestRenderer.create(<Baz />);
ReactTestRenderer.create(<Foo />);
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Stateless function components cannot be given refs ' +
'(See ref "foo" in Bar created by Foo). ' +
'Attempts to access this ref will fail.'
);
});
it('allows an optional createNodeMock function', () => {
var mockDivInstance = { appendChild: () => {} };
var mockInputInstance = { focus: () => {} };
var mockListItemInstance = { click: () => {} };
var mockAnchorInstance = { hover: () => {} };
var log = [];
class Foo extends React.Component {
componentDidMount() {
log.push(this.refs.bar);
}
render() {
return (
<a ref="bar">Hello, world</a>
);
}
}
function createNodeMock(element) {
switch (element.type) {
case 'div':
return mockDivInstance;
case 'input':
return mockInputInstance;
case 'li':
return mockListItemInstance;
case 'a':
return mockAnchorInstance;
default:
return {};
}
}
ReactTestRenderer.create(
<div ref={(r) => log.push(r)} />,
{createNodeMock}
);
ReactTestRenderer.create(
<input ref={(r) => log.push(r)} />,
{createNodeMock},
);
ReactTestRenderer.create(
<div>
<span>
<ul>
<li ref={(r) => log.push(r)} />
</ul>
<ul>
<li ref={(r) => log.push(r)} />
<li ref={(r) => log.push(r)} />
</ul>
</span>
</div>,
{createNodeMock, foobar: true},
);
ReactTestRenderer.create(
<Foo />,
{createNodeMock},
);
ReactTestRenderer.create(
<div ref={(r) => log.push(r)} />,
);
ReactTestRenderer.create(
<div ref={(r) => log.push(r)} />,
{}
);
expect(log).toEqual([
mockDivInstance,
mockInputInstance,
mockListItemInstance,
mockListItemInstance,
mockListItemInstance,
mockAnchorInstance,
null,
null,
]);
});
it('supports error boundaries', () => {
var log = [];
class Angry extends React.Component {
render() {
log.push('Angry render');
throw new Error('Please, do not render me.');
}
componentDidMount() {
log.push('Angry componentDidMount');
}
componentWillUnmount() {
log.push('Angry componentWillUnmount');
}
}
class Boundary extends React.Component {
constructor(props) {
super(props);
this.state = {error: false};
}
render() {
log.push('Boundary render');
if (!this.state.error) {
return (
<div><button onClick={this.onClick}>ClickMe</button><Angry /></div>
);
} else {
return <div>Happy Birthday!</div>;
}
}
componentDidMount() {
log.push('Boundary componentDidMount');
}
componentWillUnmount() {
log.push('Boundary componentWillUnmount');
}
onClick() {
/* do nothing */
}
unstable_handleError() {
this.setState({error: true});
}
}
var renderer = ReactTestRenderer.create(<Boundary />);
expect(renderer.toJSON()).toEqual({
type: 'div',
props: {},
children: ['Happy Birthday!'],
});
expect(log).toEqual([
'Boundary render',
'Angry render',
'Boundary render',
'Boundary componentDidMount',
]);
});
});