vuetify
Version:
Vue Material Component Framework
531 lines (396 loc) • 13.5 kB
text/typescript
// Components
import VAutocomplete from '../VAutocomplete'
// Utilities
import {
mount,
Wrapper,
} from '@vue/test-utils'
import { keyCodes } from '../../../util/helpers'
describe('VAutocomplete.ts', () => {
type Instance = InstanceType<typeof VAutocomplete>
let mountFunction: (options?: object) => Wrapper<Instance>
beforeEach(() => {
document.body.setAttribute('data-app', 'true')
mountFunction = (options = {}) => {
return mount(VAutocomplete, {
...options,
// https://github.com/vuejs/vue-test-utils/issues/1130
sync: false,
mocks: {
$vuetify: {
lang: {
t: (val: string) => val,
},
theme: {
dark: false,
},
},
},
})
}
})
it('should have explicit tabindex passed through when autocomplete', () => {
const wrapper = mountFunction({
attrs: {
tabindex: 10,
},
})
expect(wrapper.vm.$refs.input.tabIndex).toBe(10)
expect(wrapper.vm.$el.tabIndex).toBe(-1)
})
it('should emit search input changes', async () => {
const wrapper = mountFunction({
propsData: {
},
})
const input = wrapper.find('input')
const element = input.element as HTMLInputElement
const update = jest.fn()
wrapper.vm.$on('update:search-input', update)
element.value = 'test'
input.trigger('input')
await wrapper.vm.$nextTick()
expect(update).toHaveBeenCalledWith('test')
})
it('should filter autocomplete search results', async () => {
const wrapper = mountFunction({
propsData: { items: ['foo', 'bar'] },
})
wrapper.setData({ internalSearch: 'foo' })
expect(wrapper.vm.filteredItems).toHaveLength(1)
expect(wrapper.vm.filteredItems[0]).toBe('foo')
})
it('should filter numeric primitives', () => {
const wrapper = mountFunction({
propsData: {
items: [1, 2],
},
})
wrapper.setData({ internalSearch: 1 })
expect(wrapper.vm.filteredItems).toHaveLength(1)
expect(wrapper.vm.filteredItems[0]).toBe(1)
})
it('should activate when search changes and not active', async () => {
const wrapper = mountFunction({
propsData: {
items: [1, 2, 3, 4],
multiple: true,
},
})
wrapper.vm.isMenuActive = true
await wrapper.vm.$nextTick()
wrapper.setData({ internalSearch: 2 })
await wrapper.vm.$nextTick()
expect(wrapper.vm.isMenuActive).toBe(true)
})
// TODO: this fails without sync, nextTick doesn't help
// https://github.com/vuejs/vue-test-utils/issues/1130
it.skip('should set searchValue to null when deactivated', async () => {
const wrapper = mountFunction({
propsData: {
items: [1, 2, 3, 4],
multiple: true,
},
})
await wrapper.vm.$nextTick()
const input = wrapper.find('input')
const element = input.element as HTMLInputElement
input.trigger('focus')
element.value = '2'
input.trigger('input')
expect(wrapper.vm.internalSearch).toBe('2')
wrapper.setProps({
multiple: false,
value: 1,
})
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalSearch).toBe(1)
input.trigger('focus')
element.value = '3'
input.trigger('input')
input.trigger('blur')
expect(wrapper.vm.internalSearch).toBe('3')
})
it('should render role=combobox correctly when autocomplete', async () => {
const wrapper = mountFunction()
expect(wrapper.vm.$el.getAttribute('role')).toBeFalsy()
const input = wrapper.find('.v-input__slot')
expect(input.element.getAttribute('role')).toBe('combobox')
})
it('should not duplicate items after items update when caching is turned on', async () => {
const wrapper = mountFunction({
propsData: {
cacheItems: true,
returnObject: true,
itemText: 'text',
itemValue: 'id',
items: [],
},
})
wrapper.setProps({ items: [{ id: 1, text: 'A' }] })
expect(wrapper.vm.computedItems).toHaveLength(1)
wrapper.setProps({ items: [{ id: 1, text: 'A' }] })
expect(wrapper.vm.computedItems).toHaveLength(1)
})
it('should cache items passed via prop', async () => {
const wrapper = mountFunction({
propsData: {
cacheItems: true,
items: [1, 2, 3, 4],
},
})
expect(wrapper.vm.computedItems).toHaveLength(4)
wrapper.setProps({ items: [5] })
expect(wrapper.vm.computedItems).toHaveLength(5)
})
it('should not filter text with no items', async () => {
const wrapper = mountFunction({
propsData: {
eager: true,
items: ['foo', 'bar'],
},
})
await wrapper.vm.$nextTick()
wrapper.setProps({ searchInput: 'asdf' })
// Wait for watcher
await wrapper.vm.$nextTick()
const tile = wrapper.find('.v-list-item__title')
expect(tile.text()).toBe('$vuetify.noDataText')
})
it('should not display menu when tab focused', async () => {
const wrapper = mountFunction({
propsData: {
items: [1, 2],
value: 1,
},
})
const input = wrapper.find('input')
input.trigger('focus')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isMenuActive).toBe(false)
})
// TODO: this fails without sync, nextTick doesn't help
// https://github.com/vuejs/vue-test-utils/issues/1130
// eslint-disable-next-line max-statements
it.skip('should change selected index', async () => {
const wrapper = mountFunction({
attachToDocument: true,
propsData: {
items: ['foo', 'bar', 'fizz'],
multiple: true,
value: ['foo', 'bar', 'fizz'],
},
})
await wrapper.vm.$nextTick()
expect(wrapper.vm.selectedIndex).toBe(-1)
expect(wrapper.vm.selectedItems).toHaveLength(3)
// Right arrow
wrapper.vm.changeSelectedIndex(keyCodes.right)
expect(wrapper.vm.selectedIndex).toBe(0)
wrapper.vm.changeSelectedIndex(keyCodes.right)
expect(wrapper.vm.selectedIndex).toBe(1)
wrapper.vm.changeSelectedIndex(keyCodes.right)
expect(wrapper.vm.selectedIndex).toBe(2)
// Left arrow
wrapper.vm.changeSelectedIndex(keyCodes.left)
expect(wrapper.vm.selectedIndex).toBe(1)
wrapper.vm.changeSelectedIndex(keyCodes.left)
expect(wrapper.vm.selectedIndex).toBe(0)
wrapper.vm.changeSelectedIndex(keyCodes.left)
expect(wrapper.vm.selectedIndex).toBe(-1)
wrapper.vm.changeSelectedIndex(keyCodes.left)
expect(wrapper.vm.selectedIndex).toBe(2)
wrapper.vm.changeSelectedIndex(keyCodes.left)
expect(wrapper.vm.selectedIndex).toBe(1)
// Delete key
wrapper.vm.changeSelectedIndex(keyCodes.backspace)
await wrapper.vm.$nextTick()
expect(wrapper.vm.selectedIndex).toBe(1)
wrapper.vm.changeSelectedIndex(keyCodes.left)
expect(wrapper.vm.selectedIndex).toBe(0)
wrapper.vm.changeSelectedIndex(keyCodes.backspace)
await wrapper.vm.$nextTick()
expect(wrapper.vm.selectedIndex).toBe(0)
// Should not change index if search is dirty
wrapper.setProps({ searchInput: 'foo' })
wrapper.vm.changeSelectedIndex(keyCodes.backspace)
expect(wrapper.vm.selectedIndex).toBe(0)
expect(wrapper.vm.selectedItems).toHaveLength(1)
wrapper.setProps({ searchInput: undefined })
// Should not proceed if keyCode doesn't match
wrapper.vm.changeSelectedIndex(99)
await wrapper.vm.$nextTick()
expect(wrapper.vm.selectedIndex).toBe(0)
expect(wrapper.vm.selectedItems).toHaveLength(1)
wrapper.vm.changeSelectedIndex(keyCodes.backspace)
await wrapper.vm.$nextTick()
expect(wrapper.vm.selectedItems).toHaveLength(0)
expect(wrapper.vm.selectedIndex).toBe(-1)
// Should not change/error if called with no selection
wrapper.vm.changeSelectedIndex(keyCodes.backspace)
await wrapper.vm.$nextTick()
expect(wrapper.vm.selectedIndex).toBe(-1)
wrapper.setProps({ value: ['foo', 'bar', 'fizz'] })
await wrapper.vm.$nextTick()
expect(wrapper.vm.selectedItems).toHaveLength(3)
wrapper.vm.selectedIndex = 2
// Simulating removing items when an index already selected
wrapper.setProps({ value: ['foo', 'bar'] })
await wrapper.vm.$nextTick()
expect(wrapper.vm.selectedIndex).toBe(2)
// Backspace
wrapper.vm.changeSelectedIndex(keyCodes.delete)
expect(wrapper.vm.selectedIndex).toBe(-1)
})
it('should conditionally show the menu', async () => {
const wrapper = mountFunction({
attachToDocument: true,
propsData: {
items: ['foo', 'bar', 'fizz'],
},
})
const slot = wrapper.find('.v-input__slot')
const input = wrapper.find('input')
// Focus input should only focus
input.trigger('focus')
expect(wrapper.vm.isFocused).toBe(true)
expect(wrapper.vm.menuCanShow).toBe(true)
expect(wrapper.vm.isMenuActive).toBe(false)
// Clicking input should open menu
slot.trigger('click')
expect(wrapper.vm.isMenuActive).toBe(true)
expect(wrapper.vm.menuCanShow).toBe(true)
wrapper.setProps({ searchInput: 'foo' })
expect(wrapper.vm.isMenuActive).toBe(true)
expect(wrapper.vm.menuCanShow).toBe(true)
// Should close menu but keep focus
input.trigger('keydown.esc')
expect(wrapper.vm.isFocused).toBe(true)
expect(wrapper.vm.isMenuActive).toBe(false)
expect(wrapper.vm.menuCanShow).toBe(true)
// TODO: Add expects for tags when impl
})
// TODO: this fails without sync, nextTick doesn't help
// https://github.com/vuejs/vue-test-utils/issues/1130
it.skip('should have the correct selected item', async () => {
const wrapper = mountFunction({
propsData: {
items: ['foo', 'bar', 'fizz'],
multiple: true,
value: ['foo'],
},
})
expect(wrapper.vm.selectedItem).toBeNull()
wrapper.setProps({
multiple: false,
value: 'foo',
})
expect(wrapper.vm.selectedItem).toBe('foo')
})
it('should reset lazySearch', async () => {
const wrapper = mountFunction({
propsData: {
chips: true,
items: ['foo', 'bar', 'fizz'],
searchInput: 'foo',
},
})
expect(wrapper.vm.lazySearch).toBe('foo')
expect(wrapper.vm.hasSlot).toBe(true)
wrapper.setData({ isMenuActive: true })
await wrapper.vm.$nextTick()
wrapper.setData({ isMenuActive: false })
await wrapper.vm.$nextTick()
expect(wrapper.vm.lazySearch).toBeUndefined()
})
it('should select input text on focus', async () => {
const wrapper = mountFunction()
const select = jest.fn()
wrapper.vm.$refs.input.select = select
const input = wrapper.find('input')
input.trigger('focus')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isFocused).toBe(true)
expect(select).toHaveBeenCalledTimes(1)
input.trigger('keydown.tab')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isFocused).toBe(false)
expect(select).toHaveBeenCalledTimes(1)
})
it('should not respond to click', () => {
const onFocus = jest.fn()
const wrapper = mountFunction({
propsData: { disabled: true },
methods: { onFocus },
})
const slot = wrapper.find('.v-input__slot')
slot.trigger('click')
expect(onFocus).not.toHaveBeenCalled()
wrapper.setProps({ disabled: false, readonly: true })
slot.trigger('click')
expect(onFocus).not.toHaveBeenCalled()
wrapper.setProps({ readonly: false })
slot.trigger('click')
expect(onFocus).toHaveBeenCalled()
})
it('should react to keydown', () => {
const activateMenu = jest.fn()
const changeSelectedIndex = jest.fn()
const onEscDown = jest.fn()
const onTabDown = jest.fn()
const wrapper = mountFunction({
methods: {
activateMenu,
changeSelectedIndex,
onEscDown,
onTabDown,
},
})
const input = wrapper.find('input')
const element = input.element as HTMLInputElement
expect(wrapper.vm.isMenuActive).toBe(false)
input.trigger('keydown.enter')
input.trigger('keydown.space')
input.trigger('keydown.up')
input.trigger('keydown.down')
expect(activateMenu).toHaveBeenCalledTimes(4)
input.trigger('keydown.esc')
expect(onEscDown).toHaveBeenCalledTimes(1)
input.trigger('keydown.tab')
expect(onTabDown).toHaveBeenCalledTimes(1)
// Skip menu activation
wrapper.setData({ isMenuActive: true })
element.value = 'foo'
input.trigger('input')
wrapper.setProps({ hideSelected: true })
expect(wrapper.vm.genSelections()).toEqual([])
})
it('should change autocomplete attribute', () => {
const wrapper = mountFunction({
attrs: {
autocomplete: 'on',
},
})
expect(wrapper.vm.$attrs.autocomplete).toBe('on')
})
it('should not delete item if readonly', async () => {
const wrapper = mountFunction({
propsData: {
items: ['a', 'b', 'c'],
multiple: true,
value: ['a', 'b', 'c'],
},
})
wrapper.vm.changeSelectedIndex(keyCodes.right)
wrapper.vm.changeSelectedIndex(keyCodes.right)
wrapper.vm.changeSelectedIndex(keyCodes.backspace)
await wrapper.vm.$nextTick()
expect(wrapper.vm.selectedItems).toHaveLength(2)
wrapper.setProps({
readonly: true,
})
wrapper.vm.changeSelectedIndex(keyCodes.backspace)
await wrapper.vm.$nextTick()
expect(wrapper.vm.selectedItems).toHaveLength(2)
})
})