Files
element-plus/packages/components/tabs/__tests__/tabs.test.tsx
Jeremy a30ae9d103 refactor(project): removing packages/tokens (#11769)
* refactor(tokens): remove tokens

* Remove tokens/breadcrumb.

* refactor(tokens): remove tokens/button

* refactor(tokens): remove tokens/carousel

* refactor(tokens): removing tokens/checkbox

* refactor(tokens): removing tokens/collapse

* refactor(tokens): removing tokens/dialog

* refactor(tokens): removing tokens/pagination

* refactor(tokens): removing tokens/radio

* refactor(tokens): removing tokens/row

* refactor(tokens): removing tokens/scrollbar

* refactor(tokens): removing tokens/slider

* refactor(tokens): removing tokens/tabs

* refactor(tokens): removing tokens/upload

* refactor(tokens): removing tokens/popper

* refactor(tokens): removing tokens/tooltip

* refactor(tokens): removing tokens/tooltip-v2

* refactor(tokens): removing tokens/date-picker

* refactor(project): removing tokens/experimentals

* Remove tokens/experimentals
* Remove package/tokens
* Remove tokens related parts

* refactor(project): removing packages/tokens completely

* chore: update import statement
2023-03-01 13:59:21 +08:00

781 lines
22 KiB
TypeScript

import { defineComponent, nextTick, ref } from 'vue'
import { mount } from '@vue/test-utils'
import { describe, expect, test, vi } from 'vitest'
import { EVENT_CODE } from '@element-plus/constants'
import Tabs from '../src/tabs'
import TabPane from '../src/tab-pane.vue'
import TabNav from '../src/tab-nav'
import type { TabPaneName } from '../src/tabs'
import type { TabsPaneContext } from '@element-plus/components/tabs'
const Comp = defineComponent({
components: {
TabPane,
},
setup() {
return () => (
<TabPane name="tab1" label="tab1">
Tab 1 content
</TabPane>
)
},
})
describe('Tabs.vue', () => {
test('create', async () => {
const wrapper = mount(() => (
<Tabs>
<TabPane label="label-1">A</TabPane>
<TabPane label="label-2">B</TabPane>
<TabPane label="label-3" ref="pane-click">
C
</TabPane>
<TabPane label="label-4">D</TabPane>
</Tabs>
))
const tabsWrapper = wrapper.findComponent(Tabs)
const navWrapper = wrapper.findComponent(TabNav)
const panesWrapper = wrapper.findAllComponents(TabPane)
await nextTick()
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
expect(navItemsWrapper[0].classes('is-active')).toBe(true)
expect(panesWrapper[0].classes('el-tab-pane')).toBe(true)
expect(panesWrapper[0].attributes('id')).toBe('pane-0')
expect(panesWrapper[0].attributes('aria-hidden')).toEqual('false')
expect(tabsWrapper.vm.$.exposed!.currentName.value).toEqual('0')
await navItemsWrapper[2].trigger('click')
expect(navItemsWrapper[0].classes('is-active')).toBe(false)
expect(panesWrapper[0].attributes('aria-hidden')).toEqual('true')
expect(navItemsWrapper[2].classes('is-active')).toBe(true)
expect(panesWrapper[2].attributes('aria-hidden')).toEqual('false')
expect(tabsWrapper.vm.$.exposed!.currentName.value).toEqual('2')
})
test('active-name', async () => {
const activeName = ref<TabPaneName | undefined>('b')
const handleClick = (tab: TabsPaneContext) => {
activeName.value = tab.paneName
}
const wrapper = mount(() => (
<Tabs v-model={activeName.value} onTabClick={handleClick}>
<TabPane name="a" label="label-1">
A
</TabPane>
<TabPane name="b" label="label-2">
B
</TabPane>
<TabPane name="c" label="label-3" ref="pane-click">
C
</TabPane>
<TabPane name="d" label="label-4">
D
</TabPane>
</Tabs>
))
const tabsWrapper = wrapper.findComponent(Tabs)
const navWrapper = wrapper.findComponent(TabNav)
const panesWrapper = wrapper.findAllComponents(TabPane)
await nextTick()
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
expect(navItemsWrapper[1].classes('is-active')).toBe(true)
expect(panesWrapper[1].classes('el-tab-pane')).toBe(true)
expect(panesWrapper[1].attributes('id')).toBe('pane-b')
expect(panesWrapper[1].attributes('aria-hidden')).toEqual('false')
expect(tabsWrapper.vm.$.exposed!.currentName.value).toEqual('b')
await navItemsWrapper[2].trigger('click')
expect(navItemsWrapper[1].classes('is-active')).toBe(false)
expect(panesWrapper[1].attributes('aria-hidden')).toEqual('true')
expect(navItemsWrapper[2].classes('is-active')).toBe(true)
expect(panesWrapper[2].attributes('aria-hidden')).toEqual('false')
expect(tabsWrapper.vm.$.exposed!.currentName.value).toEqual('c')
})
test('card', async () => {
const wrapper = mount(() => (
<Tabs type="card">
<TabPane label="label-1">A</TabPane>
<TabPane label="label-2">B</TabPane>
<TabPane label="label-3" ref="pane-click">
C
</TabPane>
<TabPane label="label-4">D</TabPane>
</Tabs>
))
const tabsWrapper = wrapper.findComponent(Tabs)
expect(tabsWrapper.classes('el-tabs--card')).toBe(true)
})
test('border card', async () => {
const wrapper = mount(() => (
<Tabs type="border-card">
<TabPane label="label-1">A</TabPane>
<TabPane label="label-2">B</TabPane>
<TabPane label="label-3" ref="pane-click">
C
</TabPane>
<TabPane label="label-4">D</TabPane>
</Tabs>
))
const tabsWrapper = wrapper.findComponent(Tabs)
expect(tabsWrapper.classes('el-tabs--border-card')).toBe(true)
})
test('dynamic', async () => {
const tabs = ref([
{
label: 'tab1',
name: 'tab1',
},
{
label: 'tab2',
name: 'tab2',
},
{
label: 'tab3',
name: 'tab3',
},
{
label: 'tab4',
name: 'tab4',
},
])
const wrapper = mount(() => (
<Tabs type="card" ref="tabs">
{tabs.value.map((tab) => (
<TabPane label={tab.label} name={tab.name} key={tab.name}>
Test Content
</TabPane>
))}
</Tabs>
))
let navWrapper = wrapper.findComponent(TabNav)
let panesWrapper = wrapper.findAllComponents(TabPane)
await nextTick()
let navItemsWrapper = navWrapper.findAll('.el-tabs__item')
expect(navItemsWrapper.length).toEqual(4)
expect(panesWrapper.length).toEqual(4)
tabs.value.push({ label: 'tab5', name: 'tab5' })
await nextTick()
navWrapper = wrapper.findComponent(TabNav)
panesWrapper = wrapper.findAllComponents(TabPane)
navItemsWrapper = navWrapper.findAll('.el-tabs__item')
expect(navItemsWrapper.length).toEqual(5)
expect(panesWrapper.length).toEqual(5)
})
test('editable', async () => {
const editableTabsValue = ref('2')
const editableTabs = ref([
{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content',
},
{
title: 'Tab 2',
name: '2',
content: 'Tab 2 content',
},
{
title: 'Tab 3',
name: '3',
content: 'Tab 3 content',
},
])
const tabIndex = ref(3)
const handleTabsEdit = (
targetName: TabPaneName | undefined,
action: 'remove' | 'add'
) => {
if (action === 'add') {
const newTabName = `${++tabIndex.value}`
editableTabs.value.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content',
})
editableTabsValue.value = newTabName
}
if (action === 'remove') {
const tabs = editableTabs.value
let activeName = editableTabsValue.value
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
const nextTab = tabs[index + 1] || tabs[index - 1]
if (nextTab) {
activeName = nextTab.name
}
}
})
}
editableTabsValue.value = activeName
editableTabs.value = tabs.filter((tab) => tab.name !== targetName)
}
}
const wrapper = mount(() => (
<Tabs
ref="tabs"
v-model={editableTabsValue.value}
type="card"
editable
onEdit={handleTabsEdit}
>
{editableTabs.value.map((tab) => (
<TabPane key={tab.name} label={tab.title} name={tab.name}>
{tab.content}
</TabPane>
))}
</Tabs>
))
const navWrapper = wrapper.findComponent(TabNav)
let panesWrapper = wrapper.findAllComponents(TabPane)
await nextTick()
let navItemsWrapper = navWrapper.findAll('.el-tabs__item')
expect(navItemsWrapper.length).toEqual(3)
expect(panesWrapper.length).toEqual(3)
expect(navItemsWrapper[1].classes('is-active')).toBe(true)
// remove one tab, check panes length
await navItemsWrapper[1].find('.is-icon-close').trigger('click')
panesWrapper = wrapper.findAllComponents(TabPane)
navItemsWrapper = navWrapper.findAll('.el-tabs__item')
expect(navItemsWrapper.length).toEqual(2)
expect(panesWrapper.length).toEqual(2)
// add one tab, check panes length and current tab
await wrapper.find('.el-tabs__new-tab').trigger('click')
panesWrapper = wrapper.findAllComponents(TabPane)
navItemsWrapper = navWrapper.findAll('.el-tabs__item')
expect(navItemsWrapper.length).toEqual(3)
expect(panesWrapper.length).toEqual(3)
expect(navItemsWrapper[2].classes('is-active')).toBe(true)
})
test('addable & closable', async () => {
const editableTabsValue = ref('2')
const editableTabs = ref([
{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content',
},
{
title: 'Tab 2',
name: '2',
content: 'Tab 2 content',
},
])
const tabIndex = ref(2)
const addTab = () => {
const newTabName = `${++tabIndex.value}`
editableTabs.value.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content',
})
editableTabsValue.value = newTabName
}
const removeTab = (targetName: TabPaneName) => {
const tabs = editableTabs.value
let activeName = editableTabsValue.value
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
const nextTab = tabs[index + 1] || tabs[index - 1]
if (nextTab) {
activeName = nextTab.name
}
}
})
}
editableTabsValue.value = activeName
editableTabs.value = tabs.filter((tab) => tab.name !== targetName)
}
const wrapper = mount(() => (
<Tabs
ref="tabs"
v-model={editableTabsValue.value}
type="card"
addable
closable
onTabAdd={addTab}
onTabRemove={removeTab}
>
{editableTabs.value.map((item) => (
<TabPane
label={item.title}
key={item.name}
name={item.name}
></TabPane>
))}
</Tabs>
))
const navWrapper = wrapper.findComponent(TabNav)
await nextTick()
await wrapper.find('.el-tabs__new-tab').trigger('click')
let navItemsWrapper = navWrapper.findAll('.el-tabs__item')
let panesWrapper = wrapper.findAllComponents(TabPane)
expect(navItemsWrapper.length).toEqual(3)
expect(panesWrapper.length).toEqual(3)
expect(navItemsWrapper[2].classes('is-active')).toBe(true)
await navItemsWrapper[2].find('.is-icon-close').trigger('click')
panesWrapper = wrapper.findAllComponents(TabPane)
navItemsWrapper = navWrapper.findAll('.el-tabs__item')
expect(navItemsWrapper.length).toEqual(2)
expect(panesWrapper.length).toEqual(2)
})
test('tab order', async () => {
const editableTabs = ref([
{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content',
},
{
title: 'Tab 2',
name: '2',
content: 'Tab 2 content',
},
])
const wrapper = mount(() => (
<Tabs ref="tabs" type="card">
{editableTabs.value.map((item) => (
<TabPane
label={item.title}
key={item.name}
name={item.name}
></TabPane>
))}
</Tabs>
))
editableTabs.value.splice(1, 0, {
title: 'Tab 3',
name: '3',
content: 'Tab 3 content',
})
await nextTick()
const items = wrapper.findAll('.el-tabs__item')
editableTabs.value.forEach((tab, index) => {
expect(items[index].element.textContent).toEqual(tab.title)
})
})
test('closable in tab-pane', async () => {
const wrapper = mount(() => (
<Tabs type="card" ref="tabs">
<TabPane label="label-1" closable>
A
</TabPane>
<TabPane label="label-2">B</TabPane>
<TabPane label="label-3" closable>
C
</TabPane>
<TabPane label="label-4">D</TabPane>
</Tabs>
))
const navWrapper = wrapper.findComponent(TabNav)
await nextTick()
expect(navWrapper.findAll('.is-icon-close').length).toBe(2)
})
test('disabled', async () => {
const wrapper = mount(() => (
<Tabs type="card" ref="tabs">
<TabPane label="label-1">A</TabPane>
<TabPane disabled label="label-2" ref="disabled">
B
</TabPane>
<TabPane label="label-3">C</TabPane>
<TabPane label="label-4">D</TabPane>
</Tabs>
))
const navWrapper = wrapper.findComponent(TabNav)
await nextTick()
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
expect(navItemsWrapper[1].classes('is-active')).toBe(false)
await navItemsWrapper[1].trigger('click')
expect(navItemsWrapper[1].classes('is-active')).toBe(false)
})
test('tab-position', async () => {
const wrapper = mount(() => (
<Tabs ref="tabs" tab-position="left">
<TabPane label="label-1">A</TabPane>
<TabPane label="label-2">B</TabPane>
<TabPane label="label-3" ref="pane-click">
C
</TabPane>
<TabPane label="label-4">D</TabPane>
</Tabs>
))
const tabsWrapper = wrapper.findComponent(Tabs)
await nextTick()
expect(tabsWrapper.classes('el-tabs--left')).toBe(true)
expect(tabsWrapper.find('.el-tabs__header').classes('is-left')).toBe(true)
expect(tabsWrapper.find('.el-tabs__nav-wrap').classes('is-left')).toBe(true)
expect(tabsWrapper.find('.el-tabs__nav').classes('is-left')).toBe(true)
expect(tabsWrapper.find('.el-tabs__active-bar').classes('is-left')).toBe(
true
)
expect(tabsWrapper.find('.el-tabs__item').classes('is-left')).toBe(true)
})
test('stretch', async () => {
const tabPosition = ref('bottom')
const wrapper = mount(() => (
<Tabs ref="tabs" stretch tab-position={tabPosition.value}>
<TabPane label="label-1">A</TabPane>
<TabPane label="label-2">B</TabPane>
<TabPane label="label-3">C</TabPane>
<TabPane label="label-4">D</TabPane>
</Tabs>
))
const tabsWrapper = wrapper.findComponent(Tabs)
await nextTick()
expect(tabsWrapper.find('.el-tabs__nav').classes('is-stretch')).toBe(true)
tabPosition.value = 'left'
await nextTick()
expect(tabsWrapper.find('.el-tabs__nav').classes('is-stretch')).toBe(false)
})
test('tab active bar offset', async () => {
const tabPosition = ref('bottom')
const wrapper = mount(() => (
<Tabs ref="tabs" stretch tab-position={tabPosition.value}>
<TabPane label="label-1" name="A">
A
</TabPane>
<TabPane label="label-2" name="B">
B
</TabPane>
<TabPane label="label-3" name="C">
C
</TabPane>
<TabPane label="label-4" name="D">
D
</TabPane>
</Tabs>
))
const tabsWrapper = wrapper.findComponent(Tabs)
await nextTick()
const mockOffsetLeft = vi
.spyOn(wrapper.find('#tab-C').element as HTMLElement, 'offsetLeft', 'get')
.mockImplementation(() => 300)
const mockComputedStyle = vi
.spyOn(window, 'getComputedStyle')
.mockReturnValue({ paddingLeft: '0px' } as CSSStyleDeclaration)
await wrapper.find('#tab-C').trigger('click')
await nextTick()
expect(tabsWrapper.find('.el-tabs__active-bar').attributes().style).toMatch(
'translateX(300px)'
)
tabPosition.value = 'left'
await nextTick()
const mockOffsetTop = vi
.spyOn(wrapper.find('#tab-C').element as HTMLElement, 'offsetTop', 'get')
.mockImplementation(() => 200)
await wrapper.find('#tab-A').trigger('click')
await wrapper.find('#tab-C').trigger('click')
await nextTick()
expect(tabsWrapper.find('.el-tabs__active-bar').attributes().style).toMatch(
'translateY(200px)'
)
mockOffsetLeft.mockRestore()
mockOffsetTop.mockRestore()
mockComputedStyle.mockRestore()
wrapper.unmount()
})
test('horizontal-scrollable', async () => {
// TODO: jsdom not support `clientWidth`.
})
test('vertical-scrollable', async () => {
// TODO: jsdom not support `clientWidth`.
})
test('should work with lazy', async () => {
const activeName = ref('A')
const wrapper = mount(() => (
<Tabs v-model={activeName.value} ref="tabs">
<TabPane label="label-1" lazy name="A">
A
</TabPane>
<TabPane label="label-2" name="B">
B
</TabPane>
<TabPane label="label-3" name="C">
C
</TabPane>
<TabPane label="label-4" lazy name="D">
D
</TabPane>
</Tabs>
))
const navWrapper = wrapper.findComponent(TabNav)
await nextTick()
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
expect(wrapper.findAll('.el-tab-pane').length).toBe(3)
await navItemsWrapper[3].trigger('click')
expect(wrapper.findAll('.el-tab-pane').length).toBe(4)
})
test('before leave', async () => {
const activeName = ref('tab-B')
const beforeLeave = () => {
return new window.Promise<void>((resolve, reject) => {
reject()
})
}
const wrapper = mount(() => (
<Tabs ref="tabs" v-model={activeName.value} beforeLeave={beforeLeave}>
<TabPane name="tab-A" label="label-1">
A
</TabPane>
<TabPane name="tab-B" label="label-2">
B
</TabPane>
<TabPane name="tab-C" label="label-3">
C
</TabPane>
<TabPane name="tab-D" label="label-4">
D
</TabPane>
</Tabs>
))
const navWrapper = wrapper.findComponent(TabNav)
const panesWrapper = wrapper.findAllComponents(TabPane)
await nextTick()
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
expect(navItemsWrapper[1].classes('is-active')).toBe(true)
expect(panesWrapper[1].attributes('style')).toBeFalsy()
await navItemsWrapper[3].trigger('click')
expect(navItemsWrapper[1].classes('is-active')).toBe(true)
expect(panesWrapper[1].attributes('style')).toBeFalsy()
})
test('keyboard event', async () => {
const activeName = ref('second')
const wrapper = mount(() => (
<Tabs v-model={activeName.value}>
<TabPane label="label-1" name="first" disabled>
A
</TabPane>
<TabPane label="label-2" name="second">
B
</TabPane>
<TabPane label="label-3" name="third">
C
</TabPane>
<TabPane label="label-4" name="fourth">
D
</TabPane>
</Tabs>
))
await nextTick()
await wrapper
.find('#tab-second')
.trigger('keydown', { code: EVENT_CODE.right })
expect(activeName.value).toEqual('third')
await wrapper
.find('#tab-third')
.trigger('keydown', { code: EVENT_CODE.right })
expect(activeName.value).toEqual('fourth')
await wrapper
.find('#tab-fourth')
.trigger('keydown', { code: EVENT_CODE.right })
expect(activeName.value).toEqual('second')
await wrapper
.find('#tab-second')
.trigger('keydown', { code: EVENT_CODE.left })
expect(activeName.value).toEqual('fourth')
})
test('resize', async () => {
// TODO: jsdom not support `clientWidth`.
})
test('DOM update finished calculating navOffset', async () => {
const tabs = Array.from({ length: 100 }, (_, i) => i.toString())
const activeName = ref('0')
const wrapper = mount(() => (
<Tabs v-model={activeName.value}>
{tabs.map((item) => (
<TabPane key={item} label={item} name={item} />
))}
</Tabs>
))
const tabsWrapper = wrapper.findComponent(Tabs)
await nextTick()
const mockOffsetLeft = vi
.spyOn(
wrapper.find('#tab-99').element as HTMLElement,
'offsetLeft',
'get'
)
.mockImplementation(() => 100)
const mockComputedStyle = vi
.spyOn(window, 'getComputedStyle')
.mockReturnValue({ paddingLeft: '0px' } as CSSStyleDeclaration)
await wrapper.find('#tab-99').trigger('click')
await nextTick()
expect(tabsWrapper.find('.el-tabs__active-bar').attributes().style).toMatch(
'translateX(100px)'
)
mockOffsetLeft.mockRestore()
mockComputedStyle.mockRestore()
wrapper.unmount()
})
test('value type', async () => {
const activeName = ref<TabPaneName | undefined>(0)
const handleClick = (tab: TabsPaneContext) => {
activeName.value = tab.paneName
}
const wrapper = mount(() => (
<Tabs v-model={activeName.value} onTabClick={handleClick}>
<TabPane name={0} label="label-1">
A
</TabPane>
<TabPane name={1} label="label-2">
B
</TabPane>
<TabPane name={2} label="label-3" ref="pane-click">
C
</TabPane>
<TabPane name={3} label="label-4">
D
</TabPane>
</Tabs>
))
const navWrapper = wrapper.findComponent(TabNav)
await nextTick()
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
;[1, 0, 2, 0, 3, 0, 1].forEach((val) => {
navItemsWrapper[val].trigger('click')
expect(activeName.value).toEqual(val)
})
})
test('both number and string for name', async () => {
const activeName = ref<TabPaneName | undefined>(0)
const handleClick = (tab: TabsPaneContext) => {
activeName.value = tab.paneName
}
const wrapper = mount(() => (
<Tabs v-model={activeName.value} onTabClick={handleClick}>
<TabPane name={0} label="n-0">
number-0
</TabPane>
<TabPane name={'0'} label="s-0">
string-0
</TabPane>
<TabPane name={1} label="n-1">
number-1
</TabPane>
<TabPane name={'1'} label="s-1">
string-1
</TabPane>
</Tabs>
))
const navWrapper = wrapper.findComponent(TabNav)
await nextTick()
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
expect(navItemsWrapper[0].classes('is-active')).toBe(true)
expect(navItemsWrapper[1].classes('is-active')).toBe(false)
await navItemsWrapper[1].trigger('click')
expect(navItemsWrapper[0].classes('is-active')).toBe(false)
expect(navItemsWrapper[1].classes('is-active')).toBe(true)
await navItemsWrapper[2].trigger('click')
expect(navItemsWrapper[0].classes('is-active')).toBe(false)
expect(navItemsWrapper[1].classes('is-active')).toBe(false)
expect(navItemsWrapper[2].classes('is-active')).toBe(true)
expect(navItemsWrapper[3].classes('is-active')).toBe(false)
})
test('tab-pane nested', async () => {
const wrapper = mount(() => (
<Tabs>
<Comp />
</Tabs>
))
const panesWrapper = wrapper.findAllComponents(TabPane)
await nextTick()
expect(panesWrapper.length).toBe(1)
})
})