Hi there I'm trying to test a Slider component created with Material-UI, but I cannot get my tests to pass. I would like test the the value changes using the fireEvent
with @testing-library/react
. I've been following this post to properly query the DOM, I cannot get the correct DOM nodes.
Thanks in advance.
<Slider />
component
// @format
// @flow
import * as React from "react";
import styled from "styled-components";
import { Slider as MaterialUISlider } from "@material-ui/core";
import { withStyles, makeStyles } from "@material-ui/core/styles";
import { priceRange } from "../../../domain/Search/PriceRange/priceRange";
const Wrapper = styled.div`
width: 93%;
display: inline-block;
margin-left: 0.5em;
margin-right: 0.5em;
margin-bottom: 0.5em;
`;
// ommited code pertaining props and styles for simplicity
function Slider(props: SliderProps) {
const initialState = [1, 100];
const [value, setValue] = React.useState(initialState);
function onHandleChangeCommitted(e, latestValue) {
e.preventDefault();
const { onUpdate } = props;
const newPriceRange = priceRange(latestValue);
onUpdate(newPriceRange);
}
function onHandleChange(e, newValue) {
e.preventDefault();
setValue(newValue);
}
return (
<Wrapper
aria-label="range-slider"
>
<SliderWithStyles
aria-labelledby="range-slider"
defaultValue={initialState}
// getAriaLabel={index =>
// index === 0 ? "Minimum Price" : "Maximum Price"
// }
getAriaValueText={valueText}
onChange={onHandleChange}
onChangeCommitted={onHandleChangeCommitted}
valueLabelDisplay="auto"
value={value}
/>
</Wrapper>
);
}
export default Slider;
Slider.test.js
// @flow
import React from "react";
import { cleanup,
render,
getAllByAltText,
fireEvent,
waitForElement } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import Slider from "../Slider";
afterEach(cleanup);
describe("<Slider /> specs", () => {
// [NOTE]: Works, but maybe a better way to do it ?
xdescribe("<Slider /> component aria-label", () => {
it("renders without crashing", () => {
const { container } = render(<Slider />);
expect(container.firstChild).toBeInTheDocument();
});
});
// [ASK]: How to test the event handlers with fireEvent.
describe("<Slider /> props", () => {
it("display a initial min value of '1'", () => {
const renderResult = render(<Slider />);
// TODO
});
it("display a initial max value of '100'", () => {
const renderResult = render(<Slider />);
// TODO
});
xit("display to values via the onHandleChangeCommitted event when dragging stop", () => {
const renderResult = render(<Slider />);
console.log(renderResult)
// fireEvent.change(renderResult.getByText("1"))
// expect(onChange).toHaveBeenCalled(0);
});
// [NOTE]: Does not work, returns undefined
xit("display to values via the onHandleChange event when dragging stop", () => {
const renderResult = render(<Slider />);
console.log(renderResult.container);
const spanNodeWithAriaAttribute = renderResult.container.firstChild.getElementsByTagName("span")[0].getAttribute('aria-label')
expect(spanNodeWithAriaAttribute).toBe(/range-slider/)
});
});
// [ASK]: Works, but a snapshot is an overkill a better way of doing this ?
xdescribe("<Slider /> snapshot", () => {
it("renders without crashing", () => {
const { container } = render(<Slider />);
expect(container.firstChild).toMatchSnapshot();
});
});
});
This is how MUI components are tested internally. A library that has a first-class API for this approach is @testing-library/react . For example, when rendering a TextField your test should not need to query for the specific MUI instance of the TextField but rather for the input , or [role="textbox"] .
Building on @rehman_00001's answer, I created a file mock for the component. I wrote it in TypeScript, but it should work just as well without the types.
__mocks__/@material-ui/core/Slider.tsx
import { SliderTypeMap } from '@material-ui/core';
import React from 'react';
export default function Slider(props: SliderTypeMap['props']): JSX.Element {
const { onChange, ...others } = props;
return (
<input
type="range"
onChange={(event) => {
onChange && onChange(event, parseInt(event.target.value));
}}
{...(others as any)}
/>
);
}
Now every usage of the Material UI <Slider/>
component will be rendered as a simple HTML <input/>
element during testing which is much easier to work with using Jest and react-testing-library
.
{...(others as any)}
is a hack that allows me to avoid worrying about ensuring that every possible prop of the original component is handled properly. Depending on what Slider
props you rely on, you may need to pull out additional props during destructuring so that you can properly translate them into something that makes sense on a vanilla <input/>
element. See this page in the Material UI docs for info on each possible property.
After battling this for hours I was able to solve my case related to testing MUI slider
It really depends on how you need to test yours, in my case I have to check if a label text content has changed after clicking a mark using marks
slider props.
The problems
1) The slider
component computes the return value base on elements getBoundingClientRect
and MouseEvent
2) How to query the slider
and fire the event.
3) JSDOM limitation on reading element actual height and width which causes the problem no.1
The solution
1) mock getBoundingClientRect
should also fix the problem no.3
2) add test id to slider and use use fireEvent.mouseDown(contaner, {....})
const sliderLabel = screen.getByText("Default text that the user should see")
// add data-testid to slider
const sliderInput = screen.getByTestId("slider")
// mock the getBoundingClientRect
sliderInput.getBoundingClientRect = jest.fn(() => {
return {
bottom: 286.22918701171875,
height: 28,
left: 19.572917938232422,
right: 583.0937919616699,
top: 258.22918701171875,
width: 563.5208740234375,
x: 19.572917938232422,
y: 258.22918701171875,
}
})
expect(sliderInput).toBeInTheDocument()
expect(sliderLabel).toHaveTextContent("Default text that the user should see")
await fireEvent.mouseDown(sliderInput, { clientX: 162, clientY: 302 })
expect(sliderLabel).toHaveTextContent(
"New text that the user should see"
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With