import { flushPromises, mount, type VueWrapper } from '@vue/test-utils';
|
import Antd, { Select } from 'ant-design-vue';
|
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
|
import DropdownOptions from '../../src/components/DropdownOptions.vue';
|
import type { DropdownOption } from '../../src/domain/types';
|
|
beforeAll(() => {
|
window.matchMedia ??= () =>
|
({
|
addEventListener() {},
|
addListener() {},
|
dispatchEvent: () => false,
|
matches: false,
|
media: '',
|
onchange: null,
|
removeEventListener() {},
|
removeListener() {},
|
}) as MediaQueryList;
|
|
globalThis.ResizeObserver ??= class ResizeObserver {
|
disconnect() {}
|
observe() {}
|
unobserve() {}
|
};
|
});
|
|
function mountOptions(options: DropdownOption[], defaultValue = '', disabled = false, errorField?: 'dropdown-default' | 'dropdown-options', errorId?: string) {
|
const wrapper = mount(DropdownOptions, {
|
props: { defaultValue, disabled, errorField, errorId, options },
|
attachTo: document.body,
|
global: { plugins: [Antd] },
|
});
|
mountedWrappers.push(wrapper);
|
return wrapper;
|
}
|
|
const mountedWrappers: VueWrapper[] = [];
|
|
afterEach(() => {
|
mountedWrappers.splice(0).forEach((wrapper) => wrapper.unmount());
|
document.body.replaceChildren();
|
});
|
|
describe('DropdownOptions', () => {
|
it('两项时禁用删除,添加第三项后允许删除且不修改 props', async () => {
|
const options = [
|
{ display: '', value: '' },
|
{ display: '', value: '' },
|
];
|
const wrapper = mountOptions(options);
|
|
expect(wrapper.findAll('[data-test^="remove-option-"]')).toHaveLength(2);
|
expect(wrapper.findAll('[data-test^="remove-option-"]').every((button) => button.attributes('disabled') !== undefined)).toBe(true);
|
|
await wrapper.get('[data-test="add-option"]').trigger('click');
|
const added = wrapper.emitted<DropdownOption[][]>('update:options')?.at(-1)?.[0];
|
expect(added).toEqual([...options, { display: '', value: '' }]);
|
expect(options).toHaveLength(2);
|
|
await wrapper.setProps({ options: added });
|
expect(wrapper.findAll('[data-test^="remove-option-"]').every((button) => button.attributes('disabled') === undefined)).toBe(true);
|
expect(wrapper.get('[data-test="option-display-0"]').attributes('aria-label')).toBe('第1项显示名称');
|
expect(wrapper.get('[data-test="option-value-1"]').attributes('aria-label')).toBe('第2项业务值');
|
expect(wrapper.get('[data-test="remove-option-2"]').attributes('aria-label')).toBe('删除第3项');
|
await wrapper.get('[data-test="remove-option-2"]').trigger('click');
|
expect(wrapper.emitted<DropdownOption[][]>('update:options')?.at(-1)?.[0]).toEqual(options);
|
});
|
|
it('编辑选项时发送新数组,并在默认业务值失效时清空默认项', async () => {
|
const options = [
|
{ display: '甲', value: 'a' },
|
{ display: '乙', value: 'b' },
|
];
|
const wrapper = mountOptions(options, 'a');
|
|
await wrapper.get('[data-test="option-display-0"]').setValue('甲项');
|
const displayUpdated = wrapper.emitted<DropdownOption[][]>('update:options')?.at(-1)?.[0];
|
expect(displayUpdated).toEqual([
|
{ display: '甲项', value: 'a' },
|
{ display: '乙', value: 'b' },
|
]);
|
expect(options[0]?.display).toBe('甲');
|
|
await wrapper.setProps({ options: displayUpdated });
|
await wrapper.get('[data-test="option-value-0"]').setValue('changed');
|
expect(wrapper.emitted<DropdownOption[][]>('update:options')?.at(-1)?.[0]).toEqual([
|
{ display: '甲项', value: 'changed' },
|
{ display: '乙', value: 'b' },
|
]);
|
expect(wrapper.emitted('update:defaultValue')?.at(-1)).toEqual(['']);
|
});
|
|
it('删除当前默认项时清空默认值,默认下拉只列出完整选项', async () => {
|
const options = [
|
{ display: '甲', value: 'a' },
|
{ display: '', value: 'incomplete' },
|
{ display: '丙', value: 'c' },
|
];
|
const wrapper = mountOptions(options, 'c');
|
const select = wrapper.findComponent(Select);
|
|
expect(select.props('options')).toEqual([
|
{ label: '无默认项', value: '' },
|
{ label: '甲', value: 'a' },
|
{ label: '丙', value: 'c' },
|
]);
|
|
await wrapper.get('[data-test="remove-option-2"]').trigger('click');
|
expect(wrapper.emitted('update:defaultValue')?.at(-1)).toEqual(['']);
|
});
|
|
it('通过默认下拉发送业务值,并把弹层挂到组件根元素', async () => {
|
const wrapper = mountOptions([
|
{ display: '甲', value: 'a' },
|
{ display: '乙', value: 'b' },
|
]);
|
const select = wrapper.findComponent(Select);
|
|
expect(wrapper.get('[data-test="option-display-0"]').attributes('id')).toBe('dropdown-option-display-0');
|
expect(wrapper.get('[data-test="option-value-0"]').attributes('id')).toBe('dropdown-option-value-0');
|
expect(select.attributes('aria-label')).toBe('默认项');
|
expect(select.get('input').attributes('id')).toBe('dropdown-default');
|
expect(wrapper.get('label.default-label').attributes('for')).toBe('dropdown-default');
|
expect(wrapper.get('[data-test="option-display-0"]').attributes('aria-invalid')).toBeUndefined();
|
expect(select.attributes('aria-invalid')).toBeUndefined();
|
select.vm.$emit('update:value', 'b');
|
await wrapper.vm.$nextTick();
|
expect(wrapper.emitted('update:defaultValue')?.at(-1)).toEqual(['b']);
|
expect(select.props('getPopupContainer')?.(document.body)).toBe(wrapper.get('[data-test="dropdown-options"]').element);
|
});
|
|
it('按错误字段关联首个选项输入或默认项 Select', () => {
|
const options = [
|
{ display: '甲', value: 'a' },
|
{ display: '乙', value: 'b' },
|
];
|
const optionsWrapper = mountOptions(options, '', false, 'dropdown-options', 'dropdown-options-error');
|
expect(optionsWrapper.get('[data-test="option-display-0"]').attributes()).toMatchObject({
|
'aria-describedby': 'dropdown-options-error',
|
'aria-invalid': 'true',
|
});
|
expect(optionsWrapper.get('[data-test="option-display-1"]').attributes('aria-describedby')).toBeUndefined();
|
|
const defaultWrapper = mountOptions(options, 'unknown', false, 'dropdown-default', 'dropdown-default-error');
|
expect(defaultWrapper.findComponent(Select).attributes()).toMatchObject({
|
'aria-describedby': 'dropdown-default-error',
|
'aria-invalid': 'true',
|
});
|
expect(defaultWrapper.get('[data-test="option-display-0"]').attributes('aria-invalid')).toBeUndefined();
|
});
|
|
it('真实打开默认项下拉并点击选项更新业务值', async () => {
|
const wrapper = mountOptions([
|
{ display: '甲', value: 'a' },
|
{ display: '乙', value: 'b' },
|
]);
|
const root = wrapper.get('[data-test="dropdown-options"]');
|
|
const selector = wrapper.get('[data-test="dropdown-default"] .ant-select-selector');
|
await selector.trigger('mousedown');
|
await selector.trigger('click');
|
await flushPromises();
|
const dropdown = root.get('.ant-select-dropdown');
|
expect(root.element.contains(dropdown.element)).toBe(true);
|
const option = dropdown.findAll('.ant-select-item-option').find((item) => item.text() === '乙')!;
|
await option.trigger('click');
|
await flushPromises();
|
|
expect(wrapper.emitted('update:defaultValue')?.at(-1)).toEqual(['b']);
|
});
|
|
it('disabled 时禁止编辑、添加、删除和选择默认项', async () => {
|
const wrapper = mountOptions(
|
[
|
{ display: '甲', value: 'a' },
|
{ display: '乙', value: 'b' },
|
{ display: '丙', value: 'c' },
|
],
|
'',
|
true,
|
);
|
|
expect(wrapper.get('[data-test="option-display-0"]').attributes('disabled')).toBeDefined();
|
expect(wrapper.get('[data-test="option-value-0"]').attributes('disabled')).toBeDefined();
|
expect(wrapper.get('[data-test="add-option"]').attributes('disabled')).toBeDefined();
|
expect(wrapper.get('[data-test="remove-option-2"]').attributes('disabled')).toBeDefined();
|
expect(wrapper.findComponent(Select).props('disabled')).toBe(true);
|
wrapper.findComponent(Select).vm.$emit('update:value', 'b');
|
await wrapper.vm.$nextTick();
|
expect(wrapper.emitted('update:defaultValue')).toBeUndefined();
|
});
|
});
|