mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
fix(components): [select] select value label rendering error (#20769)
* fix(components): [select] select value label rendering error * chore: comment * fix: update * fix: update * feat: manual render * refactor: immediate watch * test: update * test: with multiple
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import { defineComponent, markRaw, nextTick, ref } from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { afterEach, describe, expect, it, test, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest'
|
||||
import { EVENT_CODE } from '@element-plus/constants'
|
||||
import { ArrowDown, CaretTop, CircleClose } from '@element-plus/icons-vue'
|
||||
import { usePopperContainerId } from '@element-plus/hooks'
|
||||
@@ -307,11 +307,17 @@ const WRAPPER_CLASS_NAME = 'el-select__wrapper'
|
||||
const OPTION_ITEM_CLASS_NAME = 'el-select-dropdown__item'
|
||||
const PLACEHOLDER_CLASS_NAME = 'el-select__placeholder'
|
||||
const DEFAULT_PLACEHOLDER = 'Select'
|
||||
const TAG_NAME = `${WRAPPER_CLASS_NAME} .el-tag`
|
||||
|
||||
describe('Select', () => {
|
||||
let wrapper: ReturnType<typeof _mount>
|
||||
beforeEach(() => {
|
||||
// This is convenient for testing the default value label rendering when persistent is false.
|
||||
process.env.RUN_TEST_FILE_NAME = 'select'
|
||||
})
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
delete process.env.RUN_TEST_FILE_NAME
|
||||
})
|
||||
|
||||
test('create', async () => {
|
||||
@@ -380,6 +386,140 @@ describe('Select', () => {
|
||||
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe('双皮奶')
|
||||
})
|
||||
|
||||
test('the scenario of rendering label when there is a default value and persistent is false', async () => {
|
||||
wrapper = _mount(
|
||||
`
|
||||
<el-select v-model="value" :persistent="false">
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:label="item.label"
|
||||
:key="item.value"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
`,
|
||||
() => ({
|
||||
options: [
|
||||
{
|
||||
value: '选项1',
|
||||
label: '黄金糕',
|
||||
},
|
||||
{
|
||||
value: '选项2',
|
||||
label: '双皮奶',
|
||||
},
|
||||
],
|
||||
value: '选项2',
|
||||
})
|
||||
)
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe('双皮奶')
|
||||
})
|
||||
|
||||
test('when there is a default value and persistent is false, render the label and dynamically modify options', async () => {
|
||||
wrapper = _mount(
|
||||
`
|
||||
<el-select v-model="value" :persistent="false">
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:label="item.label"
|
||||
:key="item.value"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
`,
|
||||
() => ({
|
||||
options: [],
|
||||
value: '选项2',
|
||||
})
|
||||
)
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
vm.options = [
|
||||
{
|
||||
value: '选项1',
|
||||
label: '黄金糕',
|
||||
},
|
||||
{
|
||||
value: '选项2',
|
||||
label: '双皮奶',
|
||||
},
|
||||
]
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe('双皮奶')
|
||||
})
|
||||
|
||||
test('multiple is true and persistent is false', async () => {
|
||||
wrapper = _mount(
|
||||
`
|
||||
<el-select v-model="value" :persistent="false" multiple>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:label="item.label"
|
||||
:key="item.value"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
`,
|
||||
() => ({
|
||||
options: [
|
||||
{
|
||||
value: '选项1',
|
||||
label: '黄金糕',
|
||||
},
|
||||
{
|
||||
value: '选项2',
|
||||
label: '双皮奶',
|
||||
},
|
||||
],
|
||||
value: ['选项2'],
|
||||
})
|
||||
)
|
||||
await nextTick()
|
||||
|
||||
const tags = wrapper.findAll(`.${TAG_NAME}`)
|
||||
expect(tags.length).toBe(1)
|
||||
expect(tags[0].text()).toBe('双皮奶')
|
||||
})
|
||||
|
||||
test('multiple is true and persistent is false, render the label and dynamically modify options', async () => {
|
||||
wrapper = _mount(
|
||||
`
|
||||
<el-select v-model="value" :persistent="false" multiple>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:label="item.label"
|
||||
:key="item.value"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
`,
|
||||
() => ({
|
||||
options: [],
|
||||
value: ['选项2'],
|
||||
})
|
||||
)
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
vm.options = [
|
||||
{
|
||||
value: '选项1',
|
||||
label: '黄金糕',
|
||||
},
|
||||
{
|
||||
value: '选项2',
|
||||
label: '双皮奶',
|
||||
},
|
||||
]
|
||||
await nextTick()
|
||||
|
||||
const tags = wrapper.findAll(`.${TAG_NAME}`)
|
||||
expect(tags.length).toBe(1)
|
||||
expect(tags[0].text()).toBe('双皮奶')
|
||||
})
|
||||
|
||||
test('expose select label', async () => {
|
||||
wrapper = _mount(
|
||||
`
|
||||
@@ -1291,8 +1431,8 @@ describe('Select', () => {
|
||||
const triggerWrappers = wrapper.findAll('.el-tooltip__trigger')
|
||||
expect(triggerWrappers[0]).toBeDefined()
|
||||
const tags = document.querySelectorAll('.el-select__tags-text')
|
||||
expect(tags.length).toBe(4)
|
||||
expect(tags[3].textContent).toBe('蚵仔煎')
|
||||
expect(tags.length).toBe(2)
|
||||
expect(tags[1].textContent).toBe(' + 2')
|
||||
})
|
||||
|
||||
test('multiple select with maxCollapseTags', async () => {
|
||||
|
||||
@@ -300,14 +300,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, provide, reactive, toRefs } from 'vue'
|
||||
import { computed, defineComponent, provide, reactive, toRefs, watch } from 'vue'
|
||||
import { ClickOutside } from '@element-plus/directives'
|
||||
import ElTooltip from '@element-plus/components/tooltip'
|
||||
import ElScrollbar from '@element-plus/components/scrollbar'
|
||||
import ElTag from '@element-plus/components/tag'
|
||||
import ElIcon from '@element-plus/components/icon'
|
||||
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
|
||||
import { isArray } from '@element-plus/utils'
|
||||
import { flattedChildren, isArray, isObject } from '@element-plus/utils'
|
||||
import { useCalcInputWidth } from '@element-plus/hooks'
|
||||
import ElOption from './option.vue'
|
||||
import ElSelectMenu from './select-dropdown.vue'
|
||||
@@ -316,6 +316,7 @@ import { selectKey } from './token'
|
||||
import ElOptions from './options'
|
||||
import { SelectProps } from './select'
|
||||
|
||||
import type { VNode } from 'vue';
|
||||
import type { SelectContext } from './type'
|
||||
|
||||
const COMPONENT_NAME = 'ElSelect'
|
||||
@@ -344,7 +345,7 @@ export default defineComponent({
|
||||
'popup-scroll',
|
||||
],
|
||||
|
||||
setup(props, { emit }) {
|
||||
setup(props, { emit, slots }) {
|
||||
const modelValue = computed(() => {
|
||||
const { modelValue: rawModelValue, multiple } = props
|
||||
const fallback = multiple ? [] : undefined
|
||||
@@ -365,6 +366,31 @@ export default defineComponent({
|
||||
const API = useSelect(_props, emit)
|
||||
const { calculatorRef, inputStyle } = useCalcInputWidth()
|
||||
|
||||
const manuallyRenderSlots = (defaultSlots: VNode[] | undefined) => {
|
||||
// After option rendering is completed, the useSelect internal state can collect the value of each option.
|
||||
// If the persistent value is false, option will not be rendered by default, so in this case,
|
||||
// manually render and load option data here.
|
||||
if (!props.persistent && defaultSlots) {
|
||||
const children = flattedChildren(defaultSlots) as VNode[]
|
||||
children.filter((item) => {
|
||||
// @ts-expect-error
|
||||
return isObject(item) && item!.type.name === 'ElOption'
|
||||
}).forEach(item => {
|
||||
const obj = { ...item.props } as any
|
||||
obj.currentLabel = obj.label || (isObject(obj.value) ? '' : obj.value)
|
||||
API.onOptionCreate(obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
watch(() => {
|
||||
const currentSlot = slots.default?.()
|
||||
return currentSlot
|
||||
}, (newSlot) => {
|
||||
manuallyRenderSlots(newSlot)
|
||||
}, {
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
provide(
|
||||
selectKey,
|
||||
reactive({
|
||||
|
||||
@@ -87,7 +87,9 @@ const persistentRef = computed(() => {
|
||||
// For testing, we would always want the content to be rendered
|
||||
// to the DOM, so we need to return true here.
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return true
|
||||
if (process.env.RUN_TEST_FILE_NAME !== 'select') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return props.persistent
|
||||
})
|
||||
|
||||
1
typings/env.d.ts
vendored
1
typings/env.d.ts
vendored
@@ -5,6 +5,7 @@ declare global {
|
||||
const process: {
|
||||
env: {
|
||||
NODE_ENV: string
|
||||
RUN_TEST_FILE_NAME: string
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user