34

https://github.com/JedWatson/react-select

我想使用 React-Select 反应组件,但我需要添加测试。

我已经尝试了几个在谷歌上找到的选项,但似乎没有任何效果。我有下面的代码,但它不会导致更改事件。我已经能够添加一个焦点事件,它添加了 'is-focussed' 类,但仍然缺少 'is-open' 类。

我用过:https ://github.com/JedWatson/react-select/blob/master/test/Select-test.js作为参考

我曾尝试仅在输入字段上使用更改事件,但这也无济于事。我注意到有一个onInputChange={this.change}选择。

测试

import Home from '../../src/components/home';
import { mount } from 'enzyme'

describe('Home', () => {

it("renders home", () => {

    const component = mount(<Home/>);

    // default class on .Select div
    // "Select foobar Select--single is-searchable"

    const select = component.find('.Select');

    // After focus event
    // "Select foobar Select--single is-searchable is-focussed"
    // missing is-open
    TestUtils.Simulate.focus(select.find('input'));

    //this is not working
    TestUtils.Simulate.keyDown(select.find('.Select-control'), { keyCode: 40, key: 'ArrowDown' });
    TestUtils.Simulate.keyDown(select.find('.Select-control'), { keyCode: 13, key: 'Enter' });

    // as per code below I expect the h2 to have the select value in it eg 'feaure'

});
});

被测组件

import React, { Component } from 'react';
import Select from 'react-select';

class Home extends Component {
constructor(props) {
    super(props);

    this.state = {
        message: "Please select option"};
    this.change = this.change.bind(this);
}

change(event) {

    if(event.value) {
        this.setState({message: event.label});
    }
}

render () {

    const options = [ {label: 'bug', value: 1} , {label: 'feature', value: 2 }, {label: 'documents', value: 3}, {label: 'discussion', value: 4}];

    return (
      <div className='content xs-full-height'>
          <div>
              <h2>{this.state.message}</h2>

              <Select
                  name="select"
                  value={this.state.message}
                  options={options}
                  onInputChange={this.change}
                  onChange={this.change}
              />

          </div>
        </div>
    );
}
}

export default Home;

命令行 要运行测试,我会:

>> npm run test

在 package.js 我有这个脚本:

"test": "mocha --compilers js:babel-core/register -w test/browser.js ./new",

测试设置

和 browser.js 是:

import 'babel-register';
import jsdom from 'jsdom';

const exposedProperties = ['window', 'navigator', 'document'];

global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
   if (typeof global[property] === 'undefined') {
       exposedProperties.push(property);
       global[property] = document.defaultView[property];
   }
});

global.navigator = {
    userAgent: 'node.js'
};

我也尝试过使用此处概述的测​​试方法:https ://github.com/StephenGrider/ReduxSimpleStarter

任何帮助将不胜感激

4

14 回答 14

23

来自https://github.com/JedWatson/react-select/issues/1357

我发现的唯一解决方案是通过按键事件模拟​​选择:

wrapper.find('.Select-control').simulate('keyDown', { keyCode: 40 });
// you can use 'input' instead of '.Select-control'
wrapper.find('.Select-control').simulate('keyDown', { keyCode: 13 });
expect(size).to.eql('your first value in the list')
于 2017-09-13T15:34:04.307 回答
22

我已经尝试了上面列出的两个答案,但仍然没有运气。

对我有用的是:

  1. 添加classNamePrefix道具 - 即list(如其他答案中所述):

    <Select
       classNamePrefix='list'
       options={[
         { label: 'one', value: 'one' },
         { label: 'two', value: 'two' }
    ]}/>
    
  2. 选择下拉指示器并模拟 mouseDown => 打开的下拉列表:

    wrapper
      .find('.list__dropdown-indicator')
      .simulate('mouseDown', {
        button: 0 
      });
    
  3. 期望事情会发生,即在我的情况下,我正在检查下拉选项的数量

    expect(wrapper.find('.list__option').length).toEqual(2);
    

    如果您可以控制正在发送的道具,则可以添加一个menuIsOpen道具以始终打开菜单(也就是列表中的第 2 步)。

要从下拉列表中选择一个值,请在打开下拉列表后:

wrapper.find('.list__option').last().simulate('click', null);

然后你可以测试:

expect(wrapper.find('.list__value-container').text()).toEqual('two');

或者

