select.test.ts 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117
  1. // @ts-nocheck
  2. import { markRaw, nextTick } from 'vue'
  3. import { mount } from '@vue/test-utils'
  4. import { afterEach, describe, expect, it, test, vi } from 'vitest'
  5. import { EVENT_CODE } from '@element-plus/constants'
  6. import { ArrowDown, CaretTop, CircleClose } from '@element-plus/icons-vue'
  7. import { usePopperContainerId } from '@element-plus/hooks'
  8. import { hasClass } from '@element-plus/utils'
  9. import { ElFormItem } from '@element-plus/components/form'
  10. import Select from '../src/select.vue'
  11. import Group from '../src/option-group.vue'
  12. import Option from '../src/option.vue'
  13. vi.mock('lodash-unified', async () => {
  14. return {
  15. ...((await vi.importActual('lodash-unified')) as Record<string, any>),
  16. debounce: vi.fn((fn) => {
  17. fn.cancel = vi.fn()
  18. fn.flush = vi.fn()
  19. return fn
  20. }),
  21. }
  22. })
  23. interface SelectProps {
  24. filterMethod?: any
  25. remoteMethod?: any
  26. multiple?: boolean
  27. clearable?: boolean
  28. filterable?: boolean
  29. allowCreate?: boolean
  30. remote?: boolean
  31. collapseTags?: boolean
  32. automaticDropdown?: boolean
  33. multipleLimit?: number
  34. popperClass?: string
  35. defaultFirstOption?: boolean
  36. fitInputWidth?: boolean
  37. size?: 'small' | 'default' | 'large'
  38. }
  39. const _mount = (template: string, data: any = () => ({}), otherObj?) =>
  40. mount(
  41. {
  42. components: {
  43. 'el-select': Select,
  44. 'el-option': Option,
  45. 'el-group-option': Group,
  46. 'el-form-item': ElFormItem,
  47. },
  48. template,
  49. data,
  50. setup() {
  51. return usePopperContainerId()
  52. },
  53. ...otherObj,
  54. },
  55. {
  56. attachTo: 'body',
  57. global: {
  58. provide: {
  59. namespace: 'el',
  60. },
  61. },
  62. }
  63. )
  64. function getOptions(): HTMLElement[] {
  65. return Array.from(
  66. document.querySelectorAll<HTMLElement>(
  67. 'body > div:last-child .el-select-dropdown__item'
  68. )
  69. )
  70. }
  71. const getSelectVm = (configs: SelectProps = {}, options?) => {
  72. ;[
  73. 'multiple',
  74. 'clearable',
  75. 'defaultFirstOption',
  76. 'filterable',
  77. 'allowCreate',
  78. 'remote',
  79. 'collapseTags',
  80. 'automaticDropdown',
  81. 'fitInputWidth',
  82. ].forEach((config) => {
  83. configs[config] = configs[config] || false
  84. })
  85. configs.multipleLimit = configs.multipleLimit || 0
  86. if (!options) {
  87. options = [
  88. {
  89. value: '选项1',
  90. label: '黄金糕',
  91. disabled: false,
  92. },
  93. {
  94. value: '选项2',
  95. label: '双皮奶',
  96. disabled: false,
  97. },
  98. {
  99. value: '选项3',
  100. label: '蚵仔煎',
  101. disabled: false,
  102. },
  103. {
  104. value: '选项4',
  105. label: '龙须面',
  106. disabled: false,
  107. },
  108. {
  109. value: '选项5',
  110. label: '北京烤鸭',
  111. disabled: false,
  112. },
  113. ]
  114. }
  115. return _mount(
  116. `
  117. <el-select
  118. ref="select"
  119. v-model="value"
  120. :multiple="multiple"
  121. :multiple-limit="multipleLimit"
  122. :popper-class="popperClass"
  123. :clearable="clearable"
  124. :default-first-option="defaultFirstOption"
  125. :filterable="filterable"
  126. :collapse-tags="collapseTags"
  127. :allow-create="allowCreate"
  128. :filterMethod="filterMethod"
  129. :remote="remote"
  130. :loading="loading"
  131. :remoteMethod="remoteMethod"
  132. :automatic-dropdown="automaticDropdown"
  133. :size="size"
  134. :fit-input-width="fitInputWidth">
  135. <el-option
  136. v-for="item in options"
  137. :label="item.label"
  138. :key="item.value"
  139. :disabled="item.disabled"
  140. :value="item.value">
  141. </el-option>
  142. </el-select>
  143. `,
  144. () => ({
  145. options,
  146. multiple: configs.multiple,
  147. multipleLimit: configs.multipleLimit,
  148. clearable: configs.clearable,
  149. defaultFirstOption: configs.defaultFirstOption,
  150. filterable: configs.filterable,
  151. collapseTags: configs.collapseTags,
  152. allowCreate: configs.allowCreate,
  153. popperClass: configs.popperClass,
  154. automaticDropdown: configs.automaticDropdown,
  155. fitInputWidth: configs.fitInputWidth,
  156. loading: false,
  157. filterMethod: configs.filterMethod,
  158. remote: configs.remote,
  159. remoteMethod: configs.remoteMethod,
  160. value: configs.multiple ? [] : '',
  161. size: configs.size || 'default',
  162. })
  163. )
  164. }
  165. const getGroupSelectVm = (configs: SelectProps = {}, options?) => {
  166. ;[
  167. 'multiple',
  168. 'clearable',
  169. 'filterable',
  170. 'allowCreate',
  171. 'remote',
  172. 'collapseTags',
  173. 'automaticDropdown',
  174. 'fitInputWidth',
  175. ].forEach((config) => {
  176. configs[config] = configs[config] || false
  177. })
  178. configs.multipleLimit = configs.multipleLimit || 0
  179. if (!options) {
  180. options = [
  181. {
  182. label: 'Australia',
  183. options: [
  184. {
  185. value: 'Sydney',
  186. label: 'Sydney',
  187. },
  188. {
  189. value: 'Melbourne',
  190. label: 'Melbourne',
  191. },
  192. ],
  193. },
  194. {
  195. label: 'China',
  196. options: [
  197. {
  198. value: 'Shanghai',
  199. label: 'Shanghai',
  200. },
  201. {
  202. value: 'Shenzhen',
  203. label: 'Shenzhen',
  204. },
  205. {
  206. value: 'Guangzhou',
  207. label: 'Guangzhou',
  208. },
  209. {
  210. value: 'Dalian',
  211. label: 'Dalian',
  212. },
  213. ],
  214. },
  215. {
  216. label: 'India',
  217. options: [
  218. {
  219. value: 'Mumbai',
  220. label: 'Mumbai',
  221. },
  222. {
  223. value: 'Delhi',
  224. label: 'Delhi',
  225. },
  226. {
  227. value: 'Bangalore',
  228. label: 'Bangalore',
  229. },
  230. ],
  231. },
  232. {
  233. label: 'Indonesia',
  234. options: [
  235. {
  236. value: 'Bandung',
  237. label: 'Bandung',
  238. },
  239. {
  240. value: 'Jakarta',
  241. label: 'Jakarta',
  242. },
  243. ],
  244. },
  245. ]
  246. }
  247. return _mount(
  248. `
  249. <el-select
  250. ref="select"
  251. v-model="value"
  252. :multiple="multiple"
  253. :multiple-limit="multipleLimit"
  254. :popper-class="popperClass"
  255. :clearable="clearable"
  256. :filterable="filterable"
  257. :collapse-tags="collapseTags"
  258. :allow-create="allowCreate"
  259. :filterMethod="filterMethod"
  260. :remote="remote"
  261. :loading="loading"
  262. :remoteMethod="remoteMethod"
  263. :automatic-dropdown="automaticDropdown"
  264. :fit-input-width="fitInputWidth">
  265. <el-group-option
  266. v-for="group in options"
  267. :key="group.label"
  268. :disabled="group.disabled"
  269. :label="group.label">
  270. <el-option
  271. v-for="item in group.options"
  272. :key="item.value"
  273. :label="item.label"
  274. :value="item.value"/>
  275. </el-group-option>
  276. </el-select>
  277. `,
  278. () => ({
  279. options,
  280. multiple: configs.multiple,
  281. multipleLimit: configs.multipleLimit,
  282. clearable: configs.clearable,
  283. filterable: configs.filterable,
  284. collapseTags: configs.collapseTags,
  285. allowCreate: configs.allowCreate,
  286. popperClass: configs.popperClass,
  287. automaticDropdown: configs.automaticDropdown,
  288. fitInputWidth: configs.fitInputWidth,
  289. loading: false,
  290. filterMethod: configs.filterMethod,
  291. remote: configs.remote,
  292. remoteMethod: configs.remoteMethod,
  293. value: configs.multiple ? [] : '',
  294. })
  295. )
  296. }
  297. describe('Select', () => {
  298. let wrapper: ReturnType<typeof _mount>
  299. const findInnerInput = () =>
  300. wrapper.find('.el-input__inner').element as HTMLInputElement
  301. afterEach(() => {
  302. document.body.innerHTML = ''
  303. })
  304. test('create', async () => {
  305. wrapper = _mount(`<el-select v-model="value"></el-select>`, () => ({
  306. value: '',
  307. }))
  308. expect(wrapper.classes()).toContain('el-select')
  309. expect(findInnerInput().placeholder).toBe('Select')
  310. const select = wrapper.findComponent({ name: 'ElSelect' })
  311. await select.trigger('mouseenter')
  312. await select.trigger('click')
  313. await nextTick()
  314. expect((select.vm as any).visible).toBe(true)
  315. })
  316. test('options rendered correctly', () => {
  317. wrapper = getSelectVm()
  318. const options = wrapper.element.querySelectorAll(
  319. '.el-select-dropdown__item'
  320. )
  321. const result = Array.prototype.every.call(options, (option, index) => {
  322. const text = option.querySelector('span').textContent
  323. const vm = wrapper.vm as any
  324. return text === vm.options[index].label
  325. })
  326. expect(result).toBe(true)
  327. })
  328. test('custom dropdown class', () => {
  329. wrapper = getSelectVm({ popperClass: 'custom-dropdown' })
  330. const dropdown = wrapper.findComponent({ name: 'ElSelectDropdown' })
  331. expect(dropdown.classes()).toContain('custom-dropdown')
  332. })
  333. test('default value', async () => {
  334. wrapper = _mount(
  335. `
  336. <el-select v-model="value">
  337. <el-option
  338. v-for="item in options"
  339. :label="item.label"
  340. :key="item.value"
  341. :value="item.value">
  342. </el-option>
  343. </el-select>
  344. `,
  345. () => ({
  346. options: [
  347. {
  348. value: '选项1',
  349. label: '黄金糕',
  350. },
  351. {
  352. value: '选项2',
  353. label: '双皮奶',
  354. },
  355. ],
  356. value: '选项2',
  357. })
  358. )
  359. await nextTick()
  360. expect(findInnerInput().value).toBe('双皮奶')
  361. })
  362. test('set default value to object', async () => {
  363. wrapper = _mount(
  364. `
  365. <el-select v-model="value">
  366. <el-option
  367. v-for="item in options"
  368. :label="item.label"
  369. :key="item.value.value"
  370. :value="item.value">
  371. </el-option>
  372. </el-select>
  373. `,
  374. () => ({
  375. options: [
  376. {
  377. value: {
  378. value: '选项1',
  379. },
  380. label: '黄金糕',
  381. },
  382. {
  383. value: {
  384. value: '选项2',
  385. },
  386. label: '双皮奶',
  387. },
  388. ],
  389. value: {
  390. value: '选项2',
  391. },
  392. })
  393. )
  394. await nextTick()
  395. expect(findInnerInput().value).toBe('双皮奶')
  396. })
  397. test('custom label', async () => {
  398. wrapper = _mount(
  399. `
  400. <el-select v-model="value">
  401. <el-option
  402. v-for="item in options"
  403. :label="item.name"
  404. :key="item.id"
  405. :value="item.id">
  406. </el-option>
  407. </el-select>
  408. `,
  409. () => ({
  410. options: [
  411. {
  412. id: 1,
  413. name: '黄金糕',
  414. },
  415. {
  416. id: 2,
  417. name: '双皮奶',
  418. },
  419. ],
  420. value: 2,
  421. })
  422. )
  423. await nextTick()
  424. expect(findInnerInput().value).toBe('双皮奶')
  425. })
  426. test('custom label with object', async () => {
  427. wrapper = _mount(
  428. `
  429. <el-select v-model="value" value-key="id">
  430. <el-option
  431. v-for="item in options"
  432. :label="item.name"
  433. :key="item.id"
  434. :value="item">
  435. </el-option>
  436. </el-select>
  437. `,
  438. () => ({
  439. options: [
  440. {
  441. id: 1,
  442. name: '黄金糕',
  443. },
  444. {
  445. id: 2,
  446. name: '双皮奶',
  447. },
  448. ],
  449. value: {
  450. id: 2,
  451. },
  452. })
  453. )
  454. await nextTick()
  455. expect(findInnerInput().value).toBe('双皮奶')
  456. })
  457. test('sync set value and options', async () => {
  458. wrapper = _mount(
  459. `
  460. <el-select v-model="value">
  461. <el-option
  462. v-for="item in options"
  463. :label="item.label"
  464. :key="item.value"
  465. :value="item.value">
  466. </el-option>
  467. </el-select>
  468. `,
  469. () => ({
  470. options: [
  471. {
  472. value: '选项1',
  473. label: '黄金糕',
  474. },
  475. {
  476. value: '选项2',
  477. label: '双皮奶',
  478. },
  479. ],
  480. value: '选项2',
  481. })
  482. )
  483. const vm = wrapper.vm as any
  484. vm.options = [
  485. {
  486. value: '选项1',
  487. label: '黄金糕',
  488. },
  489. ]
  490. vm.value = '选项1'
  491. await nextTick()
  492. expect(findInnerInput().value).toBe('黄金糕')
  493. })
  494. test('single select', async () => {
  495. wrapper = _mount(
  496. `
  497. <el-select v-model="value" @change="handleChange">
  498. <el-option
  499. v-for="item in options"
  500. :label="item.label"
  501. :key="item.value"
  502. :value="item.value">
  503. <p>{{item.label}} {{item.value}}</p>
  504. </el-option>
  505. </el-select>
  506. `,
  507. () => ({
  508. options: [
  509. {
  510. value: '选项1',
  511. label: '黄金糕',
  512. },
  513. {
  514. value: '选项2',
  515. label: '双皮奶',
  516. },
  517. {
  518. value: '选项3',
  519. label: '蚵仔煎',
  520. },
  521. {
  522. value: '选项4',
  523. label: '龙须面',
  524. },
  525. {
  526. value: '选项5',
  527. label: '北京烤鸭',
  528. },
  529. ],
  530. value: '',
  531. count: 0,
  532. }),
  533. {
  534. methods: {
  535. handleChange() {
  536. this.count++
  537. },
  538. },
  539. }
  540. )
  541. await wrapper.find('.select-trigger').trigger('click')
  542. const options = getOptions()
  543. const vm = wrapper.vm as any
  544. expect(vm.value).toBe('')
  545. expect(findInnerInput().value).toBe('')
  546. options[2].click()
  547. await nextTick()
  548. expect(vm.value).toBe('选项3')
  549. expect(findInnerInput().value).toBe('蚵仔煎')
  550. expect(vm.count).toBe(1)
  551. options[4].click()
  552. await nextTick()
  553. expect(vm.value).toBe('选项5')
  554. expect(findInnerInput().value).toBe('北京烤鸭')
  555. expect(vm.count).toBe(2)
  556. })
  557. test('disabled option', async () => {
  558. wrapper = getSelectVm()
  559. const vm = wrapper.vm as any
  560. wrapper.find('.select-trigger').trigger('click')
  561. vm.options[1].disabled = true
  562. await nextTick()
  563. const options = getOptions()
  564. expect(options[1].className).toContain('is-disabled')
  565. options[1].click()
  566. await nextTick()
  567. expect(vm.value).toBe('')
  568. })
  569. test('disabled select', () => {
  570. wrapper = _mount(`<el-select disabled></el-select>`)
  571. expect(wrapper.find('.el-input').classes()).toContain('is-disabled')
  572. })
  573. test('group disabled option', () => {
  574. const optionGroupData = [
  575. {
  576. label: 'Australia',
  577. disabled: true,
  578. options: [
  579. {
  580. value: 'Sydney',
  581. label: 'Sydney',
  582. },
  583. {
  584. value: 'Melbourne',
  585. label: 'Melbourne',
  586. },
  587. ],
  588. },
  589. ]
  590. wrapper = getGroupSelectVm({}, optionGroupData)
  591. const options = wrapper.findAllComponents(Option)
  592. expect(options[0].classes('is-disabled')).toBeTruthy()
  593. })
  594. test('keyboard operations when option-group is disabled', async () => {
  595. const optionGroupData = [
  596. {
  597. label: 'Australia',
  598. disabled: true,
  599. options: [
  600. {
  601. value: 'Sydney',
  602. label: 'Sydney',
  603. },
  604. {
  605. value: 'Melbourne',
  606. label: 'Melbourne',
  607. },
  608. ],
  609. },
  610. {
  611. label: 'China',
  612. options: [
  613. {
  614. value: 'Shanghai',
  615. label: 'Shanghai',
  616. },
  617. {
  618. value: 'Shenzhen',
  619. label: 'Shenzhen',
  620. },
  621. {
  622. value: 'Guangzhou',
  623. label: 'Guangzhou',
  624. },
  625. {
  626. value: 'Dalian',
  627. label: 'Dalian',
  628. },
  629. ],
  630. },
  631. ]
  632. wrapper = getGroupSelectVm({}, optionGroupData)
  633. const select = wrapper.findComponent({ name: 'ElSelect' })
  634. const vm = select.vm as any
  635. let i = 8
  636. while (i--) {
  637. vm.navigateOptions('next')
  638. }
  639. vm.navigateOptions('prev')
  640. vm.navigateOptions('prev')
  641. vm.navigateOptions('prev')
  642. await nextTick()
  643. vm.selectOption()
  644. await nextTick()
  645. expect((wrapper.vm as any).value).toBe('Dalian')
  646. })
  647. test('visible event', async () => {
  648. wrapper = _mount(
  649. `
  650. <el-select v-model="value" @visible-change="handleVisibleChange">
  651. <el-option
  652. v-for="item in options"
  653. :label="item.label"
  654. :key="item.value"
  655. :value="item.value">
  656. </el-option>
  657. </el-select>`,
  658. () => ({
  659. options: [],
  660. value: '',
  661. visible: '',
  662. }),
  663. {
  664. methods: {
  665. handleVisibleChange(val) {
  666. this.visible = val
  667. },
  668. },
  669. }
  670. )
  671. const select = wrapper.findComponent({ name: 'ElSelect' })
  672. const vm = wrapper.vm as any
  673. const selectVm = select.vm as any
  674. selectVm.visible = true
  675. await selectVm.$nextTick()
  676. expect(vm.visible).toBe(true)
  677. })
  678. test('keyboard operations', async () => {
  679. vi.useFakeTimers()
  680. wrapper = getSelectVm()
  681. const select = wrapper.findComponent({ name: 'ElSelect' })
  682. const vm = select.vm as any
  683. let i = 8
  684. while (i--) {
  685. vm.navigateOptions('next')
  686. }
  687. vm.navigateOptions('prev')
  688. vm.navigateOptions('prev')
  689. vm.navigateOptions('prev')
  690. await nextTick()
  691. expect(vm.hoverIndex).toBe(3)
  692. vm.selectOption()
  693. await nextTick()
  694. expect((wrapper.vm as any).value).toBe('选项4')
  695. vm.toggleMenu()
  696. vi.runAllTimers()
  697. await nextTick()
  698. vm.toggleMenu()
  699. await nextTick()
  700. expect(vm.hoverIndex).toBe(3)
  701. vi.useRealTimers()
  702. })
  703. test('clearable', async () => {
  704. wrapper = getSelectVm({ clearable: true })
  705. const select = wrapper.findComponent({ name: 'ElSelect' })
  706. const vm = wrapper.vm as any
  707. const selectVm = select.vm as any
  708. vm.value = '选项1'
  709. await nextTick()
  710. selectVm.inputHovering = true
  711. await selectVm.$nextTick()
  712. const iconClear = wrapper.findComponent(CircleClose)
  713. expect(iconClear.exists()).toBe(true)
  714. await iconClear.trigger('click')
  715. expect(vm.value).toBe('')
  716. })
  717. test('suffix icon', async () => {
  718. wrapper = _mount(`<el-select></el-select>`)
  719. let suffixIcon = wrapper.findComponent(ArrowDown)
  720. expect(suffixIcon.exists()).toBe(true)
  721. await wrapper.setProps({ suffixIcon: markRaw(CaretTop) })
  722. suffixIcon = wrapper.findComponent(CaretTop)
  723. expect(suffixIcon.exists()).toBe(true)
  724. })
  725. test('test remote show suffix', async () => {
  726. wrapper = _mount(`<el-select></el-select>`)
  727. await wrapper.setProps({
  728. remote: true,
  729. filters: true,
  730. remoteShowSuffix: true,
  731. })
  732. const suffixIcon = wrapper.findComponent(ArrowDown)
  733. expect(suffixIcon.exists()).toBe(true)
  734. })
  735. test('fitInputWidth', async () => {
  736. wrapper = getSelectVm({ fitInputWidth: true })
  737. const selectWrapper = wrapper.findComponent({ name: 'ElSelect' })
  738. const selectDom = selectWrapper.element
  739. const selectRect = {
  740. height: 40,
  741. width: 221,
  742. x: 44,
  743. y: 8,
  744. top: 8,
  745. }
  746. const mockSelectWidth = vi
  747. .spyOn(selectDom, 'getBoundingClientRect')
  748. .mockReturnValue(selectRect as DOMRect)
  749. const dropdown = wrapper.findComponent({ name: 'ElSelectDropdown' })
  750. dropdown.vm.minWidth = `${
  751. selectWrapper.element.getBoundingClientRect().width
  752. }px`
  753. await nextTick()
  754. expect(dropdown.element.style.width).toBe('221px')
  755. mockSelectWidth.mockRestore()
  756. })
  757. test('check default first option', async () => {
  758. wrapper = getSelectVm({
  759. filterable: true,
  760. defaultFirstOption: true,
  761. })
  762. const select = wrapper.findComponent({ name: 'ElSelect' })
  763. const selectVm = select.vm as any
  764. const input = wrapper.find('input')
  765. input.element.focus()
  766. expect(selectVm.hoverIndex).toBe(0)
  767. selectVm.navigateOptions('next')
  768. expect(selectVm.hoverIndex).toBe(1)
  769. })
  770. test('check default first option when the very first option is disabled', async () => {
  771. const demoOptions = [
  772. {
  773. value: 'HTML',
  774. label: 'HTML',
  775. disabled: true,
  776. },
  777. {
  778. value: 'CSS',
  779. label: 'CSS',
  780. disabled: false,
  781. },
  782. {
  783. value: 'JavaScript',
  784. label: 'JavaScript',
  785. disabled: false,
  786. },
  787. ]
  788. wrapper = getSelectVm(
  789. {
  790. filterable: true,
  791. defaultFirstOption: true,
  792. },
  793. demoOptions
  794. )
  795. const select = wrapper.findComponent({ name: 'ElSelect' })
  796. const selectVm = select.vm as any
  797. const input = wrapper.find('input')
  798. input.element.focus()
  799. expect(selectVm.hoverIndex).toBe(1) // index 0 was skipped
  800. selectVm.navigateOptions('next')
  801. expect(selectVm.hoverIndex).toBe(2)
  802. selectVm.navigateOptions('next')
  803. expect(selectVm.hoverIndex).toBe(1) // index 0 was skipped
  804. })
  805. test('allow create', async () => {
  806. wrapper = getSelectVm({ filterable: true, allowCreate: true })
  807. const select = wrapper.findComponent({ name: 'ElSelect' })
  808. const selectVm = select.vm as any
  809. const input = wrapper.find('input')
  810. input.element.focus()
  811. selectVm.selectedLabel = 'new'
  812. selectVm.debouncedOnInputChange()
  813. await nextTick()
  814. const options = [...getOptions()]
  815. const target = options.find((option) => option.textContent === 'new')
  816. target.click()
  817. expect((wrapper.vm as any).value).toBe('new')
  818. })
  819. test('allow create async option', async () => {
  820. const options = [
  821. {
  822. value: '选项1',
  823. label: '黄金糕',
  824. },
  825. {
  826. value: '选项2',
  827. label: '双皮奶',
  828. },
  829. ]
  830. wrapper = _mount(
  831. `
  832. <el-select
  833. v-model="value"
  834. filterable
  835. allowCreate
  836. >
  837. <el-option
  838. v-for="item in options"
  839. :label="item.label"
  840. :key="item.value"
  841. :value="item.value">
  842. </el-option>
  843. </el-select>
  844. `,
  845. () => ({
  846. options: [],
  847. value: '选项2',
  848. })
  849. )
  850. await nextTick()
  851. expect(getOptions()).toHaveLength(1)
  852. await wrapper.setData({
  853. options,
  854. })
  855. expect(getOptions()).toHaveLength(options.length)
  856. })
  857. test('multiple select', async () => {
  858. wrapper = getSelectVm({ multiple: true })
  859. await wrapper.find('.select-trigger').trigger('click')
  860. const options = getOptions()
  861. const vm = wrapper.vm as any
  862. vm.value = ['选项1']
  863. nextTick()
  864. options[1].click()
  865. await nextTick()
  866. options[3].click()
  867. await nextTick()
  868. expect(vm.value.includes('选项2') && vm.value.includes('选项4')).toBe(true)
  869. const tagCloseIcons = wrapper.findAll('.el-tag__close')
  870. await tagCloseIcons[0].trigger('click')
  871. expect(vm.value.indexOf('选项1')).toBe(-1)
  872. })
  873. test('multiple select when content overflow', async () => {
  874. wrapper = _mount(
  875. `
  876. <el-select v-model="selectedList" multiple placeholder="请选择">
  877. <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
  878. </el-option>
  879. </el-select>
  880. `,
  881. () => ({
  882. options: [
  883. {
  884. value: '选项1',
  885. label:
  886. '黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕',
  887. },
  888. {
  889. value: '选项2',
  890. label:
  891. '双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶',
  892. },
  893. {
  894. value: '选项3',
  895. label: '蚵仔煎蚵仔煎蚵仔煎蚵仔煎蚵仔煎蚵仔煎',
  896. },
  897. {
  898. value: '选项4',
  899. label: '龙须面',
  900. },
  901. {
  902. value: '选项5',
  903. label: '北京烤鸭',
  904. },
  905. ],
  906. selectedList: [],
  907. })
  908. )
  909. await wrapper.find('.select-trigger').trigger('click')
  910. const options = getOptions()
  911. const selectWrapper = wrapper.findComponent(Select)
  912. const inputWrapper = selectWrapper.findComponent({ ref: 'reference' })
  913. const inputDom = inputWrapper.element
  914. const mockInputWidth = vi
  915. .spyOn(inputDom as HTMLElement, 'offsetWidth', 'get')
  916. .mockReturnValue(200)
  917. selectWrapper.vm.handleResize()
  918. options[0].click()
  919. await nextTick()
  920. options[1].click()
  921. await nextTick()
  922. options[2].click()
  923. await nextTick()
  924. const tagWrappers = wrapper.findAll('.el-select__tags-text')
  925. for (const tagWrapper of tagWrappers) {
  926. const tagWrapperDom = tagWrapper.element
  927. expect(Number.parseInt(tagWrapperDom.style.maxWidth) === 200 - 75).toBe(
  928. true
  929. )
  930. }
  931. mockInputWidth.mockRestore()
  932. })
  933. test('multiple select with collapseTags when content overflow', async () => {
  934. wrapper = _mount(
  935. `
  936. <el-select v-model="selectedList" multiple collapseTags placeholder="请选择">
  937. <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
  938. </el-option>
  939. </el-select>
  940. `,
  941. () => ({
  942. options: [
  943. {
  944. value: '选项1',
  945. label:
  946. '黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕黄金糕',
  947. },
  948. {
  949. value: '选项2',
  950. label:
  951. '双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶双皮奶',
  952. },
  953. {
  954. value: '选项3',
  955. label: '蚵仔煎蚵仔煎蚵仔煎蚵仔煎蚵仔煎蚵仔煎',
  956. },
  957. {
  958. value: '选项4',
  959. label: '龙须面',
  960. },
  961. {
  962. value: '选项5',
  963. label: '北京烤鸭',
  964. },
  965. ],
  966. selectedList: [],
  967. })
  968. )
  969. await wrapper.find('.select-trigger').trigger('click')
  970. const options = getOptions()
  971. const selectWrapper = wrapper.findComponent(Select)
  972. const inputWrapper = selectWrapper.findComponent({ ref: 'reference' })
  973. const inputDom = inputWrapper.element
  974. const mockInputWidth = vi
  975. .spyOn(inputDom as HTMLElement, 'offsetWidth', 'get')
  976. .mockReturnValue(200)
  977. selectWrapper.vm.handleResize()
  978. options[0].click()
  979. await nextTick()
  980. options[1].click()
  981. await nextTick()
  982. options[2].click()
  983. await nextTick()
  984. const tagWrappers = wrapper.findAll('.el-select__tags-text')
  985. const tagWrapperDom = tagWrappers[0].element
  986. expect(Number.parseInt(tagWrapperDom.style.maxWidth) === 200 - 123).toBe(
  987. true
  988. )
  989. mockInputWidth.mockRestore()
  990. })
  991. test('multiple select with collapseTagsTooltip', async () => {
  992. wrapper = _mount(
  993. `
  994. <el-select v-model="selectedList" multiple collapseTags collapse-tags-tooltip placeholder="请选择">
  995. <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
  996. </el-option>
  997. </el-select>
  998. `,
  999. () => ({
  1000. options: [
  1001. {
  1002. value: '选项1',
  1003. label: '黄金糕',
  1004. },
  1005. {
  1006. value: '选项2',
  1007. label: '双皮奶',
  1008. },
  1009. {
  1010. value: '选项3',
  1011. label: '蚵仔煎',
  1012. },
  1013. {
  1014. value: '选项4',
  1015. label: '龙须面',
  1016. },
  1017. {
  1018. value: '选项5',
  1019. label: '北京烤鸭',
  1020. },
  1021. ],
  1022. selectedList: [],
  1023. })
  1024. )
  1025. await wrapper.find('.select-trigger').trigger('click')
  1026. const options = getOptions()
  1027. options[0].click()
  1028. await nextTick()
  1029. options[1].click()
  1030. await nextTick()
  1031. options[2].click()
  1032. await nextTick()
  1033. const triggerWrappers = wrapper.findAll('.el-tooltip__trigger')
  1034. expect(triggerWrappers[0]).toBeDefined()
  1035. const tags = document.querySelectorAll('.el-select__tags-text')
  1036. expect(tags.length).toBe(4)
  1037. expect(tags[3].textContent).toBe('蚵仔煎')
  1038. })
  1039. test('multiple remove-tag', async () => {
  1040. wrapper = _mount(
  1041. `
  1042. <el-select v-model="value" multiple @remove-tag="handleRemoveTag">
  1043. <el-option
  1044. v-for="item in options"
  1045. :label="item.label"
  1046. :key="item.value"
  1047. :value="item.value">
  1048. <p>{{item.label}} {{item.value}}</p>
  1049. </el-option>
  1050. </el-select>
  1051. `,
  1052. () => ({
  1053. options: [
  1054. {
  1055. value: '选项1',
  1056. label: '黄金糕',
  1057. },
  1058. {
  1059. value: '选项2',
  1060. label: '双皮奶',
  1061. },
  1062. {
  1063. value: '选项3',
  1064. label: '蚵仔煎',
  1065. },
  1066. {
  1067. value: '选项4',
  1068. label: '龙须面',
  1069. },
  1070. {
  1071. value: '选项5',
  1072. label: '北京烤鸭',
  1073. },
  1074. ],
  1075. value: ['选项1', '选项2'],
  1076. }),
  1077. {
  1078. methods: {
  1079. handleRemoveTag() {
  1080. // pass
  1081. },
  1082. },
  1083. }
  1084. )
  1085. const vm = wrapper.vm as any
  1086. await nextTick()
  1087. expect(vm.value.length).toBe(2)
  1088. const tagCloseIcons = wrapper.findAll('.el-tag__close')
  1089. await tagCloseIcons[1].trigger('click')
  1090. expect(vm.value.length).toBe(1)
  1091. await tagCloseIcons[0].trigger('click')
  1092. expect(vm.value.length).toBe(0)
  1093. })
  1094. test('multiple limit', async () => {
  1095. wrapper = getSelectVm({ multiple: true, multipleLimit: 1 })
  1096. const vm = wrapper.vm as any
  1097. await wrapper.find('.select-trigger').trigger('click')
  1098. const options = getOptions()
  1099. options[1].click()
  1100. await nextTick()
  1101. expect(vm.value.includes('选项2')).toBe(true)
  1102. options[3].click()
  1103. await nextTick()
  1104. expect(vm.value.indexOf('选项4')).toBe(-1)
  1105. })
  1106. test('event:focus & blur', async () => {
  1107. const handleFocus = vi.fn()
  1108. const handleBlur = vi.fn()
  1109. wrapper = _mount(
  1110. `<el-select
  1111. @focus="handleFocus"
  1112. @blur="handleBlur" />`,
  1113. () => ({
  1114. handleFocus,
  1115. handleBlur,
  1116. })
  1117. )
  1118. const select = wrapper.findComponent({ name: 'ElSelect' })
  1119. const input = select.find('input')
  1120. expect(input.exists()).toBe(true)
  1121. await input.trigger('focus')
  1122. expect(handleFocus).toHaveBeenCalled()
  1123. await input.trigger('blur')
  1124. expect(handleBlur).toHaveBeenCalled()
  1125. })
  1126. test('event:focus & blur for multiple & filterable select', async () => {
  1127. const handleFocus = vi.fn()
  1128. const handleBlur = vi.fn()
  1129. wrapper = _mount(
  1130. `
  1131. <el-select
  1132. @focus="handleFocus"
  1133. @blur="handleBlur"
  1134. multiple
  1135. filterable
  1136. />`,
  1137. () => ({
  1138. handleFocus,
  1139. handleBlur,
  1140. })
  1141. )
  1142. const select = wrapper.findComponent({ name: 'ElSelect' })
  1143. const input = select.find('input')
  1144. expect(input.exists()).toBe(true)
  1145. await input.trigger('focus')
  1146. expect(handleFocus).toHaveBeenCalled()
  1147. await input.trigger('blur')
  1148. expect(handleBlur).toHaveBeenCalled()
  1149. })
  1150. test('should not open popper when automatic-dropdown not set', async () => {
  1151. wrapper = getSelectVm()
  1152. const select = wrapper.findComponent({ name: 'ElSelect' })
  1153. await select
  1154. .findComponent({ ref: 'reference' })
  1155. .find('input')
  1156. .element.focus()
  1157. expect((select.vm as any).visible).toBe(false)
  1158. })
  1159. test('should open popper when automatic-dropdown is set', async () => {
  1160. wrapper = getSelectVm({ automaticDropdown: true })
  1161. const select = wrapper.findComponent({ name: 'ElSelect' })
  1162. await select
  1163. .findComponent({ ref: 'reference' })
  1164. .find('input')
  1165. .trigger('focus')
  1166. expect((select.vm as any).visible).toBe(true)
  1167. })
  1168. test('only emit change on user input', async () => {
  1169. let callCount = 0
  1170. wrapper = _mount(
  1171. `
  1172. <el-select v-model="value" @change="change" ref="select">
  1173. <el-option label="1" value="1" />
  1174. <el-option label="2" value="2" />
  1175. <el-option label="3" value="3" />
  1176. </el-select>`,
  1177. () => ({
  1178. value: '1',
  1179. change: () => ++callCount,
  1180. })
  1181. )
  1182. expect(callCount).toBe(0)
  1183. await wrapper.find('.select-trigger').trigger('click')
  1184. const options = getOptions()
  1185. options[2].click()
  1186. expect(callCount).toBe(1)
  1187. })
  1188. test('render slot `empty`', async () => {
  1189. wrapper = _mount(
  1190. `
  1191. <el-select v-model="value">
  1192. <template #empty>
  1193. <div class="empty-slot">EmptySlot</div>
  1194. </template>
  1195. </el-select>`,
  1196. () => ({
  1197. value: '1',
  1198. })
  1199. )
  1200. await wrapper.find('.select-trigger').trigger('click')
  1201. expect(
  1202. document.querySelector<HTMLElement>('.empty-slot')?.textContent
  1203. ).toBe('EmptySlot')
  1204. })
  1205. test('should set placeholder to label of selected option when filterable is true and multiple is false', async () => {
  1206. wrapper = _mount(
  1207. `
  1208. <el-select ref="select" v-model="value" filterable>
  1209. <el-option label="test" value="test" />
  1210. </el-select>`,
  1211. () => ({ value: 'test' })
  1212. )
  1213. const vm = wrapper.vm as any
  1214. await wrapper.trigger('mouseenter')
  1215. await wrapper.trigger('click')
  1216. const selectVm = wrapper.findComponent({ name: 'ElSelect' }).vm as any
  1217. expect(selectVm.visible).toBe(true)
  1218. expect(findInnerInput().placeholder).toBe('test')
  1219. expect(vm.value).toBe('test')
  1220. })
  1221. test('default value is null or undefined', async () => {
  1222. wrapper = _mount(
  1223. `
  1224. <el-select v-model="value">
  1225. <el-option
  1226. v-for="item in options"
  1227. :label="item.label"
  1228. :key="item.value"
  1229. :value="item.value">
  1230. </el-option>
  1231. </el-select>`,
  1232. () => ({
  1233. options: [
  1234. {
  1235. value: '选项1',
  1236. label: '黄金糕',
  1237. },
  1238. {
  1239. value: '选项2',
  1240. label: '双皮奶',
  1241. },
  1242. ],
  1243. value: undefined,
  1244. })
  1245. )
  1246. const vm = wrapper.vm as any
  1247. vm.value = null
  1248. await nextTick()
  1249. expect(findInnerInput().value).toBe('')
  1250. vm.value = '选项1'
  1251. await nextTick()
  1252. expect(findInnerInput().value).toBe('黄金糕')
  1253. })
  1254. test('emptyText error show', async () => {
  1255. wrapper = _mount(
  1256. `
  1257. <el-select :model-value="value" filterable placeholder="Select">
  1258. <el-option
  1259. v-for="item in options"
  1260. :key="item.value"
  1261. :label="item.label"
  1262. :value="item.value">
  1263. </el-option>
  1264. </el-select>`,
  1265. () => ({
  1266. options: [
  1267. {
  1268. value: 'Option1',
  1269. label: 'Option1',
  1270. },
  1271. {
  1272. value: 'Option2',
  1273. label: 'Option2',
  1274. },
  1275. {
  1276. value: 'Option3',
  1277. label: 'Option3',
  1278. },
  1279. {
  1280. value: 'Option4',
  1281. label: 'Option4',
  1282. },
  1283. {
  1284. value: 'Option5',
  1285. label: 'Option5',
  1286. },
  1287. ],
  1288. value: 'test',
  1289. })
  1290. )
  1291. const select = wrapper.findComponent({ name: 'ElSelect' })
  1292. await select.trigger('mouseenter')
  1293. await select.trigger('click')
  1294. await nextTick()
  1295. expect(
  1296. !!(document.querySelector('.el-select__popper') as HTMLElement).style
  1297. .display
  1298. ).toBeFalsy()
  1299. expect(wrapper.findAll('.el-select-dropdown__empty').length).toBe(0)
  1300. })
  1301. test('multiple select with remote load', async () => {
  1302. vi.useFakeTimers()
  1303. wrapper = mount({
  1304. template: `
  1305. <el-select
  1306. v-model="value"
  1307. multiple
  1308. filterable
  1309. remote
  1310. reserve-keyword
  1311. placeholder="请输入关键词"
  1312. :remote-method="remoteMethod"
  1313. :loading="loading"
  1314. >
  1315. <el-option
  1316. v-for="item in options"
  1317. :key="item.value"
  1318. :label="item.label"
  1319. :value="item"
  1320. />
  1321. </el-select>`,
  1322. components: { ElSelect: Select, ElOption: Option },
  1323. data() {
  1324. return {
  1325. options: [],
  1326. value: [],
  1327. list: [],
  1328. loading: false,
  1329. states: [
  1330. 'Alabama',
  1331. 'Alaska',
  1332. 'Arizona',
  1333. 'Arkansas',
  1334. 'California',
  1335. 'Colorado',
  1336. 'Connecticut',
  1337. 'Delaware',
  1338. 'Florida',
  1339. 'Georgia',
  1340. 'Hawaii',
  1341. 'Idaho',
  1342. 'Illinois',
  1343. 'Indiana',
  1344. 'Iowa',
  1345. 'Kansas',
  1346. 'Kentucky',
  1347. 'Louisiana',
  1348. 'Maine',
  1349. 'Maryland',
  1350. 'Massachusetts',
  1351. 'Michigan',
  1352. 'Minnesota',
  1353. 'Mississippi',
  1354. 'Missouri',
  1355. 'Montana',
  1356. 'Nebraska',
  1357. 'Nevada',
  1358. 'New Hampshire',
  1359. 'New Jersey',
  1360. 'New Mexico',
  1361. 'New York',
  1362. 'North Carolina',
  1363. 'North Dakota',
  1364. 'Ohio',
  1365. 'Oklahoma',
  1366. 'Oregon',
  1367. 'Pennsylvania',
  1368. 'Rhode Island',
  1369. 'South Carolina',
  1370. 'South Dakota',
  1371. 'Tennessee',
  1372. 'Texas',
  1373. 'Utah',
  1374. 'Vermont',
  1375. 'Virginia',
  1376. 'Washington',
  1377. 'West Virginia',
  1378. 'Wisconsin',
  1379. 'Wyoming',
  1380. ],
  1381. }
  1382. },
  1383. mounted() {
  1384. this.list = this.states.map((item) => {
  1385. return { value: `value:${item}`, label: `label:${item}` }
  1386. })
  1387. },
  1388. methods: {
  1389. remoteMethod(query) {
  1390. if (query !== '') {
  1391. this.loading = true
  1392. setTimeout(() => {
  1393. this.loading = false
  1394. this.options = this.list.filter((item) => {
  1395. return item.label.toLowerCase().includes(query.toLowerCase())
  1396. })
  1397. }, 200)
  1398. } else {
  1399. this.options = []
  1400. }
  1401. },
  1402. },
  1403. })
  1404. const select = wrapper.findComponent({ name: 'ElSelect' }).vm
  1405. select.debouncedQueryChange({
  1406. target: {
  1407. value: '',
  1408. },
  1409. })
  1410. select.debouncedQueryChange({
  1411. target: {
  1412. value: 'a',
  1413. },
  1414. })
  1415. vi.runAllTimers()
  1416. await nextTick()
  1417. let options = getOptions()
  1418. options[0].click()
  1419. await nextTick()
  1420. select.debouncedQueryChange({
  1421. target: {
  1422. value: 'n',
  1423. },
  1424. })
  1425. vi.runAllTimers()
  1426. await nextTick()
  1427. options = getOptions()
  1428. options[5].click()
  1429. await nextTick()
  1430. expect(select.selected.length === 2).toBeTruthy()
  1431. expect(select.selected[0].currentLabel !== '').toBeTruthy()
  1432. expect(select.selected[1].currentLabel !== '').toBeTruthy()
  1433. vi.useRealTimers()
  1434. })
  1435. test('disabled group', async () => {
  1436. wrapper = _mount(
  1437. `
  1438. <el-select v-model="value">
  1439. <el-group-option
  1440. v-for="group in options"
  1441. :key="group.label"
  1442. :label="group.label"
  1443. :disabled="group.disabled">
  1444. <el-option
  1445. v-for="item in group.options"
  1446. :key="item.value"
  1447. :label="item.label"
  1448. :value="item.value">
  1449. </el-option>
  1450. </el-group-option>
  1451. </el-select>`,
  1452. () => ({
  1453. options: [
  1454. {
  1455. label: 'Popular cities',
  1456. options: [
  1457. { value: 'Shanghai', label: 'Shanghai' },
  1458. { value: 'Beijing', label: 'Beijing' },
  1459. ],
  1460. },
  1461. {
  1462. label: 'City name',
  1463. options: [
  1464. { value: 'Chengdu', label: 'Chengdu' },
  1465. { value: 'Shenzhen', label: 'Shenzhen' },
  1466. { value: 'Guangzhou', label: 'Guangzhou' },
  1467. { value: 'Dalian', label: 'Dalian' },
  1468. ],
  1469. },
  1470. ],
  1471. value: '',
  1472. })
  1473. )
  1474. const vm = wrapper.vm as any
  1475. wrapper.find('.select-trigger').trigger('click')
  1476. await nextTick()
  1477. vm.options[1].disabled = true
  1478. await nextTick()
  1479. const options = getOptions()
  1480. expect(options[0].className).not.toContain('is-disabled')
  1481. expect(options[2].className).toContain('is-disabled')
  1482. options[0].click()
  1483. await nextTick()
  1484. expect(vm.value).toBe('Shanghai')
  1485. options[2].click()
  1486. await nextTick()
  1487. expect(vm.value).toBe('Shanghai')
  1488. })
  1489. test('tag of disabled option is not closable', async () => {
  1490. wrapper = _mount(
  1491. `
  1492. <el-select v-model="vendors" multiple :collapse-tags="isCollapsed" :clearable="isClearable" placeholder="Select Business Unit">
  1493. <el-option
  1494. v-for="(vendor, index) in options"
  1495. :key="index"
  1496. :value="index + 1"
  1497. :label="vendor.name"
  1498. :disabled="vendor.isDisabled"
  1499. >
  1500. </el-option>
  1501. </el-select>`,
  1502. () => ({
  1503. vendors: [2, 3, 4],
  1504. isCollapsed: false,
  1505. isClearable: false,
  1506. options: [
  1507. { name: 'Test 1', isDisabled: false },
  1508. { name: 'Test 2', isDisabled: true },
  1509. { name: 'Test 3', isDisabled: false },
  1510. { name: 'Test 4', isDisabled: true },
  1511. ],
  1512. })
  1513. )
  1514. const vm = wrapper.vm as any
  1515. await nextTick()
  1516. const selectVm = wrapper.findComponent({ name: 'ElSelect' }).vm as any
  1517. expect(wrapper.findAll('.el-tag').length).toBe(3)
  1518. const tagCloseIcons = wrapper.findAll('.el-tag__close')
  1519. expect(tagCloseIcons.length).toBe(1)
  1520. await tagCloseIcons[0].trigger('click')
  1521. expect(wrapper.findAll('.el-tag__close').length).toBe(0)
  1522. expect(wrapper.findAll('.el-tag').length).toBe(2)
  1523. //test if is clearable
  1524. vm.isClearable = true
  1525. vm.vendors = [2, 3, 4]
  1526. await nextTick()
  1527. selectVm.inputHovering = true
  1528. await selectVm.$nextTick()
  1529. const iconClear = wrapper.findComponent(CircleClose)
  1530. expect(wrapper.findAll('.el-tag').length).toBe(3)
  1531. await iconClear.trigger('click')
  1532. expect(wrapper.findAll('.el-tag').length).toBe(2)
  1533. // test for collapse select
  1534. vm.vendors = [1, 2, 4]
  1535. vm.isCollapsed = true
  1536. vm.isClearable = false
  1537. await nextTick()
  1538. expect(
  1539. wrapper.findAll('.el-tag').filter((item) => {
  1540. return !hasClass(item.element, 'in-tooltip')
  1541. }).length
  1542. ).toBe(2)
  1543. await wrapper.find('.el-tag__close').trigger('click')
  1544. expect(
  1545. wrapper.findAll('.el-tag').filter((item) => {
  1546. return !hasClass(item.element, 'in-tooltip')
  1547. }).length
  1548. ).toBe(2)
  1549. expect(wrapper.findAll('.el-tag__close').length).toBe(0)
  1550. // test for collapse select if is clearable
  1551. vm.vendors = [1, 2, 4]
  1552. vm.isCollapsed = true
  1553. vm.isClearable = true
  1554. await nextTick()
  1555. expect(
  1556. wrapper.findAll('.el-tag__close').filter((item) => {
  1557. return !hasClass(item.element.parentElement, 'in-tooltip')
  1558. }).length
  1559. ).toBe(1)
  1560. await wrapper.find('.el-tag__close').trigger('click')
  1561. expect(
  1562. wrapper.findAll('.el-tag').filter((item) => {
  1563. return !hasClass(item.element, 'in-tooltip')
  1564. }).length
  1565. ).toBe(2)
  1566. expect(wrapper.findAll('.el-tag__close').length).toBe(0)
  1567. })
  1568. test('tag type', async () => {
  1569. wrapper = _mount(
  1570. `
  1571. <el-select v-model="value" multiple tag-type="success">
  1572. <el-option
  1573. v-for="item in options"
  1574. :key="item.value"
  1575. :label="item.label"
  1576. :value="item.value"
  1577. >
  1578. </el-option>
  1579. </el-select>
  1580. `,
  1581. () => ({
  1582. options: [
  1583. {
  1584. value: '选项1',
  1585. label: '黄金糕',
  1586. },
  1587. {
  1588. value: '选项2',
  1589. label: '双皮奶',
  1590. },
  1591. ],
  1592. value: [],
  1593. })
  1594. )
  1595. await wrapper.find('.select-trigger').trigger('click')
  1596. const options = getOptions()
  1597. options[1].click()
  1598. await nextTick()
  1599. expect(wrapper.find('.el-tag').classes()).toContain('el-tag--success')
  1600. })
  1601. test('modelValue should be deep reactive in multiple mode', async () => {
  1602. wrapper = _mount(
  1603. `
  1604. <el-select v-model="modelValue" multiple>
  1605. <el-option
  1606. v-for="option in options"
  1607. :key="option.value"
  1608. :value="option.value"
  1609. :label="option.label"
  1610. >
  1611. </el-option>
  1612. </el-select>`,
  1613. () => ({
  1614. modelValue: [1],
  1615. options: [
  1616. { label: 'Test 1', value: 1 },
  1617. { label: 'Test 2', value: 2 },
  1618. { label: 'Test 3', value: 3 },
  1619. { label: 'Test 4', value: 4 },
  1620. ],
  1621. })
  1622. )
  1623. const vm = wrapper.vm as any
  1624. await nextTick()
  1625. expect(wrapper.findAll('.el-tag').length).toBe(1)
  1626. vm.modelValue.splice(0, 1)
  1627. await nextTick()
  1628. expect(wrapper.findAll('.el-tag').length).toBe(0)
  1629. })
  1630. test('should reset placeholder after clear when both multiple and filterable are true', async () => {
  1631. const placeholder = 'placeholder'
  1632. wrapper = _mount(
  1633. `
  1634. <el-select v-model="modelValue" multiple filterable placeholder=${placeholder}>
  1635. <el-option label="1" value="1" />
  1636. </el-select>`,
  1637. () => ({
  1638. modelValue: ['1'],
  1639. })
  1640. )
  1641. await nextTick()
  1642. const innerInput = wrapper.find('.el-input__inner')
  1643. const innerInputEl = innerInput.element as HTMLInputElement
  1644. expect(innerInputEl.placeholder).toBe('')
  1645. const tagCloseIcon = wrapper.find('.el-tag__close')
  1646. await tagCloseIcon.trigger('click')
  1647. expect(innerInputEl.placeholder).toBe(placeholder)
  1648. const selectInput = wrapper.find('.el-select__input')
  1649. const selectInputEl = selectInput.element as HTMLInputElement
  1650. selectInputEl.value = 'a'
  1651. vi.useFakeTimers()
  1652. selectInput.trigger('input')
  1653. await nextTick()
  1654. vi.runAllTimers()
  1655. await nextTick()
  1656. expect(innerInputEl.placeholder).toBe('')
  1657. selectInput.trigger('keydown', {
  1658. key: EVENT_CODE.backspace,
  1659. })
  1660. await nextTick()
  1661. expect(innerInputEl.placeholder).toBe(placeholder)
  1662. vi.useRealTimers()
  1663. })
  1664. test('should close popper when click icon twice', async () => {
  1665. wrapper = getSelectVm({
  1666. filterable: true,
  1667. clearable: true,
  1668. })
  1669. const select = wrapper.findComponent({ name: 'ElSelect' })
  1670. await select.trigger('mouseenter')
  1671. const suffixIcon = select.find('.el-input__suffix')
  1672. await suffixIcon.trigger('click')
  1673. expect((select.vm as any).visible).toBe(true)
  1674. await suffixIcon.trigger('click')
  1675. expect((select.vm as any).visible).toBe(false)
  1676. })
  1677. test('mouseenter click', async () => {
  1678. wrapper = getSelectVm({
  1679. filterable: true,
  1680. clearable: true,
  1681. })
  1682. const select = wrapper.findComponent({ name: 'ElSelect' })
  1683. await select.trigger('click')
  1684. expect((select.vm as any).visible).toBe(false)
  1685. await select.trigger('mouseenter')
  1686. await select.trigger('click')
  1687. expect((select.vm as any).visible).toBe(true)
  1688. })
  1689. describe('should show all options when open select dropdown', () => {
  1690. async function testShowOptions({ filterable, multiple }: SelectProps = {}) {
  1691. wrapper = getSelectVm({ filterable, multiple })
  1692. const options = wrapper.findAllComponents({ name: 'ElOption' })
  1693. await wrapper.find('.select-trigger').trigger('click')
  1694. expect(options.every((option) => option.vm.visible)).toBe(true)
  1695. await options[1].trigger('click')
  1696. await wrapper.find('.select-trigger').trigger('click')
  1697. expect(options.every((option) => option.vm.visible)).toBe(true)
  1698. }
  1699. test('both filterable and multiple are false', async () => {
  1700. await testShowOptions()
  1701. })
  1702. test('filterable is true and multiple is false', async () => {
  1703. await testShowOptions({ filterable: true })
  1704. })
  1705. test('filterable is false and multiple is true', async () => {
  1706. await testShowOptions({ multiple: true })
  1707. })
  1708. test('both filterable and multiple are true', async () => {
  1709. await testShowOptions({ filterable: true, multiple: true })
  1710. })
  1711. test('filterable is true with grouping', async () => {
  1712. wrapper = getGroupSelectVm({ filterable: true })
  1713. await wrapper.find('.select-trigger').trigger('click')
  1714. const vm = wrapper.findComponent(Select).vm
  1715. const event = { target: { value: 'sh' } }
  1716. vm.debouncedQueryChange(event)
  1717. await nextTick()
  1718. const groups = wrapper.findAllComponents(Group)
  1719. expect(
  1720. groups.filter((group) => {
  1721. const vm = group.vm as any
  1722. return vm.visible
  1723. }).length
  1724. ).toBe(1)
  1725. })
  1726. })
  1727. describe('after search', () => {
  1728. async function testAfterSearch({
  1729. multiple,
  1730. filterMethod,
  1731. remote,
  1732. remoteMethod,
  1733. }: SelectProps) {
  1734. wrapper = getSelectVm({
  1735. filterable: true,
  1736. multiple,
  1737. filterMethod,
  1738. remote,
  1739. remoteMethod,
  1740. })
  1741. const method = remote ? remoteMethod : filterMethod
  1742. const firstInputLetter = 'a'
  1743. const secondInputLetter = 'aa'
  1744. await nextTick()
  1745. await wrapper.trigger('mouseenter')
  1746. const input = wrapper.find(
  1747. multiple ? '.el-select__input' : '.el-input__inner'
  1748. )
  1749. const inputEl = input.element as HTMLInputElement
  1750. await input.trigger('click')
  1751. inputEl.value = firstInputLetter
  1752. await input.trigger('input')
  1753. expect(method).toBeCalled()
  1754. expect(method.mock.calls[0][0]).toBe(firstInputLetter)
  1755. inputEl.value = secondInputLetter
  1756. await input.trigger('input')
  1757. expect(method).toBeCalledTimes(2)
  1758. expect(method.mock.calls[1][0]).toBe(secondInputLetter)
  1759. }
  1760. test('should call filter method', async () => {
  1761. const filterMethod = vi.fn()
  1762. await testAfterSearch({ filterMethod })
  1763. })
  1764. test('should call filter method in multiple mode', async () => {
  1765. const filterMethod = vi.fn()
  1766. await testAfterSearch({ multiple: true, filterMethod })
  1767. })
  1768. test('should call remote method', async () => {
  1769. const remoteMethod = vi.fn()
  1770. await testAfterSearch({ remote: true, remoteMethod })
  1771. })
  1772. test('should call remote method in multiple mode', async () => {
  1773. const remoteMethod = vi.fn()
  1774. await testAfterSearch({ multiple: true, remote: true, remoteMethod })
  1775. })
  1776. })
  1777. describe('teleported API', () => {
  1778. it('should mount on popper container', async () => {
  1779. expect(document.body.innerHTML).toBe('')
  1780. wrapper = _mount(
  1781. `
  1782. <el-select v-model="modelValue" multiple>
  1783. <el-option
  1784. v-for="option in options"
  1785. :key="option.value"
  1786. :value="option.value"
  1787. :label="option.label"
  1788. >
  1789. </el-option>
  1790. </el-select>`,
  1791. () => ({
  1792. modelValue: [1],
  1793. options: [
  1794. { label: 'Test 1', value: 1 },
  1795. { label: 'Test 2', value: 2 },
  1796. { label: 'Test 3', value: 3 },
  1797. { label: 'Test 4', value: 4 },
  1798. ],
  1799. })
  1800. )
  1801. await nextTick()
  1802. const { selector } = wrapper.vm
  1803. expect(document.body.querySelector(selector).innerHTML).not.toBe('')
  1804. })
  1805. it('should not mount on the popper container', async () => {
  1806. expect(document.body.innerHTML).toBe('')
  1807. wrapper = _mount(
  1808. `
  1809. <el-select v-model="modelValue" multiple :teleported="false">
  1810. <el-option
  1811. v-for="option in options"
  1812. :key="option.value"
  1813. :value="option.value"
  1814. :label="option.label"
  1815. >
  1816. </el-option>
  1817. </el-select>`,
  1818. () => ({
  1819. modelValue: [1],
  1820. options: [
  1821. { label: 'Test 1', value: 1 },
  1822. { label: 'Test 2', value: 2 },
  1823. { label: 'Test 3', value: 3 },
  1824. { label: 'Test 4', value: 4 },
  1825. ],
  1826. })
  1827. )
  1828. await nextTick()
  1829. const { selector } = wrapper.vm
  1830. expect(document.body.querySelector(selector).innerHTML).toBe('')
  1831. })
  1832. })
  1833. it('multiple select has an initial value', async () => {
  1834. const options = [{ value: `value:Alaska`, label: `label:Alaska` }]
  1835. const modelValue = [{ value: `value:Alaska`, label: `label:Alaska` }]
  1836. const wrapper = _mount(
  1837. `
  1838. <el-select v-model="modelValue"
  1839. multiple
  1840. value-key="value"
  1841. filterable>
  1842. <el-option
  1843. v-for="option in options"
  1844. :key="option.value"
  1845. :value="option.value"
  1846. :label="option.label"
  1847. >
  1848. </el-option>
  1849. </el-select>`,
  1850. () => ({
  1851. modelValue,
  1852. options,
  1853. })
  1854. )
  1855. const select = wrapper.findComponent({ name: 'ElSelect' }).vm
  1856. expect(select.selected[0].currentLabel).toBe(options[0].label)
  1857. })
  1858. test('should reset selectedLabel when toggle multiple', async () => {
  1859. wrapper = getSelectVm({ multiple: false })
  1860. const select = wrapper.findComponent({ name: 'ElSelect' })
  1861. const vm = wrapper.vm as any
  1862. const selectVm = select.vm as any
  1863. vm.value = '选项1'
  1864. await nextTick()
  1865. expect(selectVm.selectedLabel).toBe('黄金糕')
  1866. vm.multiple = true
  1867. vm.value = []
  1868. await nextTick()
  1869. expect(selectVm.selectedLabel).toBe('')
  1870. })
  1871. test('should modify size height change', async () => {
  1872. wrapper = getSelectVm()
  1873. // large size
  1874. await wrapper.setProps({
  1875. size: 'large',
  1876. })
  1877. await nextTick(nextTick)
  1878. const inputEl = wrapper.find('input').element as HTMLDivElement
  1879. const sizeMap: Record<string, number> = {
  1880. small: 24,
  1881. default: 32,
  1882. large: 40,
  1883. }
  1884. Object.defineProperty(inputEl, 'offsetParent', {
  1885. get() {
  1886. return {}
  1887. },
  1888. })
  1889. for (const size in sizeMap) {
  1890. await wrapper.setProps({
  1891. size,
  1892. })
  1893. await nextTick(nextTick)
  1894. expect(inputEl.style.height).toEqual(`${sizeMap[size] - 2}px`)
  1895. }
  1896. })
  1897. describe('form item accessibility integration', () => {
  1898. it('automatic id attachment', async () => {
  1899. const wrapper = _mount(
  1900. `<el-form-item label="Foobar" data-test-ref="item">
  1901. <el-select v-model="modelValue">
  1902. <el-option label="1" value="1" />
  1903. </el-select>
  1904. </el-form-item>`,
  1905. () => ({
  1906. modelValue: 1,
  1907. })
  1908. )
  1909. await nextTick()
  1910. const formItem = wrapper.find('[data-test-ref="item"]')
  1911. const formItemLabel = formItem.find('.el-form-item__label')
  1912. const innerInput = wrapper.find('.el-input__inner')
  1913. expect(formItem.attributes().role).toBeFalsy()
  1914. expect(formItemLabel.attributes().for).toBe(innerInput.attributes().id)
  1915. })
  1916. it('specified id attachment', async () => {
  1917. const wrapper = _mount(
  1918. `<el-form-item label="Foobar" data-test-ref="item">
  1919. <el-select id="foobar" v-model="modelValue">
  1920. <el-option label="1" value="1" />
  1921. </el-select>
  1922. </el-form-item>`,
  1923. () => ({
  1924. modelValue: 1,
  1925. })
  1926. )
  1927. await nextTick()
  1928. const formItem = wrapper.find('[data-test-ref="item"]')
  1929. const formItemLabel = formItem.find('.el-form-item__label')
  1930. const innerInput = wrapper.find('.el-input__inner')
  1931. expect(formItem.attributes().role).toBeFalsy()
  1932. expect(innerInput.attributes().id).toBe('foobar')
  1933. expect(formItemLabel.attributes().for).toBe(innerInput.attributes().id)
  1934. })
  1935. it('form item role is group when multiple inputs', async () => {
  1936. const wrapper = _mount(
  1937. `<el-form-item label="Foobar" data-test-ref="item">
  1938. <el-select v-model="modelValue">
  1939. <el-option label="1" value="1" />
  1940. </el-select>
  1941. <el-select v-model="modelValue">
  1942. <el-option label="1" value="1" />
  1943. </el-select>
  1944. </el-form-item>`,
  1945. () => ({
  1946. modelValue: 1,
  1947. })
  1948. )
  1949. await nextTick()
  1950. const formItem = wrapper.find('[data-test-ref="item"]')
  1951. expect(formItem.attributes().role).toBe('group')
  1952. })
  1953. // fix: 8544
  1954. it('When props are changed, label can be displayed correctly after selecting operation', async () => {
  1955. wrapper = getGroupSelectVm({}, [
  1956. {
  1957. label: 'group1',
  1958. options: [
  1959. { value: 0, label: 'x' },
  1960. { value: 1, label: 'y' },
  1961. { value: 2, label: 'z' },
  1962. ],
  1963. },
  1964. ])
  1965. await wrapper.find('.select-trigger').trigger('click')
  1966. let options = getOptions()
  1967. const vm = wrapper.vm as any
  1968. expect(vm.value).toBe('')
  1969. expect(findInnerInput().value).toBe('')
  1970. await nextTick()
  1971. options[1].click()
  1972. await nextTick()
  1973. expect(vm.value).toBe(1)
  1974. expect(findInnerInput().value).toBe('y')
  1975. wrapper.vm.options = [
  1976. {
  1977. label: 'group2',
  1978. options: [
  1979. { value: 0, label: 'x' },
  1980. { value: 1, label: 'y' },
  1981. { value: 2, label: 'z' },
  1982. ],
  1983. },
  1984. ]
  1985. await nextTick()
  1986. options = getOptions()
  1987. options[1].click()
  1988. await nextTick()
  1989. expect(vm.value).toBe(1)
  1990. expect(findInnerInput().value).toBe('y')
  1991. options[2].click()
  1992. await nextTick()
  1993. expect(vm.value).toBe(2)
  1994. expect(findInnerInput().value).toBe('z')
  1995. })
  1996. })
  1997. })