import { nextTick, ref } from 'vue'
import { mount } from '@vue/test-utils'
import { afterEach, describe, expect, test, vi } from 'vitest'
import defineGetter from '@element-plus/test-utils/define-getter'
import { ElFormItem as FormItem } from '@element-plus/components/form'
import Input from '../src/input.vue'
import type { CSSProperties } from 'vue'
import type { InputAutoSize, InputInstance, InputProps } from '../src/input'
describe('Input.vue', () => {
afterEach(() => {
vi.restoreAllMocks()
})
test('create', async () => {
const input = ref('input')
const handleFocus = vi.fn()
const wrapper = mount(() => (
))
const inputElm = wrapper.find('input')
const nativeInput = inputElm.element
await inputElm.trigger('focus')
expect(inputElm.exists()).toBe(true)
expect(handleFocus).toHaveBeenCalled()
expect(nativeInput.placeholder).toMatchInlineSnapshot(`"请输入内容"`)
expect(nativeInput.value).toMatchInlineSnapshot(`"input"`)
expect(nativeInput.minLength).toMatchInlineSnapshot(`3`)
input.value = 'text'
await nextTick()
expect(inputElm.element.value).toMatchInlineSnapshot(`"text"`)
})
test('default to empty', () => {
const wrapper = mount(() => )
const inputElm = wrapper.find('input')
expect(inputElm.element.value).toBe('')
})
test('disabled', () => {
const wrapper = mount(() => )
const inputElm = wrapper.find('input')
expect(inputElm.element.disabled).not.toBeNull()
})
describe('test emoji', () => {
test('el-input should minimize value between emoji length and maxLength', async () => {
const inputVal = ref('12🌚')
const wrapper = mount(() => (
))
const vm = wrapper.vm
const inputElm = wrapper.find('input')
const nativeInput = inputElm.element
expect(nativeInput.value).toMatchInlineSnapshot(`"12🌚"`)
const elCount = wrapper.find('.el-input__count-inner')
expect(elCount.exists()).toBe(true)
expect(elCount.text()).toMatchInlineSnapshot(`"3 / 4"`)
inputVal.value = '1👌3😄'
await nextTick()
expect(nativeInput.value).toMatchInlineSnapshot(`"1👌3😄"`)
expect(elCount.text()).toMatchInlineSnapshot(`"4 / 4"`)
inputVal.value = '哈哈1👌3😄'
await nextTick()
expect(nativeInput.value).toMatchInlineSnapshot(`"哈哈1👌3😄"`)
expect(elCount.text()).toMatchInlineSnapshot(`"6 / 4"`)
expect(Array.from(vm.$el.classList)).toMatchInlineSnapshot(`
[
"el-input",
"is-exceed",
"test-exceed",
]
`)
})
test('textarea should minimize value between emoji length and maxLength', async () => {
const inputVal = ref('啊好😄')
const wrapper = mount(() => (
))
const vm = wrapper.vm
const inputElm = wrapper.find('textarea')
const nativeInput = inputElm.element
expect(nativeInput.value).toMatchInlineSnapshot(`"啊好😄"`)
const elCount = wrapper.find('.el-input__count')
expect(elCount.exists()).toBe(true)
expect(elCount.text()).toMatchInlineSnapshot(`"3 / 4"`)
inputVal.value = '哈哈1👌3😄'
await nextTick()
expect(nativeInput.value).toMatchInlineSnapshot(`"哈哈1👌3😄"`)
expect(elCount.text()).toMatchInlineSnapshot(`"6 / 4"`)
expect(Array.from(vm.$el.classList)).toMatchInlineSnapshot(`
[
"el-textarea",
"is-exceed",
]
`)
})
})
test('suffixIcon', () => {
const wrapper = mount(() => )
const icon = wrapper.find('.el-input__icon')
expect(icon.exists()).toBe(true)
})
test('prefixIcon', () => {
const wrapper = mount(() => )
const icon = wrapper.find('.el-input__icon')
expect(icon.exists()).toBe(true)
})
test('size', () => {
const wrapper = mount(() => )
expect(wrapper.classes('el-input--large')).toBe(true)
})
test('type', () => {
const wrapper = mount(() => )
expect(wrapper.classes('el-textarea')).toBe(true)
})
test('rows', () => {
const wrapper = mount(() => )
expect(wrapper.find('textarea').element.rows).toEqual(3)
})
test('resize', async () => {
const resize = ref('none')
const wrapper = mount(() => )
const textarea = wrapper.find('textarea').element
await nextTick()
expect(textarea.style.resize).toEqual(resize.value)
resize.value = 'horizontal'
await nextTick()
expect(textarea.style.resize).toEqual(resize.value)
})
test('sets value on textarea / input type change', async () => {
const type = ref('text')
const val = ref('123')
const wrapper = mount(() => )
const vm = wrapper.vm
expect(vm.$el.querySelector('input').value).toMatchInlineSnapshot(`"123"`)
type.value = 'textarea'
await nextTick()
await nextTick()
expect(vm.$el.querySelector('textarea').value).toMatchInlineSnapshot(
`"123"`
)
type.value = 'password'
await nextTick()
await nextTick()
expect(vm.$el.querySelector('input').value).toMatchInlineSnapshot(`"123"`)
})
test('limit input and show word count', async () => {
const input1 = ref('')
const input2 = ref('')
const input3 = ref('')
const input4 = ref('exceed')
const show = ref(false)
const wrapper = mount(() => (
))
const inputElm1 = wrapper.vm.$el.querySelector('.test-text')
const inputElm2 = wrapper.vm.$el.querySelector('.test-textarea')
const inputElm3 = wrapper.vm.$el.querySelector('.test-password')
const inputElm4 = wrapper.vm.$el.querySelector('.test-initial-exceed')
expect(inputElm1.querySelectorAll('.el-input__count').length).toEqual(0)
expect(inputElm2.querySelectorAll('.el-input__count').length).toEqual(1)
expect(inputElm3.querySelectorAll('.el-input__count').length).toEqual(0)
expect(Array.from(inputElm4.classList)).toMatchInlineSnapshot(`
[
"el-input",
"is-exceed",
"test-initial-exceed",
]
`)
show.value = true
await nextTick()
expect(inputElm1.querySelectorAll('.el-input__count').length).toEqual(1)
input4.value = '1'
await nextTick()
expect(Array.from(inputElm4.classList)).toMatchInlineSnapshot(`
[
"el-input",
"test-initial-exceed",
]
`)
})
test('use formatter and parser', () => {
const val = ref('10000')
const formatter = (val: string) => {
return val.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
const parser = (val: string) => {
return val.replace(/\$\s?|(,*)/g, '')
}
const wrapper = mount(() => (
))
const vm = wrapper.vm
expect(vm.$el.querySelector('input').value).toEqual('10000')
expect(vm.$el.querySelector('input').value).not.toEqual('1000')
})
describe('Input Methods', () => {
test('method:select', async () => {
const testContent = ref('test')
const wrapper = mount(() => )
const input = wrapper.find('input').element
// mock selectionRange behaviour, due to jsdom's reason this case cannot run well, may be fixed later using headlesschrome or puppeteer
let selected = false
defineGetter(input, 'selectionStart', function (this: HTMLInputElement) {
return selected ? 0 : this.value.length
})
defineGetter(input, 'selectionEnd', function (this: HTMLInputElement) {
return this.value.length
})
expect(input.selectionStart).toEqual(testContent.value.length)
expect(input.selectionEnd).toEqual(testContent.value.length)
input.select()
selected = true
await nextTick()
expect(input.selectionStart).toEqual(0)
expect(input.selectionEnd).toEqual(testContent.value.length)
})
test('method:resizeTextarea', async () => {
const text = ref('TEXT:resizeTextarea')
const wrapper = mount({
setup: () => () =>
(
),
})
const refTextarea = wrapper.vm.$refs.textarea as InputInstance
const originMinHeight = (refTextarea.textareaStyle as CSSProperties)
.minHeight
;(refTextarea.autosize as Exclude).minRows = 5
refTextarea.resizeTextarea()
// After this textarea min-height (style) will change
const nowMinHeight = (refTextarea.textareaStyle as any)[1].minHeight
expect(originMinHeight).not.toEqual(nowMinHeight)
})
})
describe('Input Events', () => {
const handleFocus = vi.fn()
const handleBlur = vi.fn()
test('event:focus & blur', async () => {
const content = ref('')
const wrapper = mount(() => (
))
const input = wrapper.find('input')
await input.trigger('focus')
expect(handleFocus).toBeCalled()
await input.trigger('blur')
expect(handleBlur).toBeCalled()
})
test('event:change', async () => {
const content = ref('a')
const value = ref('')
const handleChange = (val: string) => {
value.value = val
}
// NOTE: should be same as native's change behavior
const wrapper = mount(() => (
))
const el = wrapper.find('input').element
wrapper.vm
const simulateEvent = (text: string, event: string) => {
el.value = text
el.dispatchEvent(new Event(event))
}
// simplified test, component should emit change when native does
simulateEvent('2', 'change')
await nextTick()
expect(value.value).toBe('2')
simulateEvent('1', 'input')
await nextTick()
expect(value.value).toBe('2')
})
test('event:clear', async () => {
const handleClear = vi.fn()
const handleInput = vi.fn()
const content = ref('a')
const wrapper = mount(() => (
))
const input = wrapper.find('input')
const vm = wrapper.vm
// focus to show clear button
await input.trigger('focus')
await nextTick()
vm.$el.querySelector('.el-input__clear').click()
await nextTick()
expect(content.value).toEqual('')
expect(handleClear).toBeCalled()
expect(handleInput).toBeCalled()
})
test('event:input', async () => {
const handleInput = vi.fn()
const content = ref('a')
const wrapper = mount(() => (
))
const inputWrapper = wrapper.find('input')
const nativeInput = inputWrapper.element
nativeInput.value = '1'
await inputWrapper.trigger('compositionstart')
await inputWrapper.trigger('input')
nativeInput.value = '2'
await inputWrapper.trigger('compositionupdate')
await inputWrapper.trigger('input')
await inputWrapper.trigger('compositionend')
expect(handleInput).toBeCalledTimes(1)
// native input value is controlled
expect(content.value).toEqual('a')
expect(nativeInput.value).toEqual('a')
})
})
test('non-emit event such as keyup should work', async () => {
const handleKeyup = vi.fn()
const wrapper = mount(Input, {
attrs: {
onKeyup: handleKeyup,
},
})
await wrapper.find('input').trigger('keyup')
expect(handleKeyup).toBeCalledTimes(1)
})
test('input-style', async () => {
const wrapper = mount(() => (
<>
>
))
const input = wrapper.find('input')
const textarea = wrapper.find('textarea')
await nextTick()
expect(input.element.style.color === 'red').toBeTruthy()
expect(textarea.element.style.color === 'red').toBeTruthy()
})
describe('Textarea Events', () => {
test('event:keydown', async () => {
const handleKeydown = vi.fn()
const content = ref('')
const wrapper = mount(() => (
))
await wrapper.find('textarea').trigger('keydown')
expect(handleKeydown).toBeCalledTimes(1)
})
})
test('show-password icon', async () => {
const password = ref('123456')
const wrapper = mount(() => (
))
const icon = wrapper.find('.el-input__icon.el-input__password')
const d = icon.find('path').element.getAttribute('d')
await icon.trigger('click')
const d0 = icon.find('path').element.getAttribute('d')
expect(d !== d0).toBeTruthy()
})
describe('form item accessibility integration', () => {
test('automatic id attachment', async () => {
const wrapper = mount(() => (
))
await nextTick()
const formItem = wrapper.find('[data-test-ref="item"]')
const input = wrapper.find('[data-test-ref="input"]')
const formItemLabel = formItem.find('.el-form-item__label')
expect(formItem.attributes().role).toBeFalsy()
expect(formItemLabel.attributes().for).toBe(input.attributes().id)
})
test('specified id attachment', async () => {
const wrapper = mount(() => (
))
await nextTick()
const formItem = wrapper.find('[data-test-ref="item"]')
const input = wrapper.find('[data-test-ref="input"]')
const formItemLabel = formItem.find('.el-form-item__label')
expect(formItem.attributes().role).toBeFalsy()
expect(input.attributes().id).toBe('foobar')
expect(formItemLabel.attributes().for).toBe(input.attributes().id)
})
test('form item role is group when multiple inputs', async () => {
const wrapper = mount(() => (
))
await nextTick()
const formItem = wrapper.find('[data-test-ref="item"]')
expect(formItem.attributes().role).toBe('group')
})
})
// TODO: validateEvent & input containes select cases should be added after the rest components finished
// ...
})