How to select an option from a select list with React Testing Library - react-testing-library

I have a normal select list. I need to test handleChoice gets called when I choose an option. How can I do this with React Testing Library?
<select
onChange={handleChoice}
data-testid="select"
>
<option value="default">Make your choice</option>
{attributes.map(item => {
return (
<option key={item.key} value={item.key}>
{item.label}
</option>
);
})}
</select>
getByDisplayValue with the value of item.label doesn't return anything, perhaps this is because it's not visible on the page?

Add data-testid to the options
<option data-testid="select-option" key={item.key} value={item.key}>
{item.label}
</option>
Then, in the test call fireEvent.change, get all the options by getAllByTestId and check the selected option:
import React from 'react';
import { render, fireEvent } from '#testing-library/react';
import App from './App';
test('Simulates selection', () => {
const { getByTestId, getAllByTestId } = render(<App />);
//The value should be the key of the option
fireEvent.change(getByTestId('select'), { target: { value: 2 } })
let options = getAllByTestId('select-option')
expect(options[0].selected).toBeFalsy();
expect(options[1].selected).toBeTruthy();
expect(options[2].selected).toBeFalsy();
//...
})
For your question, the getByDisplayValue works only on displayed values

This solution didn't work for me, what did work, was userEvent.
https://testing-library.com/docs/ecosystem-user-event/
import React from 'react';
import { render } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import App from './App';
test('Simulates selection', () => {
const { getByTestId } = render(<App />);
// where <value> is the option value without angle brackets!
userEvent.selectOptions(getByTestId('select'), '<value>');
expect((getByTestId('<value>') as HTMLOptionElement).selected).toBeTruthy();
expect((getByTestId('<another value>') as HTMLOptionElement).selected).toBeFalsy();
//...
})
You can also forgo having to add a data-testid to the select element if you have a label (which you should!), and simply use getByLabelText('Select')
Further still, you can get rid of the additional data-testid on each option element, if you use getByText.
<label for="selectId">
Select
</label>
<select
onChange={handleChoice}
id="selectId"
>
<option value="default">Make your choice</option>
{attributes.map(item => {
return (
<option key={item.key} value={item.key}>
{item.label}
</option>
);
})}
</select>
Then:
import React from 'react';
import { render } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import App from './App';
test('Simulates selection', () => {
const { getByLabelText, getByText } = render(<App />);
// where <value> is the option value without angle brackets!
userEvent.selectOptions(getByLabelText('<your select label text>'), '<value>');
expect((getByText('<your selected option text>') as HTMLOptionElement).selected).toBeTruthy();
expect((getByText('<another option text>') as HTMLOptionElement).selected).toBeFalsy();
//...
})
This seems a more optimal way to do this type of test.

**In 2021, you can use userEvent which comes as part of the React Testing Library ecosystem. It allows you to write tests which are closer to real user behaviour. Example
it('should correctly set default option', () => {
render(<App />)
expect(screen.getByRole('option', {name: 'Make a choice'}).selected).toBe(true)
})
it('should allow user to change country', () => {
render(<App />)
userEvent.selectOptions(
// Find the select element
screen.getByRole('combobox'),
// Find and select the Ireland option
screen.getByRole('option', {name: 'Ireland'}),
)
expect(screen.getByRole('option', {name: 'Ireland'}).selected).toBe(true)
})
Have a look at this article for more info on how to test select elements using React Testing Library.

You can also use getByText which will save you from adding the data-testid attribute
In my case I did
import { screen } from '#testing-library/react';
....
<select>
<option value="foo">Foo</option>
<option selected value="bar">Bar</option>
</select>
....
expect((screen.getByText('Foo') as HTMLOptionElement).selected).toBeFalsy();
expect((screen.getByText('Bar') as HTMLOptionElement).selected).toBeTruthy();

This seems simpler to me:
userEvent.selectOptions(screen.getByLabelText('County'), 'Aberdeenshire');

1: Install user-event V14 and above
npm:
npm install --save-dev #testing-library/user-event
yarn:
yarn add --dev #testing-library/user-event
2: Import and set in up: (it's a default import so make sure you didn't import named one)
import userEvent from '#testing-library/user-event';
test('should change data', async () => {
const user = userEvent.setup();
});
3: Change selected option and test:
import userEvent from '#testing-library/user-event';
test('should change data', async () => {
const user = userEvent.setup();
// Check if default value in correct:
expect(screen.getByRole('option', { name: /default option/i }).selected).toBeTruthy();
// Change option: (Make sure to use Async/Await syntax)
await user.selectOptions(screen.getByRole("combobox"), 'optionToSelect');
// Check the result:
expect(screen.getByRole('option', { name: /optionToSelect/i }).selected).toBeTruthy();
});

In my case I was checking a number against a string, which failed. I had to convert the number with toString() to make it work.
The HTML:
<select>
<option value="1">Hello</option>
<option value="2">World</option>
</select>
And in my test:
await fireEvent.change(select, { target: { value: categories[0].id.toString() } });
expect(options[1].selected).toBeTruthy();

Related

I have React Hook Form With Controller With Yup as validataro The Material UI Select stays red after selecting something and won't go away

