Jest测试Twitter克隆应用推文编辑功能失败求助
问题:Jest测试推文编辑功能失败,
user.keyboard输入未生效 我用TDD模式开发Twitter克隆应用,当前测试推文编辑功能:用户点击已发布推文,会显示输入框编辑内容,点击推文按钮完成修改。该功能在Storybook手动测试正常,但Jest测试无法通过,推测是时序问题,试过act和waitFor均无效。
测试失败时,Jest打印的DOM显示'My First Tweet'仍在页面中,点击推文后输入框已出现,但调用user.keyboard时输入未生效,输入框值未更新为编辑后的内容。
组件渲染代码
beforeEach(() => { renderResult = render( <Tweet profilePic={imageUrl} username="ndland" title="Tweet Title" />, ); });
推文编辑测试代码
describe("editing a tweet", () => { const user = userEvent.setup(); let input: Element; beforeEach(async () => { input = screen.getByPlaceholderText("What's happening?"); const button = screen.getByRole("button", { name: "Tweet" }); await act(async () => { await user.click(input); await user.keyboard("My First Tweet"); await user.click(button); }); }); it("should allow a user to edit their tweet", async () => { const twxxt = screen.getByText("My First Tweet"); expect(twxxt).toBeInTheDocument(); expect(screen.queryByText("My edited Tweet")).not.toBeInTheDocument(); await user.click(twxxt); expect(input).toHaveValue("My First Tweet"); await user.keyboard("My edited Tweet"); await user.click(screen.getByRole("button", { name: "Tweet" })); await waitFor(() => expect(input).toHaveValue("My edited Tweet")); }); });
目标组件代码
const Tweet: React.FC<TweetProps> = ({ profilePic, username, title }) => { const [input, setInput] = React.useState<string | undefined>(undefined); const [tweet, setTweet] = React.useState<string | undefined>(undefined); const [placeholder, setPlaceholder] = React.useState<string | undefined>( "What's happening?", ); const [placeholderColor, setPlaceholderColor] = React.useState<string>( "placeholder:text-slate-400", ); const [likes, setLikes] = React.useState<number>(0); const [hasLiked, setHasLiked] = React.useState<boolean>(false); const handleTweet = () => { if (!input) { setPlaceholder("Enter a twxxt"); setPlaceholderColor("placeholder:text-red-500"); } setTweet(input); }; const handleLike = () => { if (hasLiked) { setLikes(likes - 1); setHasLiked(false); return; } if (input && !hasLiked) { setLikes(likes + 1); setHasLiked(true); } }; const handleTwxxtClick = () => { setPlaceholder(tweet); setTweet(undefined); }; return ( <div className="dark:bg-slate-800 mx-auto m-4 max-w-md overflow-hidden rounded-xl bg-white shadow-md md:max-w-2xl p-4 flex flex-row space-x-8"> <div className="relative w-16 h-16 flex-shrink-0"> <div> <Image className="rounded-full h-full w-full object-cover absolute" src={profilePic} alt="Profile Picture" width={100} height={100} /> </div> </div> <div className="flex-grow"> <div aria-label="username" className="text-indigo-500 font-semibold md:text-lg py-2" > @{username} </div> <div aria-label="title" className="dark:text-white font-semibold"> {title} </div> {tweet ? ( <> <div onClick={handleTwxxtClick} className="dark:text-white py-4 hover:cursor-pointer" > {tweet} </div> <div className="flex justify-normal space-x-2 items-center"> {likes === 0 ? ( <FontAwesomeIcon aria-label="like" onClick={handleLike} className="text-white" size="xl" icon={outlineThumb} /> ) : ( <FontAwesomeIcon aria-label="like" onClick={handleLike} className="text-white" size="xl" icon={solidThumb} /> )} {likes !== 0 && ( <div aria-label="likes" className="text-gray-400"> Likes: {likes} </div> )} </div> </> ) : ( <> <input type="text" placeholder={placeholder} value={tweet} onChange={(e) => setInput(e.target.value)} className={`w-full my-2 p-2 rounded-md dark:bg-slate-800 dark:text-white dark:border-2 dark:border-indigo-800 ${placeholderColor}`} /> <div className="flex justify-end items-center"> <button onClick={handleTweet} className="bg-indigo-500 text-white rounded-full px-4 py-2" > Tweet </button> </div> </> )} </div> </div> ); }; export default Tweet;
测试错误信息
● Tweet › editing a tweet › should allow a user to edit their tweet expect(element).toHaveValue(My edited Tweet) Expected the element to have value: My edited Tweet Received: My First Tweet
补充测试输出
<input aria-label="tweet input" class="w-full my-2 p-2 rounded-md dark:bg-slate-800 dark:text-white dark:border-2 dark:border-indigo-800 placeholder:text-slate-400" placeholder="My First Tweet" type="text" value="" />
点击事件已触发,但user.keyboard未将内容传入输入框,且已按建议添加输入框聚焦操作。
解决方案
1. 修复输入框value绑定错误
组件中输入框的value错误绑定了tweet状态,编辑模式下tweet被设为undefined,导致输入框显示空值,且用户输入的内容无法在输入框中体现。修改输入框的value绑定:
<input type="text" placeholder={placeholder} value={input} // 从tweet改为input状态 onChange={(e) => setInput(e.target.value)} className={`w-full my-2 p-2 rounded-md dark:bg-slate-800 dark:text-white dark:border-2 dark:border-indigo-800 ${placeholderColor}`} />
2. 编辑模式初始化输入框内容
点击推文进入编辑模式时,需要将当前推文内容赋值给input状态,确保输入框默认显示原推文内容,用户可以直接编辑:
const handleTwxxtClick = () => { setInput(tweet); // 新增:将原推文内容赋值给input setPlaceholder(tweet); setTweet(undefined); };
3. 优化测试代码
- 去掉不必要的
act包裹,userEvent方法本身已在act中执行 - 编辑模式下重新获取输入框元素,避免引用旧DOM节点
- 输入前先清空原有内容,确保新输入完全替换原内容
修改后的测试代码:
describe("editing a tweet", () => { const user = userEvent.setup(); beforeEach(async () => { const input = screen.getByPlaceholderText("What's happening?"); const button = screen.getByRole("button", { name: "Tweet" }); await user.click(input); await user.keyboard("My First Tweet"); await user.click(button); }); it("should allow a user to edit their tweet", async () => { const twxxt = screen.getByText("My First Tweet"); expect(twxxt).toBeInTheDocument(); expect(screen.queryByText("My edited Tweet")).not.toBeInTheDocument(); // 进入编辑模式 await user.click(twxxt); // 重新获取编辑状态的输入框 const editInput = screen.getByPlaceholderText("My First Tweet"); await user.click(editInput); // 清空原有内容并输入新内容 await user.keyboard("{selectall}{backspace}My edited Tweet"); await user.click(screen.getByRole("button", { name: "Tweet" })); // 验证推文已更新 await waitFor(() => { expect(screen.getByText("My edited Tweet")).toBeInTheDocument(); expect(screen.queryByText("My First Tweet")).not.toBeInTheDocument(); }); }); });
4. 优化提交逻辑(可选)
当前handleTweet会在输入为空时仍更新tweet,可以添加判断避免空内容提交:
const handleTweet = () => { if (!input?.trim()) { setPlaceholder("Enter a twxxt"); setPlaceholderColor("placeholder:text-red-500"); return; } setTweet(input); // 提交后清空输入框,恢复默认占位符 setInput(undefined); setPlaceholder("What's happening?"); setPlaceholderColor("placeholder:text-slate-400"); };
内容的提问来源于stack exchange,提问作者nland
相关产品推荐
相关产品推荐

