fix(components): [tabs] ensure correct event order (#20384)

This commit is contained in:
DDDDD12138 2025-04-12 06:17:11 +08:00 committed by GitHub
parent 08976b6ed4
commit 9473f9dcb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 111 additions and 1 deletions

View File

@ -837,4 +837,114 @@ describe('Tabs.vue', () => {
await flushPromises()
expect(activeName.value).toBe('tab2')
})
test('event order: tabClick -> update:modelValue -> tabChange', async () => {
const events: string[] = []
const handleTabClick = vi.fn(() => {
events.push('tabClick')
})
const handleUpdateModelValue = vi.fn(() => {
events.push('update:modelValue')
})
const handleTabChange = vi.fn(() => {
events.push('tabChange')
})
const activeName = ref('tab1')
const wrapper = mount(() => (
<Tabs
v-model={activeName.value}
onTabClick={handleTabClick}
onUpdate:modelValue={handleUpdateModelValue}
onTabChange={handleTabChange}
>
<TabPane name="tab1" label="tab1">
Tab 1 content
</TabPane>
<TabPane name="tab2" label="tab2">
Tab 2 content
</TabPane>
</Tabs>
))
await nextTick()
const navWrapper = wrapper.findComponent(TabNav)
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
// Click on the second tab
await navItemsWrapper[1].trigger('click')
// Verify all events have been called
expect(handleTabClick).toHaveBeenCalledTimes(1)
expect(handleUpdateModelValue).toHaveBeenCalledTimes(1)
expect(handleTabChange).toHaveBeenCalledTimes(1)
// Verify the event call order
expect(events).toEqual(['tabClick', 'update:modelValue', 'tabChange'])
})
test('event order with beforeLeave: tabClick -> update:modelValue -> tabChange', async () => {
const events: string[] = []
const handleTabClick = vi.fn(() => {
events.push('tabClick')
})
const handleUpdateModelValue = vi.fn(() => {
events.push('update:modelValue')
})
const handleTabChange = vi.fn(() => {
events.push('tabChange')
})
// Create a beforeLeave function that uses a timer
const beforeLeave = () =>
new Promise<void>((resolve) => setTimeout(resolve, 100))
const activeName = ref('tab1')
const wrapper = mount(() => (
<Tabs
v-model={activeName.value}
onTabClick={handleTabClick}
onUpdate:modelValue={handleUpdateModelValue}
onTabChange={handleTabChange}
beforeLeave={beforeLeave}
>
<TabPane name="tab1" label="tab1">
Tab 1 content
</TabPane>
<TabPane name="tab2" label="tab2">
Tab 2 content
</TabPane>
</Tabs>
))
await nextTick()
const navWrapper = wrapper.findComponent(TabNav)
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
// Click on the second tab
await navItemsWrapper[1].trigger('click')
// Verify only tabClick is called immediately
expect(handleTabClick).toHaveBeenCalledTimes(1)
expect(handleUpdateModelValue).toHaveBeenCalledTimes(0)
expect(handleTabChange).toHaveBeenCalledTimes(0)
expect(events).toEqual(['tabClick'])
// Verify the model value has not been updated yet (waiting for timer)
expect(activeName.value).toBe('tab1')
// Fast-forward time to trigger the setTimeout callback
await beforeLeave()
await flushPromises()
// Verify all events have been called in the correct order
expect(handleUpdateModelValue).toHaveBeenCalledTimes(1)
expect(handleTabChange).toHaveBeenCalledTimes(1)
expect(events).toEqual(['tabClick', 'update:modelValue', 'tabChange'])
// Verify the model value has been updated
expect(activeName.value).toBe('tab2')
})
})

View File

@ -150,8 +150,8 @@ const Tabs = defineComponent({
event: Event
) => {
if (tab.props.disabled) return
setCurrentName(tabName, true)
emit('tabClick', tab, event)
setCurrentName(tabName, true)
}
const handleTabRemove = (pane: TabsPaneContext, ev: Event) => {