mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
491 lines
15 KiB
TypeScript
491 lines
15 KiB
TypeScript
import { nextTick, ref } from 'vue'
|
|
import { mount } from '@vue/test-utils'
|
|
import { afterEach, describe, expect, test, vi } from 'vitest'
|
|
import { debugWarn } from '@element-plus/utils'
|
|
import Input from '../../input/src/input.vue'
|
|
import Collapse from '../src/collapse.vue'
|
|
import CollapseItem from '../src/collapse-item.vue'
|
|
|
|
import type { VueWrapper } from '@vue/test-utils'
|
|
import type { CollapseItemInstance } from '../src/instance'
|
|
|
|
const AXIOM = 'Rem is the best girl'
|
|
|
|
vi.mock('@element-plus/utils/error', () => ({
|
|
debugWarn: vi.fn(),
|
|
}))
|
|
|
|
describe('Collapse.vue', () => {
|
|
afterEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
test('create', async () => {
|
|
const wrapper = mount({
|
|
data() {
|
|
return {
|
|
activeNames: ['1'],
|
|
}
|
|
},
|
|
render() {
|
|
return (
|
|
<Collapse v-model={this.activeNames}>
|
|
<CollapseItem title="title1" name="1">
|
|
<div class="content">111</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title2" name="2">
|
|
<div class="content">222</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title3" name="3">
|
|
<div class="content">333</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title4" name="4">
|
|
<div class="content">444</div>
|
|
</CollapseItem>
|
|
</Collapse>
|
|
)
|
|
},
|
|
})
|
|
|
|
const vm = wrapper.vm
|
|
const collapseWrapper = wrapper.findComponent(Collapse)
|
|
const collapseItemWrappers = collapseWrapper.findAllComponents(
|
|
CollapseItem
|
|
) as VueWrapper<CollapseItemInstance>[]
|
|
const collapseItemHeaderEls = vm.$el.querySelectorAll(
|
|
'.el-collapse-item__header'
|
|
)
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
|
|
|
collapseItemHeaderEls[2].click()
|
|
await nextTick()
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
|
expect(collapseItemWrappers[2].vm.isActive).toBe(true)
|
|
collapseItemHeaderEls[0].click()
|
|
await nextTick()
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(false)
|
|
})
|
|
|
|
test('accordion', async () => {
|
|
const wrapper = mount({
|
|
data() {
|
|
return {
|
|
activeNames: ['1'],
|
|
}
|
|
},
|
|
render() {
|
|
return (
|
|
<Collapse accordion v-model={this.activeNames}>
|
|
<CollapseItem title="title1" name="1">
|
|
<div class="content">111</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title2" name="2">
|
|
<div class="content">222</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title3" name="3">
|
|
<div class="content">333</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title4" name="4">
|
|
<div class="content">444</div>
|
|
</CollapseItem>
|
|
</Collapse>
|
|
)
|
|
},
|
|
})
|
|
|
|
const vm = wrapper.vm
|
|
const collapseWrapper = wrapper.findComponent(Collapse)
|
|
const collapseItemWrappers = collapseWrapper.findAllComponents(
|
|
CollapseItem
|
|
) as VueWrapper<CollapseItemInstance>[]
|
|
const collapseItemHeaderEls = vm.$el.querySelectorAll(
|
|
'.el-collapse-item__header'
|
|
)
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
|
|
|
collapseItemHeaderEls[2].click()
|
|
await nextTick()
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(false)
|
|
expect(collapseItemWrappers[2].vm.isActive).toBe(true)
|
|
collapseItemHeaderEls[0].click()
|
|
await nextTick()
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
|
expect(collapseItemWrappers[2].vm.isActive).toBe(false)
|
|
})
|
|
|
|
test('event:change', async () => {
|
|
const onChange = vi.fn()
|
|
const wrapper = mount({
|
|
data() {
|
|
return {
|
|
activeNames: ['1'],
|
|
}
|
|
},
|
|
render() {
|
|
return (
|
|
<Collapse v-model={this.activeNames} onChange={onChange}>
|
|
<CollapseItem title="title1" name="1">
|
|
<div class="content">111</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title2" name="2">
|
|
<div class="content">222</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title3" name="3">
|
|
<div class="content">333</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title4" name="4">
|
|
<div class="content">444</div>
|
|
</CollapseItem>
|
|
</Collapse>
|
|
)
|
|
},
|
|
})
|
|
|
|
const vm = wrapper.vm
|
|
const collapseWrapper = wrapper.findComponent(Collapse)
|
|
const collapseItemWrappers = collapseWrapper.findAllComponents(
|
|
CollapseItem
|
|
) as VueWrapper<CollapseItemInstance>[]
|
|
const collapseItemHeaderEls = vm.$el.querySelectorAll(
|
|
'.el-collapse-item__header'
|
|
)
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
|
expect(vm.activeNames).toEqual(['1'])
|
|
expect(onChange).not.toHaveBeenCalled()
|
|
collapseItemHeaderEls[2].click()
|
|
await nextTick()
|
|
expect(onChange).toHaveBeenCalledTimes(1)
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
|
expect(collapseItemWrappers[2].vm.isActive).toBe(true)
|
|
expect(vm.activeNames).toEqual(['1', '3'])
|
|
collapseItemHeaderEls[0].click()
|
|
await nextTick()
|
|
expect(onChange).toHaveBeenCalledTimes(2)
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(false)
|
|
expect(vm.activeNames).toEqual(['3'])
|
|
})
|
|
|
|
test('deep watch modelValue', async () => {
|
|
const wrapper = mount({
|
|
data() {
|
|
return {
|
|
activeNames: ['1'],
|
|
}
|
|
},
|
|
mounted() {
|
|
this.activeNames.push('2')
|
|
},
|
|
render() {
|
|
return (
|
|
<Collapse v-model={this.activeNames}>
|
|
<CollapseItem title="title1" name="1">
|
|
<div class="content">111</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title2" name="2">
|
|
<div class="content">222</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title3" name="3">
|
|
<div class="content">333</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title4" name="4">
|
|
<div class="content">444</div>
|
|
</CollapseItem>
|
|
</Collapse>
|
|
)
|
|
},
|
|
})
|
|
|
|
await nextTick()
|
|
const collapseWrapper = wrapper.findComponent(Collapse)
|
|
const collapseItemWrappers = collapseWrapper.findAllComponents(
|
|
CollapseItem
|
|
) as VueWrapper<CollapseItemInstance>[]
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
|
expect(collapseItemWrappers[1].vm.isActive).toBe(true)
|
|
})
|
|
|
|
test('title slot', async () => {
|
|
const wrapper = mount({
|
|
render() {
|
|
return (
|
|
<Collapse>
|
|
<CollapseItem
|
|
name="1"
|
|
v-slots={{
|
|
title: ({ isActive }: { isActive: boolean }) => {
|
|
return (
|
|
<div class={['title-wrapper', { 'is-active': isActive }]}>
|
|
{AXIOM}
|
|
</div>
|
|
)
|
|
},
|
|
}}
|
|
></CollapseItem>
|
|
</Collapse>
|
|
)
|
|
},
|
|
})
|
|
|
|
expect(wrapper.find('.title-wrapper').text()).toBe(AXIOM)
|
|
await wrapper.find('.el-collapse-item__header').trigger('click')
|
|
expect(wrapper.find('.title-wrapper').classes()).toContain('is-active')
|
|
})
|
|
|
|
test('beforeCollapse function return promise', async () => {
|
|
const activeNames = ref([])
|
|
const asyncResult = ref('error')
|
|
const beforeCollapse = () => {
|
|
return new Promise<boolean>((resolve, reject) => {
|
|
setTimeout(() => {
|
|
return asyncResult.value == 'success'
|
|
? resolve(true)
|
|
: reject(new Error('Error'))
|
|
}, 1000)
|
|
})
|
|
}
|
|
const wrapper = mount(() => (
|
|
<Collapse v-model={activeNames.value} beforeCollapse={beforeCollapse}>
|
|
<CollapseItem title="title1" name="1">
|
|
<div class="content">111</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title2" name="2">
|
|
<div class="content">222</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title3" name="3">
|
|
<div class="content">333</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title4" name="4">
|
|
<div class="content">444</div>
|
|
</CollapseItem>
|
|
</Collapse>
|
|
))
|
|
|
|
const vm = wrapper.vm
|
|
const collapseWrapper = wrapper.findComponent(Collapse)
|
|
const collapseItemWrappers = collapseWrapper.findAllComponents(
|
|
CollapseItem
|
|
) as VueWrapper<CollapseItemInstance>[]
|
|
const collapseItemHeaderEls = vm.$el.querySelectorAll(
|
|
'.el-collapse-item__header'
|
|
)
|
|
|
|
vi.useFakeTimers()
|
|
|
|
collapseItemHeaderEls[0].click()
|
|
vi.runAllTimers()
|
|
await nextTick()
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(false)
|
|
expect(debugWarn).toHaveBeenCalledTimes(0)
|
|
|
|
asyncResult.value = 'success'
|
|
|
|
collapseItemHeaderEls[0].click()
|
|
vi.runAllTimers()
|
|
await nextTick()
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
|
expect(debugWarn).toHaveBeenCalledTimes(1)
|
|
|
|
collapseItemHeaderEls[1].click()
|
|
vi.runAllTimers()
|
|
await nextTick()
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
|
expect(collapseItemWrappers[1].vm.isActive).toBe(true)
|
|
expect(debugWarn).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
test('beforeCollapse function return boolean', async () => {
|
|
const activeNames = ref([])
|
|
const result = ref(false)
|
|
const beforeCollapse = () => {
|
|
return result.value
|
|
}
|
|
|
|
const wrapper = mount(() => (
|
|
<Collapse v-model={activeNames.value} beforeCollapse={beforeCollapse}>
|
|
<CollapseItem title="title1" name="1">
|
|
<div class="content">111</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title2" name="2">
|
|
<div class="content">222</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title3" name="3">
|
|
<div class="content">333</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title4" name="4">
|
|
<div class="content">444</div>
|
|
</CollapseItem>
|
|
</Collapse>
|
|
))
|
|
|
|
const vm = wrapper.vm
|
|
const collapseWrapper = wrapper.findComponent(Collapse)
|
|
const collapseItemWrappers = collapseWrapper.findAllComponents(
|
|
CollapseItem
|
|
) as VueWrapper<CollapseItemInstance>[]
|
|
const collapseItemHeaderEls = vm.$el.querySelectorAll(
|
|
'.el-collapse-item__header'
|
|
)
|
|
|
|
collapseItemHeaderEls[0].click()
|
|
await nextTick()
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(false)
|
|
|
|
result.value = true
|
|
|
|
collapseItemHeaderEls[0].click()
|
|
await nextTick()
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
|
|
|
collapseItemHeaderEls[1].click()
|
|
await nextTick()
|
|
expect(collapseItemWrappers[0].vm.isActive).toBe(true)
|
|
expect(collapseItemWrappers[1].vm.isActive).toBe(true)
|
|
})
|
|
|
|
describe('Nested Input Interaction', () => {
|
|
const createWrapper = (withInput: boolean = false) => {
|
|
return mount({
|
|
data: () => ({
|
|
activeNames: ['1'],
|
|
inputText: '',
|
|
}),
|
|
render() {
|
|
return (
|
|
<Collapse v-model={this.activeNames}>
|
|
<CollapseItem
|
|
name="1"
|
|
v-slots={{
|
|
title: () => (
|
|
<div>
|
|
{withInput ? (
|
|
<Input
|
|
data-testid="test-input"
|
|
v-model={this.inputText}
|
|
/>
|
|
) : (
|
|
'title1'
|
|
)}
|
|
</div>
|
|
),
|
|
}}
|
|
>
|
|
<div class="content">111</div>
|
|
</CollapseItem>
|
|
<CollapseItem name="2" title="title2">
|
|
<div class="content">222</div>
|
|
</CollapseItem>
|
|
</Collapse>
|
|
)
|
|
},
|
|
})
|
|
}
|
|
|
|
test('should not toggle collapse when clicking on input', async () => {
|
|
const wrapper = createWrapper(true)
|
|
const input = wrapper.find('[data-testid="test-input"]')
|
|
const header = wrapper.find('.el-collapse-item__header')
|
|
|
|
await input.trigger('click')
|
|
await nextTick()
|
|
|
|
const collapseItem = wrapper.findComponent(CollapseItem)
|
|
expect(collapseItem.vm.isActive).toBe(true)
|
|
|
|
await header.trigger('click')
|
|
await nextTick()
|
|
expect(collapseItem.vm.isActive).toBe(false)
|
|
})
|
|
|
|
test('should not toggle collapse when pressing enter on input', async () => {
|
|
const wrapper = createWrapper(true)
|
|
const input = wrapper.find('[data-testid="test-input"]')
|
|
const header = wrapper.find('.el-collapse-item__header')
|
|
|
|
await input.trigger('keydown.enter')
|
|
await nextTick()
|
|
|
|
const collapseItem = wrapper.findComponent(CollapseItem)
|
|
expect(collapseItem.vm.isActive).toBe(true)
|
|
|
|
await header.trigger('keydown.enter')
|
|
await nextTick()
|
|
expect(collapseItem.vm.isActive).toBe(false)
|
|
})
|
|
|
|
test('should not toggle collapse when pressing space on input', async () => {
|
|
const wrapper = createWrapper(true)
|
|
const input = wrapper.find('[data-testid="test-input"]')
|
|
const header = wrapper.find('.el-collapse-item__header')
|
|
|
|
await input.trigger('keydown.space')
|
|
await nextTick()
|
|
|
|
const collapseItem = wrapper.findComponent(CollapseItem)
|
|
expect(collapseItem.vm.isActive).toBe(true)
|
|
|
|
await header.trigger('keydown.space')
|
|
await nextTick()
|
|
expect(collapseItem.vm.isActive).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('Accessibility', () => {
|
|
test('should have appropriate ARIA attributes', async () => {
|
|
const wrapper = mount({
|
|
data() {
|
|
return {
|
|
activeNames: ['1'],
|
|
}
|
|
},
|
|
render() {
|
|
return (
|
|
<Collapse v-model={this.activeNames}>
|
|
<CollapseItem title="title1" name="1">
|
|
<div class="content">111</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title2" name="2">
|
|
<div class="content">222</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title3" name="3">
|
|
<div class="content">333</div>
|
|
</CollapseItem>
|
|
<CollapseItem title="title4" name="4" disabled>
|
|
<div class="content">444</div>
|
|
</CollapseItem>
|
|
</Collapse>
|
|
)
|
|
},
|
|
})
|
|
|
|
const collapseWrapper = wrapper.findComponent(Collapse)
|
|
const collapseItemWrappers = collapseWrapper.findAllComponents(
|
|
CollapseItem
|
|
) as VueWrapper<CollapseItemInstance>[]
|
|
|
|
collapseItemWrappers.forEach((item, index) => {
|
|
const header = item.find('.el-collapse-item__header')
|
|
const content = item.find('.el-collapse-item__wrap')
|
|
const isDisabled = item.props('disabled')
|
|
|
|
expect(header.attributes('role')).toBe('button')
|
|
expect(header.attributes('aria-controls')).toBe(
|
|
content.attributes('id')
|
|
)
|
|
expect(header.attributes('tabindex')).toBe(isDisabled ? undefined : '0')
|
|
expect(header.attributes('aria-disabled')).toBe(
|
|
isDisabled ? 'true' : 'false'
|
|
)
|
|
expect(header.attributes('aria-expanded')).toBe(
|
|
collapseItemWrappers[index].vm.isActive?.toString()
|
|
)
|
|
expect(content.attributes('aria-labelledby')).toBe(
|
|
header.attributes('id')
|
|
)
|
|
expect(content.attributes('aria-hidden')).toBe(
|
|
(!collapseItemWrappers[index].vm.isActive).toString()
|
|
)
|
|
})
|
|
})
|
|
})
|
|
})
|