对 Material UI Select 组件的更改做出反应测试库

Rob*_*ers 23 reactjs material-ui react-testing-library

我正在尝试使用react-testing-library 测试Select 组件onChange事件。

我使用getByTestId效果很好的元素获取元素,然后设置元素的值,然后调用fireEvent.change(select);onChange从未调用过并且状态从未更新过。

我已经尝试使用 select 组件本身以及获取对底层input元素的引用,但都不起作用。

任何解决方案?或者这是一个已知问题?

haa*_*dev 80

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";

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);
});
Run Code Online (Sandbox Code Playgroud)

  • 是的,这是正确的测试方法。您可以通过检查material-ui如何测试其组件来获取更多详细信息https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Select/Select.test.js (10认同)
  • @YaserAliPeedikakkal 如果您的“Select”有标签,则可以通过使用“getByLabelText()”进行第一次点击来定位“Select”。带有 `role="listbox"` 的元素会在点击后出现,因此除非您自己添加了带有 `role="listbox"` 的元素,否则下一个查询只会找到目标点击中的 1 个弹出窗口。例如,对于 `user-event`: `userEvent.click(getByLabelText("Select Label")); userEvent.click(within(getByRole("listbox")).getByText("选项文本"));` (5认同)
  • @nnattawat更正的链接:https://github.com/mui-org/material-ui/blob/master/packages/mui-material/src/Select/Select.test.js (3认同)

Alv*_*Lee 16

这证明当您使用材料的UI的是超级复杂Selectnative={false}(这是默认值)。这是因为渲染的输入甚至没有<select>HTML 元素,而是混合了 div、隐藏输入和一些 svg。然后,当您单击选择时,会显示一个表示层(有点像模态)以及您的所有选项(<option>顺便说一下,这些选项不是HTML 元素),我相信单击这些选项之一触发您作为onChange回调传递给原始 Material-UI 的任何内容<Select>

所有这一切说,如果你愿意用<Select native={true}>,那么你就必须实际<select><option>HTML元素一起工作,你可以在火灾发生变化的事件<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);
});
Run Code Online (Sandbox Code Playgroud)

您会注意到,<select>一旦 Material-UI 呈现其<Select>. 但是一旦你找到它,你就可以fireEvent.change对其进行处理。

CodeSandbox 可以在这里找到:

为 material-ui 选择编辑触发更改事件

  • 谢谢@Alvin Lee,这正是我们所需要的。为了将来参考,我们在 `inputProps` 中设置了测试 ID,因此:`inputProps={{ "data-testid": "my-wrapper" }}` 然后不必通过引用 2 个子节点来获取选择节点. (3认同)

ken*_*ntr 11

使用*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>
  );
}
Run Code Online (Sandbox Code Playgroud)

测试

// 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();
});
Run Code Online (Sandbox Code Playgroud)

codesandbox 注意:测试不在codesandbox上运行,但在本地运行并通过。


Gab*_*iel 10

使用 Material UI 5.10.3,以下是如何模拟组件上的单击Select,然后抓取/验证项目值,并单击其中之一来触发底层更改事件:

import { fireEvent, render, screen, within } from '@testing-library/react';
import { MenuItem, Select } from '@mui/material';

describe('MUI Select Component', () => {
  it('should have correct options an handle change', () => {
    const spyOnSelectChange = jest.fn();

    const { getByTestId } = render(
      <div>
        <Select
          data-testid={'component-under-test'}
          value={''}
          onChange={(evt) => spyOnSelectChange(evt.target.value)}
        >
          <MenuItem value="menu-a">OptionA</MenuItem>
          <MenuItem value="menu-b">OptionB</MenuItem>
        </Select>
      </div>
    );

    const selectCompoEl = getByTestId('component-under-test');

    const button = within(selectCompoEl).getByRole('button');
    fireEvent.mouseDown(button);

    const listbox = within(screen.getByRole('presentation')).getByRole(
      'listbox'
    );

    const options = within(listbox).getAllByRole('option');
    const optionValues = options.map((li) => li.getAttribute('data-value'));

    expect(optionValues).toEqual(['menu-a', 'menu-b']);

    fireEvent.click(options[1]);
    expect(spyOnSelectChange).toHaveBeenCalledWith('menu-b');
  });
});
Run Code Online (Sandbox Code Playgroud)

也发布在这里


the*_*Emu 7

以下是带有“选择”选项的 MUI TextField 的工作示例。

沙盒:https://codesandbox.io/s/stupefied-chandrasekhar-vq2x0 ?file=/src/__tests__/TextSelect.test.tsx:0-1668

文本域:

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>
  );
}
Run Code Online (Sandbox Code Playgroud)

测试:

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
 *
 *
 */
Run Code Online (Sandbox Code Playgroud)