mirror of
https://github.com/element-plus/element-plus.git
synced 2025-04-12 16:40:36 +08:00
test(select-v2): add some test cases & fix selectLabel/multiple-limit issue (#2626)
This commit is contained in:
parent
7aa732892d
commit
60c13b6f6b
384
packages/select-v2/__tests__/select.spec.ts
Normal file
384
packages/select-v2/__tests__/select.spec.ts
Normal file
@ -0,0 +1,384 @@
|
||||
import { nextTick } from 'vue'
|
||||
import Select from '../src/select.vue'
|
||||
import { makeMountFunc } from '@element-plus/test-utils/make-mount'
|
||||
import { NOOP } from '@vue/shared'
|
||||
|
||||
jest.useFakeTimers()
|
||||
|
||||
const _mount = makeMountFunc({
|
||||
components: {
|
||||
'el-select': Select,
|
||||
},
|
||||
})
|
||||
|
||||
const createData = (count = 1000) => {
|
||||
const initials = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
|
||||
return Array.from({ length: count }).map((_, idx) => ({
|
||||
value: `option_${idx + 1}`,
|
||||
label: `${initials[idx % 10]}${idx}`,
|
||||
}))
|
||||
}
|
||||
|
||||
interface SelectProps {
|
||||
popperClass?: string
|
||||
value?: string | string[]
|
||||
options?: any[]
|
||||
disabled?: boolean
|
||||
clearable?: boolean
|
||||
multiple?: boolean
|
||||
multipleLimit?: number
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface SelectEvents {
|
||||
onChange?: (value?: string) => void
|
||||
onVisibleChange?: (visible?: boolean) => void
|
||||
onRemoveTag?: (tag?: string) => void
|
||||
[key: string]: (...args) => any
|
||||
}
|
||||
|
||||
const createSelect = (options: {
|
||||
data?: () => SelectProps
|
||||
methods?: SelectEvents
|
||||
} = {}) => {
|
||||
return _mount(`
|
||||
<el-select
|
||||
:options="options"
|
||||
:popper-class="popperClass"
|
||||
:disabled="disabled"
|
||||
:clearable="clearable"
|
||||
:multiple="multiple"
|
||||
:multiple-limit="multipleLimit"
|
||||
@change="onChange"
|
||||
@visible-change="onVisibleChange"
|
||||
@remove-tah="onRemoveTag"
|
||||
v-model="value"></el-select>
|
||||
`, {
|
||||
data () {
|
||||
return {
|
||||
options: createData(),
|
||||
value: '',
|
||||
popperClass: '',
|
||||
disabled: false,
|
||||
clearable: false,
|
||||
multiple: false,
|
||||
multipleLimit: 0,
|
||||
...options.data && options.data(),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange: NOOP,
|
||||
onVisibleChange: NOOP,
|
||||
onRemoveTag: NOOP,
|
||||
...options.methods,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function getOptions(): HTMLElement[] {
|
||||
return Array.from(document.querySelectorAll<HTMLElement>(
|
||||
`body > div:last-child .${OPTION_ITEM_CLASS_NAME}`,
|
||||
))
|
||||
}
|
||||
|
||||
const CLASS_NAME = 'el-select-v2'
|
||||
const WRAPPER_CLASS_NAME = 'el-select-v2__wrapper'
|
||||
const OPTION_ITEM_CLASS_NAME = 'el-select-dropdown__option-item'
|
||||
const PLACEHOLDER_CLASS_NAME = 'el-select-v2__placeholder'
|
||||
const DEFAULT_PLACEHOLDER = 'Select'
|
||||
|
||||
describe('Select', () => {
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
it('create', async () => {
|
||||
const wrapper = createSelect()
|
||||
await nextTick
|
||||
expect(wrapper.classes()).toContain(CLASS_NAME)
|
||||
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toContain(DEFAULT_PLACEHOLDER)
|
||||
const select = wrapper.findComponent(Select)
|
||||
await wrapper.trigger('click')
|
||||
expect((select.vm as any).expanded).toBeTruthy()
|
||||
})
|
||||
|
||||
it('options rendered correctly', async() => {
|
||||
const wrapper = createSelect()
|
||||
await nextTick
|
||||
const vm = wrapper.vm as any
|
||||
const options = document.getElementsByClassName(OPTION_ITEM_CLASS_NAME)
|
||||
const result = [].every.call(options, (option, index) => {
|
||||
const text = option.textContent
|
||||
return text === vm.options[index].label
|
||||
})
|
||||
expect(result).toBeTruthy()
|
||||
})
|
||||
|
||||
it('custom dropdown class', async() => {
|
||||
createSelect({
|
||||
data: () => ({
|
||||
popperClass: 'custom-dropdown',
|
||||
}),
|
||||
})
|
||||
await nextTick
|
||||
expect(document.querySelector('.el-popper').classList).toContain('custom-dropdown')
|
||||
})
|
||||
|
||||
it('default value', async () => {
|
||||
const wrapper = createSelect({
|
||||
data: () => ({
|
||||
value: '2',
|
||||
options: [
|
||||
{
|
||||
value: '1',
|
||||
label: 'option_a',
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: 'option_b',
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: 'option_c',
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
const vm = wrapper.vm as any
|
||||
await nextTick
|
||||
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe(vm.options[1].label)
|
||||
})
|
||||
|
||||
it('default value is null or undefined', async () => {
|
||||
const wrapper = createSelect()
|
||||
const vm = wrapper.vm as any
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
vm.value = vm.options[2].value
|
||||
await nextTick
|
||||
expect(placeholder.text()).toBe(vm.options[2].label)
|
||||
vm.value = null
|
||||
await nextTick
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
})
|
||||
|
||||
it('sync set value and options', async () => {
|
||||
const wrapper = createSelect()
|
||||
await nextTick
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
const vm = wrapper.vm as any
|
||||
vm.value = vm.options[1].value
|
||||
await nextTick
|
||||
expect(placeholder.text()).toBe(vm.options[1].label)
|
||||
vm.options[1].label = 'option bb aa'
|
||||
await nextTick
|
||||
expect(placeholder.text()).toBe('option bb aa')
|
||||
})
|
||||
|
||||
it('single select', async () => {
|
||||
const wrapper = createSelect({
|
||||
data() {
|
||||
return {
|
||||
count: 0,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange() {
|
||||
this.count++
|
||||
},
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
const options = getOptions()
|
||||
const vm = wrapper.vm as any
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
expect(vm.value).toBe('')
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
options[2].click()
|
||||
await nextTick
|
||||
expect(vm.value).toBe(vm.options[2].value)
|
||||
expect(placeholder.text()).toBe(vm.options[2].label)
|
||||
options[4].click()
|
||||
await nextTick
|
||||
expect(vm.value).toBe(vm.options[4].value)
|
||||
expect(placeholder.text()).toBe(vm.options[4].label)
|
||||
expect(vm.count).toBe(2)
|
||||
})
|
||||
|
||||
it('disabled option', async () => {
|
||||
const wrapper = createSelect({
|
||||
data: () => {
|
||||
return {
|
||||
options: [
|
||||
{
|
||||
value: '1',
|
||||
label: 'option 1',
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: 'option 2',
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: 'option 3',
|
||||
disabled: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
const vm = wrapper.vm as any
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
const option = document.querySelector<HTMLElement>(`.el-select-dropdown__option-item.is-disabled`)
|
||||
expect(option.textContent).toBe(vm.options[1].label)
|
||||
option.click()
|
||||
await nextTick
|
||||
expect(vm.value).toBe('')
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
vm.options[2].disabled = true
|
||||
await nextTick
|
||||
const options = document.querySelectorAll<HTMLElement>(`.el-select-dropdown__option-item.is-disabled`)
|
||||
expect(options.length).toBe(2)
|
||||
expect(options.item(1).textContent).toBe(vm.options[2].label)
|
||||
options.item(1).click()
|
||||
await nextTick
|
||||
expect(vm.value).toBe('')
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
})
|
||||
|
||||
it('disabled select', async () => {
|
||||
const wrapper = createSelect({
|
||||
data: () => {
|
||||
return {
|
||||
disabled: true,
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
expect(wrapper.find(`.${WRAPPER_CLASS_NAME}`).classes()).toContain('is-disabled')
|
||||
})
|
||||
|
||||
it('visible event', async () => {
|
||||
const wrapper = createSelect({
|
||||
data: () => {
|
||||
return {
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onVisibleChange(visible) {
|
||||
this.visible = visible
|
||||
},
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
const vm = wrapper.vm as any
|
||||
await wrapper.trigger('click')
|
||||
expect(vm.visible).toBeTruthy()
|
||||
})
|
||||
|
||||
it('clearable', async () => {
|
||||
const wrapper = createSelect({
|
||||
data: () => ({ clearable: true }),
|
||||
})
|
||||
const vm = wrapper.vm as any
|
||||
vm.value = vm.options[1].value
|
||||
await nextTick
|
||||
const select = wrapper.findComponent(Select)
|
||||
const selectVm = select.vm as any
|
||||
selectVm.states.comboBoxHovering = true
|
||||
await nextTick
|
||||
const clearBtn = wrapper.find(`.${selectVm.clearIcon}`)
|
||||
expect(clearBtn.exists()).toBeTruthy()
|
||||
await clearBtn.trigger('click')
|
||||
expect(vm.value).toBe('')
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
})
|
||||
|
||||
describe('multiple', () => {
|
||||
|
||||
it('multiple select', async () => {
|
||||
const wrapper = createSelect({
|
||||
data: () => {
|
||||
return {
|
||||
multiple: true,
|
||||
value: [],
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
const vm = wrapper.vm as any
|
||||
const options = getOptions()
|
||||
options[1].click()
|
||||
await nextTick
|
||||
expect(vm.value.length).toBe(1)
|
||||
expect(vm.value[0]).toBe(vm.options[1].value)
|
||||
options[3].click()
|
||||
await nextTick
|
||||
expect(vm.value.length).toBe(2)
|
||||
expect(vm.value[1]).toBe(vm.options[3].value)
|
||||
const tagIcon = wrapper.find('.el-tag__close')
|
||||
await tagIcon.trigger('click')
|
||||
expect(vm.value.length).toBe(1)
|
||||
})
|
||||
|
||||
it('remove-tag', async () => {
|
||||
const wrapper = createSelect({
|
||||
data() {
|
||||
return {
|
||||
removeTag: '',
|
||||
multiple: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onRemoveTag(tag) {
|
||||
this.removeTag = tag
|
||||
},
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
const vm = wrapper.vm as any
|
||||
const options = getOptions()
|
||||
options[0].click()
|
||||
await nextTick()
|
||||
options[1].click()
|
||||
await nextTick()
|
||||
options[2].click()
|
||||
await nextTick()
|
||||
expect(vm.value.length).toBe(3)
|
||||
const tagCloseIcons = wrapper.findAll('.el-tag__close')
|
||||
await tagCloseIcons[1].trigger('click')
|
||||
expect(vm.value.length).toBe(2)
|
||||
await tagCloseIcons[0].trigger('click')
|
||||
expect(vm.value.length).toBe(1)
|
||||
})
|
||||
|
||||
it('limit', async () => {
|
||||
const wrapper = createSelect({
|
||||
data() {
|
||||
return {
|
||||
multiple: true,
|
||||
multipleLimit: 2,
|
||||
value: [],
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
const vm = wrapper.vm as any
|
||||
const options = getOptions()
|
||||
options[1].click()
|
||||
await nextTick
|
||||
options[2].click()
|
||||
await nextTick
|
||||
expect(vm.value.length).toBe(2)
|
||||
options[3].click()
|
||||
await nextTick
|
||||
expect(vm.value.length).toBe(2)
|
||||
})
|
||||
})
|
||||
})
|
@ -2,6 +2,7 @@ import { isValidComponentSize } from '@element-plus/utils/validators'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
import type { OptionType } from './select.types'
|
||||
import { t } from '@element-plus/locale'
|
||||
|
||||
export const SelectProps = {
|
||||
allowCreate: Boolean,
|
||||
@ -53,6 +54,7 @@ export const SelectProps = {
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: t('el.select.placeholder'),
|
||||
},
|
||||
popperAppendToBody: {
|
||||
type: Boolean,
|
||||
|
@ -86,7 +86,7 @@ export default defineComponent({
|
||||
return disabled
|
||||
|| (!selected
|
||||
&& (multiple
|
||||
? multipleLimit > 0 && modelValue.length < multipleLimit
|
||||
? multipleLimit > 0 && modelValue.length >= multipleLimit
|
||||
: false))
|
||||
}
|
||||
|
||||
|
@ -612,10 +612,13 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
} else {
|
||||
if (props.modelValue) {
|
||||
const selectedItem = filteredOptions.value.find(o => o.value === props.modelValue)
|
||||
|
||||
if (selectedItem) {
|
||||
states.selectedLabel = selectedItem.label
|
||||
} else {
|
||||
states.selectedLabel = ''
|
||||
}
|
||||
} else {
|
||||
states.selectedLabel = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -636,6 +639,8 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
|
||||
watch([() => props.modelValue, () => props.options], () => {
|
||||
initStates()
|
||||
}, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
@ -692,6 +697,7 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
handleInputBoxClick,
|
||||
handleMenuEnter,
|
||||
toggleMenu,
|
||||
scrollTo: scrollToItem,
|
||||
onCompositionUpdate,
|
||||
onInput,
|
||||
onKeyboardNavigate,
|
||||
|
@ -5,4 +5,22 @@ const makeMount = <C,O, E>(element: C, defaultOptions: O) => {
|
||||
return (props: (E | O) | (E & O)= {} as E) => mount(element, merge({}, defaultOptions, props))
|
||||
}
|
||||
|
||||
interface Options {
|
||||
data?: () => {
|
||||
[key: string]: any
|
||||
}
|
||||
methods?: {
|
||||
[key: string]: (...args) => any
|
||||
}
|
||||
}
|
||||
|
||||
export const makeMountFunc = defaultOptions => {
|
||||
return (template: string, options: Options) => {
|
||||
return mount({
|
||||
...merge({}, defaultOptions, options),
|
||||
template,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default makeMount
|
||||
|
Loading…
x
Reference in New Issue
Block a user