2

I have a React class like so

var React = require('react'),
    $ = require('jquery'),
    AccordionStep = require('./accordion-step');

var Accordion = React.createClass({

  toggleFooter: function(e) {
    var $target = $(e.target),
        $parent = $target.parents('.project-container');

    $target.toggleClass('up down');
    $parent.find('.project-accordion').toggleClass('active');
  },

  render: function() {
    return (
      <div>
        <ul className="project-accordion">
          {this.props.steps.map(function(step, i){
            return <AccordionStep step={step} key={i}/>
          })}
        </ul>
        <span className="footer-info">{this.props.completedSteps} of {this.props.totalSteps} steps completed</span>
        <span className="arrow down mt1" onClick={this.toggleFooter}></span>
      </div>
    )
  }
});

module.exports = Accordion;

and I also have a Jest test file that is checking whether the class was toggled correcly when the arrow was clicked.

jest.dontMock('../../../dashboard/projects/accordion')
    .dontMock('../../fixtures/projects.fixtures')
    .dontMock('jquery');

describe('Accordion', function() {
  var $ = require('jquery'),
      React = require('react/addons'),
      Accordion = require('../../../dashboard/projects/accordion'),
      AccordionStep = require('../../../dashboard/projects/accordion-step'),
      ProjectFixtures = require('../../fixtures/projects.fixtures'),
      TestUtils = React.addons.TestUtils;

  describe('render', function() {
    var accordion,
        projectFixtures = ProjectFixtures().projects[0],
        steps = projectFixtures.steps;

    beforeEach(function() {
      accordion = TestUtils.renderIntoDocument(
        <div className="project-container">
          <Accordion steps={steps} 
                     completedSteps={projectFixtures.completedSteps} 
                     totalSteps={projectFixtures.totalSteps} />
        </div>
      );
    });

    describe('clicking the arrow icon', function(){
      var arrow;

      beforeEach(function() {
        arrow = TestUtils.findRenderedDOMComponentWithClass(accordion, 'arrow');
      });

      it('should change the arrow to point up when it was clicked', function() {
        TestUtils.Simulate.click(arrow);

        expect(arrow.props.className).toEqual('arrow up mt1');
      });

    });

  });

});

Now I've been console logging what happens in the toggleFooter function and I know that the function works correctly. The classes are toggled but the test still fails.

Any ideas how I can test the behaviour of the button? I imagine that the issue is something to do with how the test actually renders the test DOM. I've tried using jQuery to assert that the classes are toggled correctly but this also doesn't work (I assume because of the reason above).

Any help would be really appreciated.

4

1 回答 1

2

You are editing the className attribute of the .arrow dom node directly via $target.toggleClass('up down');, but not the props of the ReactComponent that spawned it.

In your code, arrow is a ReactComponent, not a DOM Node. To find the DOM Node of a ReactComponent, use React.findDOMNode(component).

The React way would be to ditch jQuery for such a trivial task and instead update your component state with a flag that indicates if the accordion is active.

getInitialState: function() {
  return {
    accordionActive: false
  };
},

toggleFooter: function(e) {
  this.setState({
    accordionActive: !this.state.accordionActive
  });
},

render: function() {
  return (
    <div>
      <ul className={'project-accordion' + (this.state.accordionActive ? ' active' : '')}>
        {this.props.steps.map(function(step, i){
          return <AccordionStep step={step} key={i}/>
        })}
      </ul>
      <span className="footer-info">{this.props.completedSteps} of {this.props.totalSteps} steps completed</span>
      <span className={'arrow mt1 ' + (this.state.accordionActive ? 'up' : 'down')} onClick={this.toggleFooter}></span>
    </div>
  );
}

Using the classNames module, you can replace those ugly className computation with

className={classNames('project-accordion', this.state.accordionActive && 'active')}

and

className={classNames('arrow mt1', this.state.accordionActive ? 'up' : 'down')}

The value of arrow.props.className should then always be either arrow mt1 up or arrow mt1 down.

于 2015-04-15T15:34:21.227 回答