如何测试与 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
是由动态内容定义的,因此您必须在测试中考虑它。
我有一个包装 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
是由动态内容定义的,因此您必须在测试中考虑它。