如何在测试期间设置 React 组件的宽度? [英] How to set the width of a React component during test?

查看:29
本文介绍了如何在测试期间设置 React 组件的宽度?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

这个滑块组件的宽度是可变的.当您单击滑块的轨道"时,它应该更改值并触发 onChange 回调.该值基于您在轨道上单击的位置.如果您在最小值为 100 且最大值为 200 时单击中间点,则应报告值 150.

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

这里是组件 Slider.js

import React, {PropTypes} from 'react';从 'react-dom' 导入 ReactDOM;从 'lodash' 导入 { noop };从'./style.scss'导入样式;导出默认类 Slider 扩展 React.Component {使成为() {返回 (

<div className='track'></div>

);}句柄点击(e){让 node = ReactDOM.findDOMNode(this);让{clientX,clientY} = e;让 {offsetLeft,offsetWidth,clientWidth} = 节点;让 xPercent = (clientX - offsetLeft)/offsetWidth;console.log(offsetLeft, offsetWidth, clientWidth, xPercent);this.props.onChange(normalize(xPercent, this.props.min, this.props.max));}计算左(){让分子 = this.props.value - this.props.min;让分母 = this.props.max - this.props.min;返回分子/分母 * 100;}}//属性类型//-----------------------------------------------------------------------------Slider.propTypes = {//当值改变时回调.onChange: PropTypes.func,//滑块位于 0% 时的值min: PropTypes.number,//滑块处于 100% 时的值最大值:PropTypes.number,//起始值值:验证值,}Slider.defaultProps = {onChange:noop,分钟:0,最大:100,}//自定义验证//-----------------------------------------------------------------------------函数验证值(道具,道具名称,组件名称){让值 = props[propName];if (typeof(value) !== 'number') {return new Error(`value must be a number, got ${typeof(value)}`);}if (value > props.max || value < props.min) {返回新错误(`value: ${value} 必须在 max: ${props.max} 之间和分钟:${props.min}`);}}//帮手//---------------------------------------------------------------------------函数归一化(浮点值,最小值,最大值){让范围 = 最大值 - 最小值;让 normalizedValue = floatValue * range + min;//巧妙地将值限制在最小值和最大值之间返回 [min, normalizedValue, max].sort()[1];}

样式表(style.scss):

.Slider {位置:相对;显示:块;宽度:100px;.追踪 {高度:4px;背景:#666;边界半径:2px;}.处理 {宽度:12px;高度:12px;背景:#fff;边框半径:10px;位置:绝对;顶部:50%;变换:翻译(-50%,-50%);过渡:左 100ms 线性;}}

这是我的测试:

import Slider from './Slider';从反应"导入反应;进口 {渲染到文档,findRenderedDOMComponentWithClass,findRenderedDOMComponentWithTag,模拟来自'react-addons-test-utils';描述('滑块',函数(){描述('点击',功能(){it('触发 onChange 回调', function() {const onChange = sinon.spy();const 组件 = renderIntoDocument(<滑块样式={{宽度:100,高度:40}}分钟={100}最大值={200}价值={150}onChange={onChange}/>);const track = findRenderedDOMComponentWithClass(component, 'track');Simulate.click(track, {clientY: 0, clientX: 10})期望(onChange).to.have.been.CallWith(110);});});});

测试输出

LOG LOG: 0, 0, 0, Infinity点击✗ 触发 onChange 回调断言错误:预期已使用参数 10 调用 onChangeonChange(200)在/components/Slider/test.js:99 <webpack:///src/components/Slider/test.js:55:6

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

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

TLDR

在测试期间渲染组件时如何使组件具有宽度?

解决方案

今天我自己也遇到了同样的问题 - 我正在构建一个组件,该组件将根据元素的大小缩放其文本大小.因为 renderIntoDocument 将您的组件放置在分离的 DOM 节点中,所以无法计算 offsetWidth、clientWidth 等.

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

React.render(, document.body);

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

beforeEach(function() {this.iframe = document.createElement('iframe');document.body.appendChild(this.iframe);});React.render(, this.iframe.contentDocument.body);afterEach(函数(){document.body.removeChild(this.iframe);});

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

I'm trying to test a slider component.

This slider component can be variable in width. When you click on the "track" of the slider it should change the value and trigger an onChange callback. The value is a based on where you click on the track. If you click the halfway point when the min value is 100 and the max value is 200, then it should report a value of 150.

The problem I'm running into is that when I render the component using ReactTest.renderIntoDocument the component doesn't have any width, so it can't calculate a new value when you click on it.

Here is the component 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];
}

Stylesheet (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;
  }
}

Here is my test:

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

Test output

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

Those log statements are from the handleClick() function in the component.

The width is zero so the denominator ends up being zero when calculating xPercent, which causes it to be Infinity. This causes it to just use the max value of 200.

TLDR

How do I make the component have width when rendering it during a test?

解决方案

I've been fighting the same problem myself today - I'm building a component that will scale its text size based on the size of the element. Because renderIntoDocument places your component inside a detached DOM node, it isn't possible to calculate offsetWidth, clientWidth, etc.

Are you testing in a browser or node.js? (EDIT: I see you tagged the question PhantomJS so I'm guessing browser!) If you're in a browser you may be able to render the component into the DOM for real:

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

If you're worried about test isolation, you can create an IFrame to render the component into, and clean that up afterwards:

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

Then call this.iframe.contentDocument.body.querySelectorAll('.track') to get the HTML Element and run your assertions against it (This is a plain HTML element, not a React component, so use the standard APIs to query it).

这篇关于如何在测试期间设置 React 组件的宽度?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