如何用Vitest和React Testing Library测试复选框交互后的UI变化
问题:点击复选框后父组件未添加Tailwind蓝色边框类名
我正在使用vitest和react testing library测试用户与复选框交互后的UI更新,具体需求是确保用户点击复选框时,父组件能添加上Tailwind的border-p-purplish-blue类,但测试发现点击后目标类名并未添加。
待测试组件代码
export const AddOn: FC<props> = ({ title, desc, price, type, handleAdd, handleRemove, checked, }) => { const [isChecked, toggleCheck] = useState(checked); useEffect(() => { isChecked ? handleAdd(title.toLowerCase(), price) : handleRemove(title.toLowerCase(), price); }, [isChecked]); // eslint-disable-line react-hooks/exhaustive-deps return ( <div className={ "relative w-full border border-n-light-gray rounded-md p-3 lg:px-6 lg:py-3 flex gap-4 items-center hover:opacity-70 " + (checked ? " border-p-purplish-blue bg-n-alabaster" : "") } data-testid={`addon-${title}-container`} > <div className="flex gap-4"> <input autoComplete="off" className="w-6 h-6 self-center cursor-pointer" data-testid={`addon-${title}`} defaultChecked={checked} type="checkbox" onClick={() => toggleCheck(!checked)} onKeyPress={(e) => { e.preventDefault(); toggleCheck(!checked); }} /> <div className="w-8/12 sm:w-full text-left"> <h3 className="text-base text-p-marine-blue font-bold">{title}</h3> <p className="text-n-cool-gray justify-self-start">{desc}</p> </div> </div> <p className="text-p-purplish-blue text-base font-medium absolute right-2 lg:right-6"> {type === "monthly" ? `+$${price}/mo` : `+$${price}/yr`} </p> </div> ); };
我的测试代码
test("UI TEST: should show the container with a blue border after user clicks on", async () => { render( <AddOn checked={false} desc="test" handleAdd={() => {}} handleRemove={() => {}} price={0} title="title-test" type="test" /> ); const addOnOnlineService: HTMLInputElement = await screen.findByTestId( "addon-title-test" ); await userEvent.click(addOnOnlineService); const testContainer: HTMLDivElement = await screen.findByTestId( "addon-title-test-container" ); await waitFor(() => { expect(Array.from(testContainer.classList)).toContain( "border-p-purplish-blue" ); }); });
问题根源与解决方案
1. UI渲染依赖错误状态
组件父容器的className判断逻辑用的是props传入的checked,而非组件内部维护的isChecked状态。点击复选框只会更新内部状态,但UI渲染逻辑没关联这个状态,所以不会触发样式变化。
2. 状态切换逻辑错误
复选框的点击/按键事件中,调用toggleCheck(!checked)是基于props的初始值切换,而非当前内部状态isChecked,这会导致状态更新逻辑混乱,无法正确反映用户的交互操作。
修复后的组件代码
export const AddOn: FC<props> = ({ title, desc, price, type, handleAdd, handleRemove, checked, }) => { const [isChecked, toggleCheck] = useState(checked); // 可选:当外部props的checked变化时,同步内部状态 useEffect(() => { toggleCheck(checked); }, [checked]); useEffect(() => { isChecked ? handleAdd(title.toLowerCase(), price) : handleRemove(title.toLowerCase(), price); }, [isChecked, handleAdd, handleRemove, title, price]); return ( <div className={ "relative w-full border border-n-light-gray rounded-md p-3 lg:px-6 lg:py-3 flex gap-4 items-center hover:opacity-70 " + // 改为使用内部状态isChecked判断样式 (isChecked ? " border-p-purplish-blue bg-n-alabaster" : "") } data-testid={`addon-${title}-container`} > <div className="flex gap-4"> <input autoComplete="off" className="w-6 h-6 self-center cursor-pointer" data-testid={`addon-${title}`} // 用内部状态控制复选框选中状态,替代默认值 checked={isChecked} type="checkbox" // 基于当前内部状态切换 onClick={() => toggleCheck(!isChecked)} onKeyPress={(e) => { e.preventDefault(); toggleCheck(!isChecked); }} /> <div className="w-8/12 sm:w-full text-left"> <h3 className="text-base text-p-marine-blue font-bold">{title}</h3> <p className="text-n-cool-gray justify-self-start">{desc}</p> </div> </div> <p className="text-p-purplish-blue text-base font-medium absolute right-2 lg:right-6"> {type === "monthly" ? `+$${price}/mo` : `+$${price}/yr`} </p> </div> ); };
优化后的测试代码
修复后状态更新是同步的,无需waitFor,可直接断言:
test("UI TEST: should show the container with a blue border after user clicks on", async () => { render( <AddOn checked={false} desc="test" handleAdd={() => {}} handleRemove={() => {}} price={0} title="title-test" type="test" /> ); const addOnCheckbox = screen.getByTestId("addon-title-test"); const testContainer = screen.getByTestId("addon-title-test-container"); // 初始状态断言 expect(testContainer).not.toHaveClass("border-p-purplish-blue"); await userEvent.click(addOnCheckbox); // 点击后状态断言 expect(testContainer).toHaveClass("border-p-purplish-blue"); });
内容的提问来源于stack exchange,提问作者Joaquin Arlettaz
相关产品推荐
相关产品推荐