expect(wrapper.find('.list__single-value').text()).toEqual('two');
于 2018-10-24T20:40:43.583 回答
19

这是一个反复出现的问题。我正在与 100% 通过测试共享我自己的代码,这些测试涵盖了我 100% 的源代码。

我的组件看起来像这样

MySelectComponent({ options, onChange }) {

  return <div data-testid="my-select-component">
    <Select
      className="basic-single"
      classNamePrefix="select"
      name="myOptions"
      placeholder="Select an option"
      options={options}
      onChange={e => onChange(e)}
    />
</div>;
}

我在Selectwith上添加包装器的原因data-testid="my-select-component"是渲染的选项元素将可用,否则我无法检查是否存在文本选项(当你看到我的测试时你会更好地理解)。

这是一个实时运行的示例,在渲染时会显示一个带有 10 个选项的选择组件。

在此处输入图像描述

测试 1:应该没有错误地呈现

  • 我渲染组件。

  • 我搜索存在的占位符。

测试 2:选择第一个选项时应该调用 onChange

  • 我渲染组件。

  • 我检查我mockedOnChange的是否还没有被调用。

  • 模拟一个ArrowDown事件。

  • 单击第一个选项。

  • 我检查是否mockedOnChange使用第一个选项标签和值调用了 1 次。

测试3:应在选择第一个选项时调用Onchange,然后第二个选项,然后是第9个选项

  • 我渲染组件。

  • 我模拟了第一个选项的选择。

  • 我模拟了第二个选项的选择。

  • 我模拟了第 9 个选项的选择。

  • 我检查是否mockedOnChange使用第 9 个选项包和值调用了 3 次。

测试 4:按输入值过滤时应调用 onChange

  • 我渲染组件。

  • 我通过键入“选项 1”来模拟输入字段的更改。

  • 我知道,根据我mockedOptions的过滤结果将是“Mocked option 1”和“Mocked option 10”。

  • 我模拟了 2 个ArrowDown事件。

  • 我检查是否mockedOnChange使用具有正确标签和值的第二个过滤选项调用了。

完整的测试文件

import React from 'react';
import { render, fireEvent, cleanup, waitForElement } from '@testing-library/react';
import MySelectComponent from './MySelectComponent';

afterEach(cleanup);

describe ('Test react-select component', () => {

    const mockedOptions = [
        {label: 'Mocked option 1', value: 'mocked-option-1'},
        {label: 'Mocked option 2', value: 'mocked-option-2'},
        {label: 'Mocked option 3', value: 'mocked-option-3'},
        {label: 'Mocked option 4', value: 'mocked-option-4'},
        {label: 'Mocked option 5', value: 'mocked-option-5'},
        {label: 'Mocked option 6', value: 'mocked-option-6'},
        {label: 'Mocked option 7', value: 'mocked-option-7'},
        {label: 'Mocked option 8', value: 'mocked-option-8'},
        {label: 'Mocked option 9', value: 'mocked-option-9'},
        {label: 'Mocked option 10', value: 'mocked-option-10'},
    ];

    it('should render without errors', async () => {
        const mockedOnChange = jest.fn();
        const { getByText } = render(<MySelectComponent 
            options={mockedOptions} 
            onChange={mockedOnChange} />);

        const placeholder = getByText('Select an option');

        expect(placeholder).toBeTruthy();
    });

    it('should call onChange when the first option is selected', async () => {
        const mockedOnChange = jest.fn();
        const { getByText, queryByTestId } = render(<MySelectComponent 
            options={mockedOptions} 
            onChange={mockedOnChange} />);

        const mySelectComponent = queryByTestId('my-select-component');

        expect(mySelectComponent).toBeDefined();
        expect(mySelectComponent).not.toBeNull();
        expect(mockedOnChange).toHaveBeenCalledTimes(0);

        fireEvent.keyDown(mySelectComponent.firstChild, { key: 'ArrowDown' });
        await waitForElement(() => getByText('Mocked option 1'));
        fireEvent.click(getByText('Mocked option 1'));

        expect(mockedOnChange).toHaveBeenCalledTimes(1);
        expect(mockedOnChange).toHaveBeenCalledWith({label: 'Mocked option 1', value: 'mocked-option-1'});

    });

    it('should call onChange when the first option is selected then second option then the 9th one', async () => {
        const mockedOnChange = jest.fn();
        const { getByText, queryByTestId } = render(<MySelectComponent 
            options={mockedOptions} 
            onChange={mockedOnChange} />);

        const mySelectComponent = queryByTestId('my-select-component');

        expect(mySelectComponent).toBeDefined();
        expect(mySelectComponent).not.toBeNull();
        expect(mockedOnChange).toHaveBeenCalledTimes(0);

        fireEvent.keyDown(mySelectComponent.firstChild, { key: 'ArrowDown' });
        await waitForElement(() => getByText('Mocked option 1'));
        fireEvent.click(getByText('Mocked option 1'));

        fireEvent.keyDown(mySelectComponent.firstChild, { key: 'ArrowDown' });
        await waitForElement(() => getByText('Mocked option 2'));
        fireEvent.click(getByText('Mocked option 2'));

        fireEvent.keyDown(mySelectComponent.firstChild, { key: 'ArrowDown' });
        await waitForElement(() => getByText('Mocked option 9'));
        fireEvent.click(getByText('Mocked option 9'));

        expect(mockedOnChange).toHaveBeenCalledTimes(3);
        expect(mockedOnChange).toHaveBeenCalledWith({label: 'Mocked option 9', value: 'mocked-option-9'});
    });

    it('should call onChange when filtering by input value', async () => {
      const mockedOnChange = jest.fn();
      const { getByText, queryByTestId, container } = render(<MySelectComponent 
        options={mockedOptions} 
        onChange={mockedOnChange} />);

        const mySelectComponent = queryByTestId('my-select-component');

        fireEvent.change(container.querySelector('input'), {
            target: { value: 'option 1' },
        });

        // select Mocked option 1
        fireEvent.keyDown(mySelectComponent.firstChild, { key: 'ArrowDown' });  
        // select Mocked option 10
        fireEvent.keyDown(mySelectComponent.firstChild, { key: 'ArrowDown' });

        await waitForElement(() => getByText('Mocked option 10'));
        fireEvent.click(getByText('Mocked option 10'));

        expect(mockedOnChange).toHaveBeenCalledTimes(1);
        expect(mockedOnChange).toHaveBeenCalledWith({label: 'Mocked option 10', value: 'mocked-option-10'});
    });

});

