如何在测试期间设置React组件的宽度?

时间:2015-10-25 03:33:18

标签: testing reactjs phantomjs

我正在尝试测试滑块组件。

此滑块组件的宽度可以变化。当您单击滑块的“轨道”时,它应更改值并触发onChange回调。该值基于您在轨道上单击的位置。如果您在最小值 100 时单击中间点,并且最大值 200 ,则应报告 150 的值。< / p>

我遇到的问题是,当我使用ReactTest.renderIntoDocument渲染组件时,组件没有任何宽度,因此当您单击它时它无法计算新值。

这是组件Slider.js

import React, {PropTypes} from 'react';
import ReactDOM from 'react-dom';
import { noop } from 'lodash';
import style from './style.scss';

export default class Slider extends React.Component {
  render() {
    return (
      <div
        className='Slider'
        onClick={this.handleClick.bind(this)}
        {...this.props}
      >
        <div
          className='handle'
          style={{left: `${this.calculateLeft()}%`}}>
        </div>
        <div className='track'></div>
      </div>
    );
  }

  handleClick(e) {
    let node = ReactDOM.findDOMNode(this);
    let {clientX, clientY} = e;
    let {offsetLeft, offsetWidth, clientWidth} = node;
    let xPercent = (clientX - offsetLeft) / offsetWidth;
    console.log(offsetLeft, offsetWidth, clientWidth, xPercent);
    this.props.onChange(normalize(xPercent, this.props.min, this.props.max));
  }

  calculateLeft() {
    let numerator = this.props.value - this.props.min;
    let denominator = this.props.max - this.props.min;
    return numerator / denominator * 100;
  }
}

// Proptypes
// ----------------------------------------------------------------------------
Slider.propTypes = {
  // Callback for when the value changes.
  onChange: PropTypes.func,
  // The value for when the slider is at 0%
  min: PropTypes.number,
  // The value for when the slider is at 100%
  max: PropTypes.number,
  // The starting value
  value: validateValue,
}

Slider.defaultProps = {
  onChange: noop,
  min: 0,
  max: 100,
}

// Custom Validation
// ----------------------------------------------------------------------------
function validateValue(props, propName, componentName) {
  let value = props[propName];

  if (typeof(value) !== 'number') {
    return new Error(`value must be a number, got ${typeof(value)}`);
  }

  if (value > props.max || value < props.min) {
    return new Error(
      `value: ${value} must be between max: ${props.max}
      and min: ${props.min}`
    );
  }
}

// Helpers
// ---------------------------------------------------------------------------

function normalize(floatValue, min, max) {
  let range = max - min;
  let normalizedValue = floatValue * range + min;
  // cleverly restrict the value be between the min and max
  return [min, normalizedValue, max].sort()[1];
}

样式表(style.scss):

.Slider {
  position: relative;
  display: block;
  width: 100px;

  .track {
    height: 4px;
    background: #666;
    border-radius: 2px;
  }

  .handle {
    width: 12px;
    height: 12px;
    background: #fff;
    border-radius: 10px;
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%);
    transition: left 100ms linear;
  }
}

这是我的测试:

import Slider from './Slider';
import React from 'react';
import {
  renderIntoDocument,
  findRenderedDOMComponentWithClass,
  findRenderedDOMComponentWithTag,
  Simulate
} from 'react-addons-test-utils';

describe('Slider', function() {

  describe('click', function() {
    it('triggers the onChange callback', function() {
      const onChange = sinon.spy();
      const component = renderIntoDocument(
        <Slider
          style={{width: 100, height: 40}}
          min={100}
          max={200}
          value={150}
          onChange={onChange}
        />
      );

      const track = findRenderedDOMComponentWithClass(component, 'track');

      Simulate.click(track, {clientY: 0, clientX: 10})
      expect(onChange).to.have.been.calledWith(110);
    });
  });
});

测试输出

LOG LOG: 0, 0, 0, Infinity
click
  ✗ triggers the onChange callback
AssertionError: expected onChange to have been called with arguments 10
    onChange(200)

    at /components/Slider/test.js:99 < webpack:///src/components/Slider/test.js:55:6

这些日志语句来自组件中的handleClick()函数。

宽度为零,因此分母在计算xPercent时最终为零,这导致它为无穷大。这导致它只使用max值200。

TLDR

在测试过程中如何使组件具有宽度?

2 个答案:

答案 0 :(得分:9)

我自己今天一直在解决同样的问题 - 我正在构建一个基于元素大小来扩展文本大小的组件。因为renderIntoDocument将组件放在一个分离的DOM节点中,所以无法计算offsetWidth,clientWidth等。

您是在浏览器或node.js中进行测试吗? (编辑:我看到你标记了PhantomJS的问题,所以我猜测浏览器!)如果你在浏览器中,你可以将组件渲染到DOM中:

React.render(<Slider />, document.body);

如果您担心测试隔离,可以创建一个IFrame来渲染组件,然后清理它:

beforeEach(function() {
    this.iframe = document.createElement('iframe');
    document.body.appendChild(this.iframe);
});

React.render(<Slider />, this.iframe.contentDocument.body);

afterEach(function() {
    document.body.removeChild(this.iframe);
});

然后调用this.iframe.contentDocument.body.querySelectorAll('.track')获取HTML元素并对其运行断言(这是一个纯HTML元素,而不是React组件,因此请使用标准API进行查询)。

答案 1 :(得分:0)

这是一个例子。 React 0.14警告将文档渲染到正文中。 就像马特所说的那样,我们需要追加&#39; div&#39; div在iframe中防止此类错误。

describe('html tooltip utility class', function() {

let iframe;
let div;

beforeEach(() => {
    iframe = document.createElement('iframe');
    document.body.appendChild(iframe);
    div = document.createElement('div');
});


it('returns true if text overflows', () => {
    // jshint ignore:start
    let style = {
        width: 5
    };
    let data = 'hello this is a long text.';
    iframe.contentDocument.body.appendChild(div);
    ReactDOM.render(<div style={style}>{data}</div>, div);

    // jshint ignore:end
    let textNode = div.querySelectorAll('div')[0];

    expect(HTMLTooltip.showTooltip(textNode)).to.be.true;
});

afterEach(() => {
    document.body.removeChild(iframe);
});
});