Тестирование компонента React Select
https://github.com/JedWatson/react-select
Я хотел бы использовать React-Select реагирующий компонент, но мне нужно добавить тесты.
Я пробовал несколько вариантов, которые я нашел с Google, но, похоже, ничего не работает. У меня есть код ниже, но он не вызывает событие изменения. Мне удалось добавить событие фокуса, которое добавляет класс "is-focus", но класс "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
Любая помощь будет оценена
17 ответов
Это повторяющийся вопрос. Я делюсь своим кодом со 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>;
}
Причина, по которой я добавляю оболочку на свой Select
с участием data-testid="my-select-component"
заключается в том, что на нем будет доступен отображаемый элемент параметров, иначе я не могу проверить, существует ли текстовый параметр (вы поймете лучше, когда увидите мои тесты).
Это рабочий пример, и при рендеринге он покажет компонент выбора с 10 вариантами.
Тест 1: должен отображаться без ошибок
Я визуализирую компонент.
Я ищу заполнитель.
Тест 2: должен вызывать onChange при выборе первого варианта
Я визуализирую компонент.
Я проверяю, если мой
mockedOnChange
еще не называется.Смоделировать
ArrowDown
событие.Щелкните по первому варианту.
Я проверяю, если
mockedOnChange
вызывается 1 раз с меткой и значением 1-й опции.
Тест 3: должен вызывать onChange, когда выбран первый вариант, затем второй вариант, затем 9-й.
Я визуализирую компонент.
Я моделирую выбор первого варианта.
Имитирую выбор 2-го варианта.
Имитирую выбор 9-го варианта.
Я проверяю, есть ли
mockedOnChange
is called 3 times with the 9th option bale and value.
Test 4: should call onChange when filtering by input value
I render the component.
I simulate a change on the input field by typing "option 1".
I know, based on my
mockedOptions
that the filtered result will be "Mocked option 1" and "Mocked option 10".I simulate 2
ArrowDown
events.I check that the
mockedOnChange
is called with the 2nd filtered option with right label and value.
Complete test file
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'});
});
});
I hope that this help.
Я попробовал оба ответа, перечисленных выше, и до сих пор не повезло.
Что сработало для меня:
добавлять
classNamePrefix
опора - то естьlist
(как указано в других ответах):<Select classNamePrefix='list' options={[ { label: 'one', value: 'one' }, { label: 'two', value: 'two' } ]}/>
выберите индикатор выпадающего меню и смоделируйте mouseDown => открытый раскрывающийся список:
wrapper.find('.list__dropdown-indicator').simulate('mouseDown', { button: 0 });
ожидать, что что-то произойдет, т.е. в моем случае я проверял количество выпадающих опций
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');
С 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')
Использование библиотеки тестирования и 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'});
Чтобы добавить к тому, что сказал Кит, использование метода имитации, кажется, является единственным способом реализовать функциональность. Однако, когда я попробовал это в своем решении, это не сработало - я использую Typescript, хотя и не уверен, имеет ли это отношение, но я обнаружил, что необходимо также передать свойство key при моделировании события:
wrapper.find('.Select-control').simulate('keyDown', { key: 'ArrowDown', keyCode: 40 });
wrapper.find('.Select-control').simulate('keyDown', { key: 'Enter', keyCode: 13 });
Я также обнаружил, что установка свойства classNamePrefix значительно упростила проведение простого теста, чтобы дать мне уверенность в том, что компонент правильно реагировал на смоделированные события. При установке этого префикса полезные части компонента украшаются именами классов, обеспечивающими легкий доступ к ним (вы можете определить эти полезные имена классов, проверив элементы в инструментах Google Dev). Простой тест 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');
});
Для более новой версии реакции-выбора (2.x+), вышеупомянутый метод не работает, потому что реакция-выбора использует эмоцию. Таким образом, wrapper.find('.Select-control')
или же wrapper.find('.list__option')
больше не работает. реакция-выберите 2.x + оборачивает Select
компонент внутри государственного менеджера, но вы можете получить доступ к onChange
метод Select
составная часть. Я использую следующий код для запуска выбора:
wrapper.find(Select).props().onChange({ value: ... })
Если кто-то использует энзим, это работает для меня:
wrapper.find('Select').simulate('change', {
target: { name: 'select', value: 1 },
});
где "select" - это имя атрибута, как определено здесь:
<Select
name="select"
...
и "значение" является значением желаемой опции.
Просто хочу добавить, я на самом деле должен был добавить classNamePrefix
опора для Select
компонент в противном случае я не получил *__control
классы, чтобы захватить.
С реактивной тестовой библиотекой:
<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"],
}
);
У меня была такая же проблема, чтобы протестировать реакцию на выбор, поэтому мое решение было таким.
мой компонент реакции-выбора:
<Select
options={options}
placeholder="Select an Option"
/>
мой тест:
it('should select an option', () => {
const { getByText } = render(
<MySelect/>
);
fireEvent.focus(getByText('Select an Option'));
fireEvent.keyDown(getByText('Select an Option'), {
key: 'ArrowDown',
code: 40,
});
fireEvent.click(getByText("my wanted option"));
expect(getByText("my wanted option")).toBeDefined();
}
Существует библиотека для имитации пользовательских событий на элементах response-select для использования с react-testing-library. Работает с react select 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"],
});
Я пробовал все решения здесь - у меня ничего не получалось.
Мне удалось решить проблему с библиотекой пользовательских событий .Оформить заказ
selectOptions
функция.
Для тех, кто использует фермент 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);
});
wrapper.find(ReactSelectComponent.instance().onChange(...params));
/questions/4628196/testirovanie-komponenta-react-select/55127077#55127077 почти работает для меня. Я только что добавил событие 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);
});
У меня была аналогичная проблема с моей реализацией MUI, реагирующей на компонент Select. Я не смог найти хорошего решения, кроме этого:
it('changes the state when an option is checked', async () => {
const result = renderWithContext(
<div>
<MySelectImplementation />
</div>
);
const selectOptions= screen.getByRole('button', {
name: `${btnText}`,
});
await act(async () => {
await fireEvent.keyDown(selectOptions.firstChild as ChildNode, {
key: 'ArrowDown',
});
});
await waitFor(() => screen.getByText('Text In My List'));
const checkBoxes = screen.getAllByRole('checkbox');
// check the first checkbox
fireEvent.click(checkBoxes[0]);
expect(
result.store.getState().optionState
).toEqual(['Text In My List']);
});
Я все еще боролся с той же проблемой, но, потратив на это некоторое время, нашел способ обрабатывать раскрывающийся список реакции-выбора для автоматического тестирования . Решение можно найти здесь .
Стоит отметить, что для этого не требуется никакого дополнительного dom.