Testing React Class is Of Type - reactjs

I'm attempting to test a react class which renders out several other react classes. All working perfectly apart from i'm not totally sure about the best practise in terms of testing this.
Code:
Parent Class:
module.exports = function (deps) {
var MixLink = require('views/components/mixLink')(deps);
return React.createClass({
render: function() {
return (
<div class="packshotData mixesPage" data-view="mixlist">
{
this.props.mixes.map(function (mix) {
return <MixLink mix={mix} />;
})
}
</div>
);
}
});
};
Child Class:
module.exports = function (deps) {
var Link = require('views/components/link')(deps);
var mixLink = React.createClass({
render: function() {
return (
<div className="packshotData-item packshotData-item-overlay">
<Link className="packshotData-item-link" href={this.props.mix.href} data-control="mixplay">
<img className="packshotData-item-image packshotHeadphones" src={this.props.mix.thumbnailHref} />
<div className="packshotData-item-title-overlay">
<span className="packshotData-item-title-text">{this.props.mix.name}</span>
</div>
</Link>
</div>
);
}
});
return mixLink;
};
Test:
describe('mixLinkList Component', function () {
var MixLinkList = require('views/components/mixLinkList')({}),
MixLink = require('views/components/mixLink')({}),
TestUtils = React.addons.TestUtils;
it('is a function', function () {
expect(MixLinkList).to.be.a(Function);
});
it('is create a MixLink for every mix', function () {
var mixes = [
{
href: 'http://mixlink.com/One',
name: "thunderbirds",
thumbnailHref: 'someUri'
},
{
href: 'http://mixlink.com/Two',
name: "captain scarlet",
thumbnailHref: 'someOtherUri'
}
],
renderedMixLinkList,
renderedComponents;
renderedMixLinkList = TestUtils.renderIntoDocument(
<MixLinkList mixes={mixes} />
);
renderedComponents = TestUtils.findAllInRenderedTree(renderedMixLinkList, function (elem) {
return TestUtils.isCompositeComponentWithType(elem, MixLink);
});
expect(renderedComponents.length).to.be(2);
});
});
The test currently fails.
I can achieve my goals by grabbing the DOM node and checking the actual HTML which to me seems messy as the HTML produced by MixLink is the concern of MixLink Class not the MixLinkList class.
What i would like to do is simply interrogate the rendered component and check it has two children of type MixLink. Is that possible?
Thanks in advance!

