123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- 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(() => (
- <Input
- minlength={3}
- maxlength={5}
- placeholder="请输入内容"
- onFocus={handleFocus}
- modelValue={input.value}
- />
- ))
- 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(() => <Input />)
- const inputElm = wrapper.find('input')
- expect(inputElm.element.value).toBe('')
- })
- test('disabled', () => {
- const wrapper = mount(() => <Input disabled />)
- 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(() => (
- <Input
- class="test-exceed"
- maxlength="4"
- showWordLimit
- v-model={inputVal.value}
- />
- ))
- 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(() => (
- <Input
- type="textarea"
- maxlength="4"
- showWordLimit
- v-model={inputVal.value}
- />
- ))
- 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(() => <Input suffix-icon="time" />)
- const icon = wrapper.find('.el-input__icon')
- expect(icon.exists()).toBe(true)
- })
- test('prefixIcon', () => {
- const wrapper = mount(() => <Input prefix-icon="time" />)
- const icon = wrapper.find('.el-input__icon')
- expect(icon.exists()).toBe(true)
- })
- test('size', () => {
- const wrapper = mount(() => <Input size="large" />)
- expect(wrapper.classes('el-input--large')).toBe(true)
- })
- test('type', () => {
- const wrapper = mount(() => <Input type="textarea" />)
- expect(wrapper.classes('el-textarea')).toBe(true)
- })
- test('rows', () => {
- const wrapper = mount(() => <Input type="textarea" rows={3} />)
- expect(wrapper.find('textarea').element.rows).toEqual(3)
- })
- test('resize', async () => {
- const resize = ref<InputProps['resize']>('none')
- const wrapper = mount(() => <Input type="textarea" resize={resize.value} />)
- 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(() => <Input type={type.value} v-model={val.value} />)
- 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(() => (
- <div>
- <Input
- class="test-text"
- type="text"
- v-model={input1.value}
- maxlength="10"
- showWordLimit={show.value}
- />
- <Input
- class="test-textarea"
- type="textarea"
- v-model={input2.value}
- maxlength="10"
- showWordLimit
- />
- <Input
- class="test-password"
- type="password"
- v-model={input3.value}
- maxlength="10"
- showWordLimit
- />
- <Input
- class="test-initial-exceed"
- type="text"
- v-model={input4.value}
- maxlength="2"
- showWordLimit
- />
- </div>
- ))
- 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(() => (
- <Input v-model={val.value} formatter={formatter} parser={parser} />
- ))
- 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(() => <Input v-model={testContent.value} />)
- 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: () => () =>
- (
- <Input
- ref="textarea"
- autosize={{ minRows: 1, maxRows: 1 }}
- type="textarea"
- v-model={text.value}
- />
- ),
- })
- const refTextarea = wrapper.vm.$refs.textarea as InputInstance
- const originMinHeight = (refTextarea.textareaStyle as CSSProperties)
- .minHeight
- ;(refTextarea.autosize as Exclude<InputAutoSize, boolean>).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(() => (
- <Input
- placeholder="请输入内容"
- modelValue={content.value}
- onFocus={handleFocus}
- onBlur={handleBlur}
- />
- ))
- 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(() => (
- <Input
- placeholder="请输入内容"
- modelValue={content.value}
- onChange={handleChange}
- />
- ))
- 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(() => (
- <Input
- placeholder="请输入内容"
- clearable
- v-model={content.value}
- onClear={handleClear}
- onInput={handleInput}
- />
- ))
- 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(() => (
- <Input
- placeholder="请输入内容"
- clearable
- modelValue={content.value}
- onInput={handleInput}
- />
- ))
- 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(() => (
- <>
- <Input placeholder="请输入内容" input-style={{ color: 'red' }} />
- <Input
- placeholder="请输入内容"
- input-style={{ color: 'red' }}
- type="textarea"
- />
- </>
- ))
- 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(() => (
- <Input
- type="textarea"
- modelValue={content.value}
- onKeydown={handleKeydown}
- />
- ))
- await wrapper.find('textarea').trigger('keydown')
- expect(handleKeydown).toBeCalledTimes(1)
- })
- })
- test('show-password icon', async () => {
- const password = ref('123456')
- const wrapper = mount(() => (
- <Input type="password" modelValue={password.value} show-password />
- ))
- 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(() => (
- <FormItem label="Foobar" data-test-ref="item">
- <Input data-test-ref="input" />
- </FormItem>
- ))
- 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(() => (
- <FormItem label="Foobar" data-test-ref="item">
- <Input id="foobar" data-test-ref="input" />
- </FormItem>
- ))
- 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(() => (
- <FormItem label="Foobar" data-test-ref="item">
- <Input data-test-ref="input1" />
- <Input data-test-ref="input2" />
- </FormItem>
- ))
- 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
- // ...
- })
|