reactjs 使用@testing-library/react测试材质用户界面滑块

m528fe3b  于 2022-12-03  发布在  React
关注(0)|答案(5)|浏览(147)

大家好,我正在尝试测试一个用Material-UI创建的Slider组件,但是我无法将我的测试转到pass。我想使用fireEvent@testing-library/react来测试值的变化。我一直在关注这篇文章,以正确地查询DOM,我无法获得正确的DOM节点。
先谢谢你。

<Slider />元件

// @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();
    });
  });
});
bogh5gae

bogh5gae1#

经过几个小时的斗争,我能够解决我的情况下有关测试MUI滑块
这真的取决于你需要如何测试你的,在我的情况下,我必须检查一个标签文本内容是否已经改变后,点击一个标记使用marks滑块道具。
"问题"
1)slider组件根据元素getBoundingClientRectMouseEvent计算返回值
2)如何查询slider并引发事件。
3)JSDOM对阅读元素实际高度和宽度的限制导致了问题1

解决方案

1)mock getBoundingClientRect还应该修复第3个问题
2)将测试ID添加到滑块并使用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"
    )
70gysomp

70gysomp2#

我建议不要为自定义组件编写测试,并相信该组件适用于我们所有的情况。
阅读this article以了解更多细节。因为他们已经提到了如何为 Package react-select的组件编写单元测试。
我采用了类似的方法,并为我的第三方滑块组件编写了一个mock。
setupTests.js中:

jest.mock('@material-ui/core/Slider', () => (props) => {
  const { id, name, min, max, onChange, testid } = props;
  return (
    <input
      data-testid={testid}
      type="range"
      id={id}
      name={name}
      min={min}
      max={max}
      onChange={(event) => onChange(event.target.value)}
    />
  );
});

通过这个模拟,您可以简单地在测试中触发一个change事件,如下所示:

fireEvent.change(getByTestId(`slider`), { target: { value: 25 } });

确保将正确的testid作为属性传递给SliderWithStyles组件

iqjalb3h

iqjalb3h3#

我把上面提到的解决方案变成了简单的(Typescript)助手

export class Slider {
  private static height = 10

  // For simplicity pretend that slider's width is 100
  private static width = 100

  private static getBoundingClientRectMock() {
    return {
      bottom: Slider.height,
      height: Slider.height,
      left: 0,
      right: Slider.width,
      top: 0,
      width: Slider.width,
      x: 0,
      y: 0,
    } as DOMRect
  }

  static change(element: HTMLElement, value: number, min: number = 0, max: number = 100) {
    const getBoundingClientRect = element.getBoundingClientRect
    element.getBoundingClientRect = Slider.getBoundingClientRectMock
    fireEvent.mouseDown(
        element,
        {
            clientX: ((value - min) / (max - min)) * Slider.width,
            clientY: Slider.height
        }
    )
    element.getBoundingClientRect = getBoundingClientRect
  }
}

用法:

Slider.change(getByTestId('mySlider'), 40) // When min=0, max=100 (default)
// Otherwise
Slider.change(getByTestId('mySlider'), 4, 0, 5) // Sets 4 with scale set to 0-5
omqzjyyz

omqzjyyz4#

基于@reman_00001的答案,我为组件创建了一个文件mock。我用TypeScript编写了它,但是没有类型它应该也能正常工作。

x1月0n1x日

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)}
        />
    );
}

现在,Material UI <Slider/>组件的每次使用都将在测试期间呈现为一个简单的HTML <input/>元素,使用Jest和react-testing-library更容易处理。
{...(others as any)}是一个黑客,它让我不必担心原始组件的每一个可能的属性都被正确处理。根据你所依赖的Slider属性,你可能需要在反结构化过程中提取额外的属性,这样你就可以正确地将它们转换成对普通<input/>元素有意义的东西。See this page in the Material UI docs了解每一个可能的属性的信息。

46scxncf

46scxncf5#

下面是一个简单而灵活的方法:
1.导入Mui,如下所示:

import * as MuiModule from '@mui/material';

1.模拟@mui/材料库并为Slider指定一个函数

jest.mock('@mui/material', () => ({
  __esModule: true,
  ...jest.requireActual('@mui/material'),
  Slider: () => {}, // Important: resolves issues where Jest sees Slider as an object instead of a function and allows jest.spy to work
}));

1.监视Slider并编写自己的实现

describe('Given a MyCustomSlider component', () => {
  beforeEach(() => {
    jest.spyOn(MuiModule, 'Slider').mockImplementation((props) => {
      const { value, onChange, onChangeCommitted } = props;
      const simulateChange = (event) => {
        if (!onChange) return;
        onChange(event, [0, 10], 0);
      };
      const simulateChangeCommitted = (event) => {
        if (!onChangeCommitted) return;
        onChangeCommitted(event, [0, 10]);
      };
      return (
        <>
          Value: {value}
          <button onClick={simulateChange}>Trigger Change</button>
          <button onClick={simulateChangeCommitted}>
            Trigger Change Committed
          </button>
        </>
      );
    });
  });

  // ... tests here
});

1.呈现组件并触发change事件:

describe('When it is rendered', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    render(<MyCustomSlider />);
  });

  describe('When a change event occurs', () => {
    beforeEach(async () => {
      jest.clearAllMocks();
      await userEvent.click(screen.getByText('Trigger Change'));
    });

    test('Then ... (your assertion here)', () => {
      // your test
    });
  });
});

完整示例:

import userEvent from '@testing-library/user-event';
import * as MuiModule from '@mui/material';
import { render } from '../../../libs/test-utils';

jest.mock('@mui/material', () => ({
  __esModule: true,
  ...jest.requireActual('@mui/material'),
  Slider: () => {},
}));

describe('Given a MyCustomSlider component', () => {
  beforeEach(() => {
    jest.spyOn(MuiModule, 'Slider').mockImplementation((props) => {
      const { value, onChange, onChangeCommitted } = props;
      const simulateChange = (event) => {
        if (!onChange) return;
        onChange(event, [0, 10], 0);
      };
      const simulateChangeCommitted = (event) => {
        if (!onChangeCommitted) return;
        onChangeCommitted(event, [0, 10]);
      };
      return (
        <>
          Value: {value}
          <button onClick={simulateChange}>Trigger Change</button>
          <button onClick={simulateChangeCommitted}>
            Trigger Change Committed
          </button>
        </>
      );
    });
  });

  describe('When it is rendered', () => {
    beforeEach(() => {
      jest.clearAllMocks();
      render(<MyCustomSlider />);
    });

    describe('When a change event occurs', () => {
      beforeEach(async () => {
        jest.clearAllMocks();
        await userEvent.click(screen.getByText('Trigger Change'));
      });

      test('Then ... (your assertion here)', () => {
        // your test
      });
    });
  });
});

相关问题