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

Javascript 当document.addListener处理程序中的状态更新时,React测试通过,但组件无法正常工作,javascript,reactjs,jestjs,react-testing-library,Javascript,Reactjs,Jestjs,React Testing Library,我有一个日期选择器组件,如下所示: const MonthPanel = ({ setMode = () => {} }) => { return ( <div> <button onClick={(e) => setMode("year")} data-testid="datepicker-year-button" > 2021

我有一个日期选择器组件,如下所示:


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();
  });

const MonthPanel=({setMode=()=>{}})=>{
返回(
设置模式(“年份”)}
data testid=“日期选择器年份按钮”
>
2021
);
};
const YearPanel=()=>{
回返问题小组;
};
常量日期选择器=(道具)=>{
const[open,setOpen]=useState(false);
const[mode,setMode]=使用状态(“月”);
const containerRef=useRef();
const handleOpen=()=>{
setOpen(真);
};
常量handleClose=()=>{
setOpen(假);
};
const handleModeChange=(newMode)=>{
设置模式(新模式);
};
const generatePanel=()=>{
开关(模式){
案例“月份”:
返回;
案件“年份”:
返回;
违约:
返回null;
}
};
useffect(()=>{
const handleOutsideClick=(e)=>{
if(containerRef.current&!containerRef.current.contains(e.target)){
handleClose();
}
};
window.addEventListener(“单击”,手柄外侧单击);
return()=>{
window.removeEventListener(“单击”,手柄外侧单击);
};
}, []);
返回(
选择一个日期
{open&&{generatePanel()}}
);
};
我有一个这样的测试文件:


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();
  });
it.only(“仅在日期选择器外部单击时应关闭日期选择器对话框”,()=>{
常量{getByTestId,queryByTestId}=render(
);
单击(getByTestId(“日期选择器输入”);
点击(getByTestId(“日期选择器年份按钮”);
expect(queryByTestId(“日期选择器对话框”).toBeInTheDocument();
点击(document.body);
expect(queryByTestId(“日期选择器对话框”)).toBeNull();
});
所需状态: 当您单击外部日期选择器时,它应该关闭。当您单击
[data testid=“datepicker year button”]
时,它应该将datepicker模式更改为“year”,因此将显示year面板

当前状态: 当您单击
[data testid=“datepicker year button”]
时,它会将datepicker模式更改为“year”,并删除MonthPanel(及其按钮本身)。由于该按钮是事件目标且已被删除,
containerRef.current.contains(e.target)
条件为
false
,对话框也将被删除。但是测试显示对话框在文档中


问题是如何正确测试此功能。

您可以在
中的按钮单击处理程序上调用
e.stopPropagation()
,以防止事件冒泡并被
窗口的
事件侦听器捕获

<button
    onClick={(e) => {
        e.stopPropagation();
        setMode("year");
    }}
    data-testid="datepicker-year-button"
>
{
e、 停止传播();
设定模式(“年”);
}}
data testid=“日期选择器年份按钮”
>
尝试使用like
waitFor
waitForElementToBeRemoved

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

React-dom
引入了
act-API
来包装呈现或更新组件的代码,这使测试运行更接近React在浏览器中的工作方式。React测试库将它的一些API封装在act函数中,但在某些情况下,您仍然需要使用waitFor或waitForElementToBeRemoved,我相信您的
userEvent.click(document.body)工作不正常

最有可能的是
document.body
的解析方式不正确。 您可能正在使用测试库用户事件,其基本示例是:

从'@testing library/dom'导入{screen}
从“@测试库/用户事件”导入userEvent
测试('文本区域内的类型',()=>{
document.body.innerHTML=``
userEvent.type(screen.getByRole('textbox'),'Hello,World!')
expect(screen.getByRole('textbox')).toHaveValue('Hello,World!')
})

检查您的
document.body.innerHTML
是否设置为某个值,我知道。问题是测试必须失败,但它通过了。我想知道我的测试出了什么问题,我应该如何测试。那么你是故意让测试失败的?你可能应该在最初的帖子中提到这一点。代码工作不正常,对吗。因此,测试必须失败,但它通过了。我问我的测试出了什么问题。我的意思是,你故意更改了代码,使其不能正常工作,这样测试就会失败。但在这种情况下,它实际上并没有失败。就是这样。您确定您的测试运行程序知道如何处理
document.body
?您希望的状态实际上是测试在应该的时候失败了。您可以更新问题以反映这一点。