tabs.test.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. import { defineComponent, nextTick, ref } from 'vue'
  2. import { mount } from '@vue/test-utils'
  3. import { describe, expect, test, vi } from 'vitest'
  4. import { EVENT_CODE } from '@element-plus/constants'
  5. import Tabs from '../src/tabs'
  6. import TabPane from '../src/tab-pane.vue'
  7. import TabNav from '../src/tab-nav'
  8. import type { TabPaneName } from '../src/tabs'
  9. import type { TabsPaneContext } from '@element-plus/components/tabs'
  10. const Comp = defineComponent({
  11. components: {
  12. TabPane,
  13. },
  14. setup() {
  15. return () => (
  16. <TabPane name="tab1" label="tab1">
  17. Tab 1 content
  18. </TabPane>
  19. )
  20. },
  21. })
  22. describe('Tabs.vue', () => {
  23. test('create', async () => {
  24. const wrapper = mount(() => (
  25. <Tabs>
  26. <TabPane label="label-1">A</TabPane>
  27. <TabPane label="label-2">B</TabPane>
  28. <TabPane label="label-3" ref="pane-click">
  29. C
  30. </TabPane>
  31. <TabPane label="label-4">D</TabPane>
  32. </Tabs>
  33. ))
  34. const tabsWrapper = wrapper.findComponent(Tabs)
  35. const navWrapper = wrapper.findComponent(TabNav)
  36. const panesWrapper = wrapper.findAllComponents(TabPane)
  37. await nextTick()
  38. const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  39. expect(navItemsWrapper[0].classes('is-active')).toBe(true)
  40. expect(panesWrapper[0].classes('el-tab-pane')).toBe(true)
  41. expect(panesWrapper[0].attributes('id')).toBe('pane-0')
  42. expect(panesWrapper[0].attributes('aria-hidden')).toEqual('false')
  43. expect(tabsWrapper.vm.$.exposed!.currentName.value).toEqual('0')
  44. await navItemsWrapper[2].trigger('click')
  45. expect(navItemsWrapper[0].classes('is-active')).toBe(false)
  46. expect(panesWrapper[0].attributes('aria-hidden')).toEqual('true')
  47. expect(navItemsWrapper[2].classes('is-active')).toBe(true)
  48. expect(panesWrapper[2].attributes('aria-hidden')).toEqual('false')
  49. expect(tabsWrapper.vm.$.exposed!.currentName.value).toEqual('2')
  50. })
  51. test('active-name', async () => {
  52. const activeName = ref<TabPaneName | undefined>('b')
  53. const handleClick = (tab: TabsPaneContext) => {
  54. activeName.value = tab.paneName
  55. }
  56. const wrapper = mount(() => (
  57. <Tabs v-model={activeName.value} onTabClick={handleClick}>
  58. <TabPane name="a" label="label-1">
  59. A
  60. </TabPane>
  61. <TabPane name="b" label="label-2">
  62. B
  63. </TabPane>
  64. <TabPane name="c" label="label-3" ref="pane-click">
  65. C
  66. </TabPane>
  67. <TabPane name="d" label="label-4">
  68. D
  69. </TabPane>
  70. </Tabs>
  71. ))
  72. const tabsWrapper = wrapper.findComponent(Tabs)
  73. const navWrapper = wrapper.findComponent(TabNav)
  74. const panesWrapper = wrapper.findAllComponents(TabPane)
  75. await nextTick()
  76. const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  77. expect(navItemsWrapper[1].classes('is-active')).toBe(true)
  78. expect(panesWrapper[1].classes('el-tab-pane')).toBe(true)
  79. expect(panesWrapper[1].attributes('id')).toBe('pane-b')
  80. expect(panesWrapper[1].attributes('aria-hidden')).toEqual('false')
  81. expect(tabsWrapper.vm.$.exposed!.currentName.value).toEqual('b')
  82. await navItemsWrapper[2].trigger('click')
  83. expect(navItemsWrapper[1].classes('is-active')).toBe(false)
  84. expect(panesWrapper[1].attributes('aria-hidden')).toEqual('true')
  85. expect(navItemsWrapper[2].classes('is-active')).toBe(true)
  86. expect(panesWrapper[2].attributes('aria-hidden')).toEqual('false')
  87. expect(tabsWrapper.vm.$.exposed!.currentName.value).toEqual('c')
  88. })
  89. test('card', async () => {
  90. const wrapper = mount(() => (
  91. <Tabs type="card">
  92. <TabPane label="label-1">A</TabPane>
  93. <TabPane label="label-2">B</TabPane>
  94. <TabPane label="label-3" ref="pane-click">
  95. C
  96. </TabPane>
  97. <TabPane label="label-4">D</TabPane>
  98. </Tabs>
  99. ))
  100. const tabsWrapper = wrapper.findComponent(Tabs)
  101. expect(tabsWrapper.classes('el-tabs--card')).toBe(true)
  102. })
  103. test('border card', async () => {
  104. const wrapper = mount(() => (
  105. <Tabs type="border-card">
  106. <TabPane label="label-1">A</TabPane>
  107. <TabPane label="label-2">B</TabPane>
  108. <TabPane label="label-3" ref="pane-click">
  109. C
  110. </TabPane>
  111. <TabPane label="label-4">D</TabPane>
  112. </Tabs>
  113. ))
  114. const tabsWrapper = wrapper.findComponent(Tabs)
  115. expect(tabsWrapper.classes('el-tabs--border-card')).toBe(true)
  116. })
  117. test('dynamic', async () => {
  118. const tabs = ref([
  119. {
  120. label: 'tab1',
  121. name: 'tab1',
  122. },
  123. {
  124. label: 'tab2',
  125. name: 'tab2',
  126. },
  127. {
  128. label: 'tab3',
  129. name: 'tab3',
  130. },
  131. {
  132. label: 'tab4',
  133. name: 'tab4',
  134. },
  135. ])
  136. const wrapper = mount(() => (
  137. <Tabs type="card" ref="tabs">
  138. {tabs.value.map((tab) => (
  139. <TabPane label={tab.label} name={tab.name} key={tab.name}>
  140. Test Content
  141. </TabPane>
  142. ))}
  143. </Tabs>
  144. ))
  145. let navWrapper = wrapper.findComponent(TabNav)
  146. let panesWrapper = wrapper.findAllComponents(TabPane)
  147. await nextTick()
  148. let navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  149. expect(navItemsWrapper.length).toEqual(4)
  150. expect(panesWrapper.length).toEqual(4)
  151. tabs.value.push({ label: 'tab5', name: 'tab5' })
  152. await nextTick()
  153. navWrapper = wrapper.findComponent(TabNav)
  154. panesWrapper = wrapper.findAllComponents(TabPane)
  155. navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  156. expect(navItemsWrapper.length).toEqual(5)
  157. expect(panesWrapper.length).toEqual(5)
  158. })
  159. test('editable', async () => {
  160. const editableTabsValue = ref('2')
  161. const editableTabs = ref([
  162. {
  163. title: 'Tab 1',
  164. name: '1',
  165. content: 'Tab 1 content',
  166. },
  167. {
  168. title: 'Tab 2',
  169. name: '2',
  170. content: 'Tab 2 content',
  171. },
  172. {
  173. title: 'Tab 3',
  174. name: '3',
  175. content: 'Tab 3 content',
  176. },
  177. ])
  178. const tabIndex = ref(3)
  179. const handleTabsEdit = (
  180. targetName: TabPaneName | undefined,
  181. action: 'remove' | 'add'
  182. ) => {
  183. if (action === 'add') {
  184. const newTabName = `${++tabIndex.value}`
  185. editableTabs.value.push({
  186. title: 'New Tab',
  187. name: newTabName,
  188. content: 'New Tab content',
  189. })
  190. editableTabsValue.value = newTabName
  191. }
  192. if (action === 'remove') {
  193. const tabs = editableTabs.value
  194. let activeName = editableTabsValue.value
  195. if (activeName === targetName) {
  196. tabs.forEach((tab, index) => {
  197. if (tab.name === targetName) {
  198. const nextTab = tabs[index + 1] || tabs[index - 1]
  199. if (nextTab) {
  200. activeName = nextTab.name
  201. }
  202. }
  203. })
  204. }
  205. editableTabsValue.value = activeName
  206. editableTabs.value = tabs.filter((tab) => tab.name !== targetName)
  207. }
  208. }
  209. const wrapper = mount(() => (
  210. <Tabs
  211. ref="tabs"
  212. v-model={editableTabsValue.value}
  213. type="card"
  214. editable
  215. onEdit={handleTabsEdit}
  216. >
  217. {editableTabs.value.map((tab) => (
  218. <TabPane key={tab.name} label={tab.title} name={tab.name}>
  219. {tab.content}
  220. </TabPane>
  221. ))}
  222. </Tabs>
  223. ))
  224. const navWrapper = wrapper.findComponent(TabNav)
  225. let panesWrapper = wrapper.findAllComponents(TabPane)
  226. await nextTick()
  227. let navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  228. expect(navItemsWrapper.length).toEqual(3)
  229. expect(panesWrapper.length).toEqual(3)
  230. expect(navItemsWrapper[1].classes('is-active')).toBe(true)
  231. // remove one tab, check panes length
  232. await navItemsWrapper[1].find('.is-icon-close').trigger('click')
  233. panesWrapper = wrapper.findAllComponents(TabPane)
  234. navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  235. expect(navItemsWrapper.length).toEqual(2)
  236. expect(panesWrapper.length).toEqual(2)
  237. // add one tab, check panes length and current tab
  238. await wrapper.find('.el-tabs__new-tab').trigger('click')
  239. panesWrapper = wrapper.findAllComponents(TabPane)
  240. navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  241. expect(navItemsWrapper.length).toEqual(3)
  242. expect(panesWrapper.length).toEqual(3)
  243. expect(navItemsWrapper[2].classes('is-active')).toBe(true)
  244. })
  245. test('addable & closable', async () => {
  246. const editableTabsValue = ref('2')
  247. const editableTabs = ref([
  248. {
  249. title: 'Tab 1',
  250. name: '1',
  251. content: 'Tab 1 content',
  252. },
  253. {
  254. title: 'Tab 2',
  255. name: '2',
  256. content: 'Tab 2 content',
  257. },
  258. ])
  259. const tabIndex = ref(2)
  260. const addTab = () => {
  261. const newTabName = `${++tabIndex.value}`
  262. editableTabs.value.push({
  263. title: 'New Tab',
  264. name: newTabName,
  265. content: 'New Tab content',
  266. })
  267. editableTabsValue.value = newTabName
  268. }
  269. const removeTab = (targetName: TabPaneName) => {
  270. const tabs = editableTabs.value
  271. let activeName = editableTabsValue.value
  272. if (activeName === targetName) {
  273. tabs.forEach((tab, index) => {
  274. if (tab.name === targetName) {
  275. const nextTab = tabs[index + 1] || tabs[index - 1]
  276. if (nextTab) {
  277. activeName = nextTab.name
  278. }
  279. }
  280. })
  281. }
  282. editableTabsValue.value = activeName
  283. editableTabs.value = tabs.filter((tab) => tab.name !== targetName)
  284. }
  285. const wrapper = mount(() => (
  286. <Tabs
  287. ref="tabs"
  288. v-model={editableTabsValue.value}
  289. type="card"
  290. addable
  291. closable
  292. onTabAdd={addTab}
  293. onTabRemove={removeTab}
  294. >
  295. {editableTabs.value.map((item) => (
  296. <TabPane
  297. label={item.title}
  298. key={item.name}
  299. name={item.name}
  300. ></TabPane>
  301. ))}
  302. </Tabs>
  303. ))
  304. const navWrapper = wrapper.findComponent(TabNav)
  305. await nextTick()
  306. await wrapper.find('.el-tabs__new-tab').trigger('click')
  307. let navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  308. let panesWrapper = wrapper.findAllComponents(TabPane)
  309. expect(navItemsWrapper.length).toEqual(3)
  310. expect(panesWrapper.length).toEqual(3)
  311. expect(navItemsWrapper[2].classes('is-active')).toBe(true)
  312. await navItemsWrapper[2].find('.is-icon-close').trigger('click')
  313. panesWrapper = wrapper.findAllComponents(TabPane)
  314. navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  315. expect(navItemsWrapper.length).toEqual(2)
  316. expect(panesWrapper.length).toEqual(2)
  317. })
  318. test('tab order', async () => {
  319. const editableTabs = ref([
  320. {
  321. title: 'Tab 1',
  322. name: '1',
  323. content: 'Tab 1 content',
  324. },
  325. {
  326. title: 'Tab 2',
  327. name: '2',
  328. content: 'Tab 2 content',
  329. },
  330. ])
  331. const wrapper = mount(() => (
  332. <Tabs ref="tabs" type="card">
  333. {editableTabs.value.map((item) => (
  334. <TabPane
  335. label={item.title}
  336. key={item.name}
  337. name={item.name}
  338. ></TabPane>
  339. ))}
  340. </Tabs>
  341. ))
  342. editableTabs.value.splice(1, 0, {
  343. title: 'Tab 3',
  344. name: '3',
  345. content: 'Tab 3 content',
  346. })
  347. await nextTick()
  348. const items = wrapper.findAll('.el-tabs__item')
  349. editableTabs.value.forEach((tab, index) => {
  350. expect(items[index].element.textContent).toEqual(tab.title)
  351. })
  352. })
  353. test('closable in tab-pane', async () => {
  354. const wrapper = mount(() => (
  355. <Tabs type="card" ref="tabs">
  356. <TabPane label="label-1" closable>
  357. A
  358. </TabPane>
  359. <TabPane label="label-2">B</TabPane>
  360. <TabPane label="label-3" closable>
  361. C
  362. </TabPane>
  363. <TabPane label="label-4">D</TabPane>
  364. </Tabs>
  365. ))
  366. const navWrapper = wrapper.findComponent(TabNav)
  367. await nextTick()
  368. expect(navWrapper.findAll('.is-icon-close').length).toBe(2)
  369. })
  370. test('disabled', async () => {
  371. const wrapper = mount(() => (
  372. <Tabs type="card" ref="tabs">
  373. <TabPane label="label-1">A</TabPane>
  374. <TabPane disabled label="label-2" ref="disabled">
  375. B
  376. </TabPane>
  377. <TabPane label="label-3">C</TabPane>
  378. <TabPane label="label-4">D</TabPane>
  379. </Tabs>
  380. ))
  381. const navWrapper = wrapper.findComponent(TabNav)
  382. await nextTick()
  383. const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  384. expect(navItemsWrapper[1].classes('is-active')).toBe(false)
  385. await navItemsWrapper[1].trigger('click')
  386. expect(navItemsWrapper[1].classes('is-active')).toBe(false)
  387. })
  388. test('tab-position', async () => {
  389. const wrapper = mount(() => (
  390. <Tabs ref="tabs" tab-position="left">
  391. <TabPane label="label-1">A</TabPane>
  392. <TabPane label="label-2">B</TabPane>
  393. <TabPane label="label-3" ref="pane-click">
  394. C
  395. </TabPane>
  396. <TabPane label="label-4">D</TabPane>
  397. </Tabs>
  398. ))
  399. const tabsWrapper = wrapper.findComponent(Tabs)
  400. await nextTick()
  401. expect(tabsWrapper.classes('el-tabs--left')).toBe(true)
  402. expect(tabsWrapper.find('.el-tabs__header').classes('is-left')).toBe(true)
  403. expect(tabsWrapper.find('.el-tabs__nav-wrap').classes('is-left')).toBe(true)
  404. expect(tabsWrapper.find('.el-tabs__nav').classes('is-left')).toBe(true)
  405. expect(tabsWrapper.find('.el-tabs__active-bar').classes('is-left')).toBe(
  406. true
  407. )
  408. expect(tabsWrapper.find('.el-tabs__item').classes('is-left')).toBe(true)
  409. })
  410. test('stretch', async () => {
  411. const tabPosition = ref('bottom')
  412. const wrapper = mount(() => (
  413. <Tabs ref="tabs" stretch tab-position={tabPosition.value}>
  414. <TabPane label="label-1">A</TabPane>
  415. <TabPane label="label-2">B</TabPane>
  416. <TabPane label="label-3">C</TabPane>
  417. <TabPane label="label-4">D</TabPane>
  418. </Tabs>
  419. ))
  420. const tabsWrapper = wrapper.findComponent(Tabs)
  421. await nextTick()
  422. expect(tabsWrapper.find('.el-tabs__nav').classes('is-stretch')).toBe(true)
  423. tabPosition.value = 'left'
  424. await nextTick()
  425. expect(tabsWrapper.find('.el-tabs__nav').classes('is-stretch')).toBe(false)
  426. })
  427. test('tab active bar offset', async () => {
  428. const tabPosition = ref('bottom')
  429. const wrapper = mount(() => (
  430. <Tabs ref="tabs" stretch tab-position={tabPosition.value}>
  431. <TabPane label="label-1" name="A">
  432. A
  433. </TabPane>
  434. <TabPane label="label-2" name="B">
  435. B
  436. </TabPane>
  437. <TabPane label="label-3" name="C">
  438. C
  439. </TabPane>
  440. <TabPane label="label-4" name="D">
  441. D
  442. </TabPane>
  443. </Tabs>
  444. ))
  445. const tabsWrapper = wrapper.findComponent(Tabs)
  446. await nextTick()
  447. const mockOffsetLeft = vi
  448. .spyOn(wrapper.find('#tab-C').element as HTMLElement, 'offsetLeft', 'get')
  449. .mockImplementation(() => 300)
  450. const mockComputedStyle = vi
  451. .spyOn(window, 'getComputedStyle')
  452. .mockReturnValue({ paddingLeft: '0px' } as CSSStyleDeclaration)
  453. await wrapper.find('#tab-C').trigger('click')
  454. await nextTick()
  455. expect(tabsWrapper.find('.el-tabs__active-bar').attributes().style).toMatch(
  456. 'translateX(300px)'
  457. )
  458. tabPosition.value = 'left'
  459. await nextTick()
  460. const mockOffsetTop = vi
  461. .spyOn(wrapper.find('#tab-C').element as HTMLElement, 'offsetTop', 'get')
  462. .mockImplementation(() => 200)
  463. await wrapper.find('#tab-A').trigger('click')
  464. await wrapper.find('#tab-C').trigger('click')
  465. await nextTick()
  466. expect(tabsWrapper.find('.el-tabs__active-bar').attributes().style).toMatch(
  467. 'translateY(200px)'
  468. )
  469. mockOffsetLeft.mockRestore()
  470. mockOffsetTop.mockRestore()
  471. mockComputedStyle.mockRestore()
  472. wrapper.unmount()
  473. })
  474. test('horizontal-scrollable', async () => {
  475. // TODO: jsdom not support `clientWidth`.
  476. })
  477. test('vertical-scrollable', async () => {
  478. // TODO: jsdom not support `clientWidth`.
  479. })
  480. test('should work with lazy', async () => {
  481. const activeName = ref('A')
  482. const wrapper = mount(() => (
  483. <Tabs v-model={activeName.value} ref="tabs">
  484. <TabPane label="label-1" lazy name="A">
  485. A
  486. </TabPane>
  487. <TabPane label="label-2" name="B">
  488. B
  489. </TabPane>
  490. <TabPane label="label-3" name="C">
  491. C
  492. </TabPane>
  493. <TabPane label="label-4" lazy name="D">
  494. D
  495. </TabPane>
  496. </Tabs>
  497. ))
  498. const navWrapper = wrapper.findComponent(TabNav)
  499. await nextTick()
  500. const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  501. expect(wrapper.findAll('.el-tab-pane').length).toBe(3)
  502. await navItemsWrapper[3].trigger('click')
  503. expect(wrapper.findAll('.el-tab-pane').length).toBe(4)
  504. })
  505. test('before leave', async () => {
  506. const activeName = ref('tab-B')
  507. const beforeLeave = () => {
  508. return new window.Promise<void>((resolve, reject) => {
  509. reject()
  510. })
  511. }
  512. const wrapper = mount(() => (
  513. <Tabs ref="tabs" v-model={activeName.value} beforeLeave={beforeLeave}>
  514. <TabPane name="tab-A" label="label-1">
  515. A
  516. </TabPane>
  517. <TabPane name="tab-B" label="label-2">
  518. B
  519. </TabPane>
  520. <TabPane name="tab-C" label="label-3">
  521. C
  522. </TabPane>
  523. <TabPane name="tab-D" label="label-4">
  524. D
  525. </TabPane>
  526. </Tabs>
  527. ))
  528. const navWrapper = wrapper.findComponent(TabNav)
  529. const panesWrapper = wrapper.findAllComponents(TabPane)
  530. await nextTick()
  531. const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  532. expect(navItemsWrapper[1].classes('is-active')).toBe(true)
  533. expect(panesWrapper[1].attributes('style')).toBeFalsy()
  534. await navItemsWrapper[3].trigger('click')
  535. expect(navItemsWrapper[1].classes('is-active')).toBe(true)
  536. expect(panesWrapper[1].attributes('style')).toBeFalsy()
  537. })
  538. test('keyboard event', async () => {
  539. const activeName = ref('second')
  540. const wrapper = mount(() => (
  541. <Tabs v-model={activeName.value}>
  542. <TabPane label="label-1" name="first" disabled>
  543. A
  544. </TabPane>
  545. <TabPane label="label-2" name="second">
  546. B
  547. </TabPane>
  548. <TabPane label="label-3" name="third">
  549. C
  550. </TabPane>
  551. <TabPane label="label-4" name="fourth">
  552. D
  553. </TabPane>
  554. </Tabs>
  555. ))
  556. await nextTick()
  557. await wrapper
  558. .find('#tab-second')
  559. .trigger('keydown', { code: EVENT_CODE.right })
  560. expect(activeName.value).toEqual('third')
  561. await wrapper
  562. .find('#tab-third')
  563. .trigger('keydown', { code: EVENT_CODE.right })
  564. expect(activeName.value).toEqual('fourth')
  565. await wrapper
  566. .find('#tab-fourth')
  567. .trigger('keydown', { code: EVENT_CODE.right })
  568. expect(activeName.value).toEqual('second')
  569. await wrapper
  570. .find('#tab-second')
  571. .trigger('keydown', { code: EVENT_CODE.left })
  572. expect(activeName.value).toEqual('fourth')
  573. })
  574. test('resize', async () => {
  575. // TODO: jsdom not support `clientWidth`.
  576. })
  577. test('DOM update finished calculating navOffset', async () => {
  578. const tabs = Array.from({ length: 100 }, (_, i) => i.toString())
  579. const activeName = ref('0')
  580. const wrapper = mount(() => (
  581. <Tabs v-model={activeName.value}>
  582. {tabs.map((item) => (
  583. <TabPane key={item} label={item} name={item} />
  584. ))}
  585. </Tabs>
  586. ))
  587. const tabsWrapper = wrapper.findComponent(Tabs)
  588. await nextTick()
  589. const mockOffsetLeft = vi
  590. .spyOn(
  591. wrapper.find('#tab-99').element as HTMLElement,
  592. 'offsetLeft',
  593. 'get'
  594. )
  595. .mockImplementation(() => 100)
  596. const mockComputedStyle = vi
  597. .spyOn(window, 'getComputedStyle')
  598. .mockReturnValue({ paddingLeft: '0px' } as CSSStyleDeclaration)
  599. await wrapper.find('#tab-99').trigger('click')
  600. await nextTick()
  601. expect(tabsWrapper.find('.el-tabs__active-bar').attributes().style).toMatch(
  602. 'translateX(100px)'
  603. )
  604. mockOffsetLeft.mockRestore()
  605. mockComputedStyle.mockRestore()
  606. wrapper.unmount()
  607. })
  608. test('value type', async () => {
  609. const activeName = ref<TabPaneName | undefined>(0)
  610. const handleClick = (tab: TabsPaneContext) => {
  611. activeName.value = tab.paneName
  612. }
  613. const wrapper = mount(() => (
  614. <Tabs v-model={activeName.value} onTabClick={handleClick}>
  615. <TabPane name={0} label="label-1">
  616. A
  617. </TabPane>
  618. <TabPane name={1} label="label-2">
  619. B
  620. </TabPane>
  621. <TabPane name={2} label="label-3" ref="pane-click">
  622. C
  623. </TabPane>
  624. <TabPane name={3} label="label-4">
  625. D
  626. </TabPane>
  627. </Tabs>
  628. ))
  629. const navWrapper = wrapper.findComponent(TabNav)
  630. await nextTick()
  631. const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  632. ;[1, 0, 2, 0, 3, 0, 1].forEach((val) => {
  633. navItemsWrapper[val].trigger('click')
  634. expect(activeName.value).toEqual(val)
  635. })
  636. })
  637. test('both number and string for name', async () => {
  638. const activeName = ref<TabPaneName | undefined>(0)
  639. const handleClick = (tab: TabsPaneContext) => {
  640. activeName.value = tab.paneName
  641. }
  642. const wrapper = mount(() => (
  643. <Tabs v-model={activeName.value} onTabClick={handleClick}>
  644. <TabPane name={0} label="n-0">
  645. number-0
  646. </TabPane>
  647. <TabPane name={'0'} label="s-0">
  648. string-0
  649. </TabPane>
  650. <TabPane name={1} label="n-1">
  651. number-1
  652. </TabPane>
  653. <TabPane name={'1'} label="s-1">
  654. string-1
  655. </TabPane>
  656. </Tabs>
  657. ))
  658. const navWrapper = wrapper.findComponent(TabNav)
  659. await nextTick()
  660. const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
  661. expect(navItemsWrapper[0].classes('is-active')).toBe(true)
  662. expect(navItemsWrapper[1].classes('is-active')).toBe(false)
  663. await navItemsWrapper[1].trigger('click')
  664. expect(navItemsWrapper[0].classes('is-active')).toBe(false)
  665. expect(navItemsWrapper[1].classes('is-active')).toBe(true)
  666. await navItemsWrapper[2].trigger('click')
  667. expect(navItemsWrapper[0].classes('is-active')).toBe(false)
  668. expect(navItemsWrapper[1].classes('is-active')).toBe(false)
  669. expect(navItemsWrapper[2].classes('is-active')).toBe(true)
  670. expect(navItemsWrapper[3].classes('is-active')).toBe(false)
  671. })
  672. test('tab-pane nested', async () => {
  673. const wrapper = mount(() => (
  674. <Tabs>
  675. <Comp />
  676. </Tabs>
  677. ))
  678. const panesWrapper = wrapper.findAllComponents(TabPane)
  679. await nextTick()
  680. expect(panesWrapper.length).toBe(1)
  681. })
  682. })