如何测试与 Ant Design 的 Popover 内容的交互?

How to test interaction with Ant Design's Popover content?

我有一个包装 Ant Design 弹出框的 React 组件。该组件获取由动态生成的内容中的用户交互(比如 click)调用的回调。类似于此:

const { Popover, Button } = antd;
const PopoverExtended = ({ onWhatever, children }) => {
    const handleClick = (event) => {
        if (event.target.className === 'some-class') {
            onWhatever(event.target.dataset.value);
        }
    };
    const dynamic = () => '<span class="some-class" data-value="42">Click this text</span>';
    const content = () => {
        return (
            <div>
                <p>Some HTML</p>
                <div dangerouslySetInnerHTML={{ __html: dynamic() }} onClick={handleClick}></div>
            </div>
        );
    };
    return (
        <Popover content={content()} placement="right" trigger="click">
            {children}
        </Popover>
    );
};

ReactDOM.render(
    <PopoverExtended onWhatever={(x) => console.log(x)}>
        <Button>Click me</Button>
    </PopoverExtended>,
    document.getElementById('root')
);
<link href="https://cdnjs.cloudflare.com/ajax/libs/antd/3.26.20/antd.css" rel="stylesheet"/>

<div id="root" style="margin: 2em 0 0 2em"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment-with-locales.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/antd/3.26.20/antd-with-locales.js" crossorigin="anonymous"></script>

一切都按预期工作,但使用 Jest 和 Enzyme 我正在尝试测试是否正在调用 onWhatever 回调,但到目前为止我还无法将动态内容作为目标ShallowWrapper 或 ReactWrapper。我试过:

describe(`<PopoverExtended /> interaction`, () => {
  const mockChildren = <Button>Mock me</Button>;
  const mockCallback = jest.fn();
  const wrapper = mount(<PopoverExtended onWhatever={mockCallback}>{mockChildren}</PopoverExtended>);

  // Try 1
  const trigger = wrapper.find('.some-class[data-value="42"]'); // Nothing found.

  // Try 2
  const content = mount(<>{wrapper.find(Popover).prop('content')}</>); 
  console.log(content.html()); // Is apparently the correct Popover content HTML
  const trigger = wrapper.find('.some-class[data-value="42"]'); // Nothing found.

  // Try 3
  const content = mount(<>{wrapper.find(Popover).prop('content')}</>);
  const rendered = content.render();
  const trigger = wrapper.find('.some-class[data-value="42"]'); // Node found, but
    // it's a CheerioWrapper, so I cannot call trigger.simulate('click');
});

关于如何正确测试正在调用回调的任何想法?

Enzyme 看不到动态内容,因此您无法模拟点击动态内容中的元素。您可以通过执行 console.log(wrapper.debug()) 来验证这一点,这将向您展示 Enzyme 看到的内容。尝试后:

const mockCallback = jest.fn();
const wrapper = mount(<PopoverExtended onWhatever={mockCallback}>{<Button>Mock me</Button>}</PopoverExtended>);

const trigger = wrapper.find("button");
trigger.simulate("click");

Enzyme 仅适用于托管 div:

...
<Content trigger={{...}} prefixCls="ant-popover" id={[undefined]} overlay={{...}}>
  <div className="ant-popover-inner" id={[undefined]} role="tooltip">
    <div>
      <div className="ant-popover-inner-content">
        <div>
          <p>
            Some HTML
          </p>
          <div dangerouslySetInnerHTML={{...}} onClick={[Function: handleClick]} />
        </div>
      </div>
    </div>
  </div>
</Content>
...

现在调用 wrapper.html() 实际上 returns 完整的 DOM 包括动态内容,但是正如您提到的那样,这对我们的情况毫无用处。在为 Enzyme 辩护时,动态内容使用 html 风格而不是 JSX(class 而不是 className)使其更难包装。

除此之外,我不明白您为什么需要在测试场景中包含动态内容。只需在主机 div 上模拟 click。事实上,我认为这是正确的做法,因为您在主机 div 而不是动态内容中定义了事件处理程序:

it(`<PopoverExtended /> interaction`, () => {
  const mockCallback = jest.fn();
  const wrapper = mount(<PopoverExtended onWhatever={mockCallback}>{<Button>Mock me</Button>}</PopoverExtended>);
  const mockEvent = {
    type: "click",
    target: {
      dataset: { value: 42 },
      className: "some-class"
    }
  };

  const trigger = wrapper.find("button");
  trigger.simulate("click");

  const hostDiv = wrapper.find("div.trigger-wrapper");
  hostDiv.simulate("click", mockEvent);

  expect(mockCallback.mock.calls.length).toBe(1);
  expect(mockCallback.mock.calls[0][0]).toBe(42);
});

选项 2

有趣的是,react 测试库在包含您的动态内容时没有问题,因此您可能想使用它而不是 Enzyme:

import React from "react";
import { render, fireEvent, screen } from '@testing-library/react'
import { Button } from "antd";

import PopoverExtended from "./PopOverExtended";

it(`<PopoverExtended /> interaction`, async () => {
  const mockCallback = jest.fn();
  
  render(<PopoverExtended onWhatever={mockCallback}>{<Button>Mock me</Button>}</PopoverExtended>);

  fireEvent.click(screen.getByText('Mock me'))    
  fireEvent.click(screen.getByText('Click this text'))

  expect(mockCallback.mock.calls.length).toBe(1);
  expect(mockCallback.mock.calls[0][0]).toBe("42");
});

对此的争论是 dataset 是由动态内容定义的,因此您必须在测试中考虑它。