我有一个简单的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
更好的解决方案呢?
1条答案
按热度按时间jckbn6z71#
最好的方法是等待单独的userEvent调用。我确信这将达到您的目标。
如果做不到这一点,对于最后一个Assert,可以导入waitFor(从RTL)并执行
await waitFor(()=>{expect(...)})
。