antd + testing-library

click 失效问题

https://github.com/ant-design/ant-design/issues/31595
https://stackoverflow.com/questions/61080116/ant-design-v4-breaks-react-testing-library-tests-for-select-and-autocomplete/61115234#61115234
很常见的一个操作,表格中的分页器的 pageSize 调节,点击后再选择option,但是通过 testing-library 发现 click 无法生效,且 dom 快照中始终无法出现 选择的options。

解决方法,触发 mousedown,而不是 click;然后等待 options 出现,再 click 具体的 option。

所以,这类问题属于组件库实现的不严谨,不规范,这种在交互界面正常,测试环境操作失败的情况,大部分都是类似的同作用不同名事件,支持不全导致的。shit。

waitFor & act

act 是 react 官方提供的一个测试用工具函数,用于等待,测试操作后,导致的所有 rerender 确保完成,并 dom 生效,再往下执行。
https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning
一般不需要 act 包裹,常见于 某个组件在初始化过程中,进行了 setState,导致了额外的 rerender;某个操作触发组件渲染,但其实只启动了动画逻辑,或渲染多次,这种情况需要 act 包裹。有了警报时,就最好对对应内容进行包裹:

  1. await act(async () => {
  2. fireEvent.mouseDown(getByText('15 / page'));
  3. fireEvent.click(await screen.findByText('20 / page'));
  4. });

waitFor 则用于等待某个操作,或者叫 失败重试,常见的是某些 dom的显隐 带有动画,所以不是 fireEvent 后立即出现的,此时需要使用 waitFor,其实就是 多次尝试寻找该 dom。
如,多次尝试,寻找 text 为 20 / page 的 DOM:

  1. fireEvent.click(await waitFor(() => getByText('20 / page')));

由于 waitFor 其实就是将某个 同步操作 做 失败重试,所以会将其异步化,上述操作可以转换为:

  1. fireEvent.click(await screen.findByText('20 / page'));

findXxx,与 getXxx / queryXxx 的区别就是 自动异步化,并进行失败重试。

query 寻找不到会返回 null,get 则会直接报错,所以可以用 queryXxx 方法验证 无某些DOM 的场景。 queryAllXxx 则会返回 []。

同理,如果是存在动画干扰,需要多次重试的 expect 验证,则可通过 query + toBeInTheDocument 来进行:

  1. expect(screen.queryByText('Go to')).not.toBeInTheDocument();

以及如果等待一个元素消失,可用 waitForElementToBeRemoved:

toBeVisible 不会重试,所以只可用于同步场景

  1. await waitForElementToBeRemoved(() => getByText('No Data'));

screen

现在 testing-library 提供了 screen,来代表 within document.body,这样让 render 更加纯粹了,不需要再从 render 的返回值里读东西了。

screen#

All of the queries exported by DOM Testing Library accept a container as the first argument. Because querying the entire document.body is very common, DOM Testing Library also exports a screen object which has every query that is pre-bound to document.body (using the within functionality). Wrappers such as React Testing Library re-export screen so you can use it the same way.

所以,现在可以直接通过 screen.getXxx 进行操作了:

  1. import {render, screen} from '@testing-library/react' // (or /dom, /vue, ...)
  2. test('should show login form', () => {
  3. render(<Login />)
  4. const input = screen.getByLabelText('Username')
  5. // Events and assertions...
  6. })

fireEvent|user-event

https://github.com/testing-library/dom-testing-library/blob/main/src/event-map.js
fireEvent 的操作,类似直接触发监听函数回调,所以不会被太多东西干扰。
但 user-event 不同,user-event 被设计为,仿造 用户的行为 进行操作,但是,却没有仿造用户的实际操作时机,所以如果组件在 连续 的操作过程中,有阻断行为,会造成失败,如 antd 大量存在的 point-events:none:
会被 user-event 库中的这段逻辑阻断,版本需要 回退到 @testing-library/user-event@13.0.16
https://github.com/testing-library/user-event/commit/32e971226e41c8bb458d6f56982f3e603152cffc#diff-a681e050ffe760739876a36e05c9cfb1bb1ce0b6dae27c54152e16b9f6fac2f4
作者提供了类似的解决方案:
image.png
实际实验,也可以通过对 user 操作加 waitFor 来解决,但实际还是变扭:

  1. await waitFor(() => userEvent.click(searchInput!));
  2. userEvent.type(searchInput!, '14278');

https://github.com/ant-design/ant-design/issues/31105
所以,由于 antd 的设计原因,不适合用 user-event 这类库进行操作,实际还是直接使用 fireEvent 更为合适。