diff --git a/packages/element-plus/index.ts b/packages/element-plus/index.ts index 7c669a19dfc40..d31fb9d159787 100644 --- a/packages/element-plus/index.ts +++ b/packages/element-plus/index.ts @@ -37,6 +37,7 @@ import ElDialog from '@element-plus/dialog' import ElCalendar from '@element-plus/calendar' import ElInfiniteScroll from '@element-plus/infinite-scroll' import ElDrawer from '@element-plus/drawer' +import ElForm from '@element-plus/form' import ElUpload from '@element-plus/upload' import ElTree from '@element-plus/tree' @@ -78,6 +79,7 @@ export { ElCalendar, ElInfiniteScroll, ElDrawer, + ElForm, ElUpload, ElTree, } diff --git a/packages/element-plus/package.json b/packages/element-plus/package.json index 72f967553af73..2bc73c63d5343 100644 --- a/packages/element-plus/package.json +++ b/packages/element-plus/package.json @@ -38,6 +38,7 @@ "@element-plus/collapse": "^0.0.0", "@element-plus/time-picker": "^0.0.0", "@element-plus/tabs": "^0.0.0", + "@element-plus/form": "^0.0.0", "@element-plus/tree": "^0.0.0" } } diff --git a/packages/form/__tests__/form.spec.ts b/packages/form/__tests__/form.spec.ts new file mode 100644 index 0000000000000..43ae5d3e07d06 --- /dev/null +++ b/packages/form/__tests__/form.spec.ts @@ -0,0 +1,1014 @@ +import { nextTick } from 'vue' +import { mount, VueWrapper } from '@vue/test-utils' +import Form from '../src/form.vue' +import FormItem from '../src/form-item.vue' +import installStyle from '@element-plus/test-utils/style-plugin' +import Input from '@element-plus/input/src/index.vue' +import Checkbox from '@element-plus/checkbox/src/checkbox.vue' +import CheckboxGroup from '@element-plus/checkbox/src/checkbox-group.vue' + +type Methods = Record any> +function mountForm( + config: C & {data?(): D; methods?: M;}, +) { + return mount({ + components: { + [Form.name]: Form, + [FormItem.name]: FormItem, + [Input.name]: Input, + [Checkbox.name]: Checkbox, + [CheckboxGroup.name]: CheckboxGroup, + }, + ...config, + }) +} + +function findStyle(wrapper: VueWrapper, selector: string) { + return wrapper.find(selector).element.style +} + +describe('Form', () => { + beforeAll(installStyle) + + test('label width', async () => { + const wrapper = mountForm({ + template: ` + + + + + + `, + data() { + return { + form: { + name: '', + }, + } + }, + }) + expect(findStyle(wrapper, '.el-form-item__label').width).toBe('80px') + expect(findStyle(wrapper, '.el-form-item__content').marginLeft).toBe('80px') + }) + + /* + // commented out because no style support in JSDOM + test('auto label width', async() => { + const wrapper = mountForm({ + template: ` + + + + + + + + + `, + data() { + return { + display: true, + form: { + name: '', + intro: '' + } + } + } + }) + + await nextTick() + + const formItems = wrapper.findAll('.el-form-item__content') + const marginLeft = parseInt(formItems[0].element.style.marginLeft, 10) + const marginLeft1 = parseInt(formItems[1].element.style.marginLeft, 10) + expect(marginLeft).toEqual(marginLeft1) + + wrapper.vm.display = false + await nextTick() + + const formItemStyle = findStyle(wrapper, '.el-form-item__content') + const newMarginLeft = parseInt(formItemStyle.marginLeft, 10) + expect(newMarginLeft).toBeLessThan(marginLeft) + }) + */ + + test('inline form', () => { + const wrapper = mountForm({ + template: ` + + + + + + + + + `, + data() { + return { + form: { + name: '', + address: '', + }, + } + }, + }) + expect(wrapper.classes()).toContain('el-form--inline') + }) + + test('label position', () => { + const wrapper = mountForm({ + template: ` +
+ + + + + + + + + + + + + + + + +
+ `, + data() { + return { + form: { + name: '', + address: '', + }, + } + }, + }) + expect(wrapper.findComponent({ ref: 'labelTop' }).classes()).toContain('el-form--label-top') + expect(wrapper.findComponent({ ref: 'labelLeft' }).classes()).toContain('el-form--label-left') + }) + + test('label size', () => { + const wrapper = mountForm({ + template: ` +
+ + + + + +
+ `, + data() { + return { + form: { + name: '', + }, + } + }, + }) + expect(wrapper.findComponent(FormItem).classes()).toContain('el-form-item--mini') + }) + + test('show message', done => { + const wrapper = mountForm({ + template: ` + + + + + + `, + data() { + return { + form: { + name: '', + }, + } + }, + }) + const form: any = wrapper.findComponent(Form).vm + form.validate(async valid => { + expect(valid).toBe(false) + await nextTick() + expect(wrapper.find('.el-form-item__error').exists()).toBe(false) + done() + }) + }) + + test('reset field', async () => { + const wrapper = mountForm({ + template: ` + + + + + + + + + + + + + + + + + `, + data() { + return { + form: { + name: '', + address: '', + type: [], + }, + rules: { + 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' }, + ], + }, + } + }, + methods: { + setValue() { + this.form.name = 'jack' + this.form.address = 'aaaa' + this.form.type.push('type1') + }, + }, + }) + const vm = wrapper.vm + vm.setValue() + const form: any = wrapper.findComponent({ ref: 'form' }).vm + form.resetFields() + await nextTick() + expect(vm.form.name).toBe('') + expect(vm.form.address).toBe('') + expect(vm.form.type.length).toBe(0) + }) + + test('clear validate', async () => { + const wrapper = mountForm({ + template: ` + + + + + + + + + + + + + + + + + `, + data() { + return { + form: { + name: '', + address: '', + type: [], + }, + rules: { + 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' }, + ], + }, + } + }, + }) + const form: any = wrapper.findComponent({ ref: 'form' }).vm + const nameField: any = wrapper.findComponent({ ref: 'name' }).vm + const addressField: any = wrapper.findComponent({ ref: 'address' }).vm + form.validate() + 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('') + }) + + /* + test('form item nest', done => { + const wrapper = mountForm({ + template: ` + + + + + + + + - + + + + + + + + `, + data() { + return { + form: { + date1: '', + date2: '' + }, + rules: { + date1: [ + { type: 'date', required: true, message: '请选择日期', trigger: 'change' } + ] + } + } + }, + methods: { + setValue() { + this.name = 'jack' + this.address = 'aaaa' + } + } + }) + vm.$refs.form.validate(valid => { + expect(valid).to.not.true + done() + }) + }) + */ + + + /* + test('validate event', async done => { + const wrapper = mountForm({ + template: ` + + + + + + + + + `, + data() { + return { + form: { + name: '', + addr: '' + }, + valid: { + name: null, + addr: null + }, + error: { + name: null, + addr: null + }, + rules: { + name: [ + { required: true, message: 'Please input name', trigger: 'change', min: 3, max: 6 } + ], + addr: [ + { required: true, message: 'Please input name', trigger: 'change' } + ] + } + } + }, + methods: { + onValidate(prop, valid, msg) { + this.valid[prop] = valid + this.error[prop] = msg + }, + setValue(prop, value) { + this.form[prop] = value + } + } + }) + const vm = wrapper.vm + vm.setValue('name', '1') + await nextTick() + expect(vm.valid.name).toBe(false) + expect(vm.error.name).toBe('Please input name') + vm.setValue('addr', '1') + await nextTick() + expect(vm.valid.addr).toBe(true) + expect(vm.error.addr).toBe(null) + vm.setValue('name', '111') + await nextTick() + expect(vm.valid.name).toBe(true) + expect(vm.error.name).toBe(null) + done() + }) + */ +}) + +/* +awaiting other components complete +describe('validate', () => { + test('input', async () => { + const wrapper = mountForm({ + template: ` + + + + + + `, + data() { + return { + form: { + name: '' + }, + rules: { + name: [ + { required: true, message: 'Please input name', trigger: 'change', min: 3, max: 6 } + ] + } + } + }, + methods: { + setValue(value) { + this.form.name = value + } + } + }) + const vm = wrapper.vm + const form: any = wrapper.findComponent({ref: 'form'}).vm + const valid = await form.validate() + .then(() => true) + .catch(e => false) + expect(valid).toBe(false) + const field: any = wrapper.findComponent({ref: 'field'}).vm + expect(field.validateMessage).toBe('Please input name') + vm.setValue('aaaaa') + await nextTick() + expect(field.validateMessage).toBe('') + vm.setValue('aa') + await nextTick() + expect(field.validateMessage).toBe('Please input name') + }) + + test('textarea', async () => { + const wrapper = mountForm({ + template: ` + + + + + + `, + data() { + return { + form: { + name: '' + }, + rules: { + name: [ + { required: true, message: 'Please input name', trigger: 'change', min: 3, max: 6 } + ] + } + } + }, + methods: { + setValue(value) { + this.form.name = value + } + } + }) + const vm = wrapper.vm + const form: any = wrapper.findComponent({ref: 'form'}).vm + const valid = await form.validate() + .then(() => true) + .catch(e => false) + expect(valid).toBe(false) + const field: any = wrapper.findComponent({ref: 'field'}).vm + await nextTick() + expect(field.validateMessage).toBe('Please input name') + vm.setValue('aaaaa') + await nextTick() + expect(field.validateMessage).toBe('') + vm.setValue('aa') + await nextTick() + expect(field.validateMessage).toBe('Please input name') + }) + + test('selector', done => { + const wrapper = mountForm({ + template: ` + + + + + + + + + `, + data() { + return { + form: { + region: '' + }, + rules: { + region: [ + {required: true, message: '请选择活动区域', trigger: 'change' } + ] + } + } + } + }) + vm.$refs.form.validate(valid => { + let field = vm.$refs.field + expect(valid).to.false + setTimeout(_ => { + expect(field.validateMessage).toBe('请选择活动区域') + // programatic modification triggers change validation + vm.form.region = 'shanghai' + setTimeout(_ => { + expect(field.validateMessage).toBe('') + vm.form.region = '' + setTimeout(_ => { + expect(field.validateMessage).toBe('请选择活动区域') + // user modification of bound value triggers change validation + vm.$refs.opt.$el.click() + setTimeout(_ => { + expect(field.validateMessage).toBe('') + done() + }, 100) + }, 100) + }, 100) + }, 100) + }) + }) + test('datepicker', done => { + const wrapper = mountForm({ + template: ` + + + + + + `, + data() { + return { + form: { + date: '' + }, + rules: { + date: [ + {type: 'date', required: true, message: '请选择日期', trigger: 'change' } + ] + } + } + } + }) + vm.$refs.form.validate(valid => { + let field = vm.$refs.field + expect(valid).to.not.true + setTimeout(_ => { + expect(field.validateMessage).toBe('请选择日期') + // programatic modification triggers change validation + vm.form.date = new Date() + setTimeout(_ => { + expect(field.validateMessage).toBe('') + vm.form.date = '' + // user modification triggers change + const input = vm.$refs.picker.$el.querySelector('input') + input.blur() + input.focus() + setTimeout(_ => { + const keyDown = (el, keyCode) => { + const evt = document.createEvent('Events') + evt.initEvent('keydown') + evt.keyCode = keyCode + el.dispatchEvent(evt) + } + keyDown(input, 37) + setTimeout(_ => { + keyDown(input, 13) + setTimeout(_ => { + expect(field.validateMessage).toBe('') + done() + }, DELAY) + }, DELAY) + }, DELAY) + }, DELAY) + }, DELAY) + }) + }) + test('timepicker', done => { + const wrapper = mountForm({ + template: ` + + + + + + `, + data() { + return { + form: { + date: '' + }, + rules: { + date: [ + {type: 'date', required: true, message: '请选择时间', trigger: 'change' } + ] + } + } + } + }) + vm.$refs.form.validate(valid => { + let field = vm.$refs.field + expect(valid).to.not.true + setTimeout(_ => { + expect(field.validateMessage).toBe('请选择时间') + // programatic modification does not trigger change + vm.value = new Date() + setTimeout(_ => { + expect(field.validateMessage).toBe('请选择时间') + vm.value = '' + // user modification triggers change + const input = vm.$refs.picker.$el.querySelector('input') + input.blur() + input.focus() + setTimeout(_ => { + vm.$refs.picker.picker.$el.querySelector('.confirm').click() + setTimeout(_ => { + expect(field.validateMessage).toBe('') + done() + }, DELAY) + }, DELAY) + }, DELAY) + }, DELAY) + }) + }) + test('checkbox', async done => { + const wrapper = mountForm({ + template: ` + + + + Accept + + + + `, + data() { + return { + form: { + accept: true + }, + rules: { + accept: [ + { + validator: (rule, value, callback) => { + value ? callback() : callback(new Error('You need accept terms')) + }, + trigger: 'change' + } + ] + } + } + }, + methods: { + setValue(value) { + this.form.accept = value + } + } + }) + const vm = wrapper.vm + vm.form.accept = false + await nextTick() + expect(vm.$refs.field.validateMessage).toBe('You need accept terms') + const valid = await vm.$refs.form.validate() + .then(() => true, () => false) + expect(valid).toBe(false) + let field: any = vm.$refs.field + expect(field.validateMessage).toBe('You need accept terms') + await nextTick() + vm.setValue(true) + await nextTick() + expect(field.validateMessage).toBe('') + }) + + test('checkbox group', done => { + const wrapper = mountForm({ + template: ` + + + + + + + + + + + `, + data() { + return { + form: { + type: [] + }, + rules: { + type: [ + { type: 'array', required: true, message: 'Please select type', trigger: 'change' } + ] + } + } + }, + methods: { + setValue(value) { + this.form.type = value + } + } + }) + const vm = wrapper.vm + const form: any = wrapper.findComponent({ref: 'form'}).vm + form.validate(async valid => { + let field: any = vm.$refs.field + expect(valid).toBe(false) + await nextTick() + expect(field.validateMessage).toBe('Please select type') + vm.setValue(['type4']) + await nextTick() + expect(field.validateMessage).toBe('') + done() + }) + }) + + test('radio group', done => { + const wrapper = mountForm({ + template: ` + + + + + + + + + `, + data() { + return { + form: { + type: '' + }, + rules: { + type: [ + { required: true, message: 'Please select type', trigger: 'change' } + ] + } + } + }, + methods: { + setValue(value) { + this.form.type = value + } + } + }) + const vm = wrapper.vm + const form: any = wrapper.findComponent({ref: 'form'}).vm + form.validate(async valid => { + let field: any = vm.$refs.field + expect(valid).toBe(false) + await nextTick() + expect(field.validateMessage).toBe('Please select type') + vm.setValue(['type2']) + await nextTick() + expect(field.validateMessage).toBe('') + done() + }) + }) + + test('validate field', done => { + const wrapper = mountForm({ + template: ` + + + + + + `, + data() { + return { + form: { + name: '' + }, + rules: { + name: [ + { required: true, message: 'Please input name', trigger: 'change', min: 3, max: 6 } + ] + } + } + }, + methods: { + setValue(value) { + this.form.name = value + } + } + }) + const vm: any = wrapper.vm + vm.$refs.form.validateField('name', valid => { + expect(valid).toBe(false) + done() + }) + }) + + test('custom validate', done => { + var checkName = (rule, value, callback) => { + if (value.length < 5) { + callback(new Error('length must be at least 5')) + } else { + callback() + } + } + const wrapper = mountForm({ + template: ` + + + + + + `, + data() { + return { + form: { + name: '' + }, + rules: { + name: [ + { validator: checkName, trigger: 'change' } + ] + } + } + }, + methods: { + setValue(value) { + this.form.name = value + } + } + }) + const vm: any = wrapper.vm + + vm.$refs.form.validate(async valid => { + let field = vm.$refs.field + expect(valid).toBe(false) + await nextTick() + expect(field.validateMessage).toBe('length must be at least 5') + vm.setValue('aaaaaa') + await nextTick() + expect(field.validateMessage).toBe('') + done() + }) + }) + + test('error', done => { + const wrapper = mountForm({ + template: ` + + + + + + `, + data() { + return { + error: 'dsad', + form: { + name: 'sada' + }, + rules: { + name: [ + { required: true, message: 'Please input name', trigger: 'change', min: 3, max: 6 } + ] + } + } + }, + methods: { + setValue(value) { + this.form.name = value + } + } + }) + const vm: any = wrapper.vm + vm.$refs.form.validate(async valid => { + let field = vm.$refs.field + expect(valid).toBe(true) + vm.error = 'illegal input' + await nextTick() + expect(field.validateState).toBe('error') + expect(field.validateMessage).toBe('illegal input') + done() + }) + }) + + test('invalid fields', done => { + var checkName = (rule, value, callback) => { + if (value.length < 5) { + callback(new Error('length must be at least 5')) + } else { + callback() + } + } + const wrapper = mountForm({ + template: ` + + + + + + `, + data() { + return { + form: { + name: '' + }, + rules: { + name: [ + { validator: checkName, trigger: 'change' } + ] + } + } + } + }) + const vm: any = wrapper.vm + vm.$refs.form.validate((valid, invalidFields) => { + expect(invalidFields.name.length).toBe(1) + done() + }) + }) + + test('validate return promise', async done => { + var checkName = (rule, value, callback) => { + if (value.length < 5) { + callback(new Error('length must be at least 5')) + } else { + callback() + } + } + const wrapper = mountForm({ + template: ` + + + + + + `, + data() { + return { + form: { + name: '' + }, + rules: { + name: [ + { validator: checkName, trigger: 'change' } + ] + } + } + } + }) + const vm: any = wrapper.vm + try { + vm.$refs.form.validate() + } catch (e) { + expect(e).toBeDefined() + done() + } + }) +}) +*/ diff --git a/packages/form/index.ts b/packages/form/index.ts new file mode 100644 index 0000000000000..37d113e2fbf75 --- /dev/null +++ b/packages/form/index.ts @@ -0,0 +1,10 @@ +import { App } from 'vue' +import Form from './src/form.vue' +import FormItem from './src/form-item.vue' +import LabelWrap from './src/label-wrap.vue' + +export default (app: App): void => { + app.component(Form.name, Form) + app.component(FormItem.name, FormItem) + app.component(LabelWrap.name, LabelWrap) +} diff --git a/packages/form/package.json b/packages/form/package.json new file mode 100644 index 0000000000000..0c6ad96884528 --- /dev/null +++ b/packages/form/package.json @@ -0,0 +1,15 @@ +{ + "name": "@element-plus/form", + "version": "0.0.0", + "main": "dist/index.js", + "license": "MIT", + "peerDependencies": { + "vue": "^3.0.0-rc.5" + }, + "devDependencies": { + "@vue/test-utils": "^2.0.0-beta.0" + }, + "dependencies": { + "async-validator": "^3.4.0" + } +} diff --git a/packages/form/src/form-item.vue b/packages/form/src/form-item.vue new file mode 100644 index 0000000000000..08dd82765ab8a --- /dev/null +++ b/packages/form/src/form-item.vue @@ -0,0 +1,374 @@ + + + diff --git a/packages/form/src/form.vue b/packages/form/src/form.vue new file mode 100644 index 0000000000000..3a06e082752db --- /dev/null +++ b/packages/form/src/form.vue @@ -0,0 +1,224 @@ + + + diff --git a/packages/form/src/label-wrap.ts b/packages/form/src/label-wrap.ts new file mode 100644 index 0000000000000..322e7b994b147 --- /dev/null +++ b/packages/form/src/label-wrap.ts @@ -0,0 +1,88 @@ +import { + defineComponent, + h, + inject, + ref, + watch, + onMounted, + onUpdated, + onBeforeUnmount, + nextTick, +} from 'vue' + +import { + elFormKey, elFormItemKey, +} from './token' + +export default defineComponent({ + props: { + isAutoWidth: Boolean, + updateAll: Boolean, + }, + setup(props, { slots }) { + const el = ref>(null) + const elForm = inject(elFormKey) + const elFormItem = inject(elFormItemKey) + + const computedWidth = ref(0) + watch(computedWidth, (val, oldVal) => { + if (props.updateAll) { + elForm.registerLabelWidth(val, oldVal) + elFormItem.updateComputedLabelWidth(val) + } + }) + + const getLabelWidth = () => { + if (el.value?.firstElementChild) { + const width = window.getComputedStyle(el.value.firstElementChild) + .width + return Math.ceil(parseFloat(width)) + } else { + return 0 + } + } + const updateLabelWidth = (action = 'update') => { + nextTick(() => { + if (slots.default && props.isAutoWidth) { + if (action === 'update') { + computedWidth.value = getLabelWidth() + } else if (action === 'remove') { + elForm.deregisterLabelWidth(computedWidth.value) + } + } + }) + } + + onMounted(() => updateLabelWidth('update')) + + onUpdated(() => updateLabelWidth('update')) + + onBeforeUnmount(() => updateLabelWidth('remove')) + + function render() { + if (!slots) return null + if (props.isAutoWidth) { + const autoLabelWidth = elForm.autoLabelWidth + const style = {} as CSSStyleDeclaration + if (autoLabelWidth && autoLabelWidth !== 'auto') { + const marginLeft = parseInt(autoLabelWidth, 10) - computedWidth.value + if (marginLeft) { + style.marginLeft = marginLeft + 'px' + } + } + return h( + 'div', + { + ref: el, + class: ['el-form-item__label-wrap'], + style, + }, + slots.default?.(), + ) + } else { + return h('div', { ref: el }, slots.default?.()) + } + } + return render + }, +}) diff --git a/packages/form/src/token.ts b/packages/form/src/token.ts new file mode 100644 index 0000000000000..f8ebcef1aa590 --- /dev/null +++ b/packages/form/src/token.ts @@ -0,0 +1,48 @@ +import type { InjectionKey } from 'vue' +import type { Emitter } from 'mitt' +import type { + FieldErrorList, +} from 'async-validator' + +export interface ElFormContext { + registerLabelWidth(width: number, oldWidth: number): void + deregisterLabelWidth(width: number): void + autoLabelWidth: string | undefined + formMitt: Emitter + emit: (evt: string, ...args: any[]) => void + + labelSuffix: string + inline?: boolean + model?: Record + size?: string + showMessage?: boolean + labelPosition?: string + labelWidth?: string + rules?: Record + statusIcon?: boolean + hideRequiredAsterisk?: boolean +} + +export interface ValidateFieldCallback { + (message?: string, invalidFields?: FieldErrorList): void +} + +export interface ElFormItemContext { + prop?: string + validate(trigger?: string, callback?: ValidateFieldCallback): void + updateComputedLabelWidth(width: number): void + addValidateEvents(): void + removeValidateEvents(): void + resetField(): void + clearValidate(): void +} + +// TODO: change it to symbol +export const elFormKey: InjectionKey = 'elForm' as any + +export const elFormItemKey: InjectionKey = 'elFormItem' as any + +export const elFormEvents = { + addField: 'el.form.addField', + removeField: 'el.form.removeField', +} as const diff --git a/packages/test-utils/style-plugin.ts b/packages/test-utils/style-plugin.ts new file mode 100644 index 0000000000000..ada9bcfd3e05a --- /dev/null +++ b/packages/test-utils/style-plugin.ts @@ -0,0 +1,13 @@ +import { config } from '@vue/test-utils' + +const stylePlugin = (wrapper: any) => { + return { + style: wrapper.element.style, + } +} + +export default function install() { + config.plugins.DOMWrapper.install(stylePlugin) + config.plugins.VueWrapper.install(stylePlugin) +} + diff --git a/packages/utils/util.ts b/packages/utils/util.ts index 1eba907c10c0a..335047534d2a7 100644 --- a/packages/utils/util.ts +++ b/packages/utils/util.ts @@ -16,6 +16,7 @@ import { import isServer from './isServer' import type { AnyFunction } from './types' import type { Ref } from 'vue' +import { getCurrentInstance } from 'vue' export type PartialCSSStyleDeclaration = Partial< Pick @@ -171,3 +172,11 @@ export function isUndefined(val: any) { } export { isVNode } from 'vue' + +export function useGlobalConfig() { + const vm: any = getCurrentInstance() + if ('$ELEMENT' in vm.proxy) { + return vm.proxy.$ELEMENT + } + return {} +} diff --git a/typings/vue-test-utils.d.ts b/typings/vue-test-utils.d.ts new file mode 100644 index 0000000000000..3c0bd43610d43 --- /dev/null +++ b/typings/vue-test-utils.d.ts @@ -0,0 +1,11 @@ +import { ComponentPublicInstance } from '@vue/test-utils' + +declare module '@vue/test-utils' { + interface DOMWrapper { + style: CSSStyleDeclaration + } + + interface VueWrapper { + style: CSSStyleDeclaration + } +} diff --git a/website/play/index.vue b/website/play/index.vue index c03b7d83e7f44..243f8ae15c0e2 100644 --- a/website/play/index.vue +++ b/website/play/index.vue @@ -9,6 +9,6 @@ export default { data() { return {} - } + }, } diff --git a/yarn.lock b/yarn.lock index 897b4a76bf7b0..758c34d9a89bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3250,6 +3250,11 @@ async-settle@^1.0.0: dependencies: async-done "^1.2.2" +async-validator@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-3.4.0.tgz#871b3e594124bf4c4eb7bcd1a9e78b44f3b09cae" + integrity sha512-VrFk4eYiJAWKskEz115iiuCf9O0ftnMMPXrOFMqyzGH2KxO7YwncKyn/FgOOP+0MDHMfXL7gLExagCutaZGigA== + async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"