How to simulate selecting from dropdown in Jest / enzyme testing? - reactjs

I'm trying to write jest tests for my React component that has a dropdown like this:
<select id="dropdown" onChange={this.handlechange} ref={this.refDropdown}>
{this.props.items.map(item => {
return (
<option key={item.id} value={item.id}>
{item.name}
</option>
);
})}
</select>
and the handler looks like this:
handlechange = () => {
const sel = this.refDropdown.current;
const value = sel.options[sel.selectedIndex].value;
//...
}
I want to simulate a user selecting the 2nd entry (or anything other than the first) in the list but am having trouble. If I simulate a "change" event it does fire the call to handlechange() but selectedIndex is always set to zero.
I tried this code in the jest test but it doesn't cause selectedIndex to be accessible in the handler.
const component = mount(<MyComponent/>);
component.find("#dropdown").simulate("change", {
target: { value: "item1", selectedIndex: 1 }
});
What happens is almost correct. If I look at the incoming event, I can see e.value is set to "item1" as I set, but it doesn't act like the selection was actually made.
I've also tried trying to send "click" simulations to the Option element directly but that does nothing.
What's the right way to simulate a selection from a dropdown?

Try this approach:
wrapper.find('option').at(0).instance().selected = false;
wrapper.find('option').at(1).instance().selected = true;

You can trigger a change event since you have your this.handlechange trigger onChange:
const component = mount(<MyComponent/>);
component.find('#dropdown').at(0).simulate('change', {
target: { value: 'item1', name: 'item1' }
});
I would say you have to add .at(0) because Enzyme will find a list of values even if you only have one element with that ID.

Try changing the html "onInput" to "onChange" because you are simulating the "change" event in jest.

Short Answer -
Use the following snippet
import userEvent from "#testing-library/user-event";
userEvent.selectOptions(screen.getByTestId("select-element-test-id"), ["option1"]);
Detailed Answer -
.tsx file
.
.
<Form.Select
aria-label="Select a value from the select dropdown"
required
onChange={(e) => {
console.log("Option selected from the dropdown list", e.target.value);
optionChangedHandler(e.target.value);
}}
data-testid="select-element-test-id"
>
...CODE FOR RENDERING THE LIST OF OPTIONS...
</Form.Select>
.
.
.test.tsx file
import userEvent from "#testing-library/user-event";
it("Check entire flow", async () => {
render(
<YourComponent/>
);
// CHECK IF SELECT DROPDOWN EXISTS
const selectDropdown = await waitFor(
() => screen.getByTestId("select-element-test-id"),
{
timeout: 3000,
}
);
expect(selectDropdown ).toBeInTheDocument();
//"option2" is the element in the select dropdown list
userEvent.selectOptions(screen.getByTestId("select-element-test-id"), [
"option2",
]);
}
The above code will trigger the onChange function of the select element.

Related

how to test radio button in react testing library(User can select other radio button)

I am new to react and writing test case in react. I have two radio buttons (IPV4 and IPV6). I want to test that a user can select another radio button or not. I don't know which event listener to apply on radio button, is it change event or click event?
I broke down my problem into two parts: 1) I am checking whether I can select or deselect one radio button or not 2) I can select another radio button or not. I failed in my 1 part only so please help me. Here is my code and test case.
import React from 'react'
import constants from 'LookingGlass/constants'
import { Label, RadioButton } from 'LookingGlass/common/components'
export const componentTestId = 'SelectorIPVersion'
export default function SelectorIPVersion(props = {}) {
const { checkedVersion, onChange, disabled } = props
const { choices, groupName, label } = constants.SelectorIPVersion
const _onChange = (e) => {
onChange && onChange(e.target.value)
}
let selectorChoices = choices.map((c) => {
return (
<RadioButton
key={c.value}
id={c.value}
name={groupName}
value={c.value}
label={c.label}
checked={c.value === checkedVersion}
onChange={_onChange}
disabled={disabled}
/>
)
})
return (
<div data-testid={componentTestId}>
<Label text={label} />
{selectorChoices}
</div>
)
}
Here is my test case:
Here i am trying select and deselect the radio button but this is giving me error so how can I check that user can select another option or not?
const renderComponent = (props) => render(<SelectorIPVersion {...props} />)
test('Verify that user can select another version', () => {
const { getByRole, debug } = renderComponent({ checkedVersion: 'ipv4' })
const radio = getByRole('radio', { name: 'IPv4' })
expect(radio).toBeChecked()
debug(radio)
fireEvent.click(radio)
expect(radio).not.toBeChecked()
})
Output of Error:
Received element is checked:
<input class="hidden" id="ipv4" name="ipVersion" readonly="" tabindex="0" type="radio" value="ipv4" checked=""/>
When I debug the IPV4 radio button this is the output:
● Console
console.log node_modules/#testing-library/react/dist/pure.js:94
<input
checked=""
class="hidden"
id="ipv4"
name="ipVersion"
readonly=""
tabindex="0"
type="radio"
value="ipv4"
/>
And when I don't pass checkedVersion: 'ipv4' as props radio button is not checked.
Where am I wrong. Is it right or wrong?
Radio buttons are meant to be used in groups, so that when clicking on one of them deselects the currently selected one. To test that a radio button is deselected simply select the other one.
test('Verify that user can select another version', () => {
const { getByRole } = renderComponent({ checkedVersion: 'ipv4' })
const ipv4Radio = getByRole('radio', { name: 'IPv4' })
const ipv6Radio = getByRole('radio', { name: 'IPv6' })
expect(ipv4Radio).toBeChecked()
fireEvent.click(ipv6Radio)
expect(ipv4Radio).not.toBeChecked()
expect(ipv6Radio).toBeChecked()
})
However, as a test this doesn't bring much value since you're just testing that radio buttons work as expected (we already know they do). You should focus on testing your own logic around these buttons instead.

