React测试通过,但在document.addListener处理程序中更新状态时,组件无法正常工作



我有一个DatePicker组件,如下所示:


const MonthPanel = ({ setMode = () => {} }) => {
return (
<div>
<button
onClick={(e) => setMode("year")}
data-testid="datepicker-year-button"
>
2021
</button>
</div>
);
};
const YearPanel = () => {
return <div data-testid="datepicker-year-panel">YearPanel</div>;
};
const DatePicker = (props) => {
const [open, setOpen] = useState(false);
const [mode, setMode] = useState("month");
const containerRef = useRef();
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleModeChange = (newMode) => {
setMode(newMode);
};
const generatePanel = () => {
switch (mode) {
case "month":
return <MonthPanel setMode={handleModeChange} />;
case "year":
return <YearPanel />;
default:
return null;
}
};
useEffect(() => {
const handleOutsideClick = (e) => {
if (containerRef.current && !containerRef.current.contains(e.target)) {
handleClose();
}
};
window.addEventListener("click", handleOutsideClick);
return () => {
window.removeEventListener("click", handleOutsideClick);
};
}, []);
return (
<div
ref={containerRef}
data-testid="datepicker-container"
>
<div onClick={handleOpen} data-testid="datepicker-input">
Select a date
</div>
{open && <div data-testid="datepicker-dialog">{generatePanel()}</div>}
</div>
);
};

我有一个这样的测试文件:

it.only("should close DatePicker dialog when clicked only outside DatePicker", () => {
const { getByTestId, queryByTestId } = render(
<DatePicker />
);
userEvent.click(getByTestId("datepicker-input"));
userEvent.click(getByTestId("datepicker-year-button"));
expect(queryByTestId("datepicker-dialog")).toBeInTheDocument();
userEvent.click(document.body);
expect(queryByTestId("datepicker-dialog")).toBeNull();
});

所需状态:

当您在DatePicker外部单击时,它应该会关闭。当您单击[data-testid="datepicker-year-button"]时,它应该将DatePicker模式更改为";年";,因此将显示年份面板。

当前状态:

当您单击[data-testid="datepicker-year-button"]时,它会将Datepicker模式更改为";年;和MonthPanel(以及按钮本身(被移除。因为按钮是事件目标并且已经删除,所以containerRef.current.contains(e.target)条件是false,对话框也将被删除。但测试显示对话框在文档中。

问题是我应该如何正确地测试这个功能。

您可以在<MonthPanel>中的按钮单击处理程序上调用e.stopPropagation(),以防止事件冒泡并被windoweventListener捕获。

<button
onClick={(e) => {
e.stopPropagation();
setMode("year");
}}
data-testid="datepicker-year-button"
>

尝试使用waitForwaitForElementToBeRemoved:等异步方法

await waitFor(() => {
expect(queryByTestId("datepicker-dialog")).toBeNull();
});

React-dom引入了act API来包装渲染或更新组件的代码,这使您的测试运行更接近React在浏览器中的工作方式。React测试库将其一些API封装在act函数中,但在某些情况下,您仍然需要使用waitFor或waitForElementToBeRemoved

我认为您的userEvent.click(document.body);工作不正常。

很可能document.body没有按应有的方式解决。您可能正在使用测试库用户事件,它们的基本示例是:

import { screen } from '@testing-library/dom'
import userEvent from '@testing-library/user-event'
test('types inside textarea', () => {
document.body.innerHTML = `<textarea />`
userEvent.type(screen.getByRole('textbox'), 'Hello, World!')
expect(screen.getByRole('textbox')).toHaveValue('Hello, World!')
})

检查您的document.body.innerHTML是否设置为

最新更新