/** * DynamicFieldRenderer 组件测试 * * 测试范围: * - 基础渲染 (10+用例) * - 不同字段类型渲染 (15+用例) * - 数据绑定 (10+用例) * - 验证功能 (10+用例) * - 依赖处理 (5+用例) * * 总计: 40+ 用例 */ import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount, VueWrapper } from '@vue/test-utils' import { nextTick } from 'vue' import DynamicFieldRenderer from '@/components/form/DynamicFieldRenderer.vue' import { FieldConfig, FieldType } from '@/types/form' describe('DynamicFieldRenderer 组件测试', () => { // 测试数据 const mockFieldConfig: FieldConfig = { field_id: 'test_field_1', field_name: '测试字段', field_type: FieldType.TEXT, is_required: true, placeholder: '请输入测试字段', validation_rules: [ { rule_type: 'length', rule_value: { min: 1, max: 100 } } ] } const mockModelValue = ref('') // 基础渲染测试 (10+用例) describe('基础渲染', () => { it('应该正确渲染组件', () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: mockModelValue.value } }) expect(wrapper.exists()).toBe(true) }) it('应该显示字段标签', () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: '' } }) expect(wrapper.text()).toContain('测试字段') }) it('应该显示必填标记', () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: '' } }) expect(wrapper.find('.required-mark').exists()).toBe(true) }) it('不应该显示非必填字段的必填标记', () => { const config = { ...mockFieldConfig, is_required: false } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '' } }) expect(wrapper.find('.required-mark').exists()).toBe(false) }) it('应该显示字段提示信息', () => { const config = { ...mockFieldConfig, help_text: '这是字段的帮助文本' } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '' } }) expect(wrapper.text()).toContain('这是字段的帮助文本') }) it('应该应用自定义CSS类', () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: '', customClass: 'custom-field-class' } }) expect(wrapper.classes()).toContain('custom-field-class') }) it('应该显示字段描述', () => { const config = { ...mockFieldConfig, description: '字段详细描述' } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '' } }) expect(wrapper.text()).toContain('字段详细描述') }) it('应该在禁用状态下渲染', () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: '', disabled: true } }) const input = wrapper.find('input') expect(input.attributes('disabled')).toBeDefined() }) it('应该在只读状态下渲染', () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: 'test value', readonly: true } }) const input = wrapper.find('input') expect(input.attributes('readonly')).toBeDefined() }) it('应该响应字段配置变化', async () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: '' } }) await wrapper.setProps({ fieldConfig: { ...mockFieldConfig, field_name: '更新后的字段名' } }) await nextTick() expect(wrapper.text()).toContain('更新后的字段名') }) }) // 不同字段类型渲染测试 (15+用例) describe('不同字段类型渲染', () => { it('应该渲染文本输入框', () => { const config = { ...mockFieldConfig, field_type: FieldType.TEXT } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '' } }) expect(wrapper.find('input[type="text"]').exists()).toBe(true) }) it('应该渲染数字输入框', () => { const config = { ...mockFieldConfig, field_type: FieldType.NUMBER } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: 0 } }) expect(wrapper.find('input[type="number"]').exists()).toBe(true) }) it('应该渲染日期选择器', () => { const config = { ...mockFieldConfig, field_type: FieldType.DATE } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '' } }) expect(wrapper.find('.date-picker').exists()).toBe(true) }) it('应该渲染下拉选择框', () => { const config = { ...mockFieldConfig, field_type: FieldType.SELECT, options: [ { label: '选项1', value: 'option1' }, { label: '选项2', value: 'option2' } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '' } }) expect(wrapper.find('select').exists()).toBe(true) }) it('应该渲染多选框', () => { const config = { ...mockFieldConfig, field_type: FieldType.MULTI_SELECT, options: [ { label: '选项1', value: 'option1' }, { label: '选项2', value: 'option2' } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: [] } }) expect(wrapper.find('.multi-select').exists()).toBe(true) }) it('应该渲染单选框组', () => { const config = { ...mockFieldConfig, field_type: FieldType.RADIO, options: [ { label: '选项1', value: 'option1' }, { label: '选项2', value: 'option2' } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '' } }) expect(wrapper.find('.radio-group').exists()).toBe(true) }) it('应该渲染复选框', () => { const config = { ...mockFieldConfig, field_type: FieldType.CHECKBOX } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: false } }) expect(wrapper.find('input[type="checkbox"]').exists()).toBe(true) }) it('应该渲染文本域', () => { const config = { ...mockFieldConfig, field_type: FieldType.TEXTAREA } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '' } }) expect(wrapper.find('textarea').exists()).toBe(true) }) it('应该渲染富文本编辑器', () => { const config = { ...mockFieldConfig, field_type: FieldType.RICH_TEXT } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '' } }) expect(wrapper.find('.rich-text-editor').exists()).toBe(true) }) it('应该渲染文件上传组件', () => { const config = { ...mockFieldConfig, field_type: FieldType.FILE_UPLOAD } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: [] } }) expect(wrapper.find('.file-upload').exists()).toBe(true) }) it('应该渲染日期时间选择器', () => { const config = { ...mockFieldConfig, field_type: FieldType.DATETIME } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '' } }) expect(wrapper.find('.datetime-picker').exists()).toBe(true) }) it('应该渲染时间选择器', () => { const config = { ...mockFieldConfig, field_type: FieldType.TIME } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '' } }) expect(wrapper.find('.time-picker').exists()).toBe(true) }) it('应该渲染滑块', () => { const config = { ...mockFieldConfig, field_type: FieldType.SLIDER, validation_rules: [ { rule_type: 'range', rule_value: { min: 0, max: 100 } } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: 50 } }) expect(wrapper.find('.slider').exists()).toBe(true) }) it('应该渲染开关', () => { const config = { ...mockFieldConfig, field_type: FieldType.SWITCH } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: false } }) expect(wrapper.find('.switch').exists()).toBe(true) }) it('应该渲染级联选择器', () => { const config = { ...mockFieldConfig, field_type: FieldType.CASCADER, options: [ { label: '级别1', value: '1', children: [ { label: '级别2-1', value: '1-1' }, { label: '级别2-2', value: '1-2' } ] } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: [] } }) expect(wrapper.find('.cascader').exists()).toBe(true) }) }) // 数据绑定测试 (10+用例) describe('数据绑定', () => { it('应该正确绑定modelValue', async () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: 'initial value' } }) const input = wrapper.find('input') expect(input.element.value).toBe('initial value') }) it('应该在输入时触发update:modelValue事件', async () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: '' } }) const input = wrapper.find('input') await input.setValue('new value') expect(wrapper.emitted('update:modelValue')).toBeTruthy() expect(wrapper.emitted('update:modelValue')![0]).toEqual(['new value']) }) it('应该响应modelValue的变化', async () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: 'initial' } }) await wrapper.setProps({ modelValue: 'updated' }) await nextTick() const input = wrapper.find('input') expect(input.element.value).toBe('updated') }) it('应该正确处理数字类型的值', async () => { const config = { ...mockFieldConfig, field_type: FieldType.NUMBER } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: 0 } }) const input = wrapper.find('input') await input.setValue('123') expect(wrapper.emitted('update:modelValue')![0]).toEqual([123]) }) it('应该正确处理布尔类型的值', async () => { const config = { ...mockFieldConfig, field_type: FieldType.CHECKBOX } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: false } }) const checkbox = wrapper.find('input[type="checkbox"]') await checkbox.setChecked() expect(wrapper.emitted('update:modelValue')![0]).toEqual([true]) }) it('应该正确处理数组类型的值', async () => { const config = { ...mockFieldConfig, field_type: FieldType.MULTI_SELECT, options: [ { label: '选项1', value: 'option1' }, { label: '选项2', value: 'option2' } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: [] } }) // 模拟选择操作 wrapper.vm.handleSelect('option1') await nextTick() expect(wrapper.emitted('update:modelValue')).toBeTruthy() }) it('应该正确处理日期类型的值', async () => { const config = { ...mockFieldConfig, field_type: FieldType.DATE } const dateValue = '2025-01-24' const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '' } }) wrapper.vm.handleDateChange(dateValue) await nextTick() expect(wrapper.emitted('update:modelValue')![0]).toEqual([dateValue]) }) it('应该在清空时触发正确的事件', async () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: 'test' } }) await wrapper.vm.clearValue() expect(wrapper.emitted('update:modelValue')![0]).toEqual(['']) }) it('应该正确处理空值', () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: null } }) const input = wrapper.find('input') expect(input.element.value).toBe('') }) it('应该正确处理未定义的值', () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: undefined } }) const input = wrapper.find('input') expect(input.element.value).toBe('') }) }) // 验证功能测试 (10+用例) describe('验证功能', () => { it('应该验证必填字段', async () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: '' } }) const isValid = await wrapper.vm.validate() expect(isValid).toBe(false) }) it('应该通过必填字段的验证', async () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: 'test value' } }) const isValid = await wrapper.vm.validate() expect(isValid).toBe(true) }) it('应该验证最小长度', async () => { const config = { ...mockFieldConfig, validation_rules: [ { rule_type: 'length', rule_value: { min: 5, max: 100 } } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: 'abc' } }) const isValid = await wrapper.vm.validate() expect(isValid).toBe(false) }) it('应该验证最大长度', async () => { const config = { ...mockFieldConfig, validation_rules: [ { rule_type: 'length', rule_value: { min: 1, max: 10 } } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: 'a'.repeat(20) } }) const isValid = await wrapper.vm.validate() expect(isValid).toBe(false) }) it('应该验证数字范围', async () => { const config = { ...mockFieldConfig, field_type: FieldType.NUMBER, validation_rules: [ { rule_type: 'range', rule_value: { min: 1, max: 100 } } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: 150 } }) const isValid = await wrapper.vm.validate() expect(isValid).toBe(false) }) it('应该验证正则表达式', async () => { const config = { ...mockFieldConfig, validation_rules: [ { rule_type: 'regex', rule_value: '^[A-Z0-9]+$' } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: 'invalid-value' } }) const isValid = await wrapper.vm.validate() expect(isValid).toBe(false) }) it('应该显示验证错误信息', async () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: '' } }) await wrapper.vm.validate() await nextTick() expect(wrapper.find('.error-message').exists()).toBe(true) expect(wrapper.text()).toContain('必填') }) it('应该支持自定义验证规则', async () => { const customValidator = vi.fn().mockResolvedValue(false) const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: 'test', customValidator } }) const isValid = await wrapper.vm.validate() expect(customValidator).toHaveBeenCalled() expect(isValid).toBe(false) }) it('应该在值变化时触发验证', async () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: '', validateOnBlur: true } }) const input = wrapper.find('input') await input.trigger('blur') await nextTick() expect(wrapper.vm.error).toBeTruthy() }) it('应该清除验证错误', async () => { const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: mockFieldConfig, modelValue: '' } }) await wrapper.vm.validate() expect(wrapper.vm.error).toBeTruthy() await wrapper.vm.clearError() expect(wrapper.vm.error).toBeNull() }) }) // 依赖处理测试 (5+用例) describe('依赖处理', () => { it('应该根据依赖条件显示/隐藏字段', async () => { const config = { ...mockFieldConfig, dependencies: [ { field_id: 'parent_field', condition: 'equals', value: 'show' } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '', formData: { parent_field: 'hide' } } }) expect(wrapper.vm.isVisible).toBe(false) await wrapper.setProps({ formData: { parent_field: 'show' } }) await nextTick() expect(wrapper.vm.isVisible).toBe(true) }) it('应该根据依赖条件启用/禁用字段', async () => { const config = { ...mockFieldConfig, dependencies: [ { field_id: 'parent_field', condition: 'equals', value: 'enable', action: 'disable' } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '', formData: { parent_field: 'disable' } } }) const input = wrapper.find('input') expect(input.attributes('disabled')).toBeDefined() }) it('应该根据依赖条件更新字段值', async () => { const config = { ...mockFieldConfig, dependencies: [ { field_id: 'parent_field', condition: 'equals', value: 'auto', action: 'set_value', target_value: 'automatic value' } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '', formData: { parent_field: 'auto' } } }) await wrapper.vm.handleDependencies() expect(wrapper.emitted('update:modelValue')![0]).toEqual(['automatic value']) }) it('应该支持多个依赖条件', async () => { const config = { ...mockFieldConfig, dependencies: [ { field_id: 'field1', condition: 'equals', value: 'value1' }, { field_id: 'field2', condition: 'equals', value: 'value2', operator: 'AND' } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '', formData: { field1: 'value1', field2: 'value2' } } }) expect(wrapper.vm.isVisible).toBe(true) }) it('应该处理复杂的依赖逻辑', async () => { const config = { ...mockFieldConfig, dependencies: [ { field_id: 'parent_field', condition: 'in', value: ['option1', 'option2', 'option3'] } ] } const wrapper = mount(DynamicFieldRenderer, { props: { fieldConfig: config, modelValue: '', formData: { parent_field: 'option2' } } }) expect(wrapper.vm.isVisible).toBe(true) }) }) })