使用React测试库测试文档监听器



我正在尝试测试类似于以下内容的React组件:

import React, { useState, useEffect, useRef } from "react";
export default function Tooltip({ children }) {
const [open, setOpen] = useState(false);
const wrapperRef = useRef(null);
const handleClickOutside = (event) => {
if (
open &&
wrapperRef.current &&
!wrapperRef.current.contains(event.target)
) {
setOpen(false);
}
};
useEffect(() => {
document.addEventListener("click", handleClickOutside);
return () => {
document.removeEventListener("click", handleClickOutside);
};
});
const className = `tooltip-wrapper${(open && " open") || ""}`;
return (
<span ref={wrapperRef} className={className}>
<button type="button" onClick={() => setOpen(!open)} />
<span>{children}</span>
<br />
<span>DEBUG: className is {className}</span>
</span>
);
}

单击工具提示按钮将状态更改为打开(更改className(,然后在组件外部再次单击将其更改为关闭。

该组件可以工作(具有适当的样式(,所有React测试库(具有用户事件(测试都可以工作,除了单击外部。

it("should close the tooltip on click outside", () => {
// Arrange
render(
<div>
<p>outside</p>
<Tooltip>content</Tooltip>
</div>
);
const button = screen.getByRole("button");
userEvent.click(button);
// Temporary assertion - passes
expect(button.parentElement).toHaveClass("open");
// Act
const outside = screen.getByText("outside");
// Gives should be wrapped into act(...) warning otherwise
act(() => {
userEvent.click(outside);
});
// Assert
expect(button.parentElement).not.toHaveClass("open"); // FAILS
});

我不明白为什么我必须将点击事件包装在act中——这对于React测试库来说通常是不必要的。

我也不明白为什么最后的断言失败了。单击处理程序被调用两次,但open两次都是true

有很多关于React合成事件局限性的文章,但我不清楚如何将所有这些放在一起。

我终于让它工作起来了。

it("should close the tooltip on click outside", async () => {
// Arrange
render(
<div>
<p data-testid="outside">outside</p>
<Tooltip>content</Tooltip>
</div>
);
const button = screen.getByRole("button");
userEvent.click(button);
// Verify initial state
expect(button.parentElement).toHaveClass("open");
const outside = screen.getByTestId("outside");
// Act
userEvent.click(outside);
// Assert
await waitFor(() => expect(button.parentElement).not.toHaveClass("open"));
});

关键似乎是要确保所有活动都在测试结束前完成。

假设一个测试触发了一个点击事件,然后设置状态。设置状态通常会导致重新应答,您的测试需要等待这种情况发生。通常情况下,您可以通过等待显示新状态来完成此操作。

在这种特殊情况下,等待是合适的。

最新更新