Testing Semantic-UI Dropdown: How to wait for it to be populated?

I've successfully implemented the Semantic-UI Dropdown to display a list of companies. Now I'm trying to build Jest / React Testing Library tests for it. To accomplish this, I built this Mock Request:
beforeEach(() => {
request.get = jest.fn(() => Promise.resolve({
companies: [
{"company_id": 1, "company_name": "ABC"},
{"company_id": 2, "company_name": "DEF"}
]
}));
});
Based on a console.log I added to my component code, this appears to work as expected.
Here's an abridged example of my instance of this element:
<div id="companyId" data-testid="companies-dropdown" role="combobox">
<input autocomplete="companyId" class="search" type="text">
<div class="default text" "role="alert">Ex. ABC Corp</div>
<div role="listbox">
<div role="option"><span class="text">ABC</span></div>
<div role="option"><span class="text">DEF</span></div>
</div>
</div>
Where I'm struggling is to correctly wait in my test for the Dropdown to get populated:
it('Should populate the Search and Select Company dropdown with 2 companies', async () => {
const { getByTestId, getByText, getByRole } = displayModal();
const listBox = await waitForElement(() => getByRole('listbox'));
});
The error message is: Unable to find an accessible element with the role "listbox"
I also tried this alternate approach but got the same error message:
const listBox = await waitForElement(() => within(getByTestId('companies-dropdown')).getByRole('listbox'));
Might anyone have any ideas what I'm doing wrong?
I happened to see this while I was searching something else. But in your case, you are using wrong role. I achieved my use case with the following:
First focus the input element of the div to open dropdown. I use the input element:
userEvent.click(getByLabelText('Company'))
The above can be replaced by using data-testid or with role 'combobox'
Get all the roles for the select options:
getAllByRole('option')
const companyAbc = getAllByRole('option').find(ele => ele.textContent === 'ABC') userEvent.click(companyAbc); // verify your onChange event
In your async case:
const companyAbc = await findAllByRole('option').find(ele => ele.textContent === 'ABC') userEvent.click(companyAbc); // verify your onChange event
SemanticReact also has onSearchChange event that you can use to trigger for every input search if that's the use case.

Trying to test FormControlLabel in enzyme won't trigger click when simulate

I am trying to test a FormControlLabel component from material UI.
When I try to simulate click Enzyme won't fire a click event on my component.
I tried using shallow, createShallow, mount, createMount.
In debug I get the component from the find and findWhere query and by the looks of it it has a props of control that contains the wanted checkbox.
I also tried to wrap the checkbox from the retrieved props with shallow and mount and it didn't work...
//parent.jsx
export public Parent = () => {
const [selected, setSelected] = useState(false);
const handleChange = (e,s) => {setSelected(s);};
...
return (
...
<FormControlLabel control={
<Checkbox onChange={handleChange}
checked={selected}}
label='some label'/>
...
);
}
//test.js
...
let component = createMount()(<Parent/>);
let checkbox = component.find(FormControlLabel)
.findWhere(c=>c.prop('label')==='some label').first();
checkbox.simulate('click');
checkbox.simulate('change');
//rest of the test
//the function handleChange is not called in debug.
Expected:
simulate click or change should call the onChange function
Actual:
change won't be triggered
the code works, the test dont.
please help!!!
Try this
let checkbox = component.find(FormControlLabel);
checkbox.prop('control').props.onChange({ target: { checked: true }, persist: jest.fn() });
Worked for me using shallow.
The trick is to construct an event that has a checked : true field.
modifying your code:
let checkbox = component.find(FormControlLabel).findWhere(c=>c.prop('label')==='some label').first();
// Wrap the control prop.
enzyme.shallow(checkbox.prop('control')).simulate('change', { target : { checked : true }} );

