168 lines
5.9 KiB
JavaScript
168 lines
5.9 KiB
JavaScript
/**
|
||
* 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()
|
||
})
|
||
})
|