I got TextField to work, now the Material UI Select will turn red if no selection is made but stays red after selection is made and won't let form submit. I'm using Yup as validation library.Maybe I keep using wrong Yup type I try String and array but I can't get it to work.
import {
makeStyles,
Box,
Select,
FormControl,
InputLabel,
MenuItem,
Typography,
} from "#material-ui/core";
import * as yup from 'yup';
import { yupResolver } from '#hookform/resolvers'
import { useForm, Controller } from "react-hook-form";
const FormFields = ({ typeOfInquiry, typeOfProviderSupplier, feedbackform }) => {
const schema = yup.object().shape({
typeofInquiry: yup.array().nullable().required(),
});
const { handleSubmit, control, reset, errors } = useForm();
return (
<Controller
style={{ minWidth: 220 }}
name="typeofInquiry"
render ={({ field: { ...field }, fieldState })=>{
console.log(props)
return ( <Select {...field} >
{typeOfInquiry.map((person) => (
<MenuItem key={person.value} value={person.value} >
{person.label}
</MenuItem>
))}
</Select>
)
}}
control={control}
defaultValue=" "
/>
<Typography className={classes.red}>{errors.typeofInquiry?.message}</Typography>
</FormControl>
</form>
);
}
You've to pass the ref to the TextField component.
Here is a working example
👉🏻 https://codesandbox.io/s/exciting-pateu-3n0i9
You should do something similar with Select.
Some examples with MUI: https://codesandbox.io/s/react-hook-form-v7-controller-5h1q5?file=/src/Mui.js

React-testing-library with Ionic v5 (react) and react-hook-form- change events do not fire

I am trying to test a component rendered with Controller from react-hook-form with react-testing-library
<Controller
render={({ onChange, onBlur, value }) => (
<IonInput
onIonChange={onChange}
onIonBlur={onBlur}
value={value}
type="text"
data-testid="firstname-field"
/>
)}
name="firstName"
control={control}
defaultValue={firstName}
/>
Default values are as expected when I render the component with a some mock data. However, when I go about changing values, it seems the events are not firing. From this blog post it looks like ionic exports a set of test utils to handle ionic's custom events. After setting that up in my setupTests.ts I'm attempting to use both the ionFireEvent and the fireEvent from RTU, neither of which reflect changes in the component when I use debug(). I've set it up so I can use both fireEvent and ionFireEvent to test:
import { render, screen, wait, fireEvent } from "#testing-library/react";
import { ionFireEvent } from "#ionic/react-test-utils";
// using RTL fireEvent - no change
it("fires change event on firstname", () => {
const { baseElement } = renderGolferContext(mockGolfer);
const firstNameField = screen.getByTestId("firstname-field") as HTMLInputElement;
fireEvent.change(firstNameField, { target: { detail: { value: "Jill" } } });
expect(firstNameField.value).toBe("Jill");
});
// using IRTL ionFireEvent/ionChange - no change
it("fires change event on firstname", () => {
const { baseElement } = renderGolferContext(mockGolfer);
const firstNameField = screen.getByTestId("firstname-field") as HTMLInputElement;
ionFireEvent.ionChange(firstNameField, "Jill");
expect(firstNameField.value).toBe("Jill");
});
screen.debug(baseElement);
I've also tried moving the data-testid property to the controller rather than the IonInput suggested here, with the result being the same: no event is fired.
Here are the versions I'm using:
Using Ionic 5.1.1
#ionic/react-test-utils 0.0.3
jest 24.9
#testing-library/react 9.5
#testing-library/dom 6.16
Here is a repo I've created to demonstrate.
Any help would be much appreciated!
this line appears to be incorrect...
expect(firstNameField.value).toBe("Jill");
It should be looking at detail.value since that is what you set
expect((firstNameField as any).detail.value).toBe("Jill");
this is my test,
describe("RTL fireEvent on ion-input", () => {
it("change on firstname", () => {
const { baseElement, getByTestId } = render(<IonicHookForm />);
const firstNameField = screen.getByTestId(
"firstname-field"
) as HTMLInputElement;
fireEvent.change(firstNameField, {
target: { detail: { value: "Princess" } },
});
expect((firstNameField as any).detail.value).toEqual("Princess");
});
});

React Hook Form with MUI Toggle Group

