reactjs React测试库userEvent未等待呈现更新

qxsslcnc  于 2023-03-08  发布在  React
关注(0)|答案(1)|浏览(116)

我有一个简单的RTL测试失败。
给定这个平凡的TodoList组件

import React, { useState } from "react";
import { FaTrash } from "react-icons/fa";

interface TodoItemProps {
  text: string;
  completed: boolean;
  onToggle: () => void;
  onDelete: () => void;
}

const TodoItem: React.FC<TodoItemProps> = ({
  text,
  completed,
  onToggle,
  onDelete,
}) => {
  return (
    <li>
      <div className="flex items-center py-2">
        <input
          type="checkbox"
          className="mr-2"
          checked={completed}
          onChange={onToggle}
        />
        <span
          className={`text-lg ${completed ? "line-through text-gray-400" : ""}`}
        >
          {text}
        </span>
        <button
          className="ml-auto"
          onClick={onDelete}
          data-testid="complete-todo"
        >
          <FaTrash />
        </button>
      </div>
    </li>
  );
};

const TodoList: React.FC<{
  initialValues?: string[];
}> = ({ initialValues }) => {
  const [todos, setTodos] = useState<{ text: string; completed: boolean }[]>(
    initialValues
      ? initialValues.map((v) => ({ text: v, completed: false }))
      : []
  );
  const [newTodo, setNewTodo] = useState<string>("");

  const handleAddTodo = () => {
    if (!newTodo.trim()) {
      return;
    }
    setTodos([...todos, { text: newTodo.trim(), completed: false }]);
    setNewTodo("");
  };

  const handleToggleTodo = (index: number) => {
    setTodos((prevState) =>
      prevState.map((todo, i) =>
        i === index ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  const handleDeleteTodo = (index: number) => {
    setTodos((prevState) => prevState.filter((_, i) => i !== index));
  };

  return (
    <div className="flex flex-col space-y-4 p-6 max-w-md">
      <div className="flex items-center">
        <input
          type="text"
          placeholder="Add a todo..."
          className="py-2 px-3 rounded-md bg-gray-100 w-full mr-2"
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
        />
        <button
          className="bg-blue-500 text-white py-2 px-4 rounded-md"
          onClick={handleAddTodo}
        >
          Add
        </button>
      </div>
      <ul>
        {todos.map((todo, index) => (
          <TodoItem
            key={index}
            text={todo.text}
            completed={todo.completed}
            onToggle={() => handleToggleTodo(index)}
            onDelete={() => handleDeleteTodo(index)}
          />
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

使用getByText获取todoItem时,以下测试失败。

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import TodoList from "./TodoList";

it("adds a new todo item", async () => {
    render(<TodoList />);
    const input = screen.getByRole("textbox");
    userEvent.click(input);
    userEvent.keyboard("Buy milk");
    const button = screen.getByRole("button", { name: "Add" });
    userEvent.click(button);
    const todoItem = screen.getByText("Buy milk");
    expect(todoItem).toBeInTheDocument();
  });

如果我使用pause辅助函数并在eachuserEvent之后调用它,则测试通过。

const pause = () => {
    return new Promise<void>((resolve) => {
      setTimeout(() => {
        resolve();
      }, 100);
    });
  };

  it("adds a new todo item", async () => {
    render(<TodoList />);
    const input = screen.getByRole("textbox");
    userEvent.click(input);
    await pause();
    userEvent.keyboard("Buy milk");
    await pause();
    const button = screen.getByRole("button", { name: "Add" });
    userEvent.click(button);
    await pause();
    const todoItem = screen.getByText("Buy milk");
    expect(todoItem).toBeInTheDocument();
  });

我的印象是,userEvent是专门为等待状态更新而创建的,但在这里,让todoItem及时出现在DOM中以供查询似乎还不够。
为什么会发生这种情况,还有什么比使用pause更好的解决方案呢?

jckbn6z7

jckbn6z71#

最好的方法是等待单独的userEvent调用。我确信这将达到您的目标。
如果做不到这一点,对于最后一个Assert,可以导入waitFor(从RTL)并执行await waitFor(()=>{expect(...)})

相关问题