Files
Zachary Bear 75175cb397 feat(components): [dropdown-item] add icon slot (#23300)
* feat(components): [dropdown-item] 支持通过 slot 设置图标

之前只能通过设置 `icon` prop 来设置菜单项的图标,如果要用其他图标库只能在 prop 中写 VNode 函数,大大降低可读性;要么就重复写 <ElIcon> 来进行统一布局。

现在引入了 `icon` slot,会自动将元素传入 ElIcon 的插槽中,这和 ElButton 的图标设计逻辑是一致的:既可以使用 prop 也可以使用 slot,但 slot 优先级较高。

close #22837

* test(components): [dropdown] added test for icon slot

* docs(components): [dropdown] added description for `icon` slot

* Update docs/en-US/component/dropdown.md

Co-authored-by: btea <2356281422@qq.com>

* style(docs): styling document

* fix(components): [dropdown-item] incorrectly rendered el-icon

* test(components): [dropdown-item] check incorrectly rendering

---------

Co-authored-by: btea <2356281422@qq.com>
2026-01-08 21:06:36 +08:00

1127 lines
35 KiB
TypeScript

// @ts-nocheck
import { nextTick } from 'vue'
import { mount } from '@vue/test-utils'
import { afterEach, describe, expect, test, vi } from 'vitest'
import { rAF } from '@element-plus/test-utils/tick'
import { EVENT_CODE } from '@element-plus/constants'
import { ElTooltip } from '@element-plus/components/tooltip'
import Button from '@element-plus/components/button'
import { usePopperContainerId } from '@element-plus/hooks'
import Dropdown from '../src/dropdown.vue'
import DropdownItem from '../src/dropdown-item.vue'
import DropdownMenu from '../src/dropdown-menu.vue'
const MOUSE_ENTER_EVENT = 'mouseenter'
const MOUSE_LEAVE_EVENT = 'mouseleave'
const CONTEXTMENU = 'contextmenu'
const _mount = (template: string, data, otherObj?) =>
mount({
components: {
[Button.name]: Button,
[Dropdown.name]: Dropdown,
[DropdownItem.name]: DropdownItem,
[DropdownMenu.name]: DropdownMenu,
},
template,
data,
...otherObj,
})
describe('Dropdown', () => {
afterEach(() => {
document.body.innerHTML = ''
})
test('create', async () => {
const wrapper = _mount(
`
<el-dropdown ref="b" placement="right">
<span class="el-dropdown-link" ref="a">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Apple</el-dropdown-item>
<el-dropdown-item>Orange</el-dropdown-item>
<el-dropdown-item>Cherry</el-dropdown-item>
<el-dropdown-item disabled>Peach</el-dropdown-item>
<el-dropdown-item divided>Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
typeof ElTooltip
>
vi.useFakeTimers()
const triggerElm = wrapper.find('.el-tooltip__trigger')
expect(content.open).toBe(false)
await triggerElm.trigger(MOUSE_ENTER_EVENT)
vi.runAllTimers()
expect(content.open).toBe(true)
await triggerElm.trigger(MOUSE_LEAVE_EVENT)
vi.runAllTimers()
expect(content.open).toBe(false)
vi.useRealTimers()
})
test('menu click', async () => {
const commandHandler = vi.fn()
const wrapper = _mount(
`
<el-dropdown ref="b" @command="commandHandler" placement="right">
<span class="el-dropdown-link" ref="a">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="a">Apple</el-dropdown-item>
<el-dropdown-item command="b">Orange</el-dropdown-item>
<el-dropdown-item ref="c" :command="myCommandObject">Cherry</el-dropdown-item>
<el-dropdown-item command="d">Peach</el-dropdown-item>
<el-dropdown-item command="e">Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({
myCommandObject: { name: 'CommandC' },
name: '',
}),
{
methods: {
commandHandler,
},
}
)
await nextTick()
// const content = wrapper.findComponent({ ref: 'b' }).vm as any
const triggerElm = wrapper.find('.el-tooltip__trigger')
await triggerElm.trigger(MOUSE_ENTER_EVENT)
await nextTick()
await wrapper
.findComponent({ ref: 'c' })
.findComponent({
name: 'DropdownItemImpl',
})
.find('.el-dropdown-menu__item')
.trigger('click')
await nextTick()
expect(commandHandler).toHaveBeenCalled()
})
test('trigger', async () => {
const wrapper = _mount(
`
<el-dropdown trigger="click" ref="b" placement="right">
<span class="el-dropdown-link" ref="a">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="a">Apple</el-dropdown-item>
<el-dropdown-item command="b">Orange</el-dropdown-item>
<el-dropdown-item ref="c" :command="myCommandObject">Cherry</el-dropdown-item>
<el-dropdown-item command="d">Peach</el-dropdown-item>
<el-dropdown-item command="e">Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({
myCommandObject: { name: 'CommandC' },
name: '',
})
)
await nextTick()
const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
typeof ElTooltip
>
const triggerElm = wrapper.find('.el-dropdown-link')
expect(content.open).toBe(false)
await triggerElm.trigger(MOUSE_ENTER_EVENT)
expect(content.open).toBe(false)
await triggerElm.trigger('click', {
button: 0,
})
await rAF()
expect(content.open).toBe(true)
})
test('trigger contextmenu', async () => {
const wrapper = _mount(
`
<el-dropdown trigger="contextmenu" ref="b" placement="right">
<span class="el-dropdown-link" ref="a">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="a">Apple</el-dropdown-item>
<el-dropdown-item command="b">Orange</el-dropdown-item>
<el-dropdown-item ref="c" :command="myCommandObject">Cherry</el-dropdown-item>
<el-dropdown-item command="d">Peach</el-dropdown-item>
<el-dropdown-item command="e">Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({
myCommandObject: { name: 'CommandC' },
name: '',
})
)
await nextTick()
const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
typeof ElTooltip
>
const triggerElm = wrapper.find('.el-dropdown-link')
expect(content.open).toBe(false)
await triggerElm.trigger(CONTEXTMENU)
await rAF()
expect(content.open).toBe(true)
})
test('virtual ref', async () => {
const wrapper = _mount(
`
<el-button style="height: 400px; width: 400px" @click="handleClick" @contextmenu="handleContextmenu">
Right click
</el-button>
<el-dropdown
ref="dropdownRef"
popper-class="virtual-ref-cls"
:virtual-ref="triggerRef"
virtual-triggering
:popper-options="popperOptions"
>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Apple</el-dropdown-item>
<el-dropdown-item>Orange</el-dropdown-item>
<el-dropdown-item>Cherry</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
function () {
return {
dropdownRef: null,
popperOptions: {
modifiers: [
{ name: 'offset', options: { offset: [0, 0] } },
{ name: 'flip', options: { fallbackPlacements: ['bottom'] } },
],
},
position: {
top: 0,
left: 0,
bottom: 0,
right: 0,
x: 0,
y: 0,
width: 0,
height: 0,
toJSON: () => {},
},
triggerRef: {
getBoundingClientRect: () => this.position,
},
}
},
{
methods: {
handleClick() {
this.$refs.dropdownRef?.handleClose()
},
handleContextmenu(event: MouseEvent) {
event.preventDefault()
const { clientX, clientY } = event
this.position = {
...this.position,
top: clientY,
left: clientX,
x: clientX,
y: clientY,
right: clientX,
bottom: clientY,
}
this.$refs.dropdownRef?.handleOpen()
},
},
}
)
await nextTick()
const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
typeof ElTooltip
>
const triggerElm = wrapper.find('.el-button')
expect(content.open).toBe(false)
vi.useFakeTimers()
await triggerElm.trigger('contextmenu', {
clientX: 100,
clientY: 100,
})
vi.runAllTimers()
expect(content.open).toBe(true)
const dropdownEl = document.querySelector('.virtual-ref-cls')
const dropdownStyle = getComputedStyle(dropdownEl)
expect(dropdownStyle.left).toBe('100px')
expect(dropdownStyle.top).toBe('100px')
await triggerElm.trigger('click')
vi.runAllTimers()
expect(content.open).toBe(false)
vi.useRealTimers()
})
test('handleOpen and handleClose', async () => {
const wrapper = _mount(
`
<el-dropdown trigger="click" ref="refDropdown" placement="right">
<span class="el-dropdown-link" ref="a">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="a">Apple</el-dropdown-item>
<el-dropdown-item command="b">Orange</el-dropdown-item>
<el-dropdown-item command="c">Cherry</el-dropdown-item>
<el-dropdown-item command="d">Peach</el-dropdown-item>
<el-dropdown-item command="e">Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({
name: '',
})
)
await nextTick()
const dropdown = wrapper.vm
const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
typeof ElTooltip
>
expect(content.open).toBe(false)
await dropdown.$refs.refDropdown.handleOpen()
await rAF()
expect(content.open).toBe(true)
await dropdown.$refs.refDropdown.handleClose()
await rAF()
expect(content.open).toBe(false)
})
test('split button', async () => {
const handleClick = vi.fn()
const wrapper = _mount(
`
<el-dropdown @click="handleClick" split-button type="primary" ref="b" placement="right">
dropdown
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="a">Apple</el-dropdown-item>
<el-dropdown-item command="b">Orange</el-dropdown-item>
<el-dropdown-item ref="c" :command="myCommandObject">Cherry</el-dropdown-item>
<el-dropdown-item command="d">Peach</el-dropdown-item>
<el-dropdown-item command="e">Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({
myCommandObject: { name: 'CommandC' },
name: '',
}),
{
methods: {
handleClick,
},
}
)
await nextTick()
const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
typeof ElTooltip
>
const triggerElm = wrapper.find('.el-dropdown__caret-button')
const button = wrapper.find('.el-button')
expect(content.open).toBe(false)
await button.trigger('click')
expect(handleClick).toHaveBeenCalled()
vi.useFakeTimers()
await triggerElm.trigger(MOUSE_ENTER_EVENT)
vi.runAllTimers()
vi.useRealTimers()
expect(content.open).toBe(true)
})
test('hide on click', async () => {
const wrapper = _mount(
`
<el-dropdown ref="b" placement="right" :hide-on-click="false">
<span class="el-dropdown-link" ref="a">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Apple</el-dropdown-item>
<el-dropdown-item>Orange</el-dropdown-item>
<el-dropdown-item ref="c">Cherry</el-dropdown-item>
<el-dropdown-item disabled>Peach</el-dropdown-item>
<el-dropdown-item divided>Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
typeof ElTooltip
>
expect(content.open).toBe(false)
const triggerElm = wrapper.find('.el-tooltip__trigger')
vi.useFakeTimers()
await triggerElm.trigger(MOUSE_ENTER_EVENT)
vi.runAllTimers()
expect(content.open).toBe(true)
await wrapper
.findComponent({ ref: 'c' })
.findComponent({
name: 'DropdownItemImpl',
})
.trigger('click')
vi.runAllTimers()
expect(content.open).toBe(true)
vi.useRealTimers()
})
test('triggerElm keydown', async () => {
const wrapper = _mount(
`
<el-dropdown ref="b" placement="right" :hide-on-click="false">
<span class="el-dropdown-link" ref="a">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Apple</el-dropdown-item>
<el-dropdown-item>Orange</el-dropdown-item>
<el-dropdown-item ref="c">Cherry</el-dropdown-item>
<el-dropdown-item disabled>Peach</el-dropdown-item>
<el-dropdown-item divided>Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
typeof ElTooltip
>
const triggerElm = wrapper.find('.el-tooltip__trigger')
vi.useFakeTimers()
await triggerElm.trigger(MOUSE_ENTER_EVENT)
vi.runAllTimers()
await triggerElm.trigger('keydown', {
code: EVENT_CODE.enter,
})
vi.runAllTimers()
expect(content.open).toBe(false)
await triggerElm.trigger(MOUSE_ENTER_EVENT)
vi.runAllTimers()
await triggerElm.trigger('keydown', {
code: EVENT_CODE.tab,
})
vi.runAllTimers()
expect(content.open).toBe(true)
vi.useRealTimers()
})
test('max height', async () => {
const wrapper = _mount(
`
<el-dropdown ref="b" max-height="60px">
<span class="el-dropdown-link" ref="a">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Apple</el-dropdown-item>
<el-dropdown-item>Orange</el-dropdown-item>
<el-dropdown-item>Cherry</el-dropdown-item>
<el-dropdown-item disabled>Peach</el-dropdown-item>
<el-dropdown-item divided>Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
const scrollbar = wrapper
.findComponent({
ref: 'b',
})
.findComponent({ ref: 'scrollbar' })
expect(scrollbar.find('.el-scrollbar__wrap').attributes('style')).toContain(
'max-height: 60px;'
)
})
test('tooltip debounce', async () => {
const wrapper = _mount(
`
<el-dropdown ref="b">
<span class="el-dropdown-link">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Apple</el-dropdown-item>
<el-dropdown-item>Orange</el-dropdown-item>
<el-dropdown-item>Cherry</el-dropdown-item>
<el-dropdown-item>Peach</el-dropdown-item>
<el-dropdown-item>Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
typeof ElTooltip
>
const triggerElm = wrapper.find('.el-tooltip__trigger')
expect(content.open).toBe(false)
vi.useFakeTimers()
await triggerElm.trigger(MOUSE_ENTER_EVENT)
await triggerElm.trigger(MOUSE_LEAVE_EVENT)
await triggerElm.trigger(MOUSE_ENTER_EVENT)
vi.runAllTimers()
vi.useRealTimers()
expect(content.open).toBe(true)
})
test('popperClass', async () => {
const wrapper = await _mount(
`
<el-dropdown ref="b" max-height="60px" popper-class="custom-popper-class">
<span class="el-dropdown-link" ref="a">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Apple</el-dropdown-item>
<el-dropdown-item>Orange</el-dropdown-item>
<el-dropdown-item>Cherry</el-dropdown-item>
<el-dropdown-item disabled>Peach</el-dropdown-item>
<el-dropdown-item divided>Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
const popperElement = wrapper.findComponent({
name: 'ElPopperContent',
}).element
expect(popperElement.classList.contains('custom-popper-class')).toBe(true)
})
test('custom attributes for dropdown items', async () => {
const wrapper = _mount(
`
<el-dropdown>
<span class="el-dropdown-link">
Custom Attributes
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item data-custom-attribute="hello">Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
expect(
wrapper
.findComponent({
name: 'DropdownItemImpl',
})
.find('.el-dropdown-menu__item').element.dataset.customAttribute
).toBe('hello')
})
test('disable normal dropdown', async () => {
const wrapper = _mount(
`
<el-dropdown disabled>
<span class="el-dropdown-link">
Dropdown List
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item data-custom-attribute="hello">Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
expect(
wrapper
.findComponent({
name: 'ElDropdown',
})
.classes()
).toContain('is-disabled')
})
test('disable dropdown with split button', async () => {
const wrapper = _mount(
`
<el-dropdown disabled split-button>
<span class="el-dropdown-link">
Dropdown List
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item data-custom-attribute="hello">Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
expect(
wrapper
.findAllComponents({
name: 'ElButton',
})[0]
.classes()
).toContain('is-disabled')
expect(
wrapper
.findAllComponents({
name: 'ElButton',
})[1]
.classes()
).toContain('is-disabled')
})
test('set show-timeout/hide-timeout when trigger is hover', async () => {
const wrapper = _mount(
`
<el-dropdown trigger="hover" :show-timeout="200" :hide-timeout="300">
<span class="el-dropdown-link">
Dropdown List
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
const tooltipElement = wrapper.getComponent({
name: 'ElTooltip',
})
expect(tooltipElement.vm.showAfter).toBe(200)
expect(tooltipElement.vm.hideAfter).toBe(300)
})
test('ignore show-timeout/hide-timeout when trigger is not hover', async () => {
const wrapper = _mount(
`
<el-dropdown trigger="click" :show-timeout="200" :hide-timeout="300">
<span class="el-dropdown-link">
Dropdown List
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
const tooltipElement = wrapper.getComponent({
name: 'ElTooltip',
})
expect(tooltipElement.vm.showAfter).toBe(0)
expect(tooltipElement.vm.hideAfter).toBe(0)
})
describe('accessibility', () => {
test('Custom span trigger has proper attributes', async () => {
const wrapper = _mount(
`
<el-dropdown>
<span class="el-dropdown-link" data-test-ref="trigger">
Dropdown List
</span>
<template #dropdown>
<el-dropdown-menu ref="menu">
<el-dropdown-item>Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
const trigger = wrapper.find('[data-test-ref="trigger"]')
const menu = wrapper.findComponent({ ref: 'menu' })
expect(trigger.attributes()['role']).toBe('button')
expect(trigger.attributes()['tabindex']).toBe('0')
expect(trigger.attributes()['aria-haspopup']).toBe('menu')
expect(trigger.attributes()['id']).toBe(
menu.attributes()['aria-labelledby']
)
expect(trigger.attributes()['aria-controls']).toBe(
menu.attributes()['id']
)
})
test('ElButton trigger has proper attributes', async () => {
const wrapper = _mount(
`
<el-dropdown>
<el-button ref="trigger">
Dropdown List
</el-button>
<template #dropdown>
<el-dropdown-menu ref="menu">
<el-dropdown-item>Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
const trigger = wrapper.findComponent({ ref: 'trigger' })
const menu = wrapper.findComponent({ ref: 'menu' })
expect(trigger.attributes()['role']).toBe('button')
expect(trigger.attributes()['tabindex']).toBe('0')
expect(trigger.attributes()['aria-haspopup']).toBe('menu')
expect(trigger.attributes()['id']).toBe(
menu.attributes()['aria-labelledby']
)
expect(trigger.attributes()['aria-controls']).toBe(
menu.attributes()['id']
)
})
test('Split button trigger has proper attributes', async () => {
const wrapper = _mount(
`
<el-dropdown split-button>
<template #dropdown>
<el-dropdown-menu ref="menu">
<el-dropdown-item>Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
const trigger = wrapper.find('.el-dropdown__caret-button')
const menu = wrapper.findComponent({ ref: 'menu' })
expect(trigger.attributes()['role']).toBe('button')
expect(trigger.attributes()['tabindex']).toBe('0')
expect(trigger.attributes()['aria-haspopup']).toBe('menu')
expect(trigger.attributes()['id']).toBe(
menu.attributes()['aria-labelledby']
)
expect(trigger.attributes()['aria-controls']).toBe(
menu.attributes()['id']
)
})
test('Menu items with "menu" role', async () => {
const wrapper = _mount(
`
<el-dropdown split-button>
<template #dropdown>
<el-dropdown-menu ref="menu">
<el-dropdown-item ref="menu-item">Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
const menu = wrapper.findComponent({ ref: 'menu' })
const menuItem = menu.find('.el-dropdown-menu__item')
expect(menu.attributes()['role']).toBe('menu')
expect(menuItem.attributes()['role']).toBe('menuitem')
})
test('Menu items with "navigation" role', async () => {
const wrapper = _mount(
`
<el-dropdown split-button role="navigation">
<template #dropdown>
<el-dropdown-menu ref="menu">
<el-dropdown-item ref="menu-item">Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
const menu = wrapper.findComponent({ ref: 'menu' })
const menuItem = menu.find('.el-dropdown-menu__item')
expect(menu.attributes()['role']).toBe('navigation')
expect(menuItem.attributes()['role']).toBe('link')
})
test('Menu items with "group" role', async () => {
const wrapper = _mount(
`
<el-dropdown split-button role="group">
<template #dropdown>
<el-dropdown-menu ref="menu">
<el-dropdown-item ref="menu-item">Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
const menu = wrapper.findComponent({ ref: 'menu' })
const menuItem = menu.find('.el-dropdown-menu__item')
expect(menu.attributes()['role']).toBe('group')
expect(menuItem.attributes()['role']).toBe('button')
})
test('Trigger dropdown via hover', async () => {
const wrapper = _mount(
`
<el-dropdown trigger="hover" :show-timeout="0" :hide-timeout="0">
<span class="el-dropdown-link">
Dropdown List
</span>
<template #dropdown>
<el-dropdown-menu ref="menu">
<el-dropdown-item class="item-1" disabled>Item</el-dropdown-item>
<el-dropdown-item class="item-2">Item</el-dropdown-item>
<el-dropdown-item class="item-3">Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
const trigger = wrapper.find('.el-tooltip__trigger')
await trigger.trigger(MOUSE_ENTER_EVENT)
await rAF()
const menuItem = wrapper.findComponent({ ref: 'menu' })
await menuItem.trigger('keydown', { code: EVENT_CODE.down })
await rAF()
const item2 = menuItem.find('.item-2')
expect(item2.element.getAttribute('tabindex')).toBe('0')
await item2.trigger('keydown', { code: EVENT_CODE.down })
await rAF()
const item3 = menuItem.find('.item-3')
expect(item3.element.getAttribute('tabindex')).toBe('0')
})
test('Trigger dropdown via click', async () => {
const wrapper = _mount(
`
<el-dropdown trigger="click" :show-timeout="0" :hide-timeout="0">
<span class="el-dropdown-link">
Dropdown List
</span>
<template #dropdown>
<el-dropdown-menu ref="menu">
<el-dropdown-item class="item-1" disabled>Item</el-dropdown-item>
<el-dropdown-item class="item-2">Item</el-dropdown-item>
<el-dropdown-item class="item-3">Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
const trigger = wrapper.find('.el-tooltip__trigger')
await trigger.trigger('click')
await rAF()
const menuItem = wrapper.findComponent({ ref: 'menu' })
expect(document.activeElement).toBe(menuItem.element)
await menuItem.trigger('keydown', { code: EVENT_CODE.down })
await rAF()
const item2 = menuItem.find('.item-2')
expect(item2.element.getAttribute('tabindex')).toBe('0')
await item2.trigger('keydown', { code: EVENT_CODE.down })
await rAF()
const item3 = menuItem.find('.item-3')
expect(item3.element.getAttribute('tabindex')).toBe('0')
})
test('Trigger dropdown via focus', async () => {
const wrapper = _mount(
`
<el-dropdown trigger="focus" :show-timeout="0" :hide-timeout="0">
<span class="el-dropdown-link">
Dropdown List
</span>
<template #dropdown>
<el-dropdown-menu ref="menu">
<el-dropdown-item class="item-1" disabled>Item</el-dropdown-item>
<el-dropdown-item class="item-2">Item</el-dropdown-item>
<el-dropdown-item class="item-3">Item</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
const trigger = wrapper.find('.el-tooltip__trigger')
await trigger.trigger('focus')
await trigger.trigger('keydown', { code: EVENT_CODE.down })
await rAF()
const menuItem = wrapper.findComponent({ ref: 'menu' })
const item2 = menuItem.find('.item-2')
expect(document.activeElement).toBe(item2.element)
expect(item2.element.getAttribute('tabindex')).toBe('0')
})
})
describe('teleported API', () => {
test('should mount on popper container', async () => {
expect(document.body.innerHTML).toBe('')
_mount(
`
<el-dropdown ref="b" placement="right">
<span class="el-dropdown-link" ref="a">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Apple</el-dropdown-item>
<el-dropdown-item>Orange</el-dropdown-item>
<el-dropdown-item>Cherry</el-dropdown-item>
<el-dropdown-item disabled>Peach</el-dropdown-item>
<el-dropdown-item divided>Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>`,
() => ({})
)
await nextTick()
const { selector } = usePopperContainerId()
expect(document.body.querySelector(selector.value).innerHTML).not.toBe('')
})
test('should not mount on the popper container', async () => {
expect(document.body.innerHTML).toBe('')
_mount(
`
<el-dropdown ref="b" placement="right" :teleported="false">
<span class="el-dropdown-link" ref="a">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Apple</el-dropdown-item>
<el-dropdown-item>Orange</el-dropdown-item>
<el-dropdown-item>Cherry</el-dropdown-item>
<el-dropdown-item disabled>Peach</el-dropdown-item>
<el-dropdown-item divided>Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>`,
() => ({})
)
await nextTick()
const { selector } = usePopperContainerId()
expect(document.body.querySelector(selector.value).innerHTML).toBe('')
})
test('should mount the dropdown popper under append-to', async () => {
const el = document.createElement('div')
el.classList.add('target_desu')
document.body.appendChild(el)
_mount(
`
<el-dropdown append-to=".target_desu">
<span class="el-dropdown-link">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Apple</el-dropdown-item>
<el-dropdown-item>Orange</el-dropdown-item>
<el-dropdown-item>Cherry</el-dropdown-item>
<el-dropdown-item disabled>Peach</el-dropdown-item>
<el-dropdown-item divided>Pear</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
expect(
document
.querySelector('.target_desu')
?.children[0].classList.contains('el-popper')
).toBe(true)
})
})
test('hover trigger: click dropdown-item should close immediately', async () => {
const wrapper = _mount(
`
<el-dropdown trigger="hover" :hide-timeout="3000">
<span class="el-dropdown-link">
dropdown<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu ref="menu">
<el-dropdown-item ref="item">Apple</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`,
() => ({})
)
await nextTick()
const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
typeof ElTooltip
>
vi.useFakeTimers()
const triggerElm = wrapper.find('.el-tooltip__trigger')
expect(content.open).toBe(false)
await triggerElm.trigger(MOUSE_ENTER_EVENT)
vi.runAllTimers()
await nextTick()
expect(content.open).toBe(true)
const menu = wrapper.findComponent({ ref: 'menu' })
await menu.trigger(MOUSE_ENTER_EVENT)
const item = wrapper
.findComponent({ ref: 'item' })
.findComponent({ name: 'DropdownItemImpl' })
.find('.el-dropdown-menu__item')
await item.trigger('click')
await nextTick()
vi.advanceTimersByTime(100)
expect(content.open).toBe(false)
await triggerElm.trigger(MOUSE_ENTER_EVENT)
vi.runAllTimers()
await nextTick()
expect(content.open).toBe(true)
vi.useRealTimers()
})
test('render icon slot content', async () => {
const wrapper = _mount(`
<el-dropdown trigger="hover">
<span class="el-dropdown-link">
Trigger
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<template #icon>
<i class="test-icon">ICON</i>
</template>
Action
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`)
await nextTick()
// Open dropdown
const triggerElm = wrapper.find('.el-dropdown-link')
await triggerElm.trigger(MOUSE_ENTER_EVENT)
await nextTick()
// The el-icon should be rendered
const icon = wrapper
.findComponent({ name: 'DropdownItemImpl' })
.findComponent({ name: 'ElIcon' })
expect(icon.exists()).toBe(true)
// The icon content should be rendered
const content = icon.find('.test-icon')
expect(content.exists()).toBe(true)
expect(content.text()).toBe('ICON')
})
test("no el-icon rendered if icon didn't passed in ", async () => {
const wrapper = _mount(`
<el-dropdown trigger="hover">
<span class="el-dropdown-link">
Trigger
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
Action
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
`)
await nextTick()
// Open dropdown
const triggerElm = wrapper.find('.el-dropdown-link')
await triggerElm.trigger(MOUSE_ENTER_EVENT)
await nextTick()
// Find icon element
const icon = wrapper
.findComponent({ name: 'DropdownItemImpl' })
.findComponent({ name: 'ElIcon' })
// The icon slot content shouldn't be rendered
expect(icon.exists()).toBe(false)
})
})