radix-select.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /**
  2. * Radix UI Select组件的测试工具函数
  3. * 用于在测试中处理Radix UI Select组件的交互
  4. *
  5. * 注意:这个文件应该只在测试环境中使用,不要在生产代码中导入
  6. */
  7. import { screen } from '@testing-library/react';
  8. import userEvent from '@testing-library/user-event';
  9. /**
  10. * 查找Radix UI Select组件的隐藏select元素
  11. * Radix UI Select组件在DOM中会创建一个隐藏的select元素用于表单提交
  12. * 这个函数帮助我们在测试中找到这个隐藏的select元素
  13. *
  14. * @param selectButton - Select组件的触发按钮元素
  15. * @returns 隐藏的select元素,如果找不到则返回null
  16. */
  17. export function findHiddenSelectElement(selectButton: HTMLElement): HTMLSelectElement | null {
  18. // 在按钮的父元素或附近查找隐藏的select
  19. // 优先在按钮附近查找,如果找不到再全局查找
  20. const hiddenSelect = selectButton.closest('div')?.querySelector('select[data-radix-select-viewport]') ||
  21. selectButton.closest('div')?.querySelector('select[aria-hidden="true"]') ||
  22. selectButton.closest('div')?.querySelector('select[hidden]') ||
  23. document.querySelector('select[data-radix-select-viewport]') ||
  24. document.querySelector('select[aria-hidden="true"]') ||
  25. document.querySelector('select[hidden]');
  26. return hiddenSelect as HTMLSelectElement | null;
  27. }
  28. /**
  29. * 选择Radix UI Select组件的选项
  30. * 这个函数封装了查找隐藏select元素并选择选项的逻辑
  31. *
  32. * @param selectButton - Select组件的触发按钮元素
  33. * @param optionValue - 要选择的选项值
  34. * @returns 如果成功选择了选项返回true,否则返回false
  35. */
  36. export async function selectRadixOption(selectButton: HTMLElement, optionValue: string): Promise<boolean> {
  37. const hiddenSelect = findHiddenSelectElement(selectButton);
  38. if (hiddenSelect) {
  39. // 如果有隐藏的select,使用userEvent.selectOptions
  40. await userEvent.selectOptions(hiddenSelect, optionValue);
  41. return true;
  42. }
  43. return false;
  44. }
  45. /**
  46. * 通过test ID查找并选择Radix UI Select组件的选项
  47. * 这个函数封装了完整的查找、点击、选择流程
  48. *
  49. * @param testId - Select组件的test ID
  50. * @param optionValue - 要选择的选项值
  51. * @param user - userEvent实例(可选)
  52. * @returns 如果成功选择了选项返回true,否则返回false
  53. */
  54. export async function selectRadixOptionByTestId(
  55. testId: string,
  56. optionValue: string,
  57. user = userEvent
  58. ): Promise<boolean> {
  59. // 获取Select组件的触发按钮
  60. const selectButton = screen.getByTestId(testId);
  61. // 点击打开下拉菜单
  62. await user.click(selectButton);
  63. // 查找并选择选项
  64. return await selectRadixOption(selectButton, optionValue);
  65. }
  66. /**
  67. * 等待Radix UI Select组件启用
  68. * 有些Select组件在数据加载完成前是禁用的
  69. *
  70. * @param testId - Select组件的test ID
  71. * @param timeout - 超时时间(毫秒),默认5000ms
  72. */
  73. export async function waitForRadixSelectEnabled(testId: string, timeout = 5000): Promise<void> {
  74. const startTime = Date.now();
  75. while (Date.now() - startTime < timeout) {
  76. try {
  77. const selectElement = screen.getByTestId(testId);
  78. if (!selectElement.hasAttribute('disabled') && selectElement.getAttribute('aria-disabled') !== 'true') {
  79. return;
  80. }
  81. } catch (error) {
  82. // 元素可能还没渲染出来,继续等待
  83. }
  84. // 等待一小段时间再检查
  85. await new Promise(resolve => setTimeout(resolve, 100));
  86. }
  87. throw new Error(`Radix Select with testId "${testId}" did not become enabled within ${timeout}ms`);
  88. }
  89. /**
  90. * 完整的Radix UI Select选择流程
  91. * 包含等待启用、点击打开、选择选项的完整流程
  92. *
  93. * @param testId - Select组件的test ID
  94. * @param optionValue - 要选择的选项值
  95. * @param options - 配置选项
  96. * @param options.useFireEvent - 是否使用fireEvent而不是userEvent.click(默认为false)
  97. * @param options.user - userEvent实例(可选)
  98. */
  99. export async function completeRadixSelectFlow(
  100. testId: string,
  101. optionValue: string,
  102. options: {
  103. useFireEvent?: boolean;
  104. user?: typeof userEvent;
  105. } = {}
  106. ): Promise<void> {
  107. const { useFireEvent = false, user = userEvent } = options;
  108. // 1. 等待Select组件启用
  109. await waitForRadixSelectEnabled(testId);
  110. // 2. 获取Select按钮
  111. const selectButton = screen.getByTestId(testId);
  112. // 3. 点击打开下拉菜单
  113. if (useFireEvent) {
  114. // 有些Radix UI Select组件需要使用fireEvent而不是userEvent
  115. const { fireEvent } = await import('@testing-library/react');
  116. fireEvent.click(selectButton);
  117. } else {
  118. await user.click(selectButton);
  119. }
  120. // 4. 查找并选择选项
  121. const success = await selectRadixOption(selectButton, optionValue);
  122. if (!success) {
  123. // 如果通过隐藏select找不到,尝试直接点击选项文本
  124. // 注意:这里假设选项文本已经在DOM中渲染
  125. throw new Error(`Failed to select option "${optionValue}" for Radix Select with testId "${testId}". Make sure the dropdown is open and options are rendered.`);
  126. }
  127. }