@ntohq/buefy-next
Version:
Lightweight UI components for Vue.js (v3) based on Bulma
445 lines (349 loc) • 14.3 kB
text/typescript
import { mount, shallowMount } from '@vue/test-utils'
import type { DOMWrapper, VueWrapper } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import BAutocomplete from '@components/autocomplete/Autocomplete.vue'
const findStringsStartingWith = (array: string[], value: string) =>
array.filter((x) => x.startsWith(value))
const DATA_LIST = [
'Angular',
'Angular 2',
'Aurelia',
'Backbone',
'Ember',
'jQuery',
'Meteor',
'Node.js',
'Polymer',
'React',
'RxJS',
'Vue.js'
]
const dropdownMenu = '.dropdown-menu'
let wrapper: VueWrapper<InstanceType<typeof BAutocomplete>>
let $input: DOMWrapper<HTMLInputElement | HTMLTextAreaElement>
let $dropdown: DOMWrapper<Element>
const stubs = { 'b-icon': true }
describe('BAutocomplete', () => {
beforeEach(() => {
wrapper = mount(BAutocomplete, {
props: {
checkInfiniteScroll: true
},
global: {
stubs
}
})
$input = wrapper.find('input')
$dropdown = wrapper.find(dropdownMenu)
})
it('is called', () => {
expect(wrapper.vm).toBeTruthy()
expect(wrapper.vm.$options.name).toBe('BAutocomplete')
})
it('render correctly', () => {
expect(wrapper.html()).toMatchSnapshot()
})
it('has an input type', () => {
expect(wrapper.find('.control .input[type=text]').exists()).toBeTruthy()
})
it('has a dropdown menu hidden by default', () => {
expect(wrapper.find(dropdownMenu).exists()).toBeTruthy()
expect($dropdown.isVisible()).toBeFalsy()
})
it('can apply a maximum height for the dropdown', async () => {
expect(wrapper.vm.contentStyle.maxHeight).toBeUndefined()
const maxHeight = 200
await wrapper.setProps({ maxHeight })
expect(wrapper.vm.contentStyle.maxHeight).toBe(`${maxHeight}px`)
await wrapper.setProps({ maxHeight: `${maxHeight}rem` })
expect(wrapper.vm.contentStyle.maxHeight).toBe(`${maxHeight}rem`)
})
it('can select a value using v-model', async () => {
const modelValue = DATA_LIST[0]
await wrapper.setProps({ modelValue })
expect(wrapper.vm.newValue).toBe(modelValue)
})
it('can emit input, focus and blur events', async () => {
const VALUE_TYPED = 'test'
await wrapper.setProps({ data: DATA_LIST })
await $input.trigger('focus')
expect(wrapper.emitted().focus).toBeTruthy()
await $input.setValue(VALUE_TYPED)
const valueEmitted = wrapper.emitted()['update:modelValue'][0]
expect(valueEmitted).toContainEqual(VALUE_TYPED)
await $input.trigger('blur')
expect(wrapper.emitted().blur).toBeTruthy()
})
it('can emit select-header by keyboard and click', async () => {
const VALUE_TYPED = 'test'
const wrapper = mount(BAutocomplete, {
props: {
checkInfiniteScroll: true,
selectableHeader: true,
selectableFooter: true
},
slots: {
header: '<h1>SLOT HEADER</h1>',
footer: '<h1>SLOT FOOTER</h1>'
},
global: {
stubs
}
})
const $input = wrapper.find('input')
await $input.trigger('focus')
await $input.setValue(VALUE_TYPED)
await $input.trigger('keydown', { key: 'Down' })
await $input.trigger('keydown', { key: 'Enter' })
const $header = wrapper.find('.dropdown-item.dropdown-header')
await $header.trigger('click')
const emitted = wrapper.emitted()
expect(emitted['select-header']).toBeTruthy()
expect(emitted['select-header']).toHaveLength(2)
})
it('can emit select-footer by keyboard and click', async () => {
const VALUE_TYPED = 'test'
const wrapper = mount(BAutocomplete, {
propsData: {
checkInfiniteScroll: true,
selectableHeader: true,
selectableFooter: true
},
slots: {
header: '<h1>SLOT HEADER</h1>',
footer: '<h1>SLOT FOOTER</h1>'
},
global: {
stubs
}
})
const $input = wrapper.find('input')
await $input.trigger('focus')
await $input.setValue(VALUE_TYPED)
await $input.trigger('keydown', { key: 'Down' })
await $input.trigger('keydown', { key: 'Down' })
await $input.trigger('keydown', { key: 'Enter' })
await $input.trigger('blur')
const $footer = wrapper.find('.dropdown-item.dropdown-footer')
await $footer.trigger('click')
const emitted = wrapper.emitted()
expect(emitted['select-footer']).toBeTruthy()
expect(emitted['select-footer']).toHaveLength(2)
})
it('can autocomplete with keydown', async () => {
const VALUE_TYPED = 'Ang'
await wrapper.setProps({ data: DATA_LIST })
await $input.trigger('focus')
await $input.setValue(VALUE_TYPED)
expect($dropdown.isVisible()).toBeTruthy()
const itemsInDropdowm = findStringsStartingWith(DATA_LIST, VALUE_TYPED)
await $input.trigger('keydown', { key: 'Down' })
await $input.trigger('keydown', { key: 'Enter' })
expect($input.element.value).toBe(itemsInDropdowm[0])
expect($dropdown.isVisible()).toBeFalsy()
await $input.trigger('focus')
await $input.setValue(VALUE_TYPED)
expect($dropdown.isVisible()).toBeTruthy()
await $input.trigger('keydown', { key: 'Down' })
await $input.trigger('keydown', { key: 'Down' })
await $input.trigger('keydown', { key: 'Enter' })
expect($input.element.value).toBe(itemsInDropdowm[1])
expect($dropdown.isVisible()).toBeFalsy()
})
it('close dropdown on esc', async () => {
vi.useFakeTimers()
await wrapper.setProps({ data: DATA_LIST })
await wrapper.setData({ isActive: true })
expect($dropdown.isVisible()).toBeTruthy()
await $input.trigger('keydown', { key: 'Escape' })
expect($dropdown.isVisible()).toBeFalsy()
wrapper.vm.calcDropdownInViewportVertical = vi.fn(
() => wrapper.vm.calcDropdownInViewportVertical
)
vi.runAllTimers()
expect(wrapper.vm.calcDropdownInViewportVertical).toHaveBeenCalled()
vi.useRealTimers()
})
it('close dropdown on click outside', async () => {
await wrapper.setProps({ data: DATA_LIST })
await wrapper.setData({ isActive: true })
expect($dropdown.isVisible()).toBeTruthy()
window.document.body.click()
await wrapper.vm.$nextTick()
expect($dropdown.isVisible()).toBeFalsy()
})
it('open dropdown on down key click', async () => {
wrapper.vm.setHovered = vi.fn(() => wrapper.vm.setHovered)
await wrapper.setProps({
data: DATA_LIST,
dropdownPosition: 'bottom'
})
expect($dropdown.isVisible()).toBeFalsy()
await $input.trigger('focus')
await $input.trigger('keydown.down')
expect($dropdown.isVisible()).toBeTruthy()
})
it('manages tab pressed as expected', async () => {
// hovered is null
await $input.trigger('keydown', { key: 'Tab' })
expect($dropdown.isVisible()).toBeFalsy()
// The first element will be hovered
await wrapper.setProps({
openOnFocus: true,
keepFirst: true
})
await wrapper.setProps({
data: DATA_LIST
})
// Set props in 2 steps to trigger Watch with keepFirst true so the 1st element is hovered
await $input.trigger('focus')
await $input.trigger('keydown', { key: 'Tab' })
expect($input.element.value).toBe(DATA_LIST[0])
})
it('can be used with objects', async () => {
const data = [
{
id: 1,
name: 'Value 1'
},
{
id: 2,
name: 'Value 2'
}
]
await wrapper.setProps({
data,
field: 'name'
})
await wrapper.setData({ isActive: true })
expect($dropdown.isVisible()).toBeTruthy()
await $input.trigger('keydown', { key: 'Enter' })
expect(wrapper.vm.hovered).toBeNull()
await $input.trigger('keydown', { key: 'Down' })
await $input.trigger('keydown', { key: 'Enter' })
expect($input.element.value).toBe(data[0].name)
})
it('clears the value if clearOnSelect is used', async () => {
await wrapper.setProps({
data: DATA_LIST,
clearOnSelect: true
})
await wrapper.setData({ isActive: true })
expect($dropdown.isVisible()).toBeTruthy()
await $input.trigger('keydown', { key: 'Down' })
await $input.trigger('keydown', { key: 'Enter' })
expect($input.element.value).toBe('')
})
it('can be used with a custom data formatter', async () => {
await wrapper.setProps({
data: DATA_LIST,
customFormatter: (val: string) => `${val} formatted`
})
await wrapper.setData({ isActive: true })
expect($dropdown.isVisible()).toBeTruthy()
await $input.trigger('keydown', { key: 'Down' })
await $input.trigger('keydown', { key: 'Enter' })
expect($input.element.value).toBe(`${DATA_LIST[0]} formatted`)
})
it('can openOnFocus and keepFirst', async () => {
await wrapper.setProps({
openOnFocus: true,
keepFirst: true
})
expect($dropdown.isVisible()).toBeFalsy()
await $input.trigger('focus')
expect(wrapper.vm.hovered).toBeNull()
await wrapper.setProps({
data: DATA_LIST
}) // Set props in 2 steps to trigger the Watch for data having keepFirst true
await $input.trigger('focus')
expect($dropdown.isVisible()).toBeTruthy()
expect(wrapper.vm.hovered).toBe(DATA_LIST[0])
})
it('clear button does not exist when the search input is empty', async () => {
await wrapper.setData({ newValue: '' })
await wrapper.setProps({ clearable: true })
const subject = wrapper.find('b-icon-stub').exists()
expect(subject).toBeFalsy()
})
it('clear button exists when the search input is not empty and clearable property is true', async () => {
await wrapper.setData({ newValue: 'Jquery' })
await wrapper.setProps({ clearable: true })
const subject = wrapper.find('b-icon-stub').exists()
expect(subject).toBeTruthy()
})
it('clears search input text when clear button gets clicked', async () => {
await wrapper.setData({ newValue: 'Jquery' })
await wrapper.setProps({ clearable: true })
wrapper.find('b-icon-stub').trigger('click')
const subject = wrapper.vm.newValue
expect(subject).toEqual('')
})
it('clear button does not appear when clearable property is not set to true', async () => {
await wrapper.setData({ newValue: 'Jquery' })
const subject = wrapper.find('b-icon-stub').exists()
expect(subject).toBeFalsy()
})
it('can have a custom clickable right icon', async () => {
await wrapper.setProps({
iconRight: 'delete',
iconRightClickable: true
})
const icon = wrapper.find('b-icon-stub')
expect(icon.exists()).toBeTruthy()
await icon.trigger('click')
expect(wrapper.emitted()['icon-right-click']).toBeTruthy()
})
it('reset events before destroy', () => {
document.removeEventListener = vi.fn()
window.removeEventListener = vi.fn()
wrapper.unmount()
expect(document.removeEventListener).toBeCalledWith('click', expect.any(Function))
expect(window.removeEventListener).toBeCalledWith('resize', expect.any(Function))
})
it('emit active with payload true', async () => {
await wrapper.setProps({
data: DATA_LIST,
openOnFocus: true,
keepFirst: true
})
await $input.trigger('focus')
const { active } = wrapper.emitted()
expect(active).toBeTruthy()
expect(active[0]).toEqual([true])
})
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)', async () => {
const wrapper = shallowMount(BAutocomplete, { attrs })
const root = wrapper.find('div.autocomplete.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.findComponent({ ref: '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 if compatFallthrough is false', async () => {
const wrapper = shallowMount(BAutocomplete, {
attrs,
props: {
compatFallthrough: false
}
})
const input = wrapper.findComponent({ ref: '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.autocomplete.control')
expect(root.classes(attrs.class)).toBe(false)
expect(root.attributes('style')).toBeUndefined()
expect(root.attributes('id')).toBeUndefined()
})
})
})