我正在尝试使用react-testing-library 测试Select 组件onChange
的事件。
我使用getByTestId
效果很好的元素,然后设置元素的值,然后调用fireEvent.change(select);
,但onChange
从未调用过,状态也从未更新。
我尝试过使用 select 组件本身以及获取对底层input
元素的引用,但都不起作用。
有什么解决办法吗?或者这是一个已知问题?
我正在尝试使用react-testing-library 测试Select 组件onChange
的事件。
我使用getByTestId
效果很好的元素,然后设置元素的值,然后调用fireEvent.change(select);
,但onChange
从未调用过,状态也从未更新。
我尝试过使用 select 组件本身以及获取对底层input
元素的引用,但都不起作用。
有什么解决办法吗?或者这是一个已知问题?
material-ui 的 select 组件使用 mouseDown 事件触发弹出菜单出现。如果您使用fireEvent.mouseDown
它应该触发弹出窗口,然后您可以在出现的列表框中单击您的选择。请参见下面的示例。
import React from "react";
import { render, fireEvent, within } from "react-testing-library";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import Typography from "@material-ui/core/Typography";
it('selects the correct option', () => {
const {getByRole} = render(
<>
<Select fullWidth value={selectedTab} onChange={onTabChange}>
<MenuItem value="privacy">Privacy</MenuItem>
<MenuItem value="my-account">My Account</MenuItem>
</Select>
<Typography variant="h1">{/* value set in state */}</Typography>
</>
);
fireEvent.mouseDown(getByRole('button'));
const listbox = within(getByRole('listbox'));
fireEvent.click(listbox.getByText(/my account/i));
expect(getByRole('heading')).toHaveTextContent(/my account/i);
});
当您使用 Material-UI (这是默认设置)时Select
,这会变得非常复杂。native={false}
这是因为渲染的输入甚至没有<select>
HTML 元素,而是混合了 div、隐藏输入和一些 svg。然后,当您单击选择时,会显示一个表示层(有点像模式),其中包含您的所有选项(<option>
顺便说一下,它们不是 HTML 元素),我相信这是单击这些选项之一触发您作为onChange
回调传递给原始 Material-UI的任何内容<Select>
综上所述,如果您愿意使用<Select native={true}>
,那么您将拥有实际<select>
和HTML 元素可以使用,并且您可以按预期<option>
在 上触发 change 事件。<select>
这是来自代码沙箱的测试代码,它可以工作:
import React from "react";
import { render, cleanup, fireEvent } from "react-testing-library";
import Select from "@material-ui/core/Select";
beforeEach(() => {
jest.resetAllMocks();
});
afterEach(() => {
cleanup();
});
it("calls onChange if change event fired", () => {
const mockCallback = jest.fn();
const { getByTestId } = render(
<div>
<Select
native={true}
onChange={mockCallback}
data-testid="my-wrapper"
defaultValue="1"
>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</Select>
</div>
);
const wrapperNode = getByTestId("my-wrapper")
console.log(wrapperNode)
// Dig deep to find the actual <select>
const selectNode = wrapperNode.childNodes[0].childNodes[0];
fireEvent.change(selectNode, { target: { value: "3" } });
expect(mockCallback.mock.calls).toHaveLength(1);
});
您会注意到,<select>
一旦 Material-UI 渲染出它的<Select>
. 但是一旦找到它,您就可以fireEvent.change
对其进行操作。
CodeSandbox 可以在这里找到:
这是带有 Select 选项的 MUI TextField 的工作示例。
文本域:
import { TextField, MenuItem, InputAdornment } from "@material-ui/core";
import { useState } from "react";
export const sampleData = [
{
name: "Vat-19",
value: 1900
},
{
name: "Vat-0",
value: 0
},
{
name: "Vat-7",
value: 700
}
];
export default function TextSelect() {
const [selected, setSelected] = useState(sampleData[0].name);
return (
<TextField
id="vatSelectTextField"
select
label="#ExampleLabel"
value={selected}
onChange={(evt) => {
setSelected(evt.target.value);
}}
variant="outlined"
color="secondary"
inputProps={{
id: "vatSelectInput"
}}
InputProps={{
startAdornment: <InputAdornment position="start">%</InputAdornment>
}}
fullWidth
>
{sampleData.map((vatOption) => (
<MenuItem key={vatOption.name} value={vatOption.name}>
{vatOption.name} - {vatOption.value / 100} %
</MenuItem>
))}
</TextField>
);
}
测试:
import { fireEvent, render, screen } from "@testing-library/react";
import React from "react";
import { act } from "react-dom/test-utils";
import TextSelect, { sampleData } from "../MuiTextSelect/TextSelect";
import "@testing-library/jest-dom";
describe("Tests TextField Select change", () => {
test("Changes the selected value", () => {
const { getAllByRole, getByRole, container } = render(<TextSelect />);
//CHECK DIV CONTAINER
let vatSelectTextField = container.querySelector(
"#vatSelectTextField"
) as HTMLDivElement;
expect(vatSelectTextField).toBeInTheDocument();
//CHECK DIV CONTAINER
let vatSelectInput = container.querySelector(
"#vatSelectInput"
) as HTMLInputElement;
expect(vatSelectInput).toBeInTheDocument();
expect(vatSelectInput.value).toEqual(sampleData[0].name);
// OPEN
fireEvent.mouseDown(vatSelectTextField);
//CHECKO OPTIONS
expect(getByRole("listbox")).not.toEqual(null);
// screen.debug(getByRole("listbox"));
//CHANGE
act(() => {
const options = getAllByRole("option");
// screen.debug(getAllByRole("option"));
fireEvent.mouseDown(options[1]);
options[1].click();
});
//CHECK CHANGED
vatSelectInput = container.querySelector(
"#vatSelectInput"
) as HTMLInputElement;
expect(vatSelectInput.value).toEqual(sampleData[1].name);
});
});
/**
* HAVE A LOOK AT
*
*
* https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Select/Select.test.js
* (ll. 117-121)
*
* https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/TextField/TextField.test.js
*
*
*/
*ByLabelText()
// demo.js
import * as React from "react";
import Box from "@mui/material/Box";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl";
import Select from "@mui/material/Select";
import Typography from "@mui/material/Typography";
export default function BasicSelect() {
const [theThing, setTheThing] = React.useState("None");
const handleChange = (event) => {
setTheThing(event.target.value);
};
return (
<Box sx={{ minWidth: 120 }}>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">Choose a thing</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={theThing}
label="Choose a thing"
onChange={handleChange}
>
<MenuItem value={"None"}>None</MenuItem>
<MenuItem value={"Meerkat"}>Meerkat</MenuItem>
<MenuItem value={"Marshmallow"}>Marshmallow</MenuItem>
</Select>
</FormControl>
<Box sx={{ padding: 2 }}>
<Typography>The thing is: {theThing}</Typography>
</Box>
</Box>
);
}
// demo.test.js
import "@testing-library/jest-dom";
import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Demo from "./demo";
test("When I choose a thing, then the thing changes", async () => {
render(<Demo />);
// Confirm default state.
expect(await screen.findByText(/the thing is: none/i)).toBeInTheDocument();
// Click on the MUI "select" (as found by the label).
const selectLabel = /choose a thing/i;
const selectEl = await screen.findByLabelText(selectLabel);
expect(selectEl).toBeInTheDocument();
userEvent.click(selectEl);
// Locate the corresponding popup (`listbox`) of options.
const optionsPopupEl = await screen.findByRole("listbox", {
name: selectLabel
});
// Click an option in the popup.
userEvent.click(within(optionsPopupEl).getByText(/marshmallow/i));
// Confirm the outcome.
expect(
await screen.findByText(/the thing is: marshmallow/i)
).toBeInTheDocument();
});
codeandbox 注意:测试不在codesandbox 上运行,但在本地运行并通过。
这就是使用 MUI 5 时对我有用的方法。
userEvent.click(screen.getByLabelText(/^foo/i));
userEvent.click(screen.getByRole('option', {name: /^bar/i}));
我在使用 Material UI 选择元素时遇到了一些问题,但最后我找到了这个简单的解决方案。
const handleSubmit = jest.fn()
const renderComponent = (args?: any) => {
const defaultProps = {
submitError: '',
allCurrencies: [{ name: 'CAD' }, { name: 'EUR' }],
setSubmitError: () => jest.fn(),
handleSubmit,
handleClose,
}
const props = { ...defaultProps, ...args }
return render(<NewAccontForm {...props} />)
}
afterEach(cleanup)
// TEST
describe('New Account Form tests', () => {
it('submits form with corret data', async () => {
const expectedSubmitData = {
account_type: 'Personal',
currency_type: 'EUR',
name: 'MyAccount',
}
const { getByRole, getAllByDisplayValue } = renderComponent()
const inputs = getAllByDisplayValue('')
fireEvent.change(inputs[0], { target: { value: 'Personal' } })
fireEvent.change(inputs[1], { target: { value: 'EUR' } })
fireEvent.change(inputs[2], { target: { value: 'MyAccount' } })
userEvent.click(getByRole('button', { name: 'Confirm' }))
await waitFor(() => {
expect(handleSubmit).toHaveBeenCalledWith(expectedSubmitData)
expect(handleSubmit).toHaveBeenCalledTimes(1)
})
})
})
我已经在一页中完成了多个选择,试试这个:
import { render, fireEvent, within } from '@testing-library/react'
it('Should trigger select-xxx methiod', () => {
const { getByTestId, getByRole: getByRoleParent } = component
const element = getByTestId('select-xxx');
const { getByRole } = within(element)
const select = getByRole('button')
fireEvent.mouseDown(select);
const list = within(getByRoleParent('listbox')) // get list opened by trigger fireEvent
fireEvent.click(list.getByText(/just try/i)); //select by text
})
import * as React from "react";
import ReactDOM from 'react-dom';
import * as TestUtils from 'react-dom/test-utils';
import { } from "mocha";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
let container;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
describe("Testing Select component", () => {
test('start empty, open and select second option', (done) => {
//render the component
ReactDOM.render(<Select
displayEmpty={true}
value={""}
onChange={(e) => {
console.log(e.target.value);
}}
disableUnderline
classes={{
root: `my-select-component`
}}
>
<MenuItem value={""}>All</MenuItem>
<MenuItem value={"1"}>1</MenuItem>
<MenuItem value={"2"}>2</MenuItem>
<MenuItem value={"3"}>3</MenuItem>
</Select>, container);
//open filter
TestUtils.Simulate.click(container.querySelector('.my-select-component'));
const secondOption = container.ownerDocument.activeElement.parentElement.querySelectorAll('li')[1];
TestUtils.Simulate.click(secondOption);
done();
});
});
it('Set min zoom', async () => {
const minZoomSelect = await waitForElement( () => component.getByTestId('min-zoom') );
fireEvent.click(minZoomSelect.childNodes[0]);
const select14 = await waitForElement( () => component.getByText('14') );
expect(select14).toBeInTheDocument();
fireEvent.click(select14);
});