mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
feat(components): [tree-select] add instance type and improve test cleanup logic (#22499)
* feat(components): [tree-select] add instance * chore: update * chore: update test Co-authored-by: btea <2356281422@qq.com> * Update packages/components/tree-select/src/instance.ts Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com> --------- Co-authored-by: btea <2356281422@qq.com> Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>
This commit is contained in:
@@ -85,33 +85,33 @@ slider/show-marks
|
||||
|
||||
### Attributes
|
||||
|
||||
| Name | Description | Type | Default |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| model-value / v-model | binding value | ^[number] / ^[object]`number[]` | 0 |
|
||||
| min | minimum value | ^[number] | 0 |
|
||||
| max | maximum value | ^[number] | 100 |
|
||||
| disabled | whether Slider is disabled | ^[boolean] | false |
|
||||
| step | step size | ^[number] | 1 |
|
||||
| show-input | whether to display an input box, works when `range` is false | ^[boolean] | false |
|
||||
| show-input-controls | whether to display control buttons when `show-input` is true | ^[boolean] | true |
|
||||
| size | size of the slider wrapper, will not work in vertical mode | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | default |
|
||||
| input-size | size of the input box, when set `size`, the default is the value of `size` | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | default |
|
||||
| show-stops | whether to display breakpoints | ^[boolean] | false |
|
||||
| show-tooltip | whether to display tooltip value | ^[boolean] | true |
|
||||
| format-tooltip | format to display tooltip value | ^[Function]`(value: number) => number \| string` | — |
|
||||
| range | whether to select a range | ^[boolean] | false |
|
||||
| vertical | vertical mode | ^[boolean] | false |
|
||||
| height | slider height, required in vertical mode | ^[string] | — |
|
||||
| aria-label ^(a11y) ^(2.7.2) | native `aria-label` attribute | ^[string] | — |
|
||||
| range-start-label | when `range` is true, screen reader label for the start of the range | ^[string] | — |
|
||||
| range-end-label | when `range` is true, screen reader label for the end of the range | ^[string] | — |
|
||||
| format-value-text | format to display the `aria-valuenow` attribute for screen readers | ^[Function]`(value: number) => string` | — |
|
||||
| tooltip-class | custom class name for the tooltip | ^[string] | — |
|
||||
| placement | position of Tooltip | ^[enum]`'top' \| 'top-start' \| 'top-end' \| 'bottom' \| 'bottom-start' \| 'bottom-end' \| 'left' \| 'left-start' \| 'left-end' \| 'right' \| 'right-start' \| 'right-end'` | top |
|
||||
| marks | marks, type of key must be `number` and must in closed interval `[min, max]`, each mark can custom style | ^[object]`SliderMarks` | — |
|
||||
| validate-event | whether to trigger form validation | ^[boolean] | true |
|
||||
| Name | Description | Type | Default |
|
||||
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| model-value / v-model | binding value | ^[number] / ^[object]`number[]` | 0 |
|
||||
| min | minimum value | ^[number] | 0 |
|
||||
| max | maximum value | ^[number] | 100 |
|
||||
| disabled | whether Slider is disabled | ^[boolean] | false |
|
||||
| step | step size | ^[number] | 1 |
|
||||
| show-input | whether to display an input box, works when `range` is false | ^[boolean] | false |
|
||||
| show-input-controls | whether to display control buttons when `show-input` is true | ^[boolean] | true |
|
||||
| size | size of the slider wrapper, will not work in vertical mode | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | default |
|
||||
| input-size | size of the input box, when set `size`, the default is the value of `size` | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | default |
|
||||
| show-stops | whether to display breakpoints | ^[boolean] | false |
|
||||
| show-tooltip | whether to display tooltip value | ^[boolean] | true |
|
||||
| format-tooltip | format to display tooltip value | ^[Function]`(value: number) => number \| string` | — |
|
||||
| range | whether to select a range | ^[boolean] | false |
|
||||
| vertical | vertical mode | ^[boolean] | false |
|
||||
| height | slider height, required in vertical mode | ^[string] | — |
|
||||
| aria-label ^(a11y) ^(2.7.2) | native `aria-label` attribute | ^[string] | — |
|
||||
| range-start-label | when `range` is true, screen reader label for the start of the range | ^[string] | — |
|
||||
| range-end-label | when `range` is true, screen reader label for the end of the range | ^[string] | — |
|
||||
| format-value-text | format to display the `aria-valuenow` attribute for screen readers | ^[Function]`(value: number) => string` | — |
|
||||
| tooltip-class | custom class name for the tooltip | ^[string] | — |
|
||||
| placement | position of Tooltip | ^[enum]`'top' \| 'top-start' \| 'top-end' \| 'bottom' \| 'bottom-start' \| 'bottom-end' \| 'left' \| 'left-start' \| 'left-end' \| 'right' \| 'right-start' \| 'right-end'` | top |
|
||||
| marks | marks, type of key must be `number` and must in closed interval `[min, max]`, each mark can custom style | ^[object]`SliderMarks` | — |
|
||||
| validate-event | whether to trigger form validation | ^[boolean] | true |
|
||||
| persistent ^(2.9.5) | when slider tooltip inactive and `persistent` is `false` , tooltip will be destroyed. `persistent` always be `false` when `show-tooltip ` is `false` | ^[boolean] | true |
|
||||
| label ^(a11y) ^(deprecated) | native `aria-label` attribute | ^[string] | — |
|
||||
| label ^(a11y) ^(deprecated) | native `aria-label` attribute | ^[string] | — |
|
||||
|
||||
### Events
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ tooltip/append-to
|
||||
| virtual-triggering | Indicates whether virtual triggering is enabled | ^[boolean] | — |
|
||||
| virtual-ref | Indicates the reference element to which the tooltip is attached | ^[HTMLElement] | — |
|
||||
| trigger-keys | When you click the mouse to focus on the trigger element, you can define a set of keyboard codes to control the display of tooltip through the keyboard, not valid in controlled mode | ^[Array] | ['Enter','Space'] |
|
||||
| persistent | when tooltip inactive and `persistent` is `false` , tooltip will be destroyed | ^[boolean] | — |
|
||||
| persistent | when tooltip inactive and `persistent` is `false` , tooltip will be destroyed | ^[boolean] | — |
|
||||
| aria-label ^(a11y) | same as `aria-label` | ^[string] | — |
|
||||
| focus-on-target ^(2.11.2) | when triggering tooltips through hover, whether to focus the trigger element, which improves accessibility | ^[boolean] | false |
|
||||
|
||||
|
||||
@@ -1,16 +1,53 @@
|
||||
import { nextTick, reactive, ref } from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { afterEach, describe, expect, test, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
import { CircleClose } from '@element-plus/icons-vue'
|
||||
import TreeSelect from '../src/tree-select.vue'
|
||||
import Tree from '@element-plus/components/tree/src/tree.vue'
|
||||
import defineGetter from '@element-plus/test-utils/define-getter'
|
||||
import { EVENT_CODE } from '@element-plus/constants'
|
||||
|
||||
import type { TreeSelectInstance } from '../src/instance'
|
||||
import type { RenderFunction } from 'vue'
|
||||
import type { VueWrapper } from '@vue/test-utils'
|
||||
import type ElSelect from '@element-plus/components/select'
|
||||
import type ElTree from '@element-plus/components/tree'
|
||||
|
||||
// Keep track of all mounted wrappers for cleanup
|
||||
const mountedWrappers: VueWrapper<any>[] = []
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
mountedWrappers.forEach((wrapper) => {
|
||||
if (wrapper && wrapper.exists()) {
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
mountedWrappers.length = 0
|
||||
document.body.innerHTML = ''
|
||||
vi.clearAllTimers()
|
||||
|
||||
const frameIds = new Set<number>()
|
||||
const originalRequestAnimationFrame = global.requestAnimationFrame
|
||||
|
||||
global.requestAnimationFrame = function (cb) {
|
||||
const id = originalRequestAnimationFrame((timestamp) => {
|
||||
frameIds.delete(id)
|
||||
cb(timestamp)
|
||||
})
|
||||
frameIds.add(id)
|
||||
return id
|
||||
}
|
||||
|
||||
const cancelAllAnimationFrames = () => {
|
||||
frameIds.forEach((id) => global.cancelAnimationFrame(id))
|
||||
frameIds.clear()
|
||||
}
|
||||
|
||||
cancelAllAnimationFrames()
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
const createComponent = ({
|
||||
slots = {},
|
||||
@@ -19,7 +56,7 @@ const createComponent = ({
|
||||
slots?: Record<string, any>
|
||||
props?: (typeof TreeSelect)['props']
|
||||
} = {}) => {
|
||||
const wrapperRef = ref<InstanceType<typeof TreeSelect>>()
|
||||
const wrapperRef = ref<TreeSelectInstance>()
|
||||
const defaultData = ref([
|
||||
{
|
||||
value: 1,
|
||||
@@ -53,9 +90,7 @@ const createComponent = ({
|
||||
<TreeSelect
|
||||
{...bindProps}
|
||||
onUpdate:modelValue={(val: string) => (bindProps.modelValue = val)}
|
||||
ref={(val: InstanceType<typeof TreeSelect>) =>
|
||||
(wrapperRef.value = val)
|
||||
}
|
||||
ref={(val: TreeSelectInstance) => (wrapperRef.value = val)}
|
||||
v-slots={slots}
|
||||
/>
|
||||
)
|
||||
@@ -66,17 +101,22 @@ const createComponent = ({
|
||||
}
|
||||
)
|
||||
|
||||
// Add wrapper to tracking array for cleanup
|
||||
mountedWrappers.push(wrapper)
|
||||
|
||||
return {
|
||||
wrapper,
|
||||
getWrapperRef: () =>
|
||||
new Promise<InstanceType<typeof TreeSelect>>((resolve) =>
|
||||
nextTick(() => resolve(wrapperRef.value!))
|
||||
new Promise<TreeSelectInstance>((resolve) =>
|
||||
nextTick(() =>
|
||||
resolve(wrapperRef.value! as unknown as TreeSelectInstance)
|
||||
)
|
||||
),
|
||||
select: wrapper.findComponent({ name: 'ElSelect' }) as VueWrapper<
|
||||
InstanceType<typeof ElSelect>
|
||||
>,
|
||||
select: wrapper.findComponent({
|
||||
name: 'ElSelect',
|
||||
}) as VueWrapper<TreeSelectInstance['selectRef']>,
|
||||
tree: wrapper.findComponent({ name: 'ElTree' }) as VueWrapper<
|
||||
InstanceType<typeof ElTree>
|
||||
TreeSelectInstance['treeRef']
|
||||
>,
|
||||
}
|
||||
}
|
||||
@@ -160,12 +200,12 @@ describe('TreeSelect.vue', () => {
|
||||
|
||||
await nextTick()
|
||||
expect(select.vm.modelValue).toBe(1)
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([1])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([1])
|
||||
|
||||
value.value = 11
|
||||
await nextTick(nextTick)
|
||||
expect(select.vm.modelValue).toBe(11)
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([11])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([11])
|
||||
|
||||
await tree
|
||||
.findAll('.el-select-dropdown__item')
|
||||
@@ -173,17 +213,17 @@ describe('TreeSelect.vue', () => {
|
||||
.trigger('click')
|
||||
await nextTick()
|
||||
expect(select.vm.modelValue).toBe(111)
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([111])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([111])
|
||||
|
||||
await tree.find('.el-tree-node__content').trigger('click')
|
||||
await nextTick()
|
||||
expect(select.vm.modelValue).toBe(1)
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([1])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([1])
|
||||
|
||||
await tree.findAll('.el-checkbox__original')[1].trigger('click')
|
||||
await nextTick()
|
||||
expect(select.vm.modelValue).toBe(11)
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([11])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([11])
|
||||
})
|
||||
|
||||
test('disabled', async () => {
|
||||
@@ -235,12 +275,12 @@ describe('TreeSelect.vue', () => {
|
||||
|
||||
await nextTick()
|
||||
expect(select.vm.modelValue).toEqual([1])
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([1])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([1])
|
||||
|
||||
value.value = [11]
|
||||
await nextTick(nextTick)
|
||||
expect(select.vm.modelValue).toEqual([11])
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([11])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([11])
|
||||
|
||||
await tree
|
||||
.findAll('.el-select-dropdown__item')
|
||||
@@ -248,17 +288,17 @@ describe('TreeSelect.vue', () => {
|
||||
.trigger('click')
|
||||
await nextTick()
|
||||
expect(select.vm.modelValue).toEqual([11, 111])
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([11, 111])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([11, 111])
|
||||
|
||||
await tree.find('.el-tree-node__content').trigger('click')
|
||||
await nextTick()
|
||||
expect(select.vm.modelValue).toEqual([1, 11, 111])
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([1, 11, 111])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([1, 11, 111])
|
||||
|
||||
await tree.findAll('.el-checkbox')[1].trigger('click')
|
||||
await nextTick()
|
||||
expect(select.vm.modelValue).toEqual([1, 111])
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([1, 111])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([1, 111])
|
||||
})
|
||||
|
||||
test('filter', async () => {
|
||||
@@ -384,14 +424,14 @@ describe('TreeSelect.vue', () => {
|
||||
await tree.findAll('.el-tree-node__content')[0].trigger('click')
|
||||
await nextTick()
|
||||
expect(select.vm.modelValue).toEqual([])
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([])
|
||||
|
||||
await tree
|
||||
.findAll('.el-tree-node__content .el-checkbox')[0]
|
||||
.trigger('click')
|
||||
await nextTick()
|
||||
expect(select.vm.modelValue).toEqual([1])
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([1])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([1])
|
||||
})
|
||||
|
||||
test('check-strictly showCheckbox checkOnClickNode click node', async () => {
|
||||
@@ -408,14 +448,14 @@ describe('TreeSelect.vue', () => {
|
||||
await tree.findAll('.el-tree-node__content')[0].trigger('click')
|
||||
await nextTick()
|
||||
expect(select.vm.modelValue).toEqual([1])
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([1])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([1])
|
||||
|
||||
await tree
|
||||
.findAll('.el-tree-node__content .el-checkbox')[0]
|
||||
.trigger('click')
|
||||
await nextTick()
|
||||
expect(select.vm.modelValue).toEqual([])
|
||||
expect(wrapperRef.getCheckedKeys()).toEqual([])
|
||||
expect(wrapperRef.treeRef.getCheckedKeys()).toEqual([])
|
||||
})
|
||||
|
||||
test('only show checkbox', async () => {
|
||||
@@ -978,6 +1018,10 @@ describe('TreeSelect.vue', () => {
|
||||
},
|
||||
template: `<TreeSelect v-for="item in data" v-model="item.value" :data="options" @update:modelValue="item.handleModelValue" />`,
|
||||
})
|
||||
|
||||
// Add to tracking for cleanup
|
||||
mountedWrappers.push(wrapper)
|
||||
|
||||
const select = wrapper.findComponent({
|
||||
name: 'ElSelect',
|
||||
})
|
||||
|
||||
@@ -7,3 +7,5 @@ export const ElTreeSelect: SFCWithInstall<typeof TreeSelect> =
|
||||
withInstall(TreeSelect)
|
||||
|
||||
export default ElTreeSelect
|
||||
|
||||
export type { TreeSelectInstance } from './src/instance'
|
||||
|
||||
7
packages/components/tree-select/src/instance.ts
Normal file
7
packages/components/tree-select/src/instance.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { SelectInstance } from '@element-plus/components/select'
|
||||
import type { TreeInstance } from '@element-plus/components/tree'
|
||||
|
||||
export type TreeSelectInstance = {
|
||||
treeRef: TreeInstance
|
||||
selectRef: SelectInstance
|
||||
}
|
||||
5
typings/env.d.ts
vendored
5
typings/env.d.ts
vendored
@@ -1,4 +1,3 @@
|
||||
import type { vShow } from 'vue'
|
||||
import type { INSTALLED_KEY } from '@element-plus/constants'
|
||||
|
||||
declare global {
|
||||
@@ -25,10 +24,6 @@ declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Component: (props: { is: Component | string }) => void
|
||||
}
|
||||
|
||||
export interface ComponentCustomProperties {
|
||||
vShow: typeof vShow
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
|
||||
Reference in New Issue
Block a user