react-testing-library version: 4.1.3react version: 16.4.1node version: 11.10.1npm (or yarn) version: 6.7.0 const select = await waitForElement(() =>
getByTestId("select-testid")
);
select.value = "testValue";
fireEvent.change(select);
<Select
className={classes.select}
onChange={this.handleSelectChange}
value={selectedValue}
inputProps={{
id: "select-id",
"data-testid": "select-id"
}}
>
I am trying to fire the onChange method of the Material UI Select.
onChange won't fire.
Also tried with
select.dispatchEvent(new Event('change', { bubbles: true }));
The same occurs with the Boostrap(reactstrap) Input Checkbox
According to this issue (https://github.com/kentcdodds/react-testing-library/issues/275), you should use the onClick event, not the onChange Event, but on reactstrap its not supported right now :/ I hope react-testing-library can implement the onChange function to it like browsers has it.
Edit: While inspecting this, I found out, that onClick isn't supported by react-testing-library as well.
that onClick isn't supported by react-testing-library as well.
That's not true. fireEvent.click exists.
If a "checkbox" component doesn't handle a click event then that component is inaccessible. This would be a bug in that component and should be fixed.
As for the original post, do this:
const select = await waitForElement(() =>
getByTestId("select-testid")
);
fireEvent.change(select, {target: {value: 'testValue'}});
Good luck!
As for the original post, do this:
const select = await waitForElement(() => getByTestId("select-testid") ); fireEvent.change(select, {target: {value: 'testValue'}});Good luck!
This by itself did not work. That being said, both of the following do work:
select.value = "testValue";
fireEvent.change(select, {target: {value: "testValue"}});
or
select.value = "testValue";
fireEvent.change(select);
I personally prefer the second option.
_Something to note for future readers_: If you are using a Material UI select, none of the above will work. You'll have to use the native version of the Select element.
For future reference: https://stackoverflow.com/questions/55184037/react-testing-library-on-change-for-material-ui-select-component
Hey @kentcdodds, Can you please brief us on how we can efficiently use react-testing-library with React Material-UI components ? I faced a similar issue of while testing the Material UI select(Not native select) onChange function.
This is the codeSandBox link: https://codesandbox.io/s/q94q9z1849
In the codeSandBox code I have similar Select component which I am using. For your info, the dropdown which appears in Select component is a Paper component. Best regards!!!
Hi @kentcdodds
Any help on how can we make testing select possible for onchange event when native is not true?
Sure, happy to have a look at it but your CodeSandbox is missing any unit tests at the moment
Hey @weyert, please feel free to add unit test cases in the Codesandbox example. Thank you.
I assume you already have some unit test cases you tried? The code sandbox you linked doesn't even have react-testing-library as a dependency. I would also suggest to look at https://stackoverflow.com/a/55576660
Hey @weyert , I have updated the codesandbox example and have wrote unit test for it. I would like to thank you for taking a look at this issue.
Here is the link for updated codesandbox: https://codesandbox.io/s/q94q9z1849
Please feel free to edit it or suggest improvements on it.
Try this: https://codesandbox.io/embed/94pm1qprmo
I have moved the data-testid to the inputProps-prop of the Select of ControlledOpenSelect-component also I have written a unit test for you which clicks on Thirty and ensures a onChange-props got triggered (I added the onChange to your comp).
Not sure, why it doesn't work in the browser but when you download it and test locally the unit test is successful. I hope it helps!
Thanks @weyert @anandthanki1
My tests are getting passed through your suggested way of selecting button and then clicking on element found by text. perfect.
Thank you @weyert
The same issue occurs for Input components as well.
fireEvent.change(input, { target: { value: "Valid input value" } }); does nothing.
What if you actually type it with something like UserEvents type-helper method?
Maybe @oliviertassinari can help with this
It would probably be good to have a few examples for testing MaterialUI components because we regularly get reports about difficulties with that library and I don't use it so I don't know why it's so uniquely difficult.
The solution shared by @weyert looks great to me: https://codesandbox.io/s/94pm1qprmo, thanks! The non-native variation of the select component relies on click/keyboard interactions. You can't fire a DOM change event. The input element present is only here to support HTML POST forms.
@TidyIQ for the Input component, let's move to #359.
Similar to what is in codesandbox worked for me:
const { getByText, getAllByRole, getByTestId, container } = render(
<Form />,
);
const selectNode = getByTestId('select-button-text');
const selectButton = getAllByRole('button')[0];
expect(selectButton).not.toBeNull();
expect(selectNode).not.toBeNull();
UserEvent.click(selectButton);
await waitForElement(() => getByText('Custom Text'), { container });
const itemClickable = getByText('Custom Text');
UserEvent.click(itemClickable);
getByTestId('custom1');
I had the problem that the text I want to select is elsewhere on the page, so I needed to target the 'dropdown' directly. Also I wanted it as a separate function, ideally not using the getByText etc. returned by render().
// myTestUtils.js
import {within, waitForElementToBeRemoved} from '@testing-library/react';
import UserEvent from '@testing-library/user-event';
export const selectMaterialUiSelectOption = async (element, optionText) =>
new Promise(resolve => {
// The the button that opens the dropdown, which is a sibling of the input
const selectButton = element.parentNode.querySelector('[role=button]');
// Open the select dropdown
UserEvent.click(selectButton);
// Get the dropdown element. We don't use getByRole() because it includes <select>s too.
const listbox = document.body.querySelector('ul[role=listbox]');
// Click the list item
const listItem = within(listbox).getByText(optionText);
UserEvent.click(listItem);
// Wait for the listbox to be removed, so it isn't visible in subsequent calls
waitForElementToBeRemoved(() => document.body.querySelector('ul[role=listbox]')).then(
resolve,
);
});
Edit: added async, since material-ui can take a tic to remove the listbox.
Hey @davidgilbertson,
Cool! This would be awesome for https://github.com/kentcdodds/react-testing-library-examples
Okey doke I'll do a PR tonight. FYI I updated the above to use querySelector instead of getByRole - it seems that getByRole('listbox') will include a <select> on the page without that role, does that sound right?
will include a
It does have a role implicitly. However, listbox is caused by a bug that is caused by an outdated dependency. Should be fixed soon.
I had the problem that the text I want to select is elsewhere on the page, so I needed to target the 'dropdown' directly. Also I wanted it as a separate function, ideally not using the
getByTextetc. returned byrender().// myTestUtils.js import {within} from '@testing-library/react'; import UserEvent from '@testing-library/user-event'; export const selectMaterialUiSelectOption = (element, optionText) => { // The the button that opens the dropdown, which is a sibling of the input const selectButton = element.parentNode.querySelector('[role=button]'); // Open the select dropdown UserEvent.click(selectButton); // Get the dropdown element. We don't use getByRole() because it includes <select>s too. const listbox = document.body.querySelector('ul[role=listbox]'); // Click the list item const listItem = within(listbox).getByText(optionText); UserEvent.click(listItem); };
@davidgilbertson Solution is great.But the listbox is still present in jsdom even after value selected.When I am trying to open other dropdown in same testcase the previous listbox is showing instead of selected one.
@sateesh-p, I had this issue as well. No fix, but I was able to just validate an onChange function callback.
@davidgilbertson, seriously thank you. This was a huge pain in the butt.
@sateesh-p and @vikeen I came across the same problem, the trick was to wait for the element to be removed (as far as I could work out, it happens _almost_ immediately). I've updated the code in my original comment above https://github.com/testing-library/react-testing-library/issues/322#issuecomment-581650108
So you need to call it like await selectMaterialUiSelectOption(el, 'text'); which is a pain but I couldn't see another way.
@sateesh-p and @vikeen I came across the same problem, the trick was to wait for the element to be removed (as far as I could work out, it happens _almost_ immediately). I've updated the code in my original comment above #322 (comment)
So you need to call it like
await selectMaterialUiSelectOption(el, 'text');which is a pain but I couldn't see another way.
That's great @davidgilbertson .It's working as expected now.Thanks
Thanks for sharing the snippet @davidgilbertson! I couldn't make it work with my code, but I modified it slightly and the following works for me:
export const selectMaterialUiSelectOption = async (
container: HTMLElement,
selectElement: HTMLElement,
optionText: string,
) => {
userEvent.click(selectElement);
const listbox = await within(document.body).findByRole('listbox');
const listItem = await within(listbox).findByText(optionText);
userEvent.click(listItem);
await within(container).findByText(optionText);
};
_Something to note for future readers_: If you are using a Material UI select, none of the above will work.
Here's the solution which worked for me. You can mock the component in Jest and implement it as a native element for the duration of the test.
MySelect.js
<Select
id="demo-simple-select"
value={age}
onChange={handleChange}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
__mocks__ in the same folder as MySelect.jsMySelect.js into __mocks__/MySelect.jsSelect component here.__mocks__/MySelect.js
<Select
native
id="demo-simple-select"
value={age}
onChange={handleChange}
>
<option value={10}>Ten</option>
<option value={20}>Twenty</option>
<option value={30}>Thirty</option>
</Select>
In MySelect.test.js, use jest.mock('path/to/original/MySelect.js') at the top.
MySelect.test.js
// imports
jest.mock('path/to/original/MySelect.js')
// tests
Go ahead and use your original MySelect.js for testing. Jest will automatically find your mocked implementation and substitute it.
For anyone coming across this issue like I did and is having trouble removing the list box from jsdom, Material UI uses ReactTransitionGroup under the hood for fading out the the options. You can disable the ReactTransitionGroup transitions by adding:
import { config } from 'react-transition-group';
config.disabled = true;
which solved the issue for me and removed the listbox immediately on clicking an item.
Thx to have share all of this information it help a lot.
Since the last release on Material ui, the previous sibling element of the select input must be trigger by the "mouseDown" method from the fireEvent.
onClick do not trigger anymore the option menu.
material-ui should add a testing section to each component in the docs where they document how each of their components can be tested using the most popular testing libraries.
material-ui should add a testing section to each component in the docs where they document how each of their components can be tested using the most popular testing libraries.
We don't do that because the goal is that it shouldn't matter that you're using Material-UI.
The problem here is that most folks wrote test that matched the existing behavior of Material-UI instead of writing tests that simulate what a user would do with a native <select>.
Obviously we're not perfect and for that reason you can open issues on the Material-UI repository.
@eps1lon What if Material-UI introduces a native hidden select + listening to change events? I imagine we could gain:
Please discuss this on the relevant repository with concrete examples.
Thx to have share all of this information it help a lot.
Since the last release on Material ui, the previous sibling element of the select input must be trigger by the "mouseDown" method from the fireEvent.
onClickdo not trigger anymore the option menu.
My unit tests broke after updating MaterialUI to the latest version ("@material-ui/core": "^4.11.0"). After a few days of battling with the test, this solved the issue beautifully. Thank you!
I had the problem that the text I want to select is elsewhere on the page, so I needed to target the 'dropdown' directly. Also I wanted it as a separate function, ideally not using the
getByTextetc. returned byrender().// myTestUtils.js import {within, waitForElementToBeRemoved} from '@testing-library/react'; import UserEvent from '@testing-library/user-event'; export const selectMaterialUiSelectOption = async (element, optionText) => new Promise(resolve => { // The the button that opens the dropdown, which is a sibling of the input const selectButton = element.parentNode.querySelector('[role=button]'); // Open the select dropdown UserEvent.click(selectButton); // Get the dropdown element. We don't use getByRole() because it includes <select>s too. const listbox = document.body.querySelector('ul[role=listbox]'); // Click the list item const listItem = within(listbox).getByText(optionText); UserEvent.click(listItem); // Wait for the listbox to be removed, so it isn't visible in subsequent calls waitForElementToBeRemoved(() => document.body.querySelector('ul[role=listbox]')).then( resolve, ); });Edit: added async, since material-ui can take a tic to remove the listbox.
If anyone else comes across this awesome example here, but is using a multi-select, one note of caution -- multi-selects stay open when selecting options, so the waitForElementToBeRemoved will fail as it doesn't close the select.
You could easily modify this to accept multiple options (if you are looking to test clicking one to many items), and then at the end prior to the waitForElementToBeRemoved, you can simulate an "escape" keypress on the listbox to close it:
userEvent.type(listbox, "{esc}");
This will then properly await on the close of the listbox before proceeding.
Most helpful comment
I had the problem that the text I want to select is elsewhere on the page, so I needed to target the 'dropdown' directly. Also I wanted it as a separate function, ideally not using the
getByTextetc. returned byrender().Edit: added async, since material-ui can take a tic to remove the listbox.