前言
现在很多组件库的Select组件都是基于combobox来开发的。由于combobox DOM结构复杂,即使是一个简单的操作,也需要多行代码才能在单元测试中模拟用户的操作。有没有办法用一行代码就能表达了?
一个简单的示例
test('应完成完整的选择流程', async () => {
const user = userEvent.setup()
render(<Combobox options={['Apple', 'Banana']} />)
// 触发下拉
await user.click(screen.getByRole('combobox'))
// 验证选项渲染
const options = await screen.findAllByRole('option')
expect(options).toHaveLength(2)
// 选择操作
await user.click(options[1])
expect(screen.getByRole('combobox')).toHaveValue('Banana')
})
解决方案
我们可以构造一个可以链式调用的class, 如下所示。
import { within } from '@testing-library/react';
import { ByRoleOptions, screen } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
class Combobox {
private dom: HTMLElement = document.createElement('div'); // default to a div dom
test(dom: HTMLElement) {
this.dom = dom;
}
getByLabelText(label: string) {
this.dom = screen.getByLabelText(label);
return this;
}
getByRole(role: string, options?: ByRoleOptions | undefined) {
this.dom = screen.getByRole(role, options);
return this;
}
getByTestId(testId: string) {
this.dom = screen.getByTestId(testId);
return this;
}
selectOption(text: string) {
userEvent.click(this.dom);
// Here assumes that the rendered dom will always have a single listbox
const listbox = screen.getByRole('listbox');
const option = within(listbox).getByText(text);
expect(option).toBeInTheDocument();
userEvent.click(option);
return this;
}
isSelected(text: string) {
expect(screen.getByText(text)).toBeInTheDocument();
return this;
}
isNotSelected(text: string) {
//list box is still there, which means option is not selected
const listbox = screen.getByRole('listbox');
expect(listbox).toBeInTheDocument();
return this;
}
notExist(text: string) {
userEvent.click(this.dom);
// Here assumes that the rendered dom will always have a single listbox
const listbox = screen.getByRole('listbox');
const option = within(listbox).queryByText(text);
expect(option).not.toBeInTheDocument();
return this;
}
}
const combobox = () => {
return new Combobox();
};
export default combobox;
有了这个class之后,我们再来用一行代码重单元测试,如下所示:
test('应完成完整的选择流程', async () => {
const user = userEvent.setup()
render(<Combobox label='fruits' options={['Apple', 'Banana']} />)
combobox().getByLabelText('fruits').selectOption('Banana').isSelected('Banana')
})