我希望这会有所帮助。

于 2020-05-01T22:42:40.330 回答
10

使用测试库和 v2.0

试图避免使用任何非常具体的东西,classNamePrefix或者通过寻找 onChange 道具或其他任何东西来侵入组件的运行方式。

const callback = jest.fn();
const { container, getByText} = render(<Select ... onChange={callback} />);

现在我们基本上假装是屏幕阅读器和焦点,然后按向下箭头。

fireEvent.focus(container.querySelector('input'));
fireEvent.keyDown(container.querySelector('input'), { key: 'ArrowDown', code: 40 });

现在点击你想要的值

fireEvent.click(getByText('Option Two'));

并断言。

expect(callback).toHaveBeenCalledWith({ value: 'two', label: 'Option Two'});
于 2019-08-28T19:39:11.983 回答
5

为了补充基思所说的,使用模拟方法似乎是行使功能的唯一方法。但是,当我在我的解决方案中尝试此操作时,它不起作用 - 虽然我使用的是 Typescript,所以不确定这是否有影响,但我发现在模拟事件时还需要传递key属性:

wrapper.find('.Select-control').simulate('keyDown', { key: 'ArrowDown', keyCode: 40 });
wrapper.find('.Select-control').simulate('keyDown', { key: 'Enter', keyCode: 13 });

我还发现设置classNamePrefix属性可以更轻松地进行简单测试,让我确信组件正确响应了模拟事件。设置此前缀时,组件的有用部分会使用类名进行修饰,从而可以轻松访问它们(您可以通过检查 google 开发工具中的元素来识别这些有用的类名)。一个简单的 Jest 测试:

it('react-select will respond to events correctly', () => {
    const sut = Enzyme.mount(
    <Select 
        classNamePrefix="list" 
        options={[{ label: 'item 1', value: 1 }]}
    />);

    // The intereactive element uses the suffix __control **note there are two underscores** 
    sut.find('.list__control').first().simulate('keyDown', { key: 'ArrowDown', keyCode: 40 });
    sut.find('.list__control').first().simulate('keyDown', { key: 'Enter', keyCode: 13 });

    // the selected value uses the suffix __single-value **note there are two underscores** 
    expect(sut.find('.list__single-value').first().text()).toEqual('item 1');
});
于 2018-10-02T21:33:51.190 回答
4

