首页
/ Headless UI Combobox 在 Portal 中渲染时的测试解决方案

Headless UI Combobox 在 Portal 中渲染时的测试解决方案

2025-05-06 03:35:24作者:廉皓灿Ida

Headless UI 是一个流行的无头 UI 组件库,它提供了完全可访问、无样式的 UI 组件,让开发者可以完全控制样式和行为。在最新版本(v2)中,Combobox 组件默认会将选项列表渲染到 Portal 中,这给测试带来了一些挑战。

问题背景

当 Combobox 使用 anchor 属性时,选项列表会被渲染到 body 元素下的 Portal 中。这种设计虽然提高了组件的灵活性和可访问性,但在使用 react-testing-library 进行测试时会出现问题:

  1. 测试用例无法找到渲染在 Portal 中的选项列表
  2. 测试会无限挂起或失败
  3. 控制台会出现关于无效 CSS 值的警告

解决方案

1. 使用 userEvent 代替 fireEvent

Headless UI 内部使用 mousedown 而非 click 事件来处理选项选择。因此,使用 userEvent.selectOptions() 更符合实际交互:

screen.getByRole("input").focus();
const option = await screen.getByRole("option", { name: "Some option" });
await userEvent.selectOptions(screen.getByRole("listbox"), option);

2. 处理动画帧问题

由于 Combobox 内部使用了 requestAnimationFrame 来处理焦点状态,这会导致测试时序问题。可以通过模拟这些 API 来解决:

const requestAnimationFrameMock = jest.spyOn(window, "requestAnimationFrame")
  .mockImplementation(setImmediate as any);
const cancelAnimationFrameMock = jest.spyOn(window, "cancelAnimationFrame")
  .mockImplementation(clearImmediate as any);

// 测试代码...

requestAnimationFrameMock.mockRestore();
cancelAnimationFrameMock.mockRestore();

3. 使用 immediate 属性

设置 immediate 属性可以简化焦点管理:

<Combobox
  multiple
  value={selectedPeople}
  onChange={setSelectedPeople}
  onClose={() => setQuery("")}
  immediate
>
  {/* ... */}
</Combobox>

4. 直接操作焦点

对于简单场景,直接操作焦点可能就足够了:

fireEvent.focus(screen.getByRole("combobox"));
screen.getByRole("option", { name: "Katelyn Rohan" });

最新进展

Headless UI 团队已经意识到这个问题,并在最新的 insider 版本中提供了修复。开发者可以通过以下命令尝试修复后的版本:

npm install @headlessui/react@insiders

总结

测试渲染在 Portal 中的组件确实会带来一些挑战,但通过理解 Headless UI 的内部实现机制,并采用适当的测试策略,这些问题是可以解决的。随着库的不断更新,这些测试体验也在不断改善。开发者可以根据自己的需求选择最适合的解决方案,或者等待官方正式发布修复版本。

登录后查看全文
热门项目推荐
相关项目推荐