在拍摄快照之前等待 Material-ui 波纹完成的最佳方法

cda*_*d15 6 reactjs jestjs material-ui react-testing-library

我希望从 React 测试库社区获得一些专家建议和指导,以了解确保在拍摄快照之前完成 Material UI 波纹动画的最佳方法。

这个问题一直导致我们进行不稳定的测试,所有内容始终在本地通过,但是当我们在 CI 服务器上运行时,测试是间歇性的并且失败,因为动画完成的速度比本地快,这意味着它与存储的快照不匹配。

这个问题只有在我们换掉后才出现fireEvent,这对于userEventuserEvent 提供的额外操作是有意义的,但我想要的是存储不包含任何 UI 转换的快照。

我在下面创建了一个简单的测试,这将有助于说明问题(也在codesandbox上):

    import React from "react";
    import { render, screen, fireEvent, waitFor } from "@testing-library/react";
    import userEvent from "@testing-library/user-event";
    import Button from "@material-ui/core/Button";
    
    test("clicks material button with fireEvent - no ripple implication", () => {
      render(
        <Button variant="contained" color="primary">
          Ripple
        </Button>
      );
    
      const btnContainer = screen.getByRole("button", { name: /ripple/i });
    
      expect(btnContainer).toBeInTheDocument();
      screen.debug(btnContainer);
    
      fireEvent.click(btnContainer);
      screen.debug(btnContainer);
    });
    
    test("clicks material button with userEvent", async () => {
      render(
        <Button variant="contained" color="primary">
          Ripple
        </Button>
      );
    
      const btnContainer = screen.getByRole("button", { name: /ripple/i });
    
      expect(btnContainer).toBeInTheDocument();
      screen.debug(btnContainer);
    
      userEvent.click(btnContainer);
      screen.debug(btnContainer);
    });
    
    test("clicks material button with userEvent wait for ripples", async () => {
      render(
        <Button variant="contained" color="primary">
          Ripple
        </Button>
      );
    
      const btnContainer = screen.getByRole("button", { name: /ripple/i });
    
      expect(btnContainer).toBeInTheDocument();
      screen.debug(btnContainer);
    
      userEvent.click(btnContainer);
      screen.debug(btnContainer);
    
      await waitForRippleToRemove(btnContainer);
      // without the ripple transition being complete inconsitent tests runs can occur
      // when using snapshots as per commented line below
      // (ie some have ripples some don't)
      // expect(asFragment()).toMatchSnapshot();
    
      screen.debug(btnContainer);
    });
    
    function waitForRippleToRemove(container) {
      return waitFor(() => {
        expect(
          container.querySelector("span.MuiTouchRipple-root")
        ).toBeEmptyDOMElement();
      });
    }
Run Code Online (Sandbox Code Playgroud)

第一个测试工作正常,使用fireEvent按钮始终如下所示:

    <button
      class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
      tabindex="0"
      type="button"
    >
      <span
        class="MuiButton-label"
      >
        Ripple
      </span>
      <span
        class="MuiTouchRipple-root"
      />
    </button> 
Run Code Online (Sandbox Code Playgroud)

但是,在测试 2 中使用时,userEvent单击后按钮如下所示:

    <button
      class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
      tabindex="0"
      type="button"
    >
      <span
        class="MuiButton-label"
      >
        Ripple
      </span>
      <span
        class="MuiTouchRipple-root"
      >
        <span
          class="MuiTouchRipple-ripple MuiTouchRipple-rippleVisible"
          style="width: 2.8284271247461903px; height: 2.8284271247461903px; top: -1.4142135623730951px; left: -1.4142135623730951px;"
        >
          <span
            class="MuiTouchRipple-child MuiTouchRipple-childLeaving"
          />
        </span>
      </span>
    </button> 
Run Code Online (Sandbox Code Playgroud)

span.MuiTouchRipple-ripple元素将作为过渡的一部分被删除,但我的问题是等待这种情况发生的最佳方法是什么,因为我的测试显示我正在检查实现细节(即使用第 3 方依赖项的类名),这感觉有点可怕。


    await waitFor(() => {
        expect(
            container.querySelector("span.MuiTouchRipple-root")
        ).toBeEmptyDOMElement();
    });

Run Code Online (Sandbox Code Playgroud)

另外,值得指出的是,根据被测组件的不同,我们可能不得不userEvent.click多次使用,因此waitForRippleToRemove(btnContainer)在整个测试过程中进行多次调用感觉像是错误的做法,但目前这是一个可行的解决方案。

Dou*_*oug 3

正如您已经发现的,这里的问题在于单击按钮时的波纹动画。

我没有研究过MaterialUI代码,但动画通常是用某种计时器来实现的,即setTimeout

您通常可以使用jest.useFakeTimers();如下所示的方法来模拟它们。

beforeEach(() => jest.useFakeTimers());

test('matches snapshot after clicking button', () => {
  const { asFragment } = render(<Button>Ripple</Button>);

  userEvent.click(screen.getByRole('button', { name: 'Ripple' }));
  jest.runAllTimers();

  expect(asFragment()).toMatchSnapshot();
});

Run Code Online (Sandbox Code Playgroud)

笔记

fireEvent.click在一些测试中使用似乎是一个公平的妥协。特别是如果您已经userEvent.click在不同的测试中按下同一个按钮。


来源