对于较新版本的 react-select (2.x+),上述方法不起作用,因为 react-select 使用情感。因此,wrapper.find('.Select-control')或者wrapper.find('.list__option')不再有效。react-select 2.x+ 将Select组件包装在状态管理器中,但您可以访问组件的onChange方法Select。我使用以下代码触发选择:

wrapper.find(Select).props().onChange({ value: ... })
于 2019-03-13T04:49:05.683 回答
2

使用反应测试库:

<Select id='myId' onChange=(list: ReactSelectOption[]) => {
                        props.changeGroupItem(
                            {
                                items: list ? list.map((item) => item.value) : [],
                            }
                        );
                    }
/>

然后在我的测试中

const selectInput = container.querySelector(`#myId input`) as HTMLInputElement;
    fireEvent.focus(selectInput);
    fireEvent.mouseDown(selectInput);
    fireEvent.click(getByText("myValue"));
    expect(props.changeGroupItem).toHaveBeenCalledWith(
        {
            items: ["myDefaultValue", "myValue"],
        }
    );
于 2020-10-08T10:25:51.970 回答
2

有一个库可以在 react-select 元素上模拟用户事件,用于 react-testing-library。适用于反应选择 2+。

https://www.npmjs.com/package/react-select-event

像这样:

const { getByRole, getByLabelText } = render(
  <form role="form">
    <label htmlFor="food">Food</label>
    <Select options={OPTIONS} name="food" inputId="food" isMulti />
  </form>
);
expect(getByRole("form")).toHaveFormValues({ food: "" });
 
await selectEvent.select(getByLabelText("Food"), ["Strawberry", "Mango"]);
expect(getByRole("form")).toHaveFormValues({ food: ["strawberry", "mango"] });
 
await selectEvent.select(getByLabelText("Food"), "Chocolate");
expect(getByRole("form")).toHaveFormValues({
  food: ["strawberry", "mango", "chocolate"],
});
于 2020-10-14T09:26:30.133 回答
2

只是想添加,我实际上必须classNamePrefix道具添加到Select组件中,否则我没有任何*__control类可以锁定。

于 2018-10-19T03:16:32.857 回答
1

如果有人使用酶,这对我有用:

  wrapper.find('Select').simulate('change', {
    target: { name: 'select', value: 1 },
  });

其中“select”是此处定义的属性名称:

  <Select
      name="select"
      ...

“价值”是所需选项的价值。

于 2019-05-14T21:47:17.943 回答
1

我在这里尝试了所有解决方案 - 对我没有任何帮助。

我能够解决用户事件库的问题。签出selectOptions功能。

于 2021-01-11T15:47:35.197 回答
1
wrapper.find(ReactSelectComponent.instance().onChange(...params));
于 2020-08-05T17:27:57.797 回答
1

对于那些使用酶 v3.11.0 和 react-select v3.0.8 的人来说,这对我有用

component.find('Select').simulate('keyDown', { key: 'ArrowDown', keyCode: 40 });

这是我的Select。我一起使用它redux-form

<Select
    {...input}
    styles={customStyles}
    options={props.options}
    formatGroupLabel={formatGroupLabel}
    placeholder="Custom Search..."
    isSearchable={true}
    onBlur={handleBlur}
/>

测试样本

it('should render dropdown on keyDown', () => {
    expect(component.find('MenuList').length).toEqual(1);
});

it('should render the correct amount of options', () => {
    expect(component.find('Option').length).toEqual(optionsLength);
});
于 2020-02-25T16:20:44.023 回答
0

https://stackoverflow.com/a/57699061/10786373几乎对我有用。我刚刚添加了用于打开选择菜单的 keyDown 事件。

it('my test', () => {
  const { container } = getShallow();
  const elSelector = container.querySelector('.Select-input');

  expect(propsComponent.onPageSizeChange).toHaveBeenCalledTimes(0);

  // open select menu
  fireEvent.keyDown(elSelector, { keyCode: 13 });

  // choose next option
  fireEvent.keyDown(elSelector, { key: 'ArrowDown', code: 40 });

  // send the option
  fireEvent.keyDown(elSelector, { keyCode: 13 });

  expect(propsComponent.onPageSizeChange).toHaveBeenCalledTimes(1);
});
于 2020-07-28T10:27:17.380 回答