import { nextTick, reactive, ref } from 'vue' import { mount } from '@vue/test-utils' import { afterEach, beforeAll, beforeEach, describe, expect, it, vi, } from 'vitest' import { rAF } from '@element-plus/test-utils/tick' import installStyle from '@element-plus/test-utils/style-plugin' import { ElCheckbox as Checkbox, ElCheckboxGroup as CheckboxGroup, } from '@element-plus/components/checkbox' import Input from '@element-plus/components/input' import Form from '../src/form.vue' import FormItem from '../src/form-item.vue' import DynamicDomainForm, { formatDomainError } from '../mocks/mock-data' import type { VueWrapper } from '@vue/test-utils' import type { FormRules } from '@element-plus/tokens' type FormInstance = InstanceType type FormItemInstance = InstanceType const findStyle = (wrapper: VueWrapper, selector: string) => wrapper.find(selector).element.style ;(globalThis as any).ASYNC_VALIDATOR_NO_WARNING = 1 describe('Form', () => { beforeAll(() => { installStyle() }) it('label width', async () => { const wrapper = mount({ setup() { const form = reactive({ name: '', }) return () => (
) }, }) expect(findStyle(wrapper, '.el-form-item__label').width).toBe('80px') }) it('auto label width', async () => { const labelPosition = ref('right') const wrapper = mount({ setup() { const form = reactive({ name: '', intro: '', }) return () => (
) }, }) await nextTick() const formItems = wrapper.findAll('.el-form-item__content') const marginLeft = Number.parseInt( formItems[0].element.style.marginLeft, 10 ) const marginLeft1 = Number.parseInt( formItems[1].element.style.marginLeft, 10 ) expect(marginLeft).toEqual(marginLeft1) labelPosition.value = 'left' await nextTick() const formItems1 = wrapper.findAll('.el-form-item__content') const marginRight = Number.parseInt( formItems1[0].element.style.marginRight, 10 ) const marginRight1 = Number.parseInt( formItems1[1].element.style.marginRight, 10 ) expect(marginRight).toEqual(marginRight1) }) it('form-item auto label width', async () => { const wrapper = mount({ setup() { const form = reactive({ name: '', region: '', type: '', }) return () => (
) }, }) await nextTick() const formItemLabels = wrapper.findAll('.el-form-item__label') const formItemLabelWraps = wrapper.findAll( '.el-form-item__label-wrap' ) const labelWrapMarginLeft1 = formItemLabelWraps[0].element.style.marginLeft const labelWrapMarginLeft2 = formItemLabelWraps[1].element.style.marginLeft expect(labelWrapMarginLeft1).toEqual(labelWrapMarginLeft2) expect(labelWrapMarginLeft2).toEqual('') const labelWidth0 = Number.parseInt( formItemLabels[0].element.style.width, 10 ) expect(labelWidth0).toEqual(150) const labelWidth1 = formItemLabels[1].element.style.width const labelWidth2 = formItemLabels[2].element.style.width expect(labelWidth1).toEqual(labelWidth2) expect(labelWidth2).toEqual('auto') }) it('inline form', () => { const wrapper = mount({ setup() { const form = reactive({ name: '', address: '', }) return () => (
) }, }) expect(wrapper.classes()).toContain('el-form--inline') }) it('label position', () => { const wrapper = mount({ setup() { const form = reactive({ name: '', address: '', }) return () => (
) }, }) expect(wrapper.findComponent({ ref: 'labelTop' }).classes()).toContain( 'el-form--label-top' ) expect(wrapper.findComponent({ ref: 'labelLeft' }).classes()).toContain( 'el-form--label-left' ) expect(wrapper.findComponent({ ref: 'labelRight' }).classes()).toContain( 'el-form--label-right' ) }) it('label size', () => { const wrapper = mount({ setup() { const form = reactive({ name: '', }) return () => (
) }, }) expect(wrapper.findComponent(FormItem).classes()).toContain( 'el-form-item--small' ) }) it('show message', async () => { const wrapper = mount({ setup() { const form = reactive({ name: '', }) return () => (
) }, }) const form = wrapper.findComponent(Form).vm as FormInstance vi.useFakeTimers() const valid = await form .validate() .then(() => true) .catch(() => false) vi.runAllTimers() vi.useRealTimers() await nextTick() expect(valid).toBe(false) expect(wrapper.find('.el-form-item__error').exists()).toBe(false) }) it('reset field', async () => { vi.useFakeTimers() const form = reactive({ name: '', address: '', type: new Array(), }) const wrapper = mount({ setup() { const rules: FormRules = { name: [ { required: true, message: 'Please input name', trigger: 'blur' }, ], address: [ { required: true, message: 'Please input address', trigger: 'change', }, ], type: [ { type: 'array', required: true, message: 'Please input type', trigger: 'change', }, ], } return () => (
) }, }) form.name = 'jack' form.address = 'aaaa' form.type.push('type1') const formRef = wrapper.findComponent({ ref: 'form' }).vm as FormInstance formRef.resetFields() // first await waits for the validation to be dispatched. await nextTick() // after validation dispatched, it will update `validateStateDebounced` with a 100ms delay. // That's why we put this `vi.runAllTimers` here. vi.runAllTimers() // after timer fired, we should wait for the UI to be updated. await nextTick() expect(form.name).toBe('') expect(form.address).toBe('') expect(form.type.length).toBe(0) expect(wrapper.findAll('.el-form-item__error')).toHaveLength(0) vi.useRealTimers() }) it('clear validate', async () => { const wrapper = mount({ setup() { const form = reactive({ name: '', address: '', type: [], }) const rules: FormRules = reactive({ name: [ { required: true, message: 'Please input name', trigger: 'blur' }, ], address: [ { required: true, message: 'Please input address', trigger: 'change', }, ], type: [ { type: 'array', required: true, message: 'Please input type', trigger: 'change', }, ], }) return () => (
) }, }) const form = wrapper.findComponent({ ref: 'form' }).vm as FormInstance const nameField = wrapper.findComponent({ ref: 'name' }) .vm as FormItemInstance const addressField = wrapper.findComponent({ ref: 'address' }) .vm as FormItemInstance await form.validate().catch(() => undefined) await nextTick() expect(nameField.validateMessage).toBe('Please input name') expect(addressField.validateMessage).toBe('Please input address') form.clearValidate(['name']) await nextTick() expect(nameField.validateMessage).toBe('') expect(addressField.validateMessage).toBe('Please input address') form.clearValidate() await nextTick() expect(addressField.validateMessage).toBe('') }) it('scroll to field', () => { const wrapper = mount({ setup() { return () => (
) }, }) const oldScrollIntoView = window.HTMLElement.prototype.scrollIntoView const scrollIntoViewMock = vi.fn() window.HTMLElement.prototype.scrollIntoView = function () { scrollIntoViewMock(this) } const form = wrapper.findComponent({ ref: 'form' }).vm as FormInstance form.scrollToField('name') expect(scrollIntoViewMock).toHaveBeenCalledWith( wrapper.findComponent({ ref: 'formItem' }).element ) window.HTMLElement.prototype.scrollIntoView = oldScrollIntoView }) it('validate return parameters', async () => { const form = reactive({ name: 'test', age: '', }) const wrapper = mount({ setup() { const rules = reactive({ name: [ { required: true, message: 'Please input name', trigger: 'blur' }, ], age: [ { required: true, message: 'Please input age', trigger: 'blur' }, ], }) return () => (
) }, }) const vm = wrapper.vm function validate() { return (vm.$refs.formRef as FormInstance) .validate() .then(() => ({ valid: true, fields: undefined })) .catch((fields) => ({ valid: false, fields })) } let res = await validate() expect(res.valid).toBe(false) expect(Object.keys(res.fields).length).toBe(1) form.name = '' await nextTick() res = await validate() expect(res.valid).toBe(false) expect(Object.keys(res.fields).length).toBe(2) form.name = 'test' form.age = 'age' await nextTick() res = await validate() expect(res.valid).toBe(true) expect(res.fields).toBe(undefined) }) it('validate status', async () => { const form = reactive({ age: '20', }) const wrapper = mount({ setup() { const rules = ref({ age: [ { required: true, message: 'Please input age', trigger: 'change' }, ], }) return () => (
) }, }) await (wrapper.vm.$refs.formRef as FormInstance) .validate() .catch(() => undefined) const ageField = wrapper.findComponent({ ref: 'age' }) expect(ageField.classes('is-success')).toBe(true) expect(ageField.classes()).toContain('is-success') }) describe('FormItem', () => { const onSuccess = vi.fn() const onError = vi.fn() let wrapper: VueWrapper> const createComponent = (onSubmit?: vi.MockedFunction) => { wrapper = mount(DynamicDomainForm, { props: { onSuccess, onError, onSubmit, }, }) } const findSubmitButton = () => wrapper.find('.submit') const findAddDomainButton = () => wrapper.find('.add-domain') const findDeleteDomainButton = () => wrapper.findAll('.delete-domain') const findDomainItems = () => wrapper.findAll('.domain-item') beforeEach(() => { createComponent() }) afterEach(() => { wrapper.unmount() }) it('should register form item', async () => { expect(findDomainItems()).toHaveLength(1) await findSubmitButton().trigger('click') // wait for AsyncValidator to be resolved await rAF() expect(onError).toHaveBeenCalled() }) it('should dynamically register form with items', async () => { await findAddDomainButton().trigger('click') expect(findDomainItems()).toHaveLength(2) await findSubmitButton().trigger('click') // wait for AsyncValidator to be resolved await rAF() expect(onError).toHaveBeenCalledWith(formatDomainError(2)) const deleteBtns = findDeleteDomainButton() expect(deleteBtns).toHaveLength(2) await findDeleteDomainButton().at(1)!.trigger('click') expect(findDomainItems()).toHaveLength(1) await findSubmitButton().trigger('click') // wait for AsyncValidator to be resolved await rAF() expect(onError).toHaveBeenLastCalledWith(formatDomainError(1)) }) it('should not throw error when callback passed in', async () => { const onSubmit = vi.fn() createComponent(onSubmit) await findSubmitButton().trigger('click') await rAF() expect(onError).not.toHaveBeenCalled() expect(onSubmit).toHaveBeenCalled() }) }) })