I'm trying to use the MUI toggle group with React Hook Form however I can't get the value to post when submitting the form. My toggle group component looks like this:
import FormatAlignCenterIcon from '#material-ui/icons/FormatAlignCenter';
import FormatAlignLeftIcon from '#material-ui/icons/FormatAlignLeft';
import FormatAlignRightIcon from '#material-ui/icons/FormatAlignRight';
import FormatAlignJustifyIcon from '#material-ui/icons/FormatAlignJustify';
import ToggleButton from '#material-ui/lab/ToggleButton';
import ToggleButtonGroup from '#material-ui/lab/ToggleButtonGroup';
import React from 'react';
import {Controller} from "react-hook-form";
export default function TestToggleGroup(props) {
const {control} = props;
const [alignment, setAlignment] = React.useState('left');
const handleAlignment = (event) => {
setAlignment(event[1]);
};
return (
<Controller
name="ToggleTest"
as={
<ToggleButtonGroup
value={alignment}
exclusive
onChange={handleAlignment}
aria-label="text alignment"
>
<ToggleButton value="left" aria-label="left aligned" key="left">
<FormatAlignLeftIcon/>
</ToggleButton>
<ToggleButton value="center" aria-label="centered" key="center">
<FormatAlignCenterIcon/>
</ToggleButton>
<ToggleButton value="right" aria-label="right aligned" key="right">
<FormatAlignRightIcon/>
</ToggleButton>
<ToggleButton value="justify" aria-label="justified" disabled key="justify">
<FormatAlignJustifyIcon/>
</ToggleButton>
</ToggleButtonGroup>
}
value={alignment}
onChange={(e) => {
handleAlignment(e);
}}
valueName={"alignment"}
control={control}
/>
);
}
Not sure exactly what I'm doing wrong but any assistance would be greatly appreciated.
My workaround was using an effect to manually set the value using setValue and then using getValues() inside your handleSubmit function to get the values.
const { control, setValue } = props;
//Effect
React.useEffect(() => {
setAlignment('ToggleTest', alignment);
}, [alignment, setAlignment]);

React: Dynamically generated <select> elements - setting 'selected' state in a mapped element

I'm trying to handle the selection of dynamically generated <option>'s in a <select> element. I understand that the onChange trigger is what i need to setState with but i can't seem to get her to work.
Here's what i've got going on:
See the Pen dynamic select by Archibald Hammer (#archaeopteryx) on CodePen.
import React from 'react'
import ReactDOM from 'react-dom'
import _ from 'lodash'
const ITEMS = [
{ name: 'centos', text: 'centos', value: 'centosValue' },
{ name: 'ubuntu', text: 'ubuntu', value: 'ubuntuValue' },
]
const SelectComponent = (props) => (
<select name={props.name}>
{_.map(props.items, (item, i) => <Option
key={i}
name={item.name}
value={item.value}
text={item.text}
handleSelect={props.handleSelect}
/>
)}
</select>
)
const Option = (props) => (
<option
value={props.value}
onChange={props.handleSelect}>{props.text}</option>
)
class App extends React.Component {
constructor() {
super()
this.state = {
selected: ''
}
this.handleSelect = this.handleSelect.bind(this)
}
handleSelect(e) {
this.setState({selected: e.target.value})
}
render() {
return (
<div>
<SelectComponent
name="testSelect"
items={ITEMS}
handleSelect={this.handleSelect}
/>
<div>
<p>Selected: {this.state.selected}</p>
</div>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'))
This code does render the dropdown selector as expected but it isn't triggering the setState on the selected item. Any thoughts?
Also, does anyone have any pro-tips for troubleshooting this kind of problem? Any super slick dev-tools you know of or methods for finding out which props are being passed, etc?
The problem is that the options in a select element won't trigger any event, the change is happening in the select element not the option. All you have to do is pass the handleSelect method to the <select> component:
const SelectComponent = (props) => (
<select name={props.name}
onChange={props.handleSelect}
>
{_.map(props.items, (item, i) => <Option
key={i}
name={item.name}
value={item.value}
text={item.text}
handleSelect={props.handleSelect}
/>
)}
</select>
);
const Option = (props) => (
<option
value={props.value}
>{props.text}</option>
)
Sorry I forgot to add the live sample link:
https://codesandbox.io/s/31AyQ2woR
In terms of a tip for debugging, in this particular case just know that the event is triggered by the select component and not the option element ;). But the one I use all the time is React developer tools:
https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
https://addons.mozilla.org/es/firefox/addon/react-devtools/

Ember could not get select value from template to component

I'm struggling with this. I would like to pass a select value from template to component.
Here is my template
<select name="bank" class="form-control" id="sel1" onchange={{action "updateValue" value="bank"}}>
{{#each banks as |bank|}}
<option value={{bank.id}}>{{bank.name}}</option>
{{/each}}
{{log bank.id}}
</select>
And here is my component
import Ember from 'ember';
export default Ember.Component.extend({
store: Ember.inject.service('store'),
banks: Ember.computed(function() {
return this.get('store').findAll('bank');
}),
didUpdate() {
const banques = this.get('banks');
const hash = [];
banques.forEach(function(banque) {
hash.push(banque.get('name'));
});
Ember.$(".typeahead_2").typeahead({ source: hash });
},
actions: {
expand: function() {
Ember.$('.custom-hide').attr('style', 'display: block');
Ember.$('.custom-display').attr('style', 'display: none');
},
updateValue(selectedValue) {
this.set('bank.id', selectedValue);
},
login() {
console.log(this.get('bank.id'));
}
}
});
And i've got this beautiful error : Property set failed: object in path "bank" could not be found or was destroyed.
Any idea ? Thanks
When you use value attribute then you need to specify correct property name to be retrieved from the first argument(event). in your case you just mentioned bank - which was not found in event object. that's the reason for that error.
onchange={{action "updateValue" value="target.value"}}
inside component
updateValue(selectedValue) {
this.set('bank.id', selectedValue);
},