For the most part your test seems fine. I think the reason it's failing is that MixLinkList is creating its own MixLink component distinct from the one in the test. You create one MixLink component on line 2 of the parent class, and a different one on line 3 of the test. So the rendered list does not contain elements of the test's MixLink, but rather elements of the list's own link component.
Your method of dependency injection is both the problem and the solution. Simply change the parent class to begin:
module.exports = function (deps) {
var MixLink = deps.MixLink;
And the test can then inject the MixLink component like this:
var MixLink = require('views/components/mixLink')({}),
MixLinkList = require('views/components/mixLinkList')({
MixLink: MixLink
}),
TestUtils = React.addons.TestUtils;
You might also consider using a more specific TestUtils methods such as scryRenderedComponentsWithType (docs), which makes the last part of your test read easier:
renderedComponents = TestUtils.scryRenderedComponentsWithType(
renderedMixLinkList,
MixLink
);

Related

react components not talking to each other

I've built a basic restuarant recommendation app that filters by location using the YELP api. The api was responding to my requests with the response object and everything was appending to my divs perfectly, but I realized that for my project, I needed to make a new layer for the data listing. Here are the relevant portions of my two components as they are now:
display-recs:
var DisplayRecs = React.createClass({
render: function() {
var recsLoop = [];
if (this.props.recommendations) {
for (var i=0; i < this.props.recommendations.length; i++) {
recsLoop.push(<Recommendations item={this.props.recommendations[i]} />)
}
}
console.log(this.props.recommendations);
return (
<div className="DisplayRecs">
{recsLoop}
</div>
);
}
});
var mapStateToProps = function(state, props) {
return {
recommendations: state.recommendations
};
};
recommendations:
var Recommendations = React.createClass({
render: function() {
<div id="bizData">
<div id='nameList'>{this.props.item.name}</div>
<div id='phoneList'>{this.props.item.phone}</div>
<div id='ratingList'>{this.props.item.rating}</div>
</div>
}
});
var mapStateToProps = function(state, props) {
return {
recommendations: state.recommendations
};
};
I cannot figure out why the nameList, phoneList, and ratingList will not print onto the dom. When I view the elements tab in my devtools, all i see is an empty displayrecs div. I've tried to just change things by guessing, but it's not been fruitful. Can any of you see an obvious problem with the current code?
Thanks
Your Recommendations react component's render function doesn't have any return statement. Try doing this:
var Recommendations = React.createClass({
render: function() {
return ( <div id="bizData">
<div id='nameList'>{this.props.item.name}</div>
<div id='phoneList'>{this.props.item.phone}</div>
<div id='ratingList'>{this.props.item.rating}</div>
</div>);
}
});
Also add a key to the Recommendations components as #Vikramaditya recommends:
recsLoop.push(<Recommendations key={i} item={this.props.recommendations[i]} />)

React shallow rendering generates component with type == Function instead of type == <ComponentName>

Assuming the following 2 components
var Level2 = React.createClass({
render: function () {
return React.createElement('div');
}
});
var Level1 = React.createClass({
render: function () {
return React.createElement(Level2);
}
});
and the following helper for shallow rendering testing in Jasmine
var TestUtils = React.addons.TestUtils;
var shallowRender = function(element, props, children) {
var ShallowRenderer = TestUtils.createRenderer();
ShallowRenderer.render(React.createElement(element, props, children));
return ShallowRenderer.getRenderOutput();
};
The second test does not behave as I expect:
describe ('test rendered output', function (){
it('level 2 should render a div component', function() {
l2 = shallowRender(Level2);
expect(l2.type).toBe('div') // this test PASSES
});
it('level 1 should render a level 2 component', function() {
l1 = shallowRender(Level1);
// this test FAILS with the following message 'Expected Function to be 'Level2'
expect(l1.type).toBe('Level2')
})
});
Any idea why the second test fails ?
When you type a JSX tag name with a lowercase letter
<div />
the JSX is transpiled into
React.createClass("div")
and thus the element type is a string — "div". But when you use an uppercase letter
<Level2 />
the JSX turns into
React.createClass(Level2)
and the type of the component is not a string (e.g. "Level2"), but is in fact the actual value Level2. Your test should read
expect(l1.type).toBe(Level2)

Why is TestUtils.Simulate.click in Jest not working when used directly on React Components?

Let's say I have 2 components. A parent that contains a child.
The child component is a button like so:
var React = require('react');
var ChildButton = React.createClass({
onSubmitAnswer: function(e) {
this.props.onClick(this);
},
render: function() {
return (
<div className={this.props.visibility}>
<button onClick={this.onSubmitAnswer}>Click Me</button>
</div>
)
}
});
module.exports = ChildButton;
It lives within it's parent, which looks like this:
var React = require('react'),
ChildButton = require('./face-submit-button');
var ParentComponent = React.createClass({
onButtonSubmit: function() {
//Something happens here
},
render: function() {
return (
<div>
//Some more components
<ChildButton text="Submit" onClick={this.onButtonSubmit} />
</div>
)
}
});
module.exports = ParentComponent;
So far so good. Everything works as expected in the UI. But I've encountered some issues in the Jest tests using TestUtils.Simulate.click().
My test for the ChildButton component is straightforward and behaves as I would expect.
jest.dontMock('./child-button');
describe('ChildButton', function() {
var React = require('react/addons'),
ChildButton = require('./child-button'),
TestUtils = React.addons.TestUtils;
describe('events', function() {
var button,
onClickStub;
beforeEach(function() {
onClickStub = jest.genMockFn();
button = TestUtils.renderIntoDocument(
<ChildButton onClick={onClickStub} />
);
});
it('should call onSubmitAnswer when the button is clicked', function() {
var buttonTag = TestUtils.findRenderedDOMComponentWithTag(button, 'button');
TestUtils.Simulate.click(buttonTag);
expect(onClickStub).toBeCalled();
});
});
});
My test for the parent component started out looking the same:
jest.dontMock('./parent-component');
describe('ParentComponent', function() {
var React = require('react/addons'),
ParentComponent = require('./parent-component'),
ChildButton = require('./child-button'),
TestUtils = React.addons.TestUtils;
describe('events', function() {
var parent,
onClickStub;
beforeEach(function() {
onClickStub = jest.genMockFn();
parent = TestUtils.renderIntoDocument(
<ParentComponent onClick={onClickStub} />
);
});
it('should call onButtonSubmit when a click is triggered', function() {
var childButton = TestUtils.findRenderedComponentWithType(parent, ChildButton);
TestUtils.Simulate.click(childButton);
expect(onClickStub).toBeCalled();
});
});
});
But this test fails. The only difference I can see between these two tests is that one uses an HTML tag directly and clicks on it, while the other triggers a click on a React component. Can I not use the click event on React components directly? Is my assumption correct?
And if so, is there a way to trigger a click on React components differently in the tests? I tried using SimulateNative but that had the same effect, the onClickStub doesn't get called on click.
There is currently an open bug for this issue: Let ReactTestUtils.Simulate.click work on non-dom components. So the answer is that due to bugs, you can only use Simulate.click on an actual DOM node. So you can workaround the bug by getting the DOM node until it is fixed.

How to best clone a class in ReactJS

I have a large ReactJS class which I'd like to clone. The scenario is that the original React class is in one batch of code, which I want to largely re-use in another - just with a few small changes. Ideally I was hoping I could do something like this:
var Element1 = React.createClass({
customMethod1: function() { ... },
customMethod2: function() { ... },
render: function () { ... }
});
// clone and override whatever we want
var Element2 = React.cloneClass(Component1);
Element2.customMethod1 = function () { ... };
// now we can use <Element2 />
Any idea?
Try using composition over cloning/inheritance. This is recommended approach with React.
Starting from React 0.12 the new API introduced React.createFactory.
var MyComponentClass = React.createClass(...);
var MyComponent = React.createFactory(MyComponentClass);
var MyOtherComponent = React.createClass({
render: function() {
return MyComponent({ prop: 'value' });
}
});
Another possible way of composition would be to pass react element into another component via refs: https://stackoverflow.com/a/25723635/540802

When should I use getInitialState in a React component

I have a React component that toggles a className when the component is clicked
var Foo = React.createClass({
getInitialState: function() {
return {className: ''}
},
render: function(){
var className = 'bar ' + this.state.className
return React.createElement('div', {className: className, onClick: this.onClick})
},
onClick: function() {
this.setState({className: 'baz'})
}
});
It works fine, but when I am rendering the app server side, I get the following error
Warning: getInitialState was defined on a component, a plain JavaScript class.
This is only supported for classes created using React.createClass.
Did you mean to define a state property instead?
My build step is setup like so
var Foo = require('./Foo');
var factory = React.createFactory(Foo);
module.exports = React.renderToString(factory({}));
Why is what I am doing wrong, and how should it be done?
I am not sure if this helps, but while using fluxible, this is the syntax i used with JSX as part of require component
var app = new Fluxible({
component: React.createFactory(require('./Components/startup.react.jsx'))
});

Resources