42

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

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

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

有什么解决办法吗?或者这是一个已知问题?

4

9 回答 9

105

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);
});
于 2020-04-28T23:22:50.510 回答
20

当您使用 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 可以在这里找到:

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

于 2019-03-15T15:24:12.090 回答
4

这是带有 Select 选项的 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>
  );
}

测试:

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
 *
 *
 */
于 2021-03-05T16:19:48.770 回答
1

使用*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 上运行,但在本地运行并通过。

于 2021-11-11T22:08:50.397 回答
0

这就是使用 MUI 5 时对我有用的方法。

      userEvent.click(screen.getByLabelText(/^foo/i));
      userEvent.click(screen.getByRole('option', {name: /^bar/i}));
于 2022-01-19T07:57:19.513 回答
0

我在使用 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)
    })
  })
})
于 2021-07-01T20:49:34.027 回答
0

我已经在一页中完成了多个选择,试试这个:

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
})
于 2022-01-18T05:09:40.967 回答
0
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();

    });
});
于 2020-03-18T20:57:48.783 回答
0
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);

});
于 2020-03-25T08:32:01.043 回答