React 组件在浏览器中正确呈现,但 Jest 在呈现时测试错误:"Only a ReactOwner can have refs"

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

我在 React 中有两个组件可以很好地呈现并在浏览器中产生预期的行为,但是当 运行 通过 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;

本质上,该组件是一个或多个子“Description”组件的容器,需要渲染的“Description”组件的数量取决于传递的infoprop,它持有一个数组字符串。

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;

此组件接收一个字符串(或什么都没有),将其状态设置为包含该字符串,并在输入值发生变化时使用 LinkedStateMixin 更新状态,反之亦然。

我以为这些组件没有问题,但是下面的 Jest 测试...

描述-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);
    });
});

...失败并出现以下错误:

● 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.

...在这一行:

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

这对我来说毫无意义,因为组件在浏览器中呈现良好,没有任何问题。它似乎只有在 React 的 TestUtils 尝试这样做时才会中断。

这是我的依赖项:

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"
}

有谁知道可能导致此错误的原因吗?

require 移出测试函数。

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);
    });
});