How do I trigger the change event on a react-select component with react-testing-library?

Given that I can't test internals directly with react-testing-library, how would I go about testing a component that uses react-select? For instance, if I have a conditional render based on the value of the react-select, which doesn't render a traditional <select/>, can I still trigger the change?
import React, { useState } from "react";
import Select from "react-select";
const options = [
{ value: "First", label: "First" },
{ value: "Second", label: "Second" },
{ value: "Third", label: "Third" },
];
function TestApp() {
const [option, setOption] = useState(null);
return (
<div>
<label htmlFor="option-select">Select Option</label>
<Select
value={option}
options={options}
onChange={option => setOption(option)}
/>
{option && <div>{option.label}</div>}
</div>
);
}
export default TestApp;
I'm not even sure what I should query for. Is it the hidden input?
My team has a test utility in our project that lets us select an item easily after spending too much time trying to figure out how to do this properly. Sharing it here to hopefully help others.
This doesn't rely on any React Select internals or mocking but does require you to have set up a <label> which has a for linking to the React Select input. It uses the label to select a given choice value just like a user would on the real page.
const KEY_DOWN = 40
// Select an item from a React Select dropdown given a label and
// choice label you wish to pick.
export async function selectItem(
container: HTMLElement,
label: string,
choice: string
): Promise<void> {
// Focus and enable the dropdown of options.
fireEvent.focus(getByLabelText(container, label))
fireEvent.keyDown(getByLabelText(container, label), {
keyCode: KEY_DOWN,
})
// Wait for the dropdown of options to be drawn.
await findByText(container, choice)
// Select the item we care about.
fireEvent.click(getByText(container, choice))
// Wait for your choice to be set as the input value.
await findByDisplayValue(container, choice)
}
It can be used like this:
it('selects an item', async () => {
const { container } = render(<MyComponent/>)
await selectItem(container, 'My label', 'value')
})
You can try the following to get it working:
Fire focus event on the ReactSelect component .react-select input element.
Fire a mouseDown event on the .react-select__control element
Fire a click on the option element that you want to select
You can add a className and classNamePrefix props with the value of "react-select" in order to specifically select the component you are trying to test.
PS: In case you are still stuck I'd encourage you to take a look at this conversation from where the above answer is borrowed - https://spectrum.chat/react-testing-library/general/testing-react-select~5857bb70-b3b9-41a7-9991-83f782377581

React-select multi not working the way it's defined in the documentation

I am using react-select to make a searchable dropdown menu. And it's working fine until you actually select an option, then it just throws an Each child in an array or iterator should have a unique "key" prop. error, doesn't remove the option you have picked and doesn't show what you have picked so far.
My options are an array with objects {value: crew.id, label: crew.code}, and here is my Select component
<Select
isMulti
name='teamIdsFilter'
menuPosition='fixed'
options={crewOptions}
value={teamIds}
placeholder='Nepasirinktas'
onChange={event => this.handleTeamIdsSelect(event)} />
And my handleTeamIdsSelect event handler
handleTeamIdsSelect = (event) => {
if (event) {
const selectedCrew = event.map(crew => crew.value);
this.setState({teamIds: selectedCrew});
}
};
Try using multi instead of isMulti. This will fix the problem.
<Select
multi
name='teamIdsFilter'
menuPosition='fixed'
options={crewOptions}
value={teamIds}
placeholder='Nepasirinktas'
onChange={event => this.handleTeamIdsSelect(event)} />
Check this
const changeSelect2 = (name,value)=>{
setFormState((preValue)=>{
return {
...preValue, // use spead sign for join
[name] : value // lec 43 // https://stackoverflow.com/questions/32515598/square-brackets-javascript-object-key
}
});
}
<Select options={crewOptions} onChange={(res)=>{changeSelect2('teamIdsFilter',res.value)}} name="teamIdsFilter" />
try this :
this.state = {
isMulti:true,
};
<Select isMulti={this.state.isMulti}/>
try adding
multi={true}
hopefully it will work.

Resources