fix(components): [date-picker]: use array modelValue datetime type debacle (#22033)

fix(components): [date-picker]: use array value datetime type debacle
This commit is contained in:
sea
2025-09-06 07:46:11 +08:00
committed by GitHub
parent b225f0a31d
commit ca4a798545
5 changed files with 93 additions and 13 deletions

View File

@@ -461,6 +461,24 @@ describe('DatePickerPanel', () => {
'6',
])
})
it('should handle array value without errors', async () => {
const value = ref(['2025-09-01'])
const wrapper = mount(() => (
<DatePickerPanel v-model={value.value} type="datetime" />
))
await nextTick()
const dateInput = wrapper.find(
'.el-date-picker__time-header > span:nth-child(1) input'
).element as HTMLInputElement
expect(dateInput.value).toBe('2025-09-01')
const timeInput = wrapper.find(
'.el-date-picker__time-header > span:nth-child(2) input'
).element as HTMLInputElement
expect(timeInput.value).toBe('00:00:00')
})
})
describe(':type="datetimerange"', () => {

View File

@@ -236,7 +236,7 @@ import {
extractTimeFormat,
} from '@element-plus/components/time-picker'
import { ElIcon } from '@element-plus/components/icon'
import { isArray, isFunction } from '@element-plus/utils'
import { extractFirst, isArray, isFunction } from '@element-plus/utils'
import { EVENT_CODE } from '@element-plus/constants'
import {
ArrowLeft,
@@ -347,8 +347,9 @@ const emit = (value: Dayjs | Dayjs[], ...args: any[]) => {
const handleDatePick = async (value: DateTableEmits, keepOpen?: boolean) => {
if (selectionMode.value === 'date') {
value = value as Dayjs
let newDate = props.parsedValue
? (props.parsedValue as Dayjs)
const parsedDateValue = extractFirst(props.parsedValue)
let newDate = parsedDateValue
? parsedDateValue
.year(value.year())
.month(value.month())
.date(value.date())
@@ -547,7 +548,7 @@ const onConfirm = () => {
emit(props.parsedValue as Dayjs[])
} else {
// deal with the scenario where: user opens the date time picker, then confirm without doing anything
let result = props.parsedValue as Dayjs
let result = extractFirst(props.parsedValue)
if (!result) {
const defaultTimeD = dayjs(defaultTime).locale(lang.value)
const defaultValueD = getDefaultValue()
@@ -595,17 +596,15 @@ const dateFormat = computed(() => {
const visibleTime = computed(() => {
if (userInputTime.value) return userInputTime.value
if (!props.parsedValue && !defaultValue.value) return
return ((props.parsedValue || innerDate.value) as Dayjs).format(
timeFormat.value
)
const dateValue = extractFirst(props.parsedValue) || innerDate.value
return dateValue.format(timeFormat.value)
})
const visibleDate = computed(() => {
if (userInputDate.value) return userInputDate.value
if (!props.parsedValue && !defaultValue.value) return
return ((props.parsedValue || innerDate.value) as Dayjs).format(
dateFormat.value
)
const dateValue = extractFirst(props.parsedValue) || innerDate.value
return dateValue.format(dateFormat.value)
})
const timePickerVisible = ref(false)
@@ -629,8 +628,9 @@ const getUnits = (date: Dayjs) => {
const handleTimePick = (value: Dayjs, visible: boolean, first: boolean) => {
const { hour, minute, second } = getUnits(value)
const newDate = props.parsedValue
? (props.parsedValue as Dayjs).hour(hour).minute(minute).second(second)
const parsedDateValue = extractFirst(props.parsedValue)
const newDate = parsedDateValue
? parsedDateValue.hour(hour).minute(minute).second(second)
: value
innerDate.value = newDate
emit(innerDate.value, true)

View File

@@ -1096,6 +1096,54 @@ describe('DatePicker', () => {
expect(changeHandler).toHaveBeenCalledTimes(1)
})
})
it('should handle array value for datetime type without errors', async () => {
const wrapper = _mount(
`<el-date-picker
v-model="value"
type="datetime"
/>`,
() => ({ value: ['2025-09-01'] })
)
await nextTick()
const input = wrapper.find('input')
expect(input.element.value).toBe('2025-09-01 00:00:00')
await input.trigger('focus')
await nextTick()
const dateInput = document.querySelector(
'.el-date-picker__time-header > span:nth-child(1) input'
) as HTMLInputElement
const timeInput = document.querySelector(
'.el-date-picker__time-header > span:nth-child(2) input'
) as HTMLInputElement
expect(dateInput?.value).toBe('2025-09-01')
expect(timeInput?.value).toBe('00:00:00')
})
it('should convert array value to proper format when changed', async () => {
const wrapper = _mount(
`<el-date-picker v-model="value" type="datetime" />`,
() => ({ value: ['2025-09-04'] })
)
const originalValue = wrapper.vm.value
expect(originalValue).toEqual(['2025-09-04'])
const input = wrapper.find('input')
await input.trigger('focus')
await nextTick()
const dateCell = document.querySelector('.el-date-table td.available')
await (dateCell as HTMLElement)?.click()
await nextTick()
expect(wrapper.vm.value).not.toEqual(['2025-09-04'])
expect(Array.isArray(wrapper.vm.value)).toBe(false)
})
})
describe('DatePicker Navigation', () => {

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { castArray as lodashCastArray } from 'lodash-unified'
import { castArray, ensureArray, unique } from '..'
import { castArray, ensureArray, extractFirst, unique } from '..'
describe('arrays', () => {
it('unique should work', () => {
@@ -17,4 +17,14 @@ describe('arrays', () => {
it('re-export ensureArray', () => {
expect(ensureArray).toBe(lodashCastArray)
})
it('extractFirst should work', () => {
expect(extractFirst([1, 2, 3])).toBe(1)
expect(extractFirst(['a', 'b', 'c'])).toBe('a')
expect(extractFirst(42)).toBe(42)
expect(extractFirst('hello')).toBe('hello')
expect(extractFirst(null)).toBe(null)
expect(extractFirst(undefined)).toBe(undefined)
expect(extractFirst([])).toBe(undefined)
})
})

View File

@@ -2,6 +2,10 @@ import { isArray } from './types'
export const unique = <T>(arr: T[]) => [...new Set(arr)]
export const extractFirst = <T>(arr: T | T[]): T => {
return isArray(arr) ? arr[0] : arr
}
type Many<T> = T | ReadonlyArray<T>
// TODO: rename to `ensureArray`
/** like `_.castArray`, except falsy value returns empty array. */