vuetify
Version:
Vue Material Component Framework
460 lines (374 loc) • 12 kB
text/typescript
// Libraries
import Vue from 'vue'
// Components
import VSelect from '../VSelect'
import {
VListItem,
VListItemTitle,
VListItemContent,
} from '../../VList'
// Utilities
import {
mount,
Wrapper,
} from '@vue/test-utils'
// eslint-disable-next-line max-statements
describe('VSelect.ts', () => {
type Instance = InstanceType<typeof VSelect>
let mountFunction: (options?: object) => Wrapper<Instance>
let el
beforeEach(() => {
el = document.createElement('div')
el.setAttribute('data-app', 'true')
document.body.appendChild(el)
mountFunction = (options = {}) => {
return mount(VSelect, {
// https://github.com/vuejs/vue-test-utils/issues/1130
sync: false,
mocks: {
$vuetify: {
lang: {
t: (val: string) => val,
},
theme: {
dark: false,
},
},
},
...options,
})
}
})
afterEach(() => {
document.body.removeChild(el)
})
it('should return numeric 0', async () => {
const item = { value: 0, text: '0' }
const wrapper = mountFunction({
propsData: {
value: null,
items: [item],
multiple: true,
},
})
const change = jest.fn()
wrapper.vm.$on('change', change)
wrapper.vm.selectItem(item)
await wrapper.vm.$nextTick()
expect(change).toHaveBeenCalledWith([0])
})
it('should disable list items', () => {
const wrapper = mountFunction({
propsData: {
eager: true,
items: [{
text: 'item',
disabled: true,
}],
},
})
const item = wrapper.find('.v-list-item--disabled')
expect(item.element.tabIndex).toBe(-1)
})
it('should render v-select correctly when using v-list-item in item scope slot', async () => {
const items = Array.from({ length: 2 }, (x, i) => ({ value: i, text: `Text ${i}` }))
const vm = new Vue({
components: {
VListItem,
},
})
const itemSlot = ({ item, attrs, on }) => vm.$createElement('v-list-item', {
on,
...attrs,
class: item.value % 2 === 0 ? '' : 'red lighten-1',
}, [
item.text,
])
const selectionSlot = ({ item }) => vm.$createElement('v-list-item', item.value)
const component = Vue.component('test', {
render (h) {
return h(VSelect, {
props: { items, value: 1 },
scopedSlots: {
item: itemSlot,
selection: selectionSlot,
},
})
},
})
const wrapper = mountFunction(component)
wrapper.vm.$children[0].inputValue = items[0]
await wrapper.vm.$nextTick()
expect(wrapper.html()).toMatchSnapshot()
})
it('should render v-select correctly when not using v-list-item in item scope slot', async () => {
const items = Array.from({ length: 2 }, (x, i) => ({ value: i, text: `Text ${i}` }))
const vm = new Vue({
components: {
VListItemTitle,
VListItemContent,
},
})
const itemSlot = ({ item }) => vm.$createElement('v-list-item-content', {
class: item.value % 2 === 0 ? '' : 'red lighten-1',
}, [
vm.$createElement('v-list-item-title', [item.value]),
])
const component = Vue.component('test', {
render (h) {
return h(VSelect, {
props: { items },
scopedSlots: { item: itemSlot },
})
},
})
const wrapper = mountFunction(component)
wrapper.vm.$children[0].inputValue = items[0]
await wrapper.vm.$nextTick()
expect(wrapper.html()).toMatchSnapshot()
})
it('should render v-select correctly when not using scope slot', async () => {
const items = Array.from({ length: 2 }, (x, i) => ({ value: i, text: `Text ${i}` }))
const component = Vue.component('test', {
render (h) {
return h(VSelect, {
props: { items },
})
},
})
const wrapper = mountFunction(component)
wrapper.vm.$children[0].inputValue = items[0]
await wrapper.vm.$nextTick()
expect(wrapper.html()).toMatchSnapshot()
})
it('should not close menu when using multiple prop', async () => {
const wrapper = mountFunction({
attachToDocument: true,
propsData: {
items: [1, 2, 3, 4],
multiple: true,
},
})
const blur = jest.fn()
wrapper.vm.$on('blur', blur)
const menu = wrapper.find('.v-input__slot')
menu.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isFocused).toBe(true)
expect(wrapper.vm.isMenuActive).toBe(true)
const item = wrapper.find('.v-list-item')
item.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isMenuActive).toBe(true)
})
it('should render aria-hidden=true on arrow icon', async () => {
const wrapper = mountFunction()
const icon = wrapper.find('.v-icon')
expect(icon.attributes('aria-hidden')).toBe('true')
})
// TODO: this fails without sync, nextTick doesn't help
// https://github.com/vuejs/vue-test-utils/issues/1130
it.skip('should only show items if they are in items', async () => {
const wrapper = mountFunction({
propsData: {
value: 'foo',
items: ['foo'],
},
})
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalValue).toEqual('foo')
expect(wrapper.vm.selectedItems).toEqual(['foo'])
expect(wrapper.html()).toMatchSnapshot()
wrapper.setProps({ value: 'bar' })
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalValue).toEqual('bar')
expect(wrapper.vm.selectedItems).toEqual([])
expect(wrapper.html()).toMatchSnapshot()
wrapper.setProps({ items: ['foo', 'bar'] })
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalValue).toEqual('bar')
expect(wrapper.vm.selectedItems).toEqual(['bar'])
expect(wrapper.html()).toMatchSnapshot()
wrapper.setProps({ multiple: true })
await wrapper.vm.$nextTick()
wrapper.setProps({ value: ['foo', 'bar'] })
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalValue).toEqual(['foo', 'bar'])
expect(wrapper.vm.selectedItems).toEqual(['foo', 'bar'])
expect(wrapper.html()).toMatchSnapshot()
})
// TODO: this fails without sync, nextTick doesn't help
// https://github.com/vuejs/vue-test-utils/issues/1130
it.skip('should update the displayed value when items changes', async () => {
const wrapper = mountFunction({
propsData: {
value: 1,
items: [],
},
})
wrapper.setProps({ items: [{ text: 'foo', value: 1 }] })
await wrapper.vm.$nextTick()
expect(wrapper.vm.selectedItems).toContainEqual({ text: 'foo', value: 1 })
})
it('should render select menu with content class', async () => {
const items = ['abc']
const wrapper = mountFunction({
propsData: {
menuProps: { contentClass: 'v-menu-class', eager: true },
items,
},
})
const menu = wrapper.find('.v-menu__content')
expect(menu.element.classList).toContain('v-menu-class')
})
it('should have deletable chips', async () => {
const wrapper = mountFunction({
attachToDocument: true,
propsData: {
chips: true,
deletableChips: true,
items: ['foo', 'bar'],
value: 'foo',
},
})
await wrapper.vm.$nextTick()
const chip = wrapper.find('.v-chip')
expect(!!chip).toBe(true)
})
it('should escape items in menu', async () => {
const wrapper = mountFunction({
propsData: {
eager: true,
items: ['<strong>foo</strong>'],
},
})
const tileTitle = wrapper.find('.v-list-item__title')
expect(tileTitle.html()).toMatchSnapshot()
})
it('should use value comparator', async () => {
const wrapper = mountFunction({
attachToDocument: true,
propsData: {
multiple: true,
items: [
{ text: 'one', value: 1 },
{ text: 'two', value: 2 },
{ text: 'three', value: 3 },
],
itemText: 'text',
itemValue: 'value',
valueComparator: (a, b) => Math.round(a) === Math.round(b),
value: [3.1],
},
})
expect(wrapper.vm.selectedItems).toHaveLength(1)
expect(wrapper.vm.selectedItems[0].value).toBe(3)
})
it('should not open if readonly', async () => {
const wrapper = mountFunction({
propsData: {
readonly: true,
items: ['foo', 'bar'],
},
})
wrapper.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isMenuActive).toBe(false)
wrapper.find('.v-input__append-inner').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.vm.isMenuActive).toBe(false)
})
it('can use itemValue as function', async () => {
const wrapper = mountFunction({
attachToDocument: true,
propsData: {
multiple: true,
items: [
{ text: 'one', v1: 'prop v1' },
{ text: 'two', v2: 'prop v2' },
{ text: 'three', v1: 'also prop v1' },
],
itemText: 'text',
itemValue: item => item.hasOwnProperty('v1') ? item.v1 : item.v2,
value: ['prop v1', 'prop v2'],
},
})
expect(wrapper.vm.selectedItems).toHaveLength(2)
expect(wrapper.vm.getValue(wrapper.vm.selectedItems[0])).toBe('prop v1')
expect(wrapper.vm.getValue(wrapper.vm.selectedItems[1])).toBe('prop v2')
})
it('should work correctly with return-object', async () => {
const wrapper = mountFunction({
attachToDocument: true,
propsData: {
multiple: false,
returnObject: true,
items: [
{ text: 'one', value: { x: [1, 2], y: ['a', 'b'] } },
{ text: 'two', value: { x: [3, 4], y: ['a', 'b'] } },
{ text: 'three', value: { x: [1, 2], y: ['a', 'c'] } },
],
itemText: 'text',
itemValue: 'value',
value: { text: 'two', value: { x: [3, 4], y: ['a', 'b'] } },
},
})
expect(wrapper.vm.selectedItems).toHaveLength(1)
expect(wrapper.vm.internalValue).toEqual({ text: 'two', value: { x: [3, 4], y: ['a', 'b'] } })
})
it('should work correctly with return-object [multiple]', async () => {
const wrapper = mountFunction({
attachToDocument: true,
propsData: {
multiple: true,
returnObject: true,
items: [
{ text: 'one', value: { x: [1, 2], y: ['a', 'b'] } },
{ text: 'two', value: { x: [3, 4], y: ['a', 'b'] } },
{ text: 'three', value: { x: [1, 2], y: ['a', 'c'] } },
],
itemText: 'text',
itemValue: 'value',
value: [
{ text: 'two', value: { x: [3, 4], y: ['a', 'b'] } },
{ text: 'one', value: { x: [1, 2], y: ['a', 'b'] } },
],
},
})
expect(wrapper.vm.selectedItems).toHaveLength(2)
expect(wrapper.vm.internalValue[0]).toEqual({ text: 'two', value: { x: [3, 4], y: ['a', 'b'] } })
expect(wrapper.vm.internalValue[1]).toEqual({ text: 'one', value: { x: [1, 2], y: ['a', 'b'] } })
})
it('should provide the correct default value', () => {
const wrapper = mountFunction()
expect(wrapper.vm.internalValue).toBeUndefined()
const wrapper2 = mountFunction({
propsData: { multiple: true },
})
expect(wrapper2.vm.internalValue).toEqual([])
})
it('should use slotted no-data', () => {
const wrapper = mountFunction({
propsData: {
eager: true,
items: ['foo'],
},
slots: {
'no-data': [{
render: h => h('div', 'foo'),
}],
},
})
const list = wrapper.find('.v-list')
expect(wrapper.vm.$slots['no-data']).toBeTruthy()
expect(list.html()).toMatchSnapshot()
})
it('should change autocomplete attribute', () => {
const wrapper = mountFunction({
attrs: {
autocomplete: 'on',
},
})
expect(wrapper.vm.$attrs.autocomplete).toBe('on')
})
})