I have two components in React that render just fine and produce expected behavior in the browser, but can't seem to be rendered when running a test via Jest.
var React = require('react/addons');
var $ = require('jquery');
var Description = require('./description.js');
var Descriptions = React.createClass({
getInitialState: function () { //container always starts with at least one description field that is empty, or whatever is contained in props
var descriptions = [];
if (this.props.info == null) {
descriptions.push({num: 0, data: ''});
} else {
$.each(this.props.info, function (i, string) {
descriptions.push({num: i, data: string});
});
if (descriptions.length == 0) { //we want at least one description field at all times
descriptions.push({num: 0, data: ''});
}
}
return {descriptions: descriptions, focus: -1};
},
componentWillReceiveProps: function (nextProps) { //props are updated
var descriptions = []; //we don't care about previous values, so we will make a new list
$.each(nextProps.info, function (i, string) {
descriptions.push({num: i, data: string});
});
if (descriptions.length == 0) { //we want at least one description field at all times
descriptions.push({num: 0, data: ''});
}
this.setState({descriptions: descriptions});
},
addDescription: function (pos) { //adds a new description underneath the last one in the container
var descriptions = this.state.descriptions;
var max = 0;
$.each(descriptions, function (i, item) {
if (item.num > max) max = item.num;
});
descriptions.splice(pos + 1, 0, {num: max + 1, data: ''});
this.setState({descriptions: descriptions, focus: pos + 1}); //focus the new description
},
removeDescription: function (pos) { //remove a description from the array given its array position
var descriptions = this.state.descriptions;
if (descriptions.length != 1) {
descriptions.splice(pos, 1);
this.setState({descriptions: descriptions, focus: pos == 0 ? 0 : pos - 1});
}
},
render: function () {
var items = this.state.descriptions.map(function (item, i) { //add one Description for every item in the array
return (
<Description key={item.num} addDescription={this.addDescription}
removeDescription={this.removeDescription}
descriptionNum={i} focus={i == this.state.focus} data={item.data}/>
);
}.bind(this));
return (
<div className="descriptions">
{items}
</div>
);
}
});
module.exports = Descriptions;
Essentially, this component is a container for one or more child "Description" components, and the amount of "Description" components that need to be rendered depends on the passed info
prop, which holds an array of strings.
var React = require('react/addons');
var Description = React.createClass({
mixins: [React.addons.LinkedStateMixin],
componentDidMount: function () { //focus the input if it was added after page load
if (this.props.focus) {
this.refs.descInput.getDOMNode().focus();
}
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.focus) {
this.refs.descInput.getDOMNode().focus();
}
this.setState({description: nextProps.data});
},
getInitialState: function () {
return({description: this.props.data});
},
handleKeyDown: function (e) {
var key = e.keyCode;
if (key == 13) { //enter is pressed, we need to add a new line underneath this one
e.preventDefault();
this.props.addDescription(this.props.descriptionNum);
} else if (key == 8) { //backspace was pressed, check to see if line is empty and remove if so
var value = this.refs.descInput.getDOMNode().value;
if (value == null || value == '') {
e.preventDefault();
this.props.removeDescription(this.props.descriptionNum);
}
}
},
render: function () {
return (
<div className="description">
<input type="text" onKeyDown={this.handleKeyDown} valueLink={this.linkState('description')} ref="descInput"/>
</div>
)
}
});
module.exports = Description;
This component receives a string (or nothing), sets its state to contain that string, and uses the LinkedStateMixin
to update the state whenever the value of the input changes, and vice-versa.
I thought I had no issues with these components, but the following Jest test...
jest.dontMock('../js/descriptions.js');
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
describe('Descriptions', function () {
it('creates exactly two Description components when given a string array of length 2', function() {
jest.dontMock('../js/description.js');
var Description = require('../js/description.js');
var info = ['foo','bar'];
var Descriptions = require('../js/descriptions.js');
var descriptions = TestUtils.renderIntoDocument(<Descriptions info={info}/>);
var array = TestUtils.scryRenderedComponentsWithType(descriptions, Description);
expect(array.length).toEqual(2);
});
});
...fails with the following error:
● Descriptions › it mocks Description exactly twice when given info array of length 2
- Error: Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. This usually means that you're trying to add a ref to a component that doesn't have an owner (that is, was not created inside of another component's `render` method). Try rendering this component inside of a new top-level component which will hold the ref.
...on this line:
var descriptions = TestUtils.renderIntoDocument(<Descriptions info={info}/>);
This makes no sense to me as the components render fine in the browser without any issues. It seems to only break when React's TestUtils attempts to do it.
Here are my dependencies:
"dependencies": {
"jquery": "^2.1.4",
"react": "^0.13.3",
"react-tools": "^0.13.3"
},
"devDependencies": {
"browserify": "^10.2.1",
"gulp": "^3.8.11",
"gulp-react": "^3.0.1",
"gulp-shell": "^0.4.1",
"gulp-streamify": "0.0.5",
"gulp-uglify": "~1.1.0",
"jest-cli": "^0.4.5",
"node-libs-browser": "^0.5.2",
"reactify": "^1.1.1",
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.2.1",
"webpack": "^1.9.10"
}
Does anyone know what might be causing this error?
Move the require
out of the test function.
jest.dontMock('../js/descriptions.js');
var React = require('react/addons');
var Description = require('../js/description.js');
var Descriptions = require('../js/descriptions.js');
describe('Descriptions', function () {
it('creates exactly two Description components when given a string array of length 2', function() {
var TestUtils = React.addons.TestUtils;
var info = ['foo','bar'];
var descriptions = TestUtils.renderIntoDocument(<Descriptions info={info}/>);
var array = TestUtils.scryRenderedComponentsWithType(descriptions, Description);
expect(array.length).toEqual(2);
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With