UNPKG

buefy

Version:

Lightweight UI components for Vue.js (v3) based on Bulma

397 lines (320 loc) 14.4 kB
import { defineComponent } from 'vue' import { shallowMount, mount } from '@vue/test-utils' import type { VueWrapper } from '@vue/test-utils' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { MockInstance } from 'vitest' import BInput from '@components/input/Input.vue' import BIcon from '@components/icon/Icon.vue' import FormElementMixin from '@utils/FormElementMixin' import type { ExtractComponentData } from '@utils/helpers' type BInputInstance = InstanceType<typeof BInput> type FormElementMixinInstance = InstanceType<typeof FormElementMixin> type BInputData = ExtractComponentData<typeof BInput> let wrapper: VueWrapper<BInputInstance> describe('BInput', () => { beforeEach(() => { wrapper = shallowMount(BInput) }) it('render correctly', () => { expect(wrapper.html()).toMatchSnapshot() }) it('is vue instance', () => { expect(wrapper.vm).toBeTruthy() expect(wrapper.vm.$options.name).toBe('BInput') }) it('renders input element by default', () => { expect(wrapper.find('input').exists()).toBeTruthy() expect(wrapper.classes()).toContain('control') }) it('render textarea element when type is textarea', async () => { await wrapper.setProps({ type: 'textarea' }) const target = wrapper.find('textarea') expect(target.exists()).toBeTruthy() expect(target.classes()).toContain('textarea') }) it('displays the icon when the icon property is true', async () => { await wrapper.setProps({ icon: 'magnify' }) const target = wrapper.findComponent(BIcon) expect(target.exists()).toBeTruthy() }) it('display counter when the maxlength property is passed', async () => { await wrapper.setProps({ modelValue: 'foo', maxlength: 100 }) const counter = wrapper.find('small.counter') expect(counter.exists()).toBeTruthy() expect(counter.text()).toBe('3 / 100') await wrapper.setProps({ modelValue: 1234 }) expect(counter.text()).toBe('4 / 100') }) it('display correct input value length when value contains some emoji', async () => { await wrapper.setProps({ modelValue: '😀2', maxlength: 5 }) const counter = wrapper.find('small.counter') expect(counter.exists()).toBeTruthy() expect(counter.text()).toBe('2 / 5') }) it('no display counter when hasCounter property set for false', async () => { await wrapper.setProps({ maxlength: 100 }) expect(wrapper.find('small.counter').exists()).toBeTruthy() await wrapper.setProps({ hasCounter: false }) expect(wrapper.find('small.counter').exists()).toBeFalsy() }) it('render field password when the type property is password', () => { const wrapper = shallowMount(BInput, { propsData: { type: 'password', passwordReveal: true } }) const target = wrapper.find('input') expect(target.exists()).toBeTruthy() expect(target.attributes().type).toBe('password') }) it('toggles the visibility of the password to true when the togglePasswordVisibility method is called', async () => { const wrapper = mount(BInput, { props: { modelValue: 'foo', type: 'password', passwordReveal: true } }) await wrapper.setProps({ modelValue: 'bar' }) expect(wrapper.find('input').exists()).toBeTruthy() expect(wrapper.vm.newType).toBe('password') expect(wrapper.vm.isPasswordVisible).toBeFalsy() expect(wrapper.find('input').attributes().type).toBe('password') const visibilityIcon = wrapper.find('.icon.is-clickable') expect(visibilityIcon.exists()).toBeTruthy() visibilityIcon.trigger('click') await wrapper.setProps({ passwordReveal: false }) expect(wrapper.vm.newType).toBe('text') expect(wrapper.vm.isPasswordVisible).toBeTruthy() expect(wrapper.find('input').attributes().type).toBe('text') }) it('render the placeholder and readonly attribute when passed', () => { const wrapper = shallowMount(BInput, { attrs: { placeholder: 'Awesome!', readonly: true } }) const target = wrapper.find('input') expect(target.element.getAttribute('placeholder')).toBe('Awesome!') expect(target.element.getAttribute('readonly')).toBe('') }) it('expands input when expanded property is passed', async () => { await wrapper.setProps({ expanded: true }) expect(wrapper.classes()).toContain('is-expanded') }) it('display loading icon when loading property passed', async () => { await wrapper.setProps({ loading: true, icon: 'magnify' }) expect(wrapper.classes()).toContain('is-loading') }) it('keep its value on blur', async () => { const wrapper = mount(BInput, { props: { modelValue: 'foo', // overriding the method `checkHtml5Validity` won't work // because `mount` no longer accepts `methods` option on // @vue/test-utils V2 // // kikuomax: I decided to disable validation instead. // I do not think it matters to the outcome anyway. useHtml5Validation: false } }) const input = wrapper.find('input') input.element.value = 'bar' input.trigger('input') input.trigger('blur') expect(input.element.value).toBe('bar') }) it('change status icon when statusType updated', async () => { const parent = { data: () => ({ newType: 'is-success', // the following internal property is required // so that a child Input can locate the parent (this component) // and fetch `newType` from it. // see `FormElementMixin` for more details _isField: true }), components: { BInput }, template: '<b-input />' } const wrapper = mount(parent) const input = wrapper.findComponent(BInput) expect(input.vm.statusTypeIcon).toBe('check') await wrapper.setData({ newType: 'is-danger' }) expect(input.vm.statusTypeIcon).toBe('alert-circle') await wrapper.setData({ newType: 'is-info' }) expect(input.vm.statusTypeIcon).toBe('information') await wrapper.setData({ newType: 'is-warning' }) expect(input.vm.statusTypeIcon).toBe('alert') }) it('manage the click on icon', async () => { const wrapper = mount(BInput, { propsData: { icon: 'magnify', iconClickable: true } }) expect(wrapper.find('input').exists()).toBeTruthy() const visibilityIcon = wrapper.find('.icon.is-clickable') expect(visibilityIcon.exists()).toBeTruthy() visibilityIcon.trigger('click') await wrapper.vm.$nextTick() expect(wrapper.emitted()['icon-click']).toBeTruthy() }) describe('validation', () => { let spyOnCheckHtml5Validity: MockInstance beforeEach(() => { spyOnCheckHtml5Validity = vi .spyOn(FormElementMixin.methods as FormElementMixinInstance, 'checkHtml5Validity') .mockImplementation(function () { this.isValid = false return false } as (this: FormElementMixinInstance) => boolean) }) afterEach(() => { spyOnCheckHtml5Validity.mockReset() }) it('should validate value at input event', async () => { const wrapper = shallowMount(BInput, { data: () => ({ isValid: false } as BInputData) }) const inputElement = wrapper.get('input') await inputElement.element.focus() expect(spyOnCheckHtml5Validity).toHaveBeenCalledTimes(0) await inputElement.trigger('input') expect(spyOnCheckHtml5Validity).toHaveBeenCalledTimes(1) await inputElement.trigger('change') expect(spyOnCheckHtml5Validity).toHaveBeenCalledTimes(1) }) it('should validate value at change event if lazy', async () => { const wrapper = shallowMount(BInput, { props: { lazy: true }, data: () => ({ isValid: false } as BInputData) }) const inputElement = wrapper.get('input') await inputElement.element.focus() expect(spyOnCheckHtml5Validity).toHaveBeenCalledTimes(0) await inputElement.trigger('input') expect(spyOnCheckHtml5Validity).toHaveBeenCalledTimes(0) await inputElement.trigger('change') expect(spyOnCheckHtml5Validity).toHaveBeenCalledTimes(1) }) it('should validate value when programmatically updated', async () => { const wrapper = shallowMount(BInput, { data: () => ({ isValid: false } as BInputData) }) await wrapper.setProps({ modelValue: 'foo' }) await wrapper.vm.$nextTick() expect(spyOnCheckHtml5Validity).toHaveBeenCalledTimes(1) }) describe('via v-model', () => { const rootComponent = defineComponent({ components: { 'b-input': BInput }, data: () => ({ value: '', lazy: false }), template: '<b-input v-model="value" :lazy="lazy" />' }) let root: VueWrapper<InstanceType<typeof rootComponent>> let wrapper: VueWrapper<BInputInstance> beforeEach(async () => { root = mount(rootComponent) wrapper = root.findComponent(BInput) // triggers validation and invalidates // eslint-disable-next-line @typescript-eslint/no-explicit-any wrapper.vm.onBlur({} as any) await wrapper.vm.$nextTick() spyOnCheckHtml5Validity.mockClear() }) it('should update and validate value at input event', async () => { const inputElement = wrapper.get('input') inputElement.element.value = 'foo' await inputElement.trigger('input') expect(root.vm.value).toBe('foo') expect(spyOnCheckHtml5Validity).toHaveBeenCalledTimes(1) await inputElement.trigger('change') expect(spyOnCheckHtml5Validity).toHaveBeenCalledTimes(1) }) it('should update and validate value at change event if lazy', async () => { await root.setData({ lazy: true }) // input element must be queried after changing `lazy` const inputElement = root.get('input') inputElement.element.value = 'foo' await inputElement.trigger('input') expect(root.vm.value).toBe('') expect(spyOnCheckHtml5Validity).toHaveBeenCalledTimes(0) await inputElement.trigger('change') expect(root.vm.value).toBe('foo') expect(spyOnCheckHtml5Validity).toHaveBeenCalledTimes(1) }) it('should validate value once when programmatically updated', async () => { await root.setData({ value: 'foo' }) await wrapper.vm.$nextTick() expect(spyOnCheckHtml5Validity).toHaveBeenCalledTimes(1) }) }) }) describe('with fallthrough attributes', () => { const attrs = { class: 'fallthrough-class', style: 'font-size: 2rem;', id: 'fallthrough-id' } it('should bind class, style, and id to the root div if compatFallthrough is true (default)', () => { const wrapper = shallowMount(BInput, { attrs }) const root = wrapper.find('div.control') expect(root.classes(attrs.class)).toBe(true) expect(root.attributes('style')).toBe(attrs.style) expect(root.attributes('id')).toBe(attrs.id) const input = wrapper.find('input') expect(input.classes(attrs.class)).toBe(false) expect(input.attributes('style')).toBeUndefined() expect(input.attributes('id')).toBeUndefined() }) it('should bind class, style, and id to the input element if compatFallthrough is false', () => { const wrapper = shallowMount(BInput, { attrs, props: { compatFallthrough: false } }) const input = wrapper.find('input') expect(input.classes(attrs.class)).toBe(true) expect(input.attributes('style')).toBe(attrs.style) expect(input.attributes('id')).toBe(attrs.id) const root = wrapper.find('div.control') expect(root.classes(attrs.class)).toBe(false) expect(root.attributes('style')).toBeUndefined() expect(root.attributes('id')).toBeUndefined() }) it('should bind class, style, and id to the textarea element if compatFallthrough is false', () => { const wrapper = shallowMount(BInput, { attrs, props: { compatFallthrough: false, type: 'textarea' } }) const textarea = wrapper.find('textarea') expect(textarea.classes(attrs.class)).toBe(true) expect(textarea.attributes('style')).toBe(attrs.style) expect(textarea.attributes('id')).toBe(attrs.id) const root = wrapper.find('div.control') expect(root.classes(attrs.class)).toBe(false) expect(root.attributes('style')).toBeUndefined() expect(root.attributes('id')).toBeUndefined() }) }) })