Skip to content

Commit 8fe7d2f

Browse files
committed
test(date-input): add tests for disabling range time selects until dates are completed
1 parent c0568d5 commit 8fe7d2f

2 files changed

Lines changed: 115 additions & 57 deletions

File tree

packages/varlet-ui/src/date-input/DateInput.vue

Lines changed: 58 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
:second="rangeTimes[0].second"
8484
:use-seconds="useSeconds"
8585
:readonly="readonly || formReadonly"
86-
:disabled="disabled || formDisabled || rangeSelecting"
86+
:disabled="disabled || formDisabled || rangeTimeSelectDisabled"
8787
:is-hour-allowed="startAllowFns.isHourAllowed"
8888
:is-minute-allowed="startAllowFns.isMinuteAllowed"
8989
:is-second-allowed="startAllowFns.isSecondAllowed"
@@ -96,7 +96,7 @@
9696
:second="rangeTimes[1].second"
9797
:use-seconds="useSeconds"
9898
:readonly="readonly || formReadonly"
99-
:disabled="disabled || formDisabled || rangeSelecting"
99+
:disabled="disabled || formDisabled || rangeTimeSelectDisabled"
100100
:is-hour-allowed="endAllowFns.isHourAllowed"
101101
:is-minute-allowed="endAllowFns.isMinuteAllowed"
102102
:is-second-allowed="endAllowFns.isSecondAllowed"
@@ -203,9 +203,12 @@ export default defineComponent({
203203
const isMultipleOrRange = computed(() => props.multiple || props.range)
204204
const isDatetime = computed(() => props.type === 'datetime')
205205
const pickerType = computed(() => (isDatetime.value ? 'date' : props.type) as 'year' | 'month' | 'date')
206-
const pickerMin = computed(() => truncateBound(props.min))
207-
const pickerMax = computed(() => truncateBound(props.max))
206+
const pickerMin = computed(() => getPickerBound(props.min))
207+
const pickerMax = computed(() => getPickerBound(props.max))
208208
const rangeSelecting = computed(() => Boolean(picker.value?.rangeSelecting))
209+
const rangeTimeSelectDisabled = computed(
210+
() => isDatetime.value && props.range && (rangeSelecting.value || getDayjsObjectsByModelValue().length < 2),
211+
)
209212
const singleTime = computed(() => getTimeParts(getDayjsObjectsByModelValue()[0]))
210213
const rangeTimes = computed(() => {
211214
const dayjsObjects = getDayjsObjectsByModelValue()
@@ -281,24 +284,12 @@ export default defineComponent({
281284
return props.format || getDefaultFormat()
282285
}
283286
284-
function truncateBound(value: string | undefined) {
287+
function getPickerBound(value: string | undefined) {
285288
if (!value || !isDatetime.value) {
286289
return value
287290
}
288291
289-
const dayjsObject = dayjs(value)
290-
291-
return dayjsObject.isValid() ? dayjsObject.format(DateInputDefaultFormats.Date) : value
292-
}
293-
294-
function getBoundObject(value: string | undefined) {
295-
if (!value) {
296-
return
297-
}
298-
299-
const dayjsObject = dayjs(value)
300-
301-
return dayjsObject.isValid() ? dayjsObject : undefined
292+
return dayjs(value).format(DateInputDefaultFormats.Date)
302293
}
303294
304295
function getTimeParts(dayjsObject?: Dayjs): TimeParts {
@@ -310,50 +301,50 @@ export default defineComponent({
310301
}
311302
312303
function clampDatetime(dayjsObject: Dayjs) {
313-
const min = getBoundObject(props.min)
314-
const max = getBoundObject(props.max)
304+
const minDayjsObject = props.min ? dayjs(props.min) : undefined
305+
const maxDayjsObject = props.max ? dayjs(props.max) : undefined
315306
316-
if (min && dayjsObject.isBefore(min)) {
317-
return min
307+
if (minDayjsObject && dayjsObject.isBefore(minDayjsObject)) {
308+
return minDayjsObject
318309
}
319310
320-
if (max && dayjsObject.isAfter(max)) {
321-
return max
311+
if (maxDayjsObject && dayjsObject.isAfter(maxDayjsObject)) {
312+
return maxDayjsObject
322313
}
323314
324315
return dayjsObject
325316
}
326317
327318
function createTimeAllowFns(
328-
getObject: () => Dayjs | undefined,
329-
getTime: () => TimeParts,
319+
getDayjsObjectByModelValue: () => Dayjs | undefined,
320+
getTimeParts: () => TimeParts,
330321
position?: DateInputRangePosition,
331322
) {
332-
const allowedTimeValidators = computed(() => {
333-
const date = (getObject() ?? dayjs()).format(DateInputDefaultFormats.Date)
323+
function getAllowedTimeValidators() {
324+
const date = (getDayjsObjectByModelValue() ?? dayjs()).format(DateInputDefaultFormats.Date)
334325
335326
return props.allowedTimes?.(date, position)
336-
})
327+
}
337328
338329
function isAllowed(unit: 'hour' | 'minute' | 'second', value: number) {
339-
const time = getTime()
340-
const candidate = (getObject() ?? dayjs())
330+
const time = getTimeParts()
331+
const timeOptionDayjsObject = (getDayjsObjectByModelValue() ?? dayjs())
341332
.hour(unit === 'hour' ? value : time.hour)
342333
.minute(unit === 'minute' ? value : time.minute)
343334
.second(unit === 'second' ? value : time.second)
344335
345-
const min = getBoundObject(props.min)
346-
const max = getBoundObject(props.max)
336+
const minDayjsObject = props.min ? dayjs(props.min) : undefined
337+
const maxDayjsObject = props.max ? dayjs(props.max) : undefined
347338
348-
if (min && candidate.endOf(unit).isBefore(min)) {
339+
if (minDayjsObject && timeOptionDayjsObject.endOf(unit).isBefore(minDayjsObject)) {
349340
return false
350341
}
351342
352-
if (max && candidate.startOf(unit).isAfter(max)) {
343+
if (maxDayjsObject && timeOptionDayjsObject.startOf(unit).isAfter(maxDayjsObject)) {
353344
return false
354345
}
355346
356-
const validators = allowedTimeValidators.value
347+
const validators = getAllowedTimeValidators()
357348
358349
if (unit === 'hour') {
359350
return validators?.hours ? validators.hours(value) : true
@@ -400,24 +391,24 @@ export default defineComponent({
400391
return [preferred, ...Array.from({ length: count }, (_, value) => value).filter((value) => value !== preferred)]
401392
}
402393
403-
function isTimeCandidateAllowed(candidate: Dayjs, validators: DateInputAllowedTimeValidators) {
404-
const min = getBoundObject(props.min)
405-
const max = getBoundObject(props.max)
406-
const hour = candidate.hour()
407-
const minute = candidate.minute()
408-
const second = candidate.second()
394+
function isTimeCandidateAllowed(candidateDayjsObject: Dayjs, validators: DateInputAllowedTimeValidators) {
395+
const minDayjsObject = props.min ? dayjs(props.min) : undefined
396+
const maxDayjsObject = props.max ? dayjs(props.max) : undefined
397+
const hour = candidateDayjsObject.hour()
398+
const minute = candidateDayjsObject.minute()
399+
const second = candidateDayjsObject.second()
409400
410401
return (
411-
(!min || !candidate.isBefore(min)) &&
412-
(!max || !candidate.isAfter(max)) &&
402+
(!minDayjsObject || !candidateDayjsObject.isBefore(minDayjsObject)) &&
403+
(!maxDayjsObject || !candidateDayjsObject.isAfter(maxDayjsObject)) &&
413404
(!validators.hours || validators.hours(hour)) &&
414405
(!validators.minutes || validators.minutes(minute, hour)) &&
415406
(!props.useSeconds || !validators.seconds || validators.seconds(second, minute, hour)) &&
416-
(!props.allowedDates || props.allowedDates(candidate.format(getPickerFormat())))
407+
(!props.allowedDates || props.allowedDates(candidateDayjsObject.format(getPickerFormat())))
417408
)
418409
}
419410
420-
function resolveAllowedDateTime(
411+
function resolveSelectableDateTime(
421412
base: Dayjs,
422413
time: TimeParts,
423414
position?: DateInputRangePosition,
@@ -446,10 +437,10 @@ export default defineComponent({
446437
const seconds = props.useSeconds ? getOrderedValues(time.second, 60, changedUnit === 'second') : [0]
447438
448439
for (const second of seconds) {
449-
const candidate = base.hour(hour).minute(minute).second(second).millisecond(0)
440+
const candidateDayjsObject = base.hour(hour).minute(minute).second(second).millisecond(0)
450441
451-
if (isTimeCandidateAllowed(candidate, validators)) {
452-
return candidate
442+
if (isTimeCandidateAllowed(candidateDayjsObject, validators)) {
443+
return candidateDayjsObject
453444
}
454445
}
455446
}
@@ -493,7 +484,9 @@ export default defineComponent({
493484
494485
const [dayjsObject] = getDayjsObjectsByModelValue()
495486
const base = dayjsObject ?? dayjs()
496-
const resolved = changedUnit ? resolveAllowedDateTime(base, time, undefined, changedUnit) : applyTime(base, time)
487+
const resolved = changedUnit
488+
? resolveSelectableDateTime(base, time, undefined, changedUnit)
489+
: applyTime(base, time)
497490
498491
if (resolved) {
499492
commitDateTimes([resolved])
@@ -514,7 +507,7 @@ export default defineComponent({
514507
const next = [...dayjsObjects]
515508
const position = index === 0 ? 'start' : 'end'
516509
const resolved = changedUnit
517-
? resolveAllowedDateTime(dayjsObjects[index], time, position, changedUnit)
510+
? resolveSelectableDateTime(dayjsObjects[index], time, position, changedUnit)
518511
: applyTime(dayjsObjects[index], time)
519512
520513
if (resolved) {
@@ -547,16 +540,19 @@ export default defineComponent({
547540
}
548541
549542
function isDayjsObjectSelectable(dayjsObject: Dayjs, position?: DateInputRangePosition) {
550-
const min = getBoundObject(props.min)
551-
const max = getBoundObject(props.max)
543+
const minDayjsObject = props.min ? dayjs(props.min) : undefined
544+
const maxDayjsObject = props.max ? dayjs(props.max) : undefined
552545
const unit =
553546
props.type === 'year' ? 'year' : props.type === 'month' ? 'month' : props.type === 'date' ? 'day' : undefined
554547
555-
if (min && (unit ? dayjsObject.isBefore(min, unit) : dayjsObject.isBefore(min))) {
548+
if (
549+
minDayjsObject &&
550+
(unit ? dayjsObject.isBefore(minDayjsObject, unit) : dayjsObject.isBefore(minDayjsObject))
551+
) {
556552
return false
557553
}
558554
559-
if (max && (unit ? dayjsObject.isAfter(max, unit) : dayjsObject.isAfter(max))) {
555+
if (maxDayjsObject && (unit ? dayjsObject.isAfter(maxDayjsObject, unit) : dayjsObject.isAfter(maxDayjsObject))) {
560556
return false
561557
}
562558
@@ -775,9 +771,13 @@ export default defineComponent({
775771
.filter((dayjsObject) => dayjsObject.isValid())
776772
777773
if (isDatetime.value && props.range) {
774+
if (dayjsObjects.length < 2) {
775+
return
776+
}
777+
778778
const times = rangeTimes.value
779779
const resolved = dayjsObjects.map((dayjsObject, index) =>
780-
resolveAllowedDateTime(dayjsObject, times[index] ?? getTimeParts(), index === 0 ? 'start' : 'end'),
780+
resolveSelectableDateTime(dayjsObject, times[index] ?? getTimeParts(), index === 0 ? 'start' : 'end'),
781781
)
782782
783783
if (resolved.every(isTruthy)) {
@@ -809,7 +809,7 @@ export default defineComponent({
809809
}
810810
811811
if (isDatetime.value) {
812-
const resolved = resolveAllowedDateTime(dayjsObject, singleTime.value)
812+
const resolved = resolveSelectableDateTime(dayjsObject, singleTime.value)
813813
814814
if (resolved) {
815815
if (!commitDateTimes([resolved])) {
@@ -887,6 +887,7 @@ export default defineComponent({
887887
showMenu,
888888
isFocusing,
889889
rangeSelecting,
890+
rangeTimeSelectDisabled,
890891
formDisabled,
891892
formReadonly,
892893
errorMessage,

packages/varlet-ui/src/date-input/__tests__/index.spec.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,63 @@ describe('test dateInput datetime behavior', () => {
11731173
wrapper.unmount()
11741174
})
11751175

1176+
test('dateInput disables range time selects until range dates are completed', async () => {
1177+
const wrapper = mount(VarDateInput, {
1178+
props: {
1179+
type: 'datetime',
1180+
range: true,
1181+
modelValue: [],
1182+
},
1183+
})
1184+
1185+
expect(wrapper.findAllComponents(TimeSelect).map((timeSelect) => timeSelect.props('disabled'))).toEqual([
1186+
true,
1187+
true,
1188+
])
1189+
1190+
await wrapper.setProps({
1191+
modelValue: ['2021-04-08 09:00:00', '2021-04-12 18:30:00'],
1192+
})
1193+
1194+
expect(wrapper.findAllComponents(TimeSelect).map((timeSelect) => timeSelect.props('disabled'))).toEqual([
1195+
false,
1196+
false,
1197+
])
1198+
1199+
wrapper.findComponent(DatePicker).vm.selectDayFromPanel(9)
1200+
await delay(0)
1201+
1202+
expect(wrapper.findAllComponents(TimeSelect).map((timeSelect) => timeSelect.props('disabled'))).toEqual([
1203+
true,
1204+
true,
1205+
])
1206+
1207+
wrapper.unmount()
1208+
})
1209+
1210+
test('dateInput still accepts complete datetime range from input when range time selects are disabled', async () => {
1211+
const onUpdateModelValue = vi.fn()
1212+
const wrapper = mount(VarDateInput, {
1213+
props: {
1214+
type: 'datetime',
1215+
range: true,
1216+
modelValue: [],
1217+
'onUpdate:modelValue': onUpdateModelValue,
1218+
},
1219+
})
1220+
1221+
expect(wrapper.findAllComponents(TimeSelect).map((timeSelect) => timeSelect.props('disabled'))).toEqual([
1222+
true,
1223+
true,
1224+
])
1225+
1226+
await triggerInput(wrapper, '2021-04-08 09:00:00 ~ 2021-04-12 18:30:00')
1227+
1228+
expect(onUpdateModelValue).lastCalledWith(['2021-04-08 09:00:00', '2021-04-12 18:30:00'])
1229+
1230+
wrapper.unmount()
1231+
})
1232+
11761233
test('dateInput combines picked range dates with current start and end times', async () => {
11771234
const onUpdateModelValue = vi.fn()
11781235
const wrapper = mount(VarDateInput, {

0 commit comments

Comments
 (0)