select.test.ts 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634
  1. // @ts-nocheck
  2. import { nextTick } from 'vue'
  3. import { NOOP } from '@vue/shared'
  4. import { afterEach, describe, expect, it, vi } from 'vitest'
  5. import { hasClass } from '@element-plus/utils'
  6. import { EVENT_CODE } from '@element-plus/constants'
  7. import { makeMountFunc } from '@element-plus/test-utils/make-mount'
  8. import { rAF } from '@element-plus/test-utils/tick'
  9. import { CircleClose } from '@element-plus/icons-vue'
  10. import { usePopperContainerId } from '@element-plus/hooks'
  11. import Select from '../src/select.vue'
  12. vi.mock('lodash-unified', async () => {
  13. return {
  14. ...((await vi.importActual('lodash-unified')) as Record<string, any>),
  15. debounce: vi.fn((fn) => {
  16. fn.cancel = vi.fn()
  17. fn.flush = vi.fn()
  18. return fn
  19. }),
  20. }
  21. })
  22. const _mount = makeMountFunc({
  23. components: {
  24. 'el-select': Select,
  25. },
  26. })
  27. const createData = (count = 1000) => {
  28. const initials = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
  29. return Array.from({ length: count }).map((_, idx) => ({
  30. value: `option_${idx + 1}`,
  31. label: `${initials[idx % 10]}${idx}`,
  32. }))
  33. }
  34. const clickClearButton = async (wrapper) => {
  35. const select = wrapper.findComponent(Select)
  36. const selectVm = select.vm as any
  37. selectVm.states.comboBoxHovering = true
  38. await nextTick()
  39. const clearBtn = wrapper.findComponent(CircleClose)
  40. expect(clearBtn.exists()).toBeTruthy()
  41. await clearBtn.trigger('click')
  42. }
  43. interface SelectProps {
  44. popperClass?: string
  45. value?: string | string[] | number | number[]
  46. options?: any[]
  47. disabled?: boolean
  48. clearable?: boolean
  49. multiple?: boolean
  50. collapseTags?: boolean
  51. collapseTagsTooltip?: boolean
  52. filterable?: boolean
  53. remote?: boolean
  54. multipleLimit?: number
  55. allowCreate?: boolean
  56. popperAppendToBody?: boolean
  57. placeholder?: string
  58. [key: string]: any
  59. }
  60. interface SelectEvents {
  61. onChange?: (value?: string) => void
  62. onVisibleChange?: (visible?: boolean) => void
  63. onRemoveTag?: (tag?: string) => void
  64. onFocus?: (event?: FocusEvent) => void
  65. onBlur?: (event?) => void
  66. filterMethod?: (query: string) => void | undefined
  67. remoteMethod?: (query: string) => void | undefined
  68. [key: string]: (...args) => any
  69. }
  70. const createSelect = (
  71. options: {
  72. data?: () => SelectProps
  73. methods?: SelectEvents
  74. slots?: {
  75. empty?: string
  76. default?: string
  77. }
  78. } = {}
  79. ) => {
  80. const emptySlot =
  81. (options.slots &&
  82. options.slots.empty &&
  83. `<template #empty>${options.slots.empty}</template>`) ||
  84. ''
  85. const defaultSlot =
  86. (options.slots &&
  87. options.slots.default &&
  88. `<template #default="{item}">${options.slots.default}</template>`) ||
  89. ''
  90. return _mount(
  91. `
  92. <el-select
  93. :options="options"
  94. :popper-class="popperClass"
  95. :value-key="valueKey"
  96. :disabled="disabled"
  97. :clearable="clearable"
  98. :multiple="multiple"
  99. :collapseTags="collapseTags"
  100. :collapseTagsTooltip="collapseTagsTooltip"
  101. :filterable="filterable"
  102. :multiple-limit="multipleLimit"
  103. :placeholder="placeholder"
  104. :allow-create="allowCreate"
  105. :remote="remote"
  106. :reserve-keyword="reserveKeyword"
  107. :scrollbar-always-on="scrollbarAlwaysOn"
  108. :teleported="teleported"
  109. ${
  110. options.methods && options.methods.filterMethod
  111. ? `:filter-method="filterMethod"`
  112. : ''
  113. }
  114. ${
  115. options.methods && options.methods.remoteMethod
  116. ? `:remote-method="remoteMethod"`
  117. : ''
  118. }
  119. @change="onChange"
  120. @visible-change="onVisibleChange"
  121. @remove-tag="onRemoveTag"
  122. @focus="onFocus"
  123. @blur="onBlur"
  124. v-model="value">
  125. ${defaultSlot}
  126. ${emptySlot}
  127. </el-select>
  128. `,
  129. {
  130. data() {
  131. return {
  132. options: createData(),
  133. value: '',
  134. popperClass: '',
  135. allowCreate: false,
  136. valueKey: 'value',
  137. disabled: false,
  138. clearable: false,
  139. multiple: false,
  140. collapseTags: false,
  141. collapseTagsTooltip: false,
  142. remote: false,
  143. filterable: false,
  144. reserveKeyword: false,
  145. multipleLimit: 0,
  146. placeholder: DEFAULT_PLACEHOLDER,
  147. scrollbarAlwaysOn: false,
  148. popperAppendToBody: undefined,
  149. teleported: undefined,
  150. ...(options.data && options.data()),
  151. }
  152. },
  153. methods: {
  154. onChange: NOOP,
  155. onVisibleChange: NOOP,
  156. onRemoveTag: NOOP,
  157. onFocus: NOOP,
  158. onBlur: NOOP,
  159. ...options.methods,
  160. },
  161. }
  162. )
  163. }
  164. function getOptions(): HTMLElement[] {
  165. return Array.from(
  166. document.querySelectorAll<HTMLElement>(`.${OPTION_ITEM_CLASS_NAME}`)
  167. )
  168. }
  169. const CLASS_NAME = 'el-select-v2'
  170. const WRAPPER_CLASS_NAME = 'el-select-v2__wrapper'
  171. const OPTION_ITEM_CLASS_NAME = 'el-select-dropdown__option-item'
  172. const PLACEHOLDER_CLASS_NAME = 'el-select-v2__placeholder'
  173. const DEFAULT_PLACEHOLDER = 'Select'
  174. describe('Select', () => {
  175. afterEach(() => {
  176. document.body.innerHTML = ''
  177. })
  178. it('create', async () => {
  179. const wrapper = createSelect()
  180. await nextTick()
  181. expect(wrapper.classes()).toContain(CLASS_NAME)
  182. expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe('')
  183. const select = wrapper.findComponent(Select)
  184. await wrapper.trigger('click')
  185. expect((select.vm as any).expanded).toBeTruthy()
  186. })
  187. it('options rendered correctly', async () => {
  188. const wrapper = createSelect()
  189. await nextTick()
  190. const vm = wrapper.vm as any
  191. const options = Array.from(
  192. document.querySelectorAll(`.${OPTION_ITEM_CLASS_NAME}`)
  193. )
  194. const result = options.every((option, index) => {
  195. const text = option.textContent
  196. return text === vm.options[index].label
  197. })
  198. expect(result).toBeTruthy()
  199. })
  200. it('custom dropdown class', async () => {
  201. createSelect({
  202. data: () => ({
  203. popperClass: 'custom-dropdown',
  204. }),
  205. })
  206. await nextTick()
  207. expect([...document.querySelector('.el-popper').classList]).toContain(
  208. 'custom-dropdown'
  209. )
  210. })
  211. it('default value', async () => {
  212. const wrapper = createSelect({
  213. data: () => ({
  214. value: '2',
  215. options: [
  216. {
  217. value: '1',
  218. label: 'option_a',
  219. },
  220. {
  221. value: '2',
  222. label: 'option_b',
  223. },
  224. {
  225. value: '3',
  226. label: 'option_c',
  227. },
  228. ],
  229. }),
  230. })
  231. const vm = wrapper.vm as any
  232. await nextTick()
  233. expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe(
  234. vm.options[1].label
  235. )
  236. })
  237. it('default value is null or undefined', async () => {
  238. const wrapper = createSelect({
  239. data: () => ({
  240. value: undefined,
  241. }),
  242. })
  243. const vm = wrapper.vm as any
  244. const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  245. expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
  246. vm.value = vm.options[2].value
  247. await nextTick()
  248. expect(placeholder.text()).toBe(vm.options[2].label)
  249. vm.value = null
  250. await nextTick()
  251. expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
  252. })
  253. it('default value is Object', async () => {
  254. const wrapper = createSelect({
  255. data: () => ({
  256. valueKey: 'value',
  257. value: {
  258. value: '1',
  259. label: 'option_a',
  260. },
  261. options: [
  262. {
  263. value: '1',
  264. label: 'option_a',
  265. },
  266. {
  267. value: '2',
  268. label: 'option_b',
  269. },
  270. {
  271. value: '3',
  272. label: 'option_c',
  273. },
  274. ],
  275. }),
  276. })
  277. const vm = wrapper.vm as any
  278. await nextTick()
  279. expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe(
  280. vm.options[0].label
  281. )
  282. expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe(
  283. vm.value.label
  284. )
  285. })
  286. it('sync set value and options', async () => {
  287. const wrapper = createSelect()
  288. await nextTick()
  289. const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  290. const vm = wrapper.vm as any
  291. vm.value = vm.options[1].value
  292. await nextTick()
  293. expect(placeholder.text()).toBe(vm.options[1].label)
  294. vm.options[1].label = 'option bb aa'
  295. await nextTick()
  296. expect(placeholder.text()).toBe('option bb aa')
  297. })
  298. it('single select', async () => {
  299. const wrapper = createSelect({
  300. data() {
  301. return {
  302. count: 0,
  303. }
  304. },
  305. methods: {
  306. onChange() {
  307. this.count++
  308. },
  309. },
  310. })
  311. await nextTick()
  312. const options = getOptions()
  313. const vm = wrapper.vm as any
  314. const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  315. expect(vm.value).toBe('')
  316. expect(placeholder.text()).toBe('')
  317. options[2].click()
  318. await nextTick()
  319. expect(vm.value).toBe(vm.options[2].value)
  320. expect(placeholder.text()).toBe(vm.options[2].label)
  321. options[4].click()
  322. await nextTick()
  323. expect(vm.value).toBe(vm.options[4].value)
  324. expect(placeholder.text()).toBe(vm.options[4].label)
  325. expect(vm.count).toBe(2)
  326. })
  327. it('value-key option', async () => {
  328. const wrapper = createSelect({
  329. data: () => {
  330. return {
  331. options: [
  332. {
  333. id: 'id 1',
  334. value: 'value 1',
  335. label: 'option 1',
  336. },
  337. {
  338. id: 'id 2',
  339. value: 'value 2',
  340. label: 'option 2',
  341. },
  342. {
  343. id: 'id 3',
  344. value: 'value 3',
  345. label: 'option 3',
  346. },
  347. ],
  348. value: '',
  349. valueKey: 'id',
  350. }
  351. },
  352. })
  353. await nextTick()
  354. const vm = wrapper.vm as any
  355. const options = getOptions()
  356. options[1].click()
  357. await nextTick()
  358. expect(vm.value).toBe(vm.options[1].id)
  359. vm.valueKey = 'value'
  360. await nextTick()
  361. options[2].click()
  362. await nextTick()
  363. expect(vm.value).toBe(vm.options[2].value)
  364. })
  365. it('disabled option', async () => {
  366. const wrapper = createSelect({
  367. data: () => {
  368. return {
  369. options: [
  370. {
  371. value: '1',
  372. label: 'option 1',
  373. disabled: false,
  374. },
  375. {
  376. value: '2',
  377. label: 'option 2',
  378. disabled: true,
  379. },
  380. {
  381. value: '3',
  382. label: 'option 3',
  383. disabled: false,
  384. },
  385. ],
  386. }
  387. },
  388. })
  389. await nextTick()
  390. const vm = wrapper.vm as any
  391. const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  392. const option = document.querySelector<HTMLElement>(
  393. `.el-select-dropdown__option-item.is-disabled`
  394. )
  395. expect(option.textContent).toBe(vm.options[1].label)
  396. option.click()
  397. await nextTick()
  398. expect(vm.value).toBe('')
  399. expect(placeholder.text()).toBe('')
  400. vm.options[2].disabled = true
  401. await nextTick()
  402. const options = document.querySelectorAll<HTMLElement>(
  403. `.el-select-dropdown__option-item.is-disabled`
  404. )
  405. expect(options.length).toBe(2)
  406. expect(options.item(1).textContent).toBe(vm.options[2].label)
  407. options.item(1).click()
  408. await nextTick()
  409. expect(vm.value).toBe('')
  410. expect(placeholder.text()).toBe('')
  411. })
  412. it('disabled select', async () => {
  413. const wrapper = createSelect({
  414. data: () => {
  415. return {
  416. disabled: true,
  417. }
  418. },
  419. })
  420. await nextTick()
  421. expect(wrapper.find(`.${WRAPPER_CLASS_NAME}`).classes()).toContain(
  422. 'is-disabled'
  423. )
  424. })
  425. it('visible event', async () => {
  426. const wrapper = createSelect({
  427. data: () => {
  428. return {
  429. visible: false,
  430. }
  431. },
  432. methods: {
  433. onVisibleChange(visible) {
  434. this.visible = visible
  435. },
  436. },
  437. })
  438. await nextTick()
  439. const vm = wrapper.vm as any
  440. await wrapper.trigger('click')
  441. await nextTick()
  442. expect(vm.visible).toBeTruthy()
  443. })
  444. it('clearable', async () => {
  445. const wrapper = createSelect({
  446. data: () => ({ clearable: true }),
  447. })
  448. const vm = wrapper.vm as any
  449. vm.value = vm.options[1].value
  450. await nextTick()
  451. await clickClearButton(wrapper)
  452. expect(vm.value).toBeUndefined()
  453. const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  454. expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
  455. })
  456. describe('initial value', () => {
  457. it.each([
  458. [null, DEFAULT_PLACEHOLDER],
  459. [undefined, DEFAULT_PLACEHOLDER],
  460. ['', ''],
  461. [[], DEFAULT_PLACEHOLDER],
  462. [{}, '[object Object]'],
  463. ])(
  464. '[single select] initial value is %s, placeholder is "%s"',
  465. async (value, placeholder) => {
  466. const wrapper = createSelect({
  467. data: () => {
  468. return {
  469. value,
  470. }
  471. },
  472. })
  473. await nextTick()
  474. expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe(
  475. placeholder
  476. )
  477. }
  478. )
  479. it.each([
  480. [null, DEFAULT_PLACEHOLDER],
  481. [undefined, DEFAULT_PLACEHOLDER],
  482. ['', DEFAULT_PLACEHOLDER],
  483. [[], DEFAULT_PLACEHOLDER],
  484. [{}, DEFAULT_PLACEHOLDER],
  485. ])(
  486. '[multiple select] initial value is %s, placeholder is "%s"',
  487. async (value, placeholder) => {
  488. const wrapper = createSelect({
  489. data: () => {
  490. return {
  491. multiple: true,
  492. value,
  493. }
  494. },
  495. })
  496. await nextTick()
  497. expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe(
  498. placeholder
  499. )
  500. }
  501. )
  502. })
  503. describe('multiple', () => {
  504. it('multiple select', async () => {
  505. const wrapper = createSelect({
  506. data: () => {
  507. return {
  508. multiple: true,
  509. value: [],
  510. }
  511. },
  512. })
  513. await nextTick()
  514. const vm = wrapper.vm as any
  515. const options = getOptions()
  516. options[1].click()
  517. await nextTick()
  518. expect(vm.value.length).toBe(1)
  519. expect(vm.value[0]).toBe(vm.options[1].value)
  520. options[3].click()
  521. await nextTick()
  522. expect(vm.value.length).toBe(2)
  523. expect(vm.value[1]).toBe(vm.options[3].value)
  524. const tagIcon = wrapper.find('.el-tag__close')
  525. await tagIcon.trigger('click')
  526. expect(vm.value.length).toBe(1)
  527. })
  528. it('remove-tag', async () => {
  529. const wrapper = createSelect({
  530. data() {
  531. return {
  532. removeTag: '',
  533. multiple: true,
  534. }
  535. },
  536. methods: {
  537. onRemoveTag(tag) {
  538. this.removeTag = tag
  539. },
  540. },
  541. })
  542. await nextTick()
  543. const vm = wrapper.vm as any
  544. const options = getOptions()
  545. options[0].click()
  546. await nextTick()
  547. options[1].click()
  548. await nextTick()
  549. options[2].click()
  550. await nextTick()
  551. expect(vm.value.length).toBe(3)
  552. const tagCloseIcons = wrapper.findAll('.el-tag__close')
  553. await tagCloseIcons[1].trigger('click')
  554. expect(vm.value.length).toBe(2)
  555. await tagCloseIcons[0].trigger('click')
  556. expect(vm.value.length).toBe(1)
  557. })
  558. it('limit', async () => {
  559. const wrapper = createSelect({
  560. data() {
  561. return {
  562. multiple: true,
  563. multipleLimit: 2,
  564. value: [],
  565. }
  566. },
  567. })
  568. await nextTick()
  569. const vm = wrapper.vm as any
  570. const options = getOptions()
  571. options[1].click()
  572. await nextTick()
  573. options[2].click()
  574. await nextTick()
  575. expect(vm.value.length).toBe(2)
  576. options[3].click()
  577. await nextTick()
  578. expect(vm.value.length).toBe(2)
  579. })
  580. it('value-key option', async () => {
  581. const wrapper = createSelect({
  582. data: () => {
  583. return {
  584. options: [
  585. {
  586. id: 'id 1',
  587. value: 'value 1',
  588. label: 'option 1',
  589. },
  590. {
  591. id: 'id 2',
  592. value: 'value 2',
  593. label: 'option 2',
  594. },
  595. {
  596. id: 'id 3',
  597. value: 'value 3',
  598. label: 'option 3',
  599. },
  600. ],
  601. multiple: true,
  602. value: [],
  603. valueKey: 'id',
  604. }
  605. },
  606. })
  607. await nextTick()
  608. const vm = wrapper.vm as any
  609. const options = getOptions()
  610. options[1].click()
  611. await nextTick()
  612. expect(vm.value.length).toBe(1)
  613. expect(vm.value[0]).toBe(vm.options[1].id)
  614. vm.valueKey = 'value'
  615. await nextTick()
  616. options[2].click()
  617. await nextTick()
  618. expect(vm.value.length).toBe(2)
  619. expect(vm.value[1]).toBe(vm.options[2].value)
  620. })
  621. })
  622. describe('collapseTags', () => {
  623. it('use collapseTags', async () => {
  624. const wrapper = createSelect({
  625. data: () => {
  626. return {
  627. multiple: true,
  628. collapseTags: true,
  629. value: [],
  630. }
  631. },
  632. })
  633. await nextTick()
  634. const vm = wrapper.vm as any
  635. const options = getOptions()
  636. options[0].click()
  637. await nextTick()
  638. expect(vm.value.length).toBe(1)
  639. expect(vm.value[0]).toBe(vm.options[0].value)
  640. options[1].click()
  641. await nextTick()
  642. options[2].click()
  643. await nextTick()
  644. expect(vm.value.length).toBe(3)
  645. const tags = wrapper.findAll('.el-tag').filter((item) => {
  646. return !hasClass(item.element, 'in-tooltip')
  647. })
  648. expect(tags.length).toBe(2)
  649. })
  650. it('use collapseTagsTooltip', async () => {
  651. const wrapper = createSelect({
  652. data: () => {
  653. return {
  654. multiple: true,
  655. collapseTags: true,
  656. collapseTagsTooltip: true,
  657. value: [],
  658. }
  659. },
  660. })
  661. await nextTick()
  662. const vm = wrapper.vm as any
  663. const options = getOptions()
  664. options[0].click()
  665. await nextTick()
  666. expect(vm.value.length).toBe(1)
  667. expect(vm.value[0]).toBe(vm.options[0].value)
  668. options[1].click()
  669. await nextTick()
  670. options[2].click()
  671. await nextTick()
  672. expect(vm.value.length).toBe(3)
  673. expect(wrapper.findAll('.el-tag')[3].element.textContent).toBe('c2')
  674. })
  675. })
  676. describe('manually set modelValue', () => {
  677. it('set modelValue in single select', async () => {
  678. const wrapper = createSelect({
  679. data: () => {
  680. return {
  681. value: '',
  682. }
  683. },
  684. })
  685. await nextTick()
  686. const options = getOptions()
  687. const vm = wrapper.vm as any
  688. const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  689. expect(vm.value).toBe('')
  690. expect(placeholder.text()).toBe('')
  691. options[0].click()
  692. await nextTick()
  693. expect(vm.value).toBe(vm.options[0].value)
  694. expect(placeholder.text()).toBe(vm.options[0].label)
  695. const option = vm.options[0].value
  696. vm.value = ''
  697. await nextTick()
  698. expect(vm.value).toBe('')
  699. expect(placeholder.text()).toBe('')
  700. vm.value = option
  701. await nextTick()
  702. expect(vm.value).toBe('option_1')
  703. expect(placeholder.text()).toBe('a0')
  704. })
  705. it('set modelValue in multiple select', async () => {
  706. const wrapper = createSelect({
  707. data: () => {
  708. return {
  709. multiple: true,
  710. value: [],
  711. }
  712. },
  713. })
  714. await nextTick()
  715. const vm = wrapper.vm as any
  716. let placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  717. expect(placeholder.exists()).toBeTruthy()
  718. vm.value = ['option_1']
  719. await nextTick()
  720. expect(wrapper.find('.el-select-v2__tags-text').text()).toBe('a0')
  721. placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  722. expect(placeholder.exists()).toBeFalsy()
  723. vm.value = []
  724. await nextTick()
  725. expect(wrapper.find('.el-select-v2__tags-text').exists()).toBeFalsy()
  726. placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  727. expect(placeholder.exists()).toBeTruthy()
  728. })
  729. })
  730. describe('event', () => {
  731. it('focus & blur', async () => {
  732. const onFocus = vi.fn()
  733. const onBlur = vi.fn()
  734. const wrapper = createSelect({
  735. methods: {
  736. onFocus,
  737. onBlur,
  738. },
  739. })
  740. const input = wrapper.find('input')
  741. const select = wrapper.findComponent(Select)
  742. await input.trigger('focus')
  743. const selectVm = select.vm as any
  744. // Simulate focus state to trigger menu multiple times
  745. selectVm.toggleMenu()
  746. await nextTick()
  747. selectVm.toggleMenu()
  748. await nextTick()
  749. // Simulate click the outside
  750. selectVm.handleClickOutside()
  751. await nextTick()
  752. expect(onFocus).toHaveBeenCalledTimes(1)
  753. expect(onBlur).toHaveBeenCalled()
  754. })
  755. it('focus & blur for multiple & filterable select', async () => {
  756. const onFocus = vi.fn()
  757. const onBlur = vi.fn()
  758. const wrapper = createSelect({
  759. data() {
  760. return {
  761. multiple: true,
  762. filterable: true,
  763. value: [],
  764. }
  765. },
  766. methods: {
  767. onFocus,
  768. onBlur,
  769. },
  770. })
  771. const input = wrapper.find('input')
  772. const select = wrapper.findComponent(Select)
  773. await input.trigger('focus')
  774. const selectVm = select.vm as any
  775. // Simulate focus state to trigger menu multiple times
  776. selectVm.toggleMenu()
  777. await nextTick()
  778. selectVm.toggleMenu()
  779. await nextTick()
  780. // Select multiple items in multiple mode without triggering focus
  781. const options = getOptions()
  782. options[1].click()
  783. await nextTick()
  784. options[2].click()
  785. await nextTick()
  786. expect(onFocus).toHaveBeenCalledTimes(1)
  787. // Simulate click the outside
  788. selectVm.handleClickOutside()
  789. await nextTick()
  790. await nextTick()
  791. expect(onBlur).toHaveBeenCalled()
  792. })
  793. it('only emit change on user input', async () => {
  794. const handleChanged = vi.fn()
  795. const wrapper = createSelect({
  796. methods: {
  797. onChange: handleChanged,
  798. },
  799. })
  800. await nextTick()
  801. const vm = wrapper.vm as any
  802. vm.value = 'option_2'
  803. await nextTick()
  804. expect(handleChanged).toHaveBeenCalledTimes(0)
  805. const options = getOptions()
  806. options[4].click()
  807. await nextTick()
  808. expect(handleChanged).toHaveBeenCalled()
  809. })
  810. })
  811. describe('allow-create', () => {
  812. it('single select', async () => {
  813. const wrapper = createSelect({
  814. data: () => {
  815. return {
  816. allowCreate: true,
  817. filterable: true,
  818. clearable: true,
  819. options: [
  820. {
  821. value: '1',
  822. label: 'option 1',
  823. },
  824. {
  825. value: '2',
  826. label: 'option 2',
  827. },
  828. {
  829. value: '3',
  830. label: 'option 3',
  831. },
  832. ],
  833. }
  834. },
  835. })
  836. await nextTick()
  837. const select = wrapper.findComponent(Select)
  838. const selectVm = select.vm as any
  839. selectVm.expanded = true
  840. await nextTick()
  841. await rAF()
  842. const vm = wrapper.vm as any
  843. const input = wrapper.find('input')
  844. // create a new option
  845. input.element.value = '1111'
  846. await input.trigger('input')
  847. await nextTick()
  848. expect(selectVm.filteredOptions.length).toBe(1)
  849. // selected the new option
  850. selectVm.onSelect(selectVm.filteredOptions[0])
  851. expect(vm.value).toBe('1111')
  852. selectVm.expanded = false
  853. await nextTick()
  854. await rAF()
  855. selectVm.expanded = true
  856. await nextTick()
  857. await rAF()
  858. expect(selectVm.filteredOptions.length).toBe(4)
  859. selectVm.handleClear()
  860. expect(selectVm.filteredOptions.length).toBe(3)
  861. })
  862. it('multiple', async () => {
  863. const wrapper = createSelect({
  864. data: () => {
  865. return {
  866. allowCreate: true,
  867. filterable: true,
  868. clearable: true,
  869. multiple: true,
  870. options: [
  871. {
  872. value: '1',
  873. label: 'option 1',
  874. },
  875. {
  876. value: '2',
  877. label: 'option 2',
  878. },
  879. {
  880. value: '3',
  881. label: 'option 3',
  882. },
  883. ],
  884. }
  885. },
  886. })
  887. await nextTick()
  888. const vm = wrapper.vm as any
  889. await wrapper.trigger('click')
  890. const input = wrapper.find('input')
  891. input.element.value = '1111'
  892. await input.trigger('input')
  893. await nextTick()
  894. const select = wrapper.findComponent(Select)
  895. const selectVm = select.vm as any
  896. expect(selectVm.filteredOptions.length).toBe(1)
  897. // selected the new option
  898. selectVm.onSelect(selectVm.filteredOptions[0])
  899. // closed the menu
  900. await wrapper.trigger('click')
  901. input.element.value = '2222'
  902. await input.trigger('input')
  903. await nextTick()
  904. selectVm.onSelect(selectVm.filteredOptions[0])
  905. expect(JSON.stringify(vm.value)).toBe(JSON.stringify(['1111', '2222']))
  906. await wrapper.trigger('click')
  907. expect(selectVm.filteredOptions.length).toBe(5)
  908. // remove tag
  909. const tagCloseIcons = wrapper.findAll('.el-tag__close')
  910. await tagCloseIcons[1].trigger('click')
  911. expect(selectVm.filteredOptions.length).toBe(4)
  912. // simulate backspace
  913. await wrapper.find('input').trigger('keydown', {
  914. key: EVENT_CODE.backspace,
  915. })
  916. expect(selectVm.filteredOptions.length).toBe(3)
  917. })
  918. })
  919. it('reserve-keyword', async () => {
  920. const wrapper = createSelect({
  921. data: () => {
  922. return {
  923. filterable: true,
  924. clearable: true,
  925. multiple: true,
  926. reserveKeyword: true,
  927. options: [
  928. {
  929. value: 'a1',
  930. label: 'a1',
  931. },
  932. {
  933. value: 'b1',
  934. label: 'b1',
  935. },
  936. {
  937. value: 'a2',
  938. label: 'a2',
  939. },
  940. {
  941. value: 'b2',
  942. label: 'b2',
  943. },
  944. ],
  945. }
  946. },
  947. })
  948. await nextTick()
  949. const vm = wrapper.vm as any
  950. await nextTick()
  951. await wrapper.trigger('click')
  952. const input = wrapper.find('input')
  953. input.element.value = 'a'
  954. await input.trigger('input')
  955. await nextTick()
  956. let options = getOptions()
  957. expect(options.length).toBe(2)
  958. options[0].click()
  959. await nextTick()
  960. options = getOptions()
  961. expect(options.length).toBe(2)
  962. input.element.value = ''
  963. await input.trigger('input')
  964. await nextTick()
  965. options = getOptions()
  966. expect(options.length).toBe(4)
  967. vm.reserveKeyword = false
  968. await nextTick()
  969. input.element.value = 'a'
  970. await input.trigger('input')
  971. await nextTick()
  972. options = getOptions()
  973. expect(options.length).toBe(2)
  974. options[0].click()
  975. await nextTick()
  976. options = getOptions()
  977. expect(options.length).toBe(4)
  978. })
  979. it('render empty slot', async () => {
  980. const wrapper = createSelect({
  981. data() {
  982. return {
  983. options: [],
  984. }
  985. },
  986. slots: {
  987. empty: '<div class="empty-slot">EmptySlot</div>',
  988. },
  989. })
  990. await nextTick()
  991. expect(
  992. wrapper
  993. .findComponent({
  994. name: 'ElPopperContent',
  995. })
  996. .find('.empty-slot')
  997. .exists()
  998. ).toBeTruthy()
  999. })
  1000. it('should set placeholder to label of selected option when filterable is true and multiple is false', async () => {
  1001. const wrapper = createSelect({
  1002. data() {
  1003. return {
  1004. options: [
  1005. {
  1006. value: '1',
  1007. label: 'option 1',
  1008. },
  1009. {
  1010. value: '2',
  1011. label: 'option 2',
  1012. },
  1013. {
  1014. value: '3',
  1015. label: 'option 3',
  1016. },
  1017. ],
  1018. filterable: true,
  1019. multiple: false,
  1020. }
  1021. },
  1022. })
  1023. await nextTick()
  1024. const vm = wrapper.vm as any
  1025. const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  1026. vm.value = '2'
  1027. await nextTick()
  1028. const select = wrapper.findComponent(Select)
  1029. const selectVm = select.vm as any
  1030. selectVm.toggleMenu()
  1031. const input = wrapper.find('input')
  1032. await input.trigger('focus')
  1033. expect(placeholder.text()).toBe('option 2')
  1034. })
  1035. it('default value is null or undefined', async () => {
  1036. const wrapper = createSelect({
  1037. data() {
  1038. return {
  1039. value: null,
  1040. }
  1041. },
  1042. })
  1043. await nextTick()
  1044. const vm = wrapper.vm as any
  1045. const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  1046. expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
  1047. vm.value = undefined
  1048. await nextTick()
  1049. expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
  1050. })
  1051. it('default value is 0', async () => {
  1052. const wrapper = createSelect({
  1053. data: () => ({
  1054. value: 0,
  1055. options: [
  1056. {
  1057. value: 0,
  1058. label: 'option_a',
  1059. },
  1060. {
  1061. value: 1,
  1062. label: 'option_b',
  1063. },
  1064. {
  1065. value: 2,
  1066. label: 'option_c',
  1067. },
  1068. ],
  1069. }),
  1070. })
  1071. await nextTick()
  1072. const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  1073. expect(placeholder.text()).toBe('option_a')
  1074. })
  1075. it('emptyText error show', async () => {
  1076. const wrapper = createSelect({
  1077. data() {
  1078. return {
  1079. value: `${Math.random()}`,
  1080. }
  1081. },
  1082. })
  1083. await nextTick()
  1084. const vm = wrapper.vm as any
  1085. const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
  1086. expect(placeholder.text()).toBe(vm.value)
  1087. })
  1088. it('customized option renderer', async () => {
  1089. const wrapper = createSelect({
  1090. slots: {
  1091. default: `
  1092. <div class="custom-renderer">
  1093. <span style="margin-right: 8px;">{{ item.label }}</span>
  1094. <span style="color: var(--el-text-color-secondary); font-size: 13px">
  1095. {{ item.value }}
  1096. </span>
  1097. </div>
  1098. `,
  1099. },
  1100. })
  1101. await nextTick()
  1102. expect(
  1103. wrapper
  1104. .findComponent({
  1105. name: 'ElPopperContent',
  1106. })
  1107. .findAll('.custom-renderer').length
  1108. ).toBeGreaterThan(0)
  1109. })
  1110. it('tag of disabled option is not closable', async () => {
  1111. const wrapper = createSelect({
  1112. data() {
  1113. return {
  1114. multiple: true,
  1115. options: [
  1116. {
  1117. value: 1,
  1118. label: 'option 1',
  1119. disabled: true,
  1120. },
  1121. {
  1122. value: 2,
  1123. label: 'option 2',
  1124. disabled: true,
  1125. },
  1126. {
  1127. value: 3,
  1128. label: 'option 3',
  1129. },
  1130. ],
  1131. value: [2, 3],
  1132. }
  1133. },
  1134. })
  1135. await nextTick()
  1136. expect(wrapper.findAll('.el-tag').length).toBe(2)
  1137. const tagCloseIcons = wrapper.findAll('.el-tag__close')
  1138. expect(tagCloseIcons.length).toBe(1)
  1139. await tagCloseIcons[0].trigger('click')
  1140. expect(wrapper.findAll('.el-tag__close').length).toBe(0)
  1141. expect(wrapper.findAll('.el-tag').length).toBe(1)
  1142. })
  1143. it('modelValue should be deep reactive in multiple mode', async () => {
  1144. const wrapper = createSelect({
  1145. data() {
  1146. return {
  1147. multiple: true,
  1148. value: ['option_1', 'option_2', 'option_3'],
  1149. }
  1150. },
  1151. })
  1152. await nextTick()
  1153. expect(wrapper.findAll('.el-tag').length).toBe(3)
  1154. const vm = wrapper.vm as any
  1155. vm.value.splice(0, 1)
  1156. await nextTick()
  1157. expect(wrapper.findAll('.el-tag').length).toBe(2)
  1158. })
  1159. it('should reset placeholder after clear when both multiple and filterable are true', async () => {
  1160. const wrapper = createSelect({
  1161. data() {
  1162. return {
  1163. value: ['option_1'],
  1164. clearable: true,
  1165. filterable: true,
  1166. multiple: true,
  1167. }
  1168. },
  1169. })
  1170. await nextTick()
  1171. expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).exists()).toBeFalsy()
  1172. // When all tags are removed, the placeholder should be displayed
  1173. const tagCloseIcon = wrapper.find('.el-tag__close')
  1174. await tagCloseIcon.trigger('click')
  1175. expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe(
  1176. DEFAULT_PLACEHOLDER
  1177. )
  1178. // The placeholder should disappear after it is selected again
  1179. const options = getOptions()
  1180. options[0].click()
  1181. await nextTick()
  1182. expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).exists()).toBeFalsy()
  1183. // Simulate keyboard events
  1184. const selectInput = wrapper.find('input')
  1185. await selectInput.trigger('keydown', {
  1186. key: EVENT_CODE.backspace,
  1187. })
  1188. await nextTick()
  1189. expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe(
  1190. DEFAULT_PLACEHOLDER
  1191. )
  1192. })
  1193. describe('filter method', () => {
  1194. async function testFilterMethod({ multiple = false }) {
  1195. const filterMethod = vi.fn()
  1196. const wrapper = createSelect({
  1197. data() {
  1198. return {
  1199. filterable: true,
  1200. multiple,
  1201. }
  1202. },
  1203. methods: {
  1204. filterMethod,
  1205. },
  1206. })
  1207. const input = wrapper.find('input')
  1208. input.element.value = 'query'
  1209. await input.trigger('input')
  1210. expect(filterMethod).toHaveBeenCalled()
  1211. }
  1212. it('should call filter method', async () => {
  1213. await testFilterMethod({ multiple: false })
  1214. })
  1215. it('should call filter method in multiple mode', async () => {
  1216. await testFilterMethod({ multiple: true })
  1217. })
  1218. it('should re-render', async () => {
  1219. const wrapper = createSelect({
  1220. data() {
  1221. return {
  1222. multiple: true,
  1223. filterable: true,
  1224. }
  1225. },
  1226. methods: {
  1227. filterMethod() {
  1228. this.options = [
  1229. {
  1230. value: 1,
  1231. label: 'option 1',
  1232. },
  1233. {
  1234. value: 2,
  1235. label: 'option 2',
  1236. },
  1237. {
  1238. value: 3,
  1239. label: 'option 3',
  1240. },
  1241. ]
  1242. },
  1243. },
  1244. })
  1245. const input = wrapper.find('input')
  1246. input.element.value = 'query'
  1247. await input.trigger('input')
  1248. await nextTick()
  1249. input.element.value = ''
  1250. await input.trigger('input')
  1251. await nextTick()
  1252. const options = getOptions()
  1253. expect(options.length).toBe(3)
  1254. })
  1255. })
  1256. describe('remote search', () => {
  1257. async function testRemoteSearch({ multiple = false }) {
  1258. const remoteMethod = vi.fn()
  1259. const wrapper = createSelect({
  1260. data() {
  1261. return {
  1262. filterable: true,
  1263. remote: true,
  1264. multiple,
  1265. }
  1266. },
  1267. methods: {
  1268. remoteMethod,
  1269. },
  1270. })
  1271. const input = wrapper.find('input')
  1272. input.element.value = 'query'
  1273. await input.trigger('input')
  1274. expect(remoteMethod).toHaveBeenCalled()
  1275. }
  1276. it('should call remote method', async () => {
  1277. await testRemoteSearch({ multiple: false })
  1278. })
  1279. it('should call remote method in multiple mode', async () => {
  1280. await testRemoteSearch({ multiple: true })
  1281. })
  1282. })
  1283. it('keyboard operations', async () => {
  1284. const wrapper = createSelect({
  1285. data() {
  1286. return {
  1287. multiple: true,
  1288. options: [
  1289. {
  1290. value: 1,
  1291. label: 'option 1',
  1292. disabled: true,
  1293. },
  1294. {
  1295. value: 2,
  1296. label: 'option 2',
  1297. disabled: true,
  1298. },
  1299. {
  1300. value: 3,
  1301. label: 'option 3',
  1302. },
  1303. {
  1304. value: 4,
  1305. label: 'option 4',
  1306. },
  1307. {
  1308. value: 5,
  1309. label: 'option 5',
  1310. options: [
  1311. {
  1312. value: 51,
  1313. label: 'option 5-1',
  1314. },
  1315. {
  1316. value: 52,
  1317. label: 'option 5-2',
  1318. },
  1319. {
  1320. value: 53,
  1321. label: 'option 5-3',
  1322. disabled: true,
  1323. },
  1324. ],
  1325. },
  1326. {
  1327. value: 6,
  1328. label: 'option 6',
  1329. },
  1330. ],
  1331. value: [],
  1332. }
  1333. },
  1334. })
  1335. const select = wrapper.findComponent(Select)
  1336. const selectVm = select.vm as any
  1337. const vm = wrapper.vm as any
  1338. await wrapper.trigger('click')
  1339. await nextTick()
  1340. expect(selectVm.states.hoveringIndex).toBe(-1)
  1341. // should skip the disabled option
  1342. selectVm.onKeyboardNavigate('forward')
  1343. selectVm.onKeyboardNavigate('forward')
  1344. await nextTick()
  1345. expect(selectVm.states.hoveringIndex).toBe(3)
  1346. // should skip the group option
  1347. selectVm.onKeyboardNavigate('backward')
  1348. selectVm.onKeyboardNavigate('backward')
  1349. selectVm.onKeyboardNavigate('backward')
  1350. selectVm.onKeyboardNavigate('backward')
  1351. await nextTick()
  1352. expect(selectVm.states.hoveringIndex).toBe(5)
  1353. selectVm.onKeyboardNavigate('backward')
  1354. selectVm.onKeyboardNavigate('backward')
  1355. selectVm.onKeyboardNavigate('backward')
  1356. await nextTick()
  1357. // navigate to the last one
  1358. expect(selectVm.states.hoveringIndex).toBe(9)
  1359. selectVm.onKeyboardSelect()
  1360. await nextTick()
  1361. expect(vm.value).toEqual([6])
  1362. })
  1363. it('multiple select when content overflow', async () => {
  1364. const wrapper = createSelect({
  1365. data() {
  1366. return {
  1367. options: [
  1368. {
  1369. value: '选项1',
  1370. label:
  1371. '黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕',
  1372. },
  1373. {
  1374. value: '选项2',
  1375. label:
  1376. '双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶',
  1377. },
  1378. {
  1379. value: '选项3',
  1380. label: '蚵仔煎蚵仔煎蚵仔煎蚵仔煎蚵仔煎蚵仔煎',
  1381. },
  1382. {
  1383. value: '选项4',
  1384. label: '龙须面',
  1385. },
  1386. {
  1387. value: '选项5',
  1388. label: '北京烤鸭',
  1389. },
  1390. ],
  1391. }
  1392. },
  1393. })
  1394. const select = wrapper.findComponent(Select)
  1395. const selectVm = select.vm as any
  1396. const selectDom = wrapper.find('.el-select-v2__wrapper').element
  1397. const selectRect = {
  1398. height: 40,
  1399. width: 221,
  1400. x: 44,
  1401. y: 8,
  1402. top: 8,
  1403. }
  1404. const mockSelectWidth = vi
  1405. .spyOn(selectDom, 'getBoundingClientRect')
  1406. .mockReturnValue(selectRect as DOMRect)
  1407. selectVm.handleResize()
  1408. const options = getOptions()
  1409. options[0].click()
  1410. await nextTick()
  1411. options[1].click()
  1412. await nextTick()
  1413. options[2].click()
  1414. await nextTick()
  1415. const tagWrappers = wrapper.findAll('.el-select-v2__tags-text')
  1416. for (const tagWrapper of tagWrappers) {
  1417. const tagWrapperDom = tagWrapper.element
  1418. expect(
  1419. Number.parseInt(tagWrapperDom.style.maxWidth) === selectRect.width - 42
  1420. ).toBe(true)
  1421. }
  1422. mockSelectWidth.mockRestore()
  1423. })
  1424. describe('scrollbarAlwaysOn flag control the scrollbar whether always displayed', () => {
  1425. it('The default scrollbar is not always displayed', async () => {
  1426. const wrapper = createSelect()
  1427. await nextTick()
  1428. const select = wrapper.findComponent(Select)
  1429. await wrapper.trigger('click')
  1430. expect((select.vm as any).expanded).toBeTruthy()
  1431. const box = document.querySelector<HTMLElement>('.el-vl__wrapper')
  1432. expect(hasClass(box, 'always-on')).toBe(false)
  1433. })
  1434. it('set the scrollbar-always-on value to true, keep the scroll bar displayed', async () => {
  1435. const wrapper = createSelect({
  1436. data() {
  1437. return {
  1438. scrollbarAlwaysOn: true,
  1439. }
  1440. },
  1441. })
  1442. await nextTick()
  1443. const select = wrapper.findComponent(Select)
  1444. await wrapper.trigger('click')
  1445. expect((select.vm as any).expanded).toBeTruthy()
  1446. const box = document.querySelector<HTMLElement>('.el-vl__wrapper')
  1447. expect(hasClass(box, 'always-on')).toBe(true)
  1448. })
  1449. })
  1450. describe('teleported API', () => {
  1451. it('should mount on popper container', async () => {
  1452. expect(document.body.innerHTML).toBe('')
  1453. createSelect({
  1454. data() {
  1455. return {
  1456. options: [
  1457. {
  1458. value: '选项1',
  1459. label:
  1460. '黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕',
  1461. },
  1462. {
  1463. value: '选项2',
  1464. label:
  1465. '双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶',
  1466. },
  1467. {
  1468. value: '选项3',
  1469. label: '蚵仔煎蚵仔煎蚵仔煎蚵仔煎蚵仔煎蚵仔煎',
  1470. },
  1471. {
  1472. value: '选项4',
  1473. label: '龙须面',
  1474. },
  1475. {
  1476. value: '选项5',
  1477. label: '北京烤鸭',
  1478. },
  1479. ],
  1480. }
  1481. },
  1482. })
  1483. await nextTick()
  1484. const { selector } = usePopperContainerId()
  1485. expect(document.body.querySelector(selector.value)!.innerHTML).not.toBe(
  1486. ''
  1487. )
  1488. })
  1489. it('should not mount on the popper container', async () => {
  1490. expect(document.body.innerHTML).toBe('')
  1491. createSelect({
  1492. data() {
  1493. return {
  1494. teleported: false,
  1495. options: [
  1496. {
  1497. value: '选项1',
  1498. label:
  1499. '黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕',
  1500. },
  1501. {
  1502. value: '选项2',
  1503. label:
  1504. '双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶',
  1505. },
  1506. {
  1507. value: '选项3',
  1508. label: '蚵仔煎蚵仔煎蚵仔煎蚵仔煎蚵仔煎蚵仔煎',
  1509. },
  1510. {
  1511. value: '选项4',
  1512. label: '龙须面',
  1513. },
  1514. {
  1515. value: '选项5',
  1516. label: '北京烤鸭',
  1517. },
  1518. ],
  1519. }
  1520. },
  1521. })
  1522. await nextTick()
  1523. const { selector } = usePopperContainerId()
  1524. expect(document.body.querySelector(selector.value).innerHTML).toBe('')
  1525. })
  1526. })
  1527. it('filterable case-insensitive', async () => {
  1528. const wrapper = createSelect({
  1529. data: () => {
  1530. return {
  1531. filterable: true,
  1532. options: [
  1533. {
  1534. value: '1',
  1535. label: 'option 1',
  1536. },
  1537. {
  1538. value: '2',
  1539. label: 'option 2',
  1540. },
  1541. {
  1542. value: '3',
  1543. label: 'OPtion 3',
  1544. },
  1545. ],
  1546. }
  1547. },
  1548. })
  1549. await nextTick()
  1550. const select = wrapper.findComponent(Select)
  1551. const selectVm = select.vm as any
  1552. selectVm.expanded = true
  1553. await nextTick()
  1554. await rAF()
  1555. const input = wrapper.find('input')
  1556. input.element.value = 'op'
  1557. await input.trigger('input')
  1558. await nextTick()
  1559. expect(selectVm.filteredOptions.length).toBe(3)
  1560. })
  1561. })