dropdown.test.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  1. // @ts-nocheck
  2. import { nextTick } from 'vue'
  3. import { mount } from '@vue/test-utils'
  4. import { afterEach, describe, expect, test, vi } from 'vitest'
  5. import { rAF } from '@element-plus/test-utils/tick'
  6. import { EVENT_CODE } from '@element-plus/constants'
  7. import { ElTooltip } from '@element-plus/components/tooltip'
  8. import Button from '@element-plus/components/button'
  9. import { usePopperContainerId } from '@element-plus/hooks'
  10. import Dropdown from '../src/dropdown.vue'
  11. import DropdownItem from '../src/dropdown-item.vue'
  12. import DropdownMenu from '../src/dropdown-menu.vue'
  13. const MOUSE_ENTER_EVENT = 'mouseenter'
  14. const MOUSE_LEAVE_EVENT = 'mouseleave'
  15. const CONTEXTMENU = 'contextmenu'
  16. const _mount = (template: string, data, otherObj?) =>
  17. mount({
  18. components: {
  19. [Button.name]: Button,
  20. [Dropdown.name]: Dropdown,
  21. [DropdownItem.name]: DropdownItem,
  22. [DropdownMenu.name]: DropdownMenu,
  23. },
  24. template,
  25. data,
  26. ...otherObj,
  27. })
  28. describe('Dropdown', () => {
  29. afterEach(() => {
  30. document.body.innerHTML = ''
  31. })
  32. test('create', async () => {
  33. const wrapper = _mount(
  34. `
  35. <el-dropdown ref="b" placement="right">
  36. <span class="el-dropdown-link" ref="a">
  37. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  38. </span>
  39. <template #dropdown>
  40. <el-dropdown-menu>
  41. <el-dropdown-item>Apple</el-dropdown-item>
  42. <el-dropdown-item>Orange</el-dropdown-item>
  43. <el-dropdown-item>Cherry</el-dropdown-item>
  44. <el-dropdown-item disabled>Peach</el-dropdown-item>
  45. <el-dropdown-item divided>Pear</el-dropdown-item>
  46. </el-dropdown-menu>
  47. </template>
  48. </el-dropdown>
  49. `,
  50. () => ({})
  51. )
  52. await nextTick()
  53. const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
  54. typeof ElTooltip
  55. >
  56. vi.useFakeTimers()
  57. const triggerElm = wrapper.find('.el-tooltip__trigger')
  58. expect(content.open).toBe(false)
  59. await triggerElm.trigger(MOUSE_ENTER_EVENT)
  60. vi.runAllTimers()
  61. expect(content.open).toBe(true)
  62. await triggerElm.trigger(MOUSE_LEAVE_EVENT)
  63. vi.runAllTimers()
  64. expect(content.open).toBe(false)
  65. vi.useRealTimers()
  66. })
  67. test('menu click', async () => {
  68. const commandHandler = vi.fn()
  69. const wrapper = _mount(
  70. `
  71. <el-dropdown ref="b" @command="commandHandler" placement="right">
  72. <span class="el-dropdown-link" ref="a">
  73. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  74. </span>
  75. <template #dropdown>
  76. <el-dropdown-menu>
  77. <el-dropdown-item command="a">Apple</el-dropdown-item>
  78. <el-dropdown-item command="b">Orange</el-dropdown-item>
  79. <el-dropdown-item ref="c" :command="myCommandObject">Cherry</el-dropdown-item>
  80. <el-dropdown-item command="d">Peach</el-dropdown-item>
  81. <el-dropdown-item command="e">Pear</el-dropdown-item>
  82. </el-dropdown-menu>
  83. </template>
  84. </el-dropdown>
  85. `,
  86. () => ({
  87. myCommandObject: { name: 'CommandC' },
  88. name: '',
  89. }),
  90. {
  91. methods: {
  92. commandHandler,
  93. },
  94. }
  95. )
  96. await nextTick()
  97. // const content = wrapper.findComponent({ ref: 'b' }).vm as any
  98. const triggerElm = wrapper.find('.el-tooltip__trigger')
  99. await triggerElm.trigger(MOUSE_ENTER_EVENT)
  100. await nextTick()
  101. await wrapper
  102. .findComponent({ ref: 'c' })
  103. .findComponent({
  104. name: 'DropdownItemImpl',
  105. })
  106. .find('.el-dropdown-menu__item')
  107. .trigger('click')
  108. await nextTick()
  109. expect(commandHandler).toHaveBeenCalled()
  110. })
  111. test('trigger', async () => {
  112. const wrapper = _mount(
  113. `
  114. <el-dropdown trigger="click" ref="b" placement="right">
  115. <span class="el-dropdown-link" ref="a">
  116. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  117. </span>
  118. <template #dropdown>
  119. <el-dropdown-menu>
  120. <el-dropdown-item command="a">Apple</el-dropdown-item>
  121. <el-dropdown-item command="b">Orange</el-dropdown-item>
  122. <el-dropdown-item ref="c" :command="myCommandObject">Cherry</el-dropdown-item>
  123. <el-dropdown-item command="d">Peach</el-dropdown-item>
  124. <el-dropdown-item command="e">Pear</el-dropdown-item>
  125. </el-dropdown-menu>
  126. </template>
  127. </el-dropdown>
  128. `,
  129. () => ({
  130. myCommandObject: { name: 'CommandC' },
  131. name: '',
  132. })
  133. )
  134. await nextTick()
  135. const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
  136. typeof ElTooltip
  137. >
  138. const triggerElm = wrapper.find('.el-dropdown-link')
  139. expect(content.open).toBe(false)
  140. await triggerElm.trigger(MOUSE_ENTER_EVENT)
  141. expect(content.open).toBe(false)
  142. await triggerElm.trigger('click', {
  143. button: 0,
  144. })
  145. await rAF()
  146. expect(content.open).toBe(true)
  147. })
  148. test('trigger contextmenu', async () => {
  149. const wrapper = _mount(
  150. `
  151. <el-dropdown trigger="contextmenu" ref="b" placement="right">
  152. <span class="el-dropdown-link" ref="a">
  153. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  154. </span>
  155. <template #dropdown>
  156. <el-dropdown-menu>
  157. <el-dropdown-item command="a">Apple</el-dropdown-item>
  158. <el-dropdown-item command="b">Orange</el-dropdown-item>
  159. <el-dropdown-item ref="c" :command="myCommandObject">Cherry</el-dropdown-item>
  160. <el-dropdown-item command="d">Peach</el-dropdown-item>
  161. <el-dropdown-item command="e">Pear</el-dropdown-item>
  162. </el-dropdown-menu>
  163. </template>
  164. </el-dropdown>
  165. `,
  166. () => ({
  167. myCommandObject: { name: 'CommandC' },
  168. name: '',
  169. })
  170. )
  171. await nextTick()
  172. const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
  173. typeof ElTooltip
  174. >
  175. const triggerElm = wrapper.find('.el-dropdown-link')
  176. expect(content.open).toBe(false)
  177. await triggerElm.trigger(CONTEXTMENU)
  178. await rAF()
  179. expect(content.open).toBe(true)
  180. })
  181. test('handleOpen and handleClose', async () => {
  182. const wrapper = _mount(
  183. `
  184. <el-dropdown trigger="click" ref="refDropdown" placement="right">
  185. <span class="el-dropdown-link" ref="a">
  186. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  187. </span>
  188. <template #dropdown>
  189. <el-dropdown-menu>
  190. <el-dropdown-item command="a">Apple</el-dropdown-item>
  191. <el-dropdown-item command="b">Orange</el-dropdown-item>
  192. <el-dropdown-item command="c">Cherry</el-dropdown-item>
  193. <el-dropdown-item command="d">Peach</el-dropdown-item>
  194. <el-dropdown-item command="e">Pear</el-dropdown-item>
  195. </el-dropdown-menu>
  196. </template>
  197. </el-dropdown>
  198. `,
  199. () => ({
  200. name: '',
  201. })
  202. )
  203. await nextTick()
  204. const dropdown = wrapper.vm
  205. const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
  206. typeof ElTooltip
  207. >
  208. expect(content.open).toBe(false)
  209. await dropdown.$refs.refDropdown.handleOpen()
  210. await rAF()
  211. expect(content.open).toBe(true)
  212. await dropdown.$refs.refDropdown.handleClose()
  213. await rAF()
  214. expect(content.open).toBe(false)
  215. })
  216. test('split button', async () => {
  217. const handleClick = vi.fn()
  218. const wrapper = _mount(
  219. `
  220. <el-dropdown @click="handleClick" split-button type="primary" ref="b" placement="right">
  221. dropdown
  222. <template #dropdown>
  223. <el-dropdown-menu>
  224. <el-dropdown-item command="a">Apple</el-dropdown-item>
  225. <el-dropdown-item command="b">Orange</el-dropdown-item>
  226. <el-dropdown-item ref="c" :command="myCommandObject">Cherry</el-dropdown-item>
  227. <el-dropdown-item command="d">Peach</el-dropdown-item>
  228. <el-dropdown-item command="e">Pear</el-dropdown-item>
  229. </el-dropdown-menu>
  230. </template>
  231. </el-dropdown>
  232. `,
  233. () => ({
  234. myCommandObject: { name: 'CommandC' },
  235. name: '',
  236. }),
  237. {
  238. methods: {
  239. handleClick,
  240. },
  241. }
  242. )
  243. await nextTick()
  244. const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
  245. typeof ElTooltip
  246. >
  247. const triggerElm = wrapper.find('.el-dropdown__caret-button')
  248. const button = wrapper.find('.el-button')
  249. expect(content.open).toBe(false)
  250. await button.trigger('click')
  251. expect(handleClick).toHaveBeenCalled()
  252. vi.useFakeTimers()
  253. await triggerElm.trigger(MOUSE_ENTER_EVENT)
  254. vi.runAllTimers()
  255. vi.useRealTimers()
  256. expect(content.open).toBe(true)
  257. })
  258. test('hide on click', async () => {
  259. const wrapper = _mount(
  260. `
  261. <el-dropdown ref="b" placement="right" :hide-on-click="false">
  262. <span class="el-dropdown-link" ref="a">
  263. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  264. </span>
  265. <template #dropdown>
  266. <el-dropdown-menu>
  267. <el-dropdown-item>Apple</el-dropdown-item>
  268. <el-dropdown-item>Orange</el-dropdown-item>
  269. <el-dropdown-item ref="c">Cherry</el-dropdown-item>
  270. <el-dropdown-item disabled>Peach</el-dropdown-item>
  271. <el-dropdown-item divided>Pear</el-dropdown-item>
  272. </el-dropdown-menu>
  273. </template>
  274. </el-dropdown>
  275. `,
  276. () => ({})
  277. )
  278. await nextTick()
  279. const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
  280. typeof ElTooltip
  281. >
  282. expect(content.open).toBe(false)
  283. const triggerElm = wrapper.find('.el-tooltip__trigger')
  284. vi.useFakeTimers()
  285. await triggerElm.trigger(MOUSE_ENTER_EVENT)
  286. vi.runAllTimers()
  287. expect(content.open).toBe(true)
  288. await wrapper
  289. .findComponent({ ref: 'c' })
  290. .findComponent({
  291. name: 'DropdownItemImpl',
  292. })
  293. .trigger('click')
  294. vi.runAllTimers()
  295. expect(content.open).toBe(true)
  296. vi.useRealTimers()
  297. })
  298. test('triggerElm keydown', async () => {
  299. const wrapper = _mount(
  300. `
  301. <el-dropdown ref="b" placement="right" :hide-on-click="false">
  302. <span class="el-dropdown-link" ref="a">
  303. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  304. </span>
  305. <template #dropdown>
  306. <el-dropdown-menu>
  307. <el-dropdown-item>Apple</el-dropdown-item>
  308. <el-dropdown-item>Orange</el-dropdown-item>
  309. <el-dropdown-item ref="c">Cherry</el-dropdown-item>
  310. <el-dropdown-item disabled>Peach</el-dropdown-item>
  311. <el-dropdown-item divided>Pear</el-dropdown-item>
  312. </el-dropdown-menu>
  313. </template>
  314. </el-dropdown>
  315. `,
  316. () => ({})
  317. )
  318. await nextTick()
  319. const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
  320. typeof ElTooltip
  321. >
  322. const triggerElm = wrapper.find('.el-tooltip__trigger')
  323. vi.useFakeTimers()
  324. await triggerElm.trigger(MOUSE_ENTER_EVENT)
  325. vi.runAllTimers()
  326. await triggerElm.trigger('keydown', {
  327. code: EVENT_CODE.enter,
  328. })
  329. vi.runAllTimers()
  330. expect(content.open).toBe(false)
  331. await triggerElm.trigger(MOUSE_ENTER_EVENT)
  332. vi.runAllTimers()
  333. await triggerElm.trigger('keydown', {
  334. code: EVENT_CODE.tab,
  335. })
  336. vi.runAllTimers()
  337. expect(content.open).toBe(true)
  338. vi.useRealTimers()
  339. })
  340. test('dropdown menu keydown', async () => {
  341. const wrapper = _mount(
  342. `
  343. <el-dropdown ref="b" placement="right" :hide-on-click="false">
  344. <span class="el-dropdown-link" ref="a">
  345. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  346. </span>
  347. <template #dropdown>
  348. <el-dropdown-menu ref="dropdown-menu">
  349. <el-dropdown-item ref="d">Apple</el-dropdown-item>
  350. <el-dropdown-item>Orange</el-dropdown-item>
  351. <el-dropdown-item ref="c">Cherry</el-dropdown-item>
  352. <el-dropdown-item disabled>Peach</el-dropdown-item>
  353. <el-dropdown-item divided>Pear</el-dropdown-item>
  354. </el-dropdown-menu>
  355. </template>
  356. </el-dropdown>
  357. `,
  358. () => ({})
  359. )
  360. await nextTick()
  361. const content = wrapper.findComponent({ ref: 'dropdown-menu' })
  362. const triggerElm = wrapper.find('.el-tooltip__trigger')
  363. await triggerElm.trigger(MOUSE_ENTER_EVENT)
  364. await rAF()
  365. await content.trigger('keydown', {
  366. code: EVENT_CODE.down,
  367. })
  368. await rAF()
  369. expect(
  370. wrapper
  371. .findComponent({ ref: 'd' })
  372. .findComponent({
  373. name: 'DropdownItemImpl',
  374. })
  375. .find('.el-dropdown-menu__item')
  376. .element.getAttribute('tabindex')
  377. ).toBe('0')
  378. })
  379. test('max height', async () => {
  380. const wrapper = _mount(
  381. `
  382. <el-dropdown ref="b" max-height="60px">
  383. <span class="el-dropdown-link" ref="a">
  384. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  385. </span>
  386. <template #dropdown>
  387. <el-dropdown-menu>
  388. <el-dropdown-item>Apple</el-dropdown-item>
  389. <el-dropdown-item>Orange</el-dropdown-item>
  390. <el-dropdown-item>Cherry</el-dropdown-item>
  391. <el-dropdown-item disabled>Peach</el-dropdown-item>
  392. <el-dropdown-item divided>Pear</el-dropdown-item>
  393. </el-dropdown-menu>
  394. </template>
  395. </el-dropdown>
  396. `,
  397. () => ({})
  398. )
  399. await nextTick()
  400. const scrollbar = wrapper
  401. .findComponent({
  402. ref: 'b',
  403. })
  404. .findComponent({ ref: 'scrollbar' })
  405. expect(scrollbar.find('.el-scrollbar__wrap').attributes('style')).toContain(
  406. 'max-height: 60px;'
  407. )
  408. })
  409. test('tooltip debounce', async () => {
  410. const wrapper = _mount(
  411. `
  412. <el-dropdown ref="b">
  413. <span class="el-dropdown-link">
  414. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  415. </span>
  416. <template #dropdown>
  417. <el-dropdown-menu>
  418. <el-dropdown-item>Apple</el-dropdown-item>
  419. <el-dropdown-item>Orange</el-dropdown-item>
  420. <el-dropdown-item>Cherry</el-dropdown-item>
  421. <el-dropdown-item>Peach</el-dropdown-item>
  422. <el-dropdown-item>Pear</el-dropdown-item>
  423. </el-dropdown-menu>
  424. </template>
  425. </el-dropdown>
  426. `,
  427. () => ({})
  428. )
  429. const content = wrapper.findComponent(ElTooltip).vm as InstanceType<
  430. typeof ElTooltip
  431. >
  432. const triggerElm = wrapper.find('.el-tooltip__trigger')
  433. expect(content.open).toBe(false)
  434. vi.useFakeTimers()
  435. await triggerElm.trigger(MOUSE_ENTER_EVENT)
  436. await triggerElm.trigger(MOUSE_LEAVE_EVENT)
  437. await triggerElm.trigger(MOUSE_ENTER_EVENT)
  438. vi.runAllTimers()
  439. vi.useRealTimers()
  440. expect(content.open).toBe(true)
  441. })
  442. test('popperClass', async () => {
  443. const wrapper = await _mount(
  444. `
  445. <el-dropdown ref="b" max-height="60px" popper-class="custom-popper-class">
  446. <span class="el-dropdown-link" ref="a">
  447. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  448. </span>
  449. <template #dropdown>
  450. <el-dropdown-menu>
  451. <el-dropdown-item>Apple</el-dropdown-item>
  452. <el-dropdown-item>Orange</el-dropdown-item>
  453. <el-dropdown-item>Cherry</el-dropdown-item>
  454. <el-dropdown-item disabled>Peach</el-dropdown-item>
  455. <el-dropdown-item divided>Pear</el-dropdown-item>
  456. </el-dropdown-menu>
  457. </template>
  458. </el-dropdown>
  459. `,
  460. () => ({})
  461. )
  462. const popperElement = wrapper.findComponent({
  463. name: 'ElPopperContent',
  464. }).element
  465. expect(popperElement.classList.contains('custom-popper-class')).toBe(true)
  466. })
  467. test('custom attributes for dropdown items', async () => {
  468. const wrapper = _mount(
  469. `
  470. <el-dropdown>
  471. <span class="el-dropdown-link">
  472. Custom Attributes
  473. </span>
  474. <template #dropdown>
  475. <el-dropdown-menu>
  476. <el-dropdown-item data-custom-attribute="hello">Item</el-dropdown-item>
  477. </el-dropdown-menu>
  478. </template>
  479. </el-dropdown>
  480. `,
  481. () => ({})
  482. )
  483. await nextTick()
  484. expect(
  485. wrapper
  486. .findComponent({
  487. name: 'DropdownItemImpl',
  488. })
  489. .find('.el-dropdown-menu__item').element.dataset.customAttribute
  490. ).toBe('hello')
  491. })
  492. test('disable normal dropdown', async () => {
  493. const wrapper = _mount(
  494. `
  495. <el-dropdown disabled>
  496. <span class="el-dropdown-link">
  497. Dropdown List
  498. </span>
  499. <template #dropdown>
  500. <el-dropdown-menu>
  501. <el-dropdown-item data-custom-attribute="hello">Item</el-dropdown-item>
  502. </el-dropdown-menu>
  503. </template>
  504. </el-dropdown>
  505. `,
  506. () => ({})
  507. )
  508. await nextTick()
  509. expect(
  510. wrapper
  511. .findComponent({
  512. name: 'ElDropdown',
  513. })
  514. .classes()
  515. ).toContain('is-disabled')
  516. })
  517. test('disable dropdown with split button', async () => {
  518. const wrapper = _mount(
  519. `
  520. <el-dropdown disabled split-button>
  521. <span class="el-dropdown-link">
  522. Dropdown List
  523. </span>
  524. <template #dropdown>
  525. <el-dropdown-menu>
  526. <el-dropdown-item data-custom-attribute="hello">Item</el-dropdown-item>
  527. </el-dropdown-menu>
  528. </template>
  529. </el-dropdown>
  530. `,
  531. () => ({})
  532. )
  533. await nextTick()
  534. expect(
  535. wrapper
  536. .findAllComponents({
  537. name: 'ElButton',
  538. })[0]
  539. .classes()
  540. ).toContain('is-disabled')
  541. expect(
  542. wrapper
  543. .findAllComponents({
  544. name: 'ElButton',
  545. })[1]
  546. .classes()
  547. ).toContain('is-disabled')
  548. })
  549. test('set show-timeout/hide-timeout when trigger is hover', async () => {
  550. const wrapper = _mount(
  551. `
  552. <el-dropdown trigger="hover" :show-timeout="200" :hide-timeout="300">
  553. <span class="el-dropdown-link">
  554. Dropdown List
  555. </span>
  556. <template #dropdown>
  557. <el-dropdown-menu>
  558. <el-dropdown-item>Item</el-dropdown-item>
  559. </el-dropdown-menu>
  560. </template>
  561. </el-dropdown>
  562. `,
  563. () => ({})
  564. )
  565. const tooltipElement = wrapper.getComponent({
  566. name: 'ElTooltip',
  567. })
  568. expect(tooltipElement.vm.showAfter).toBe(200)
  569. expect(tooltipElement.vm.hideAfter).toBe(300)
  570. })
  571. test('ignore show-timeout/hide-timeout when trigger is not hover', async () => {
  572. const wrapper = _mount(
  573. `
  574. <el-dropdown trigger="click" :show-timeout="200" :hide-timeout="300">
  575. <span class="el-dropdown-link">
  576. Dropdown List
  577. </span>
  578. <template #dropdown>
  579. <el-dropdown-menu>
  580. <el-dropdown-item>Item</el-dropdown-item>
  581. </el-dropdown-menu>
  582. </template>
  583. </el-dropdown>
  584. `,
  585. () => ({})
  586. )
  587. const tooltipElement = wrapper.getComponent({
  588. name: 'ElTooltip',
  589. })
  590. expect(tooltipElement.vm.showAfter).toBe(0)
  591. expect(tooltipElement.vm.hideAfter).toBe(0)
  592. })
  593. describe('accessibility', () => {
  594. test('Custom span trigger has proper attributes', async () => {
  595. const wrapper = _mount(
  596. `
  597. <el-dropdown>
  598. <span class="el-dropdown-link" data-test-ref="trigger">
  599. Dropdown List
  600. </span>
  601. <template #dropdown>
  602. <el-dropdown-menu ref="menu">
  603. <el-dropdown-item>Item</el-dropdown-item>
  604. </el-dropdown-menu>
  605. </template>
  606. </el-dropdown>
  607. `,
  608. () => ({})
  609. )
  610. await nextTick()
  611. const trigger = wrapper.find('[data-test-ref="trigger"]')
  612. const menu = wrapper.findComponent({ ref: 'menu' })
  613. expect(trigger.attributes()['role']).toBe('button')
  614. expect(trigger.attributes()['tabindex']).toBe('0')
  615. expect(trigger.attributes()['aria-haspopup']).toBe('menu')
  616. expect(trigger.attributes()['id']).toBe(
  617. menu.attributes()['aria-labelledby']
  618. )
  619. expect(trigger.attributes()['aria-controls']).toBe(
  620. menu.attributes()['id']
  621. )
  622. })
  623. test('ElButton trigger has proper attributes', async () => {
  624. const wrapper = _mount(
  625. `
  626. <el-dropdown>
  627. <el-button ref="trigger">
  628. Dropdown List
  629. </el-button>
  630. <template #dropdown>
  631. <el-dropdown-menu ref="menu">
  632. <el-dropdown-item>Item</el-dropdown-item>
  633. </el-dropdown-menu>
  634. </template>
  635. </el-dropdown>
  636. `,
  637. () => ({})
  638. )
  639. await nextTick()
  640. const trigger = wrapper.findComponent({ ref: 'trigger' })
  641. const menu = wrapper.findComponent({ ref: 'menu' })
  642. expect(trigger.attributes()['role']).toBe('button')
  643. expect(trigger.attributes()['tabindex']).toBe('0')
  644. expect(trigger.attributes()['aria-haspopup']).toBe('menu')
  645. expect(trigger.attributes()['id']).toBe(
  646. menu.attributes()['aria-labelledby']
  647. )
  648. expect(trigger.attributes()['aria-controls']).toBe(
  649. menu.attributes()['id']
  650. )
  651. })
  652. test('Split button trigger has proper attributes', async () => {
  653. const wrapper = _mount(
  654. `
  655. <el-dropdown split-button>
  656. <template #dropdown>
  657. <el-dropdown-menu ref="menu">
  658. <el-dropdown-item>Item</el-dropdown-item>
  659. </el-dropdown-menu>
  660. </template>
  661. </el-dropdown>
  662. `,
  663. () => ({})
  664. )
  665. await nextTick()
  666. const trigger = wrapper.find('.el-dropdown__caret-button')
  667. const menu = wrapper.findComponent({ ref: 'menu' })
  668. expect(trigger.attributes()['role']).toBe('button')
  669. expect(trigger.attributes()['tabindex']).toBe('0')
  670. expect(trigger.attributes()['aria-haspopup']).toBe('menu')
  671. expect(trigger.attributes()['id']).toBe(
  672. menu.attributes()['aria-labelledby']
  673. )
  674. expect(trigger.attributes()['aria-controls']).toBe(
  675. menu.attributes()['id']
  676. )
  677. })
  678. test('Menu items with "menu" role', async () => {
  679. const wrapper = _mount(
  680. `
  681. <el-dropdown split-button>
  682. <template #dropdown>
  683. <el-dropdown-menu ref="menu">
  684. <el-dropdown-item ref="menu-item">Item</el-dropdown-item>
  685. </el-dropdown-menu>
  686. </template>
  687. </el-dropdown>
  688. `,
  689. () => ({})
  690. )
  691. const menu = wrapper.findComponent({ ref: 'menu' })
  692. const menuItem = menu.find('.el-dropdown-menu__item')
  693. expect(menu.attributes()['role']).toBe('menu')
  694. expect(menuItem.attributes()['role']).toBe('menuitem')
  695. })
  696. test('Menu items with "navigation" role', async () => {
  697. const wrapper = _mount(
  698. `
  699. <el-dropdown split-button role="navigation">
  700. <template #dropdown>
  701. <el-dropdown-menu ref="menu">
  702. <el-dropdown-item ref="menu-item">Item</el-dropdown-item>
  703. </el-dropdown-menu>
  704. </template>
  705. </el-dropdown>
  706. `,
  707. () => ({})
  708. )
  709. const menu = wrapper.findComponent({ ref: 'menu' })
  710. const menuItem = menu.find('.el-dropdown-menu__item')
  711. expect(menu.attributes()['role']).toBe('navigation')
  712. expect(menuItem.attributes()['role']).toBe('link')
  713. })
  714. test('Menu items with "group" role', async () => {
  715. const wrapper = _mount(
  716. `
  717. <el-dropdown split-button role="group">
  718. <template #dropdown>
  719. <el-dropdown-menu ref="menu">
  720. <el-dropdown-item ref="menu-item">Item</el-dropdown-item>
  721. </el-dropdown-menu>
  722. </template>
  723. </el-dropdown>
  724. `,
  725. () => ({})
  726. )
  727. const menu = wrapper.findComponent({ ref: 'menu' })
  728. const menuItem = menu.find('.el-dropdown-menu__item')
  729. expect(menu.attributes()['role']).toBe('group')
  730. expect(menuItem.attributes()['role']).toBe('button')
  731. })
  732. })
  733. describe('teleported API', () => {
  734. test('should mount on popper container', async () => {
  735. expect(document.body.innerHTML).toBe('')
  736. _mount(
  737. `
  738. <el-dropdown ref="b" placement="right">
  739. <span class="el-dropdown-link" ref="a">
  740. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  741. </span>
  742. <template #dropdown>
  743. <el-dropdown-menu>
  744. <el-dropdown-item>Apple</el-dropdown-item>
  745. <el-dropdown-item>Orange</el-dropdown-item>
  746. <el-dropdown-item>Cherry</el-dropdown-item>
  747. <el-dropdown-item disabled>Peach</el-dropdown-item>
  748. <el-dropdown-item divided>Pear</el-dropdown-item>
  749. </el-dropdown-menu>
  750. </template>
  751. </el-dropdown>`,
  752. () => ({})
  753. )
  754. await nextTick()
  755. const { selector } = usePopperContainerId()
  756. expect(document.body.querySelector(selector.value).innerHTML).not.toBe('')
  757. })
  758. test('should not mount on the popper container', async () => {
  759. expect(document.body.innerHTML).toBe('')
  760. _mount(
  761. `
  762. <el-dropdown ref="b" placement="right" :teleported="false">
  763. <span class="el-dropdown-link" ref="a">
  764. dropdown<i class="el-icon-arrow-down el-icon--right"></i>
  765. </span>
  766. <template #dropdown>
  767. <el-dropdown-menu>
  768. <el-dropdown-item>Apple</el-dropdown-item>
  769. <el-dropdown-item>Orange</el-dropdown-item>
  770. <el-dropdown-item>Cherry</el-dropdown-item>
  771. <el-dropdown-item disabled>Peach</el-dropdown-item>
  772. <el-dropdown-item divided>Pear</el-dropdown-item>
  773. </el-dropdown-menu>
  774. </template>
  775. </el-dropdown>`,
  776. () => ({})
  777. )
  778. await nextTick()
  779. const { selector } = usePopperContainerId()
  780. expect(document.body.querySelector(selector.value).innerHTML).toBe('')
  781. })
  782. })
  783. })