vuetify
Version:
Vue Material Component Framework
438 lines (336 loc) • 11.6 kB
text/typescript
// Components
import VSelect from '../VSelect'
// Utilities
import { waitAnimationFrame } from '../../../../test'
import {
mount,
Wrapper,
} from '@vue/test-utils'
describe('VSelect.ts', () => {
type Instance = InstanceType<typeof VSelect>
let mountFunction: (options?: object) => Wrapper<Instance>
let el
(global as any).performance = {
now: () => {},
}
beforeEach(() => {
mountFunction = (options = {}) => {
el = document.createElement('div')
el.setAttribute('data-app', 'true')
document.body.appendChild(el)
return mount(VSelect, {
// https://github.com/vuejs/vue-test-utils/issues/1130
sync: false,
...options,
mocks: {
$vuetify: {
lang: {
t: (val: string) => val,
},
theme: {
dark: false,
},
},
},
})
}
})
afterEach(() => {
document.body.removeChild(el)
})
// https://github.com/vuetifyjs/vuetify/issues/4359
// Vue modifies the `on` property of the
// computed `listData` — easiest way to fix
it('should select value when using a scoped slot', async () => {
const wrapper = mountFunction({
propsData: {
items: ['foo', 'bar'],
},
slots: {
'no-data': {
render: h => h('div', 'No Data'),
},
},
})
// Will be undefined if fails
expect(wrapper.vm.listData.on).toBeTruthy()
})
// https://github.com/vuetifyjs/vuetify/issues/4431
it('should accept null and "" as values', async () => {
const wrapper = mountFunction({
propsData: {
clearable: true,
items: [
{ text: 'Foo', value: null },
{ text: 'Bar', value: 'bar' },
],
value: null,
},
})
const icon = wrapper.find('.v-input__append-inner .v-icon')
expect(wrapper.vm.selectedItems).toHaveLength(1)
expect(wrapper.vm.isDirty).toBe(true)
icon.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.selectedItems).toHaveLength(0)
expect(wrapper.vm.isDirty).toBe(false)
expect(wrapper.vm.internalValue).toBeUndefined()
})
it('should only calls change once when clearing', async () => {
const wrapper = mountFunction({
propsData: {
clearable: true,
items: ['foo'],
value: 'foo',
},
})
const change = jest.fn()
wrapper.vm.$on('change', change)
const icon = wrapper.find('.v-input__icon > .v-icon')
icon.trigger('click')
await wrapper.vm.$nextTick()
expect(change).toHaveBeenCalledTimes(1)
})
it('should not call change when model updated externally', async () => {
const change = jest.fn()
const wrapper = mountFunction()
wrapper.vm.$on('change', change)
wrapper.setProps({ value: 'bar' })
expect(change).not.toHaveBeenCalled()
wrapper.vm.setValue('foo')
expect(change).toHaveBeenCalledWith('foo')
expect(change).toHaveBeenCalledTimes(1)
})
// https://github.com/vuetifyjs/vuetify/issues/4713
it('should nudge select menu', () => {
const wrapper = mountFunction({
propsData: {
menuProps: {
nudgeTop: 5,
nudgeRight: 5,
nudgeBottom: 5,
nudgeLeft: 5,
},
},
})
const menu = wrapper.vm.$refs.menu
expect(menu.nudgeTop).toBe(5)
expect(menu.nudgeRight).toBe(5)
expect(menu.nudgeBottom).toBe(5)
expect(menu.nudgeLeft).toBe(5)
})
// https://github.com/vuetifyjs/vuetify/issues/5774
it('should close menu on tab down when no selectedIndex', async () => {
const wrapper = mountFunction({
propsData: {
items: ['foo', 'bar'],
},
})
const menu = wrapper.find('.v-input__slot')
const input = wrapper.find('input')
menu.trigger('click')
expect(wrapper.vm.isFocused).toBe(true)
expect(wrapper.vm.isMenuActive).toBe(true)
input.trigger('keydown.tab')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isFocused).toBe(false)
expect(wrapper.vm.isMenuActive).toBe(false)
})
// https://github.com/vuetifyjs/vuetify/issues/4853
it('should select item after typing its first few letters', async () => {
const wrapper = mountFunction({
propsData: {
items: ['aaa', 'foo', 'faa'],
},
})
const input = wrapper.find('input')
input.trigger('focus')
await wrapper.vm.$nextTick()
input.trigger('keypress', { key: 'f' })
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalValue).toEqual('foo')
input.trigger('keypress', { key: 'a' })
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalValue).toEqual('faa')
})
// https://github.com/vuetifyjs/vuetify/issues/10406
it('should load more items when typing', async () => {
const wrapper = mountFunction({
propsData: {
items: Array.from({ length: 24 }, (_, i) => 'Item ' + i).concat('foo'),
},
})
const input = wrapper.find('input')
input.trigger('focus')
await wrapper.vm.$nextTick()
expect(wrapper.vm.virtualizedItems).toHaveLength(20)
input.trigger('keypress', { key: 'f' })
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalValue).toEqual('foo')
expect(wrapper.vm.virtualizedItems).toHaveLength(25)
})
// TODO: this fails without sync, nextTick doesn't help
// https://github.com/vuejs/vue-test-utils/issues/1130
it.skip('should have the correct a11y attributes', async () => {
const wrapper = mountFunction({
propsData: {
eager: true,
items: ['Foo', 'Bar', 'Fizz', 'Buzz'],
value: 'Foo',
},
})
await wrapper.vm.$nextTick()
const inputSlot = wrapper.find('.v-input__slot')
expect(inputSlot.element.getAttribute('role')).toBe('button')
expect(inputSlot.element.getAttribute('aria-haspopup')).toBe('listbox')
expect(inputSlot.element.getAttribute('aria-expanded')).toBe('false')
expect(inputSlot.element.getAttribute('aria-owns')).toBe(wrapper.vm.computedOwns)
const list = wrapper.find('.v-select-list')
let items = list.findAll('.v-list-item')
expect(list.element.children[0].getAttribute('role')).toBe('listbox')
expect(list.element.children[0].id).toBe(wrapper.vm.computedOwns)
expect(items.at(0).element.getAttribute('role')).toBe('option')
expect(items.at(0).element.getAttribute('aria-selected')).toBe('true')
expect(items.at(1).element.getAttribute('aria-selected')).toBe('false')
wrapper.setProps({ value: 'Bar' })
items = list.findAll('.v-list-item')
expect(items.at(0).element.getAttribute('aria-selected')).toBe('false')
expect(items.at(1).element.getAttribute('aria-selected')).toBe('true')
const item = items.at(0)
const generatedId = item.find('.v-list-item__title').element.id
expect(generatedId).toMatch(/^foo-list-item-\d+$/)
expect(item.element.getAttribute('aria-labelledby')).toBe(generatedId)
})
// TODO: this fails without sync, nextTick doesn't help
// https://github.com/vuejs/vue-test-utils/issues/1130
it.skip('should not reset menu index when hide-on-selected is used', async () => {
const wrapper = mountFunction({
propsData: {
items: ['Foo', 'Bar', 'Fizz', 'Buzz'],
},
})
const input = wrapper.find('input')
input.trigger('click')
await wrapper.vm.$nextTick()
input.trigger('keydown.down')
expect(wrapper.vm.$refs.menu.listIndex).toBe(0)
input.trigger('keydown.enter')
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalValue).toBe('Foo')
expect(wrapper.vm.$refs.menu.listIndex).toBe(0)
wrapper.setProps({ value: null, hideSelected: true })
input.trigger('keydown.enter')
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalValue).toBe('Foo')
expect(wrapper.vm.$refs.menu.listIndex).toBe(-1)
})
it('should not change value when typing on readonly field', async () => {
const wrapper = mountFunction({
propsData: {
items: ['Foo', 'Bar', 'Fizz', 'Buzz'],
readonly: true,
value: 'Foo',
},
})
const input = wrapper.find('input')
input.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalValue).toBe('Foo')
input.trigger('keypress', { key: 'b' })
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalValue).toBe('Foo')
input.trigger('keydown.up')
// Wait for keydown event to propagate
await wrapper.vm.$nextTick()
// Waiting for items to be rendered
await waitAnimationFrame()
expect(wrapper.vm.internalValue).toBe('Foo')
})
it('should emit listIndex event when navigated by keyboard', async () => {
const wrapper = mountFunction({
propsData: {
items: ['foo', 'bar'],
},
})
const listIndexUpdate = jest.fn()
wrapper.vm.$on('update:list-index', listIndexUpdate)
const input = wrapper.find('input')
const slot = wrapper.find('.v-input__slot')
slot.trigger('click')
input.trigger('keydown.down')
await wrapper.vm.$nextTick()
expect(listIndexUpdate).toHaveBeenCalledWith(0)
input.trigger('keydown.down')
await wrapper.vm.$nextTick()
expect(listIndexUpdate).toHaveBeenCalledWith(1)
})
it('should close menu when append icon is clicked', async () => {
const wrapper = mountFunction({
propsData: {
items: ['foo', 'bar'],
},
})
const append = wrapper.find('.v-input__append-inner')
const slot = wrapper.find('.v-input__slot')
slot.trigger('click')
expect(wrapper.vm.isMenuActive).toBe(true)
append.trigger('mousedown')
append.trigger('mouseup')
append.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isMenuActive).toBe(false)
})
it('should open menu when append icon is clicked', async () => {
const wrapper = mountFunction({
propsData: {
items: ['foo', 'bar'],
},
})
const append = wrapper.find('.v-input__append-inner')
append.trigger('mousedown')
append.trigger('mouseup')
append.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isMenuActive).toBe(true)
})
// https://github.com/vuetifyjs/vuetify/issues/9960
it('should not manipulate menu state if is readonly or disabled', async () => {
const wrapper = mountFunction({
data: () => ({ hasMouseDown: true }),
propsData: { readonly: true },
})
const icon = wrapper.find('.v-input__append-inner')
icon.trigger('mousedown')
icon.trigger('mouseup')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isMenuActive).toBe(false)
wrapper.setProps({
disabled: true,
readonly: undefined,
})
icon.trigger('mousedown')
icon.trigger('mouseup')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isMenuActive).toBe(false)
wrapper.setProps({ disabled: undefined })
icon.trigger('mousedown')
icon.trigger('mouseup')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isMenuActive).toBe(true)
})
it('should emit click event', async () => {
const item = { value: 'hello', text: 'Hello' }
const wrapper = mountFunction({
propsData: {
value: 'hello',
items: [item],
},
})
const click = jest.fn()
wrapper.vm.$on('click', click)
const select = wrapper.find('.v-input__slot')
select.trigger('click')
await wrapper.vm.$nextTick()
expect(click).toHaveBeenCalledTimes(1)
})
})