Reactive components render correctly in the browser, but Jest test errors when rendering: "Only ReactOwner can have refs"

I have two components in React that perfectly display and produce the expected behavior in the browser, but cannot display when the test runs through Jest.

descriptions.js

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 components of the Description, and the number of Description components that need to be visualized depends on the transmitted info prop, which contains an array of strings.

description.js

 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 this string, and uses LinkedStateMixin to update the state whenever the input value changes and vice versa.

I thought I had no problems with these components, but the next Jest test ...

Descriptions-test.js

 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 `render` method). Try rendering this component inside of a new top-level component which will hold the ref. 

... in this line:

 var descriptions = TestUtils.renderIntoDocument(<Descriptions info={info}/>); 

This does not make any sense to me, since the components are not displayed in the browser without any problems. It only seems to break when React TestUtils tries to do this.

Here are my dependencies:

package.json

 "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 cause this error?

+6
source share
1 answer

Extract require from 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); }); }); 
+4
source

Source: https://habr.com/ru/post/988192/


All Articles