Files
2026-09-14 17:28:20 +08:00

168 lines
5.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* FmsField 多态控件渲染器测试。
* 覆盖契约:type→控件映射、checkbox 0/1↔boolean 归一、v-model 透传、
* control-props 直达、fallthrough(class/原生事件)透传、change 转发、
* 默认插槽逃生舱、types 注册表覆盖、selectId(FmsSelect) 双绑回显。
*/
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { h, markRaw } from 'vue'
import FmsField from '@/components/fms-field/FmsField.vue'
import { Checkbox } from '@/components/ui'
// selectId 分支会挂载 FmsSelect,其依赖 @/services/api;桩掉避免真实请求
vi.mock('@/services/api', () => ({
pageDataApi: vi.fn(async () => ({ data: { rows: [], total: 0 } })),
}))
describe('FmsField 类型 → 控件映射', () => {
it('缺省 type 兜底渲染 Input', () => {
const wrapper = mount(FmsField)
expect(wrapper.find('.input-wrapper').exists()).toBe(true)
expect(wrapper.find('input').exists()).toBe(true)
wrapper.unmount()
})
it('未知 type 兜底渲染 Input', () => {
const wrapper = mount(FmsField, { props: { type: 'not-a-real-type' } })
expect(wrapper.find('.input-wrapper').exists()).toBe(true)
wrapper.unmount()
})
it.each([
['textarea', 'textarea'],
['number', 'input[inputmode="decimal"]'],
['money', 'input[inputmode="decimal"]'],
['checkbox', 'label.checkbox'],
['select', '.select-trigger'],
['date', '.fms-date-picker'],
['datetime', '.fms-date-picker'],
['range', '.fms-range-picker'],
])('type=%s 渲染对应控件(%s)', (type, selector) => {
const wrapper = mount(FmsField, { props: { type } })
expect(wrapper.find(selector).exists()).toBe(true)
wrapper.unmount()
})
})
describe('FmsField 值绑定', () => {
it('input:v-model 双向透传', async () => {
const wrapper = mount(FmsField, { props: { modelValue: 'a' } })
expect(wrapper.find('input').element.value).toBe('a')
await wrapper.find('input').setValue('abc')
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['abc'])
wrapper.unmount()
})
it('checkbox:存储 1 → 勾选;取消勾选 → 抛 0', async () => {
const wrapper = mount(FmsField, { props: { type: 'checkbox', modelValue: 1 } })
const box = wrapper.find('input.checkbox-input')
expect(box.element.checked).toBe(true)
await box.setValue(false)
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([0])
wrapper.unmount()
})
it('checkbox:存储 0 → 未勾选;勾选 → 抛 1', async () => {
const wrapper = mount(FmsField, { props: { type: 'checkbox', modelValue: 0 } })
const box = wrapper.find('input.checkbox-input')
expect(box.element.checked).toBe(false)
await box.setValue(true)
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([1])
wrapper.unmount()
})
it('select multiple:选项切换抛出数组并保持下拉打开', async () => {
const wrapper = mount(FmsField, {
props: {
type: 'select',
modelValue: ['a'],
controlProps: {
multiple: true,
options: [
{ label: '甲', value: 'a' },
{ label: '乙', value: 'b' },
],
},
},
attachTo: document.body,
})
await wrapper.find('.select-trigger').trigger('click')
await wrapper.vm.$nextTick()
const options = document.body.querySelectorAll('.select-option')
options[1].click()
await wrapper.vm.$nextTick()
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([['a', 'b']])
expect(document.body.querySelector('.select-option')).not.toBeNull()
wrapper.unmount()
})
})
describe('FmsField 扩展口子', () => {
it('control-props 直达内部控件', () => {
const wrapper = mount(FmsField, { props: { controlProps: { maxlength: 10 } } })
expect(wrapper.find('input').attributes('maxlength')).toBe('10')
wrapper.unmount()
})
it('fallthrough class 合并到内部控件根节点', () => {
const wrapper = mount(FmsField, { attrs: { class: 'my-field' } })
expect(wrapper.find('.input-wrapper.my-field').exists()).toBe(true)
wrapper.unmount()
})
it('原生事件透传:@keydown 由内部控件冒泡命中', async () => {
const onKeydown = vi.fn()
const wrapper = mount(FmsField, { attrs: { onKeydown } })
await wrapper.find('input').trigger('keydown', { key: 'Enter' })
expect(onKeydown).toHaveBeenCalled()
wrapper.unmount()
})
it('内部控件 change 原样转发', async () => {
const wrapper = mount(FmsField)
await wrapper.find('input').trigger('change')
expect(wrapper.emitted('change')).toBeTruthy()
wrapper.unmount()
})
it('默认插槽整体接管渲染(忽略 type)', () => {
const wrapper = mount(FmsField, {
props: { type: 'select' },
slots: { default: () => h('div', { class: 'custom-control' }, '自定义') },
})
expect(wrapper.find('.custom-control').exists()).toBe(true)
expect(wrapper.find('.select-trigger').exists()).toBe(false)
wrapper.unmount()
})
it('types 注册表覆盖内置类型映射', () => {
const wrapper = mount(FmsField, {
// 组件放入响应式容器需 markRaw,避免 Vue 告警(消费者注册自定义类型时同理)
props: { type: 'input', types: { input: markRaw(Checkbox) } },
})
expect(wrapper.find('label.checkbox').exists()).toBe(true)
expect(wrapper.find('.input-wrapper').exists()).toBe(false)
wrapper.unmount()
})
})
describe('FmsField selectId(FmsSelect 双绑)', () => {
it('渲染 FmsSelect,并用 label 回显选中名', () => {
const wrapper = mount(FmsField, {
props: {
type: 'selectId',
modelValue: '7',
label: '客户甲',
controlProps: { viewName: 'v_customer' },
},
})
expect(wrapper.find('.fms-select').exists()).toBe(true)
expect(wrapper.find('.select-input').element.value).toBe('客户甲')
wrapper.unmount()
})
})