import { afterEach, describe, it, expect } from 'vitest' import { h, nextTick, ref } from 'vue' import { flushPromises, mount } from '@vue/test-utils' import Form from '@/components/ui/form/form.vue' import FormItem from '@/components/ui/form/form-item.vue' import { resetUIConfig, setUIConfig, uiConfig } from '@/components/ui/config' const model = () => ref({ code: 'a', name: 'b', note: 'c', }) function mountForm(columns = 1, extraProps = {}) { return mount(Form, { props: { model: model().value, columns, ...extraProps }, slots: { default: () => [ h(FormItem, { name: 'code', label: '编码' }, { default: () => h('input') }), h(FormItem, { name: 'name', label: '名称' }, { default: () => h('input') }), h(FormItem, { name: 'note', label: '备注', span: 2 }, { default: () => h('input') }), ], }, }) } describe('Form 多列网格布局', () => { it('columns=1 时保持原布局(无网格 class)', () => { const wrapper = mountForm() expect(wrapper.classes()).not.toContain('fms-form-grid') expect(wrapper.attributes('style')).toBeUndefined() }) it('columns>1 时启用网格并生成列模板', () => { const wrapper = mountForm(2) expect(wrapper.classes()).toContain('fms-form-grid') expect(wrapper.element.style.gridTemplateColumns).toBe('repeat(2, minmax(0, 1fr))') }) it('span>1 的 FormItem 跨列', () => { const wrapper = mountForm(2) const items = wrapper.findAllComponents(FormItem) const noteItem = items.find((i) => i.props('name') === 'note') expect(noteItem.element.style.gridColumn).toBe('span 2') const codeItem = items.find((i) => i.props('name') === 'code') expect(codeItem.element.style.gridColumn).toBe('') }) it('span 超过列数时取整行', () => { const wrapper = mount(Form, { props: { model: model().value, columns: 3 }, slots: { default: () => [ h(FormItem, { name: 'code', label: '编码', span: 5 }, { default: () => h('input') }), ], }, }) const item = wrapper.findComponent(FormItem) expect(item.element.style.gridColumn).toBe('span 3') }) it('maxWidth 数字按 px 处理', () => { const wrapper = mountForm(2, { maxWidth: 720 }) expect(wrapper.element.style.maxWidth).toBe('720px') }) it('vertical 布局下 label 在上', () => { const wrapper = mountForm(2, { layout: 'vertical' }) const item = wrapper.findComponent(FormItem) expect(item.classes()).toContain('fms-form-item-vertical') expect(item.find('.fms-form-label-vertical').exists()).toBe(true) }) }) describe('Form 全局配置(uiConfig)', () => { afterEach(() => resetUIConfig()) it('未传 layout 时取全局配置', () => { setUIConfig({ form: { layout: 'vertical' } }) const wrapper = mountForm() expect(wrapper.classes()).toContain('fms-form-vertical') expect(wrapper.findComponent(FormItem).classes()).toContain('fms-form-item-vertical') }) it('显式 layout 覆盖全局配置', () => { setUIConfig({ form: { layout: 'vertical' } }) const wrapper = mountForm(1, { layout: 'horizontal' }) expect(wrapper.classes()).toContain('fms-form-horizontal') expect(wrapper.findComponent(FormItem).classes()).not.toContain('fms-form-item-vertical') }) it('setUIConfig 局部合并,未提及的键保持原值', () => { setUIConfig({ form: { labelAlign: 'left' } }) expect(uiConfig.form.labelAlign).toBe('left') expect(uiConfig.form.layout).toBe('horizontal') setUIConfig({ form: { layout: 'vertical' } }) expect(uiConfig.form.labelAlign).toBe('left') }) it('全局 validateTrigger 生效:change 改为 blur 后值变化不立即报红', async () => { setUIConfig({ form: { validateTrigger: 'blur' } }) const model = ref({ code: '' }) const wrapper = mount(Form, { props: { model: model.value, rules: { code: [{ required: true, message: '必填' }] } }, slots: { default: () => [ h(FormItem, { name: 'code', label: '编码' }, { default: () => h('input') }), ], }, }) model.value.code = 'a' await nextTick() model.value.code = '' await nextTick() await flushPromises() expect(wrapper.findComponent(FormItem).classes()).not.toContain('fms-form-item-error') }) it('独立使用的 FormItem(无 Form 包裹)也读全局 layout', () => { setUIConfig({ form: { layout: 'vertical' } }) const wrapper = mount(FormItem, { props: { name: 'code', label: '编码' }, slots: { default: () => h('input') }, }) expect(wrapper.classes()).toContain('fms-form-item-vertical') }) }) describe('Form 必填校验与 FormItem 错误展示', () => { function mountRequiredForm(rules, modelValue = { code: '' }) { const model = ref(modelValue) const wrapper = mount(Form, { props: { model: model.value, rules }, slots: { default: () => [h(FormItem, { name: 'code', label: '编码' }, { default: () => h('input') })], }, }) return { wrapper, model } } it('必填为空时提交:FormItem 下方显示错误并标红', async () => { const { wrapper } = mountRequiredForm({ code: [{ required: true, message: '请填写编码' }] }) await wrapper.find('form').trigger('submit') await flushPromises() const item = wrapper.findComponent(FormItem) expect(item.classes()).toContain('fms-form-item-error') const help = item.find('.fms-form-item-help-error') expect(help.exists()).toBe(true) expect(help.text()).toBe('请填写编码') expect(wrapper.emitted('submitFailed')).toHaveLength(1) }) it('message 为函数时按校验时求值(动态文案)', async () => { const { wrapper } = mountRequiredForm({ code: [{ required: true, message: () => '请填写分类编码' }] }) await wrapper.find('form').trigger('submit') await flushPromises() const help = wrapper.findComponent(FormItem).find('.fms-form-item-help-error') expect(help.text()).toBe('请填写分类编码') }) it('validateAll 暴露并返回带错误的字段列表', async () => { const { wrapper } = mountRequiredForm({ code: [{ required: true, message: '请填写编码' }] }) const errors = await wrapper.vm.validateAll() expect(errors).toHaveLength(1) expect(errors[0].name).toBe('code') expect(errors[0].errors).toEqual(['请填写编码']) }) it('修正必填值后错误清除', async () => { const { wrapper, model } = mountRequiredForm({ code: [{ required: true, message: '请填写编码' }] }) await wrapper.find('form').trigger('submit') await flushPromises() expect(wrapper.findComponent(FormItem).classes()).toContain('fms-form-item-error') model.value.code = 'abc' await nextTick() await flushPromises() const item = wrapper.findComponent(FormItem) expect(item.classes()).not.toContain('fms-form-item-error') expect(item.find('.fms-form-item-help-error').exists()).toBe(false) }) })