From 01ea2b42edc6990e1bdd33ca692bd19953afa942 Mon Sep 17 00:00:00 2001 From: delangle Date: Fri, 16 Jun 2023 11:13:23 +0200 Subject: [PATCH 1/6] [pickers] Add referenceDate prop on TimeClock, DigitalClok and MultiSectionDigitalClock --- .../x/api/date-pickers/digital-clock.json | 4 ++ .../x/api/date-pickers/month-calendar.json | 2 +- .../multi-section-digital-clock.json | 4 ++ docs/pages/x/api/date-pickers/time-clock.json | 4 ++ .../x/api/date-pickers/year-calendar.json | 2 +- .../api-docs/date-pickers/digital-clock.json | 1 + .../multi-section-digital-clock.json | 1 + .../api-docs/date-pickers/time-clock.json | 1 + .../src/DigitalClock/DigitalClock.tsx | 29 ++++++---- .../src/MonthCalendar/MonthCalendar.tsx | 2 +- .../src/MonthCalendar/MonthCalendar.types.ts | 2 +- .../MultiSectionDigitalClock.tsx | 7 ++- .../src/TimeClock/TimeClock.tsx | 57 +++++++++++-------- .../src/YearCalendar/YearCalendar.tsx | 2 +- .../src/YearCalendar/YearCalendar.types.ts | 2 +- .../internals/hooks/useClockReferenceDate.ts | 32 +++++++++++ .../hooks/usePicker/usePickerValue.types.ts | 4 +- .../src/internals/models/props/clock.ts | 7 ++- .../utils/getDefaultReferenceDate.ts | 18 +++--- 19 files changed, 131 insertions(+), 50 deletions(-) create mode 100644 packages/x-date-pickers/src/internals/hooks/useClockReferenceDate.ts diff --git a/docs/pages/x/api/date-pickers/digital-clock.json b/docs/pages/x/api/date-pickers/digital-clock.json index c2c025c74cba..830d0ed07d42 100644 --- a/docs/pages/x/api/date-pickers/digital-clock.json +++ b/docs/pages/x/api/date-pickers/digital-clock.json @@ -29,6 +29,10 @@ "onViewChange": { "type": { "name": "func" } }, "openTo": { "type": { "name": "enum", "description": "'hours'" } }, "readOnly": { "type": { "name": "bool" } }, + "referenceDate": { + "type": { "name": "any" }, + "default": "The closest valid time using the validation props, except callbacks such as `shouldDisableTime`." + }, "shouldDisableClock": { "type": { "name": "func" }, "deprecated": true, diff --git a/docs/pages/x/api/date-pickers/month-calendar.json b/docs/pages/x/api/date-pickers/month-calendar.json index 1200ee986930..284949dcabce 100644 --- a/docs/pages/x/api/date-pickers/month-calendar.json +++ b/docs/pages/x/api/date-pickers/month-calendar.json @@ -17,7 +17,7 @@ "readOnly": { "type": { "name": "bool" } }, "referenceDate": { "type": { "name": "any" }, - "default": "The closest valid month using the validation props, except callbacks such as `shouldDisableDate`." + "default": "The closest valid month using the validation props, except callbacks such as `shouldDisableMonth`." }, "shouldDisableMonth": { "type": { "name": "func" } }, "sx": { diff --git a/docs/pages/x/api/date-pickers/multi-section-digital-clock.json b/docs/pages/x/api/date-pickers/multi-section-digital-clock.json index f335cfc4437e..3183edb28325 100644 --- a/docs/pages/x/api/date-pickers/multi-section-digital-clock.json +++ b/docs/pages/x/api/date-pickers/multi-section-digital-clock.json @@ -39,6 +39,10 @@ } }, "readOnly": { "type": { "name": "bool" } }, + "referenceDate": { + "type": { "name": "any" }, + "default": "The closest valid time using the validation props, except callbacks such as `shouldDisableTime`." + }, "shouldDisableClock": { "type": { "name": "func" }, "deprecated": true, diff --git a/docs/pages/x/api/date-pickers/time-clock.json b/docs/pages/x/api/date-pickers/time-clock.json index 2fd89adb17ca..ee9b8932b07c 100644 --- a/docs/pages/x/api/date-pickers/time-clock.json +++ b/docs/pages/x/api/date-pickers/time-clock.json @@ -40,6 +40,10 @@ } }, "readOnly": { "type": { "name": "bool" } }, + "referenceDate": { + "type": { "name": "any" }, + "default": "The closest valid time using the validation props, except callbacks such as `shouldDisableTime`." + }, "shouldDisableClock": { "type": { "name": "func" }, "deprecated": true, diff --git a/docs/pages/x/api/date-pickers/year-calendar.json b/docs/pages/x/api/date-pickers/year-calendar.json index e8c548f7cb51..1e3ab9a23cdb 100644 --- a/docs/pages/x/api/date-pickers/year-calendar.json +++ b/docs/pages/x/api/date-pickers/year-calendar.json @@ -13,7 +13,7 @@ "readOnly": { "type": { "name": "bool" } }, "referenceDate": { "type": { "name": "any" }, - "default": "The closest valid year using the validation props, except callbacks such as `shouldDisableDate`." + "default": "The closest valid year using the validation props, except callbacks such as `shouldDisableYear`." }, "shouldDisableYear": { "type": { "name": "func" } }, "sx": { diff --git a/docs/translations/api-docs/date-pickers/digital-clock.json b/docs/translations/api-docs/date-pickers/digital-clock.json index d3e4551ff6a9..efbdbbabe347 100644 --- a/docs/translations/api-docs/date-pickers/digital-clock.json +++ b/docs/translations/api-docs/date-pickers/digital-clock.json @@ -20,6 +20,7 @@ "onViewChange": "Callback fired on view change.

Signature:
function(view: TView) => void
view: The new view.", "openTo": "The default visible view. Used when the component view is not controlled. Must be a valid option from views list.", "readOnly": "If true, the picker views and text field are read-only.", + "referenceDate": "The date used to generate the new value when both value and defaultValue are empty.", "shouldDisableClock": "Disable specific clock time.

Signature:
function(clockValue: number, view: TimeView) => boolean
clockValue: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", "shouldDisableTime": "Disable specific time.

Signature:
function(value: TDate, view: TimeView) => boolean
value: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", "skipDisabled": "If true, disabled digital clock items will not be rendered.", diff --git a/docs/translations/api-docs/date-pickers/multi-section-digital-clock.json b/docs/translations/api-docs/date-pickers/multi-section-digital-clock.json index 217ba989e212..04bc50e0028f 100644 --- a/docs/translations/api-docs/date-pickers/multi-section-digital-clock.json +++ b/docs/translations/api-docs/date-pickers/multi-section-digital-clock.json @@ -20,6 +20,7 @@ "onViewChange": "Callback fired on view change.

Signature:
function(view: TView) => void
view: The new view.", "openTo": "The default visible view. Used when the component view is not controlled. Must be a valid option from views list.", "readOnly": "If true, the picker views and text field are read-only.", + "referenceDate": "The date used to generate the new value when both value and defaultValue are empty.", "shouldDisableClock": "Disable specific clock time.

Signature:
function(clockValue: number, view: TimeView) => boolean
clockValue: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", "shouldDisableTime": "Disable specific time.

Signature:
function(value: TDate, view: TimeView) => boolean
value: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", "skipDisabled": "If true, disabled digital clock items will not be rendered.", diff --git a/docs/translations/api-docs/date-pickers/time-clock.json b/docs/translations/api-docs/date-pickers/time-clock.json index 30a0928eda13..65efb956a91c 100644 --- a/docs/translations/api-docs/date-pickers/time-clock.json +++ b/docs/translations/api-docs/date-pickers/time-clock.json @@ -21,6 +21,7 @@ "onViewChange": "Callback fired on view change.

Signature:
function(view: TView) => void
view: The new view.", "openTo": "The default visible view. Used when the component view is not controlled. Must be a valid option from views list.", "readOnly": "If true, the picker views and text field are read-only.", + "referenceDate": "The date used to generate the new value when both value and defaultValue are empty.", "shouldDisableClock": "Disable specific clock time.

Signature:
function(clockValue: number, view: TimeView) => boolean
clockValue: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", "shouldDisableTime": "Disable specific time.

Signature:
function(value: TDate, view: TimeView) => boolean
value: The value to check.
view: The clock type of the timeValue.
returns (boolean): If true the time will be disabled.", "slotProps": "The props used for each component slot.", diff --git a/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx b/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx index c5de1361cfc1..7d7bfac7ec4d 100644 --- a/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx +++ b/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx @@ -16,6 +16,7 @@ import { DigitalClockProps } from './DigitalClock.types'; import { useViews } from '../internals/hooks/useViews'; import { TimeView } from '../models'; import { DIGITAL_CLOCK_VIEW_HEIGHT } from '../internals/constants/dimensions'; +import { useClockReferenceDate } from '../internals/hooks/useClockReferenceDate'; const useUtilityClasses = (ownerState: DigitalClockProps) => { const { classes } = ownerState; @@ -104,6 +105,8 @@ export const DigitalClock = React.forwardRef(function DigitalClock { setValue(newValue); onChange?.(newValue, 'finish'); @@ -180,11 +189,6 @@ export const DigitalClock = React.forwardRef(function DigitalClock value || utils.setSeconds(utils.setMinutes(utils.setHours(now, 0), 0), 0), - [value, now, utils], - ); - const isTimeDisabled = React.useCallback( (valueToCheck: TDate) => { const isAfter = createIsAfterIgnoreDatePart(disableIgnoringDatePartForTimeValidation, utils); @@ -242,15 +246,15 @@ export const DigitalClock = React.forwardRef(function DigitalClock { - const startOfDay = utils.startOfDay(selectedTimeOrMidnight); + const startOfDay = utils.startOfDay(valueOrReferenceDate); return [ startOfDay, ...Array.from({ length: Math.ceil((24 * 60) / timeStep) - 1 }, (_, index) => utils.addMinutes(startOfDay, timeStep * (index + 1)), ), - utils.endOfDay(selectedTimeOrMidnight), + utils.endOfDay(valueOrReferenceDate), ]; - }, [selectedTimeOrMidnight, timeStep, utils]); + }, [valueOrReferenceDate, timeStep, utils]); return ( defaultValue?: TDate | null; /** * The date used to generate the new value when both `value` and `defaultValue` are empty. - * @default The closest valid month using the validation props, except callbacks such as `shouldDisableDate`. + * @default The closest valid month using the validation props, except callbacks such as `shouldDisableMonth`. */ referenceDate?: TDate; /** diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx index 6095ab3f1947..f5b55554dd0c 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx @@ -482,7 +482,7 @@ MultiSectionDigitalClock.propTypes = { minutesStep: PropTypes.number, /** * Callback fired when the value changes. - * @template TDate + * @template TDate, TView * @param {TDate | null} value The new value. * @param {PickerSelectionState | undefined} selectionState Indicates if the date selection is complete. * @param {TView | undefined} selectedView Indicates the view in which the selection has been made. @@ -512,6 +512,11 @@ MultiSectionDigitalClock.propTypes = { * @default false */ readOnly: PropTypes.bool, + /** + * The date used to generate the new value when both `value` and `defaultValue` are empty. + * @default The closest valid time using the validation props, except callbacks such as `shouldDisableTime`. + */ + referenceDate: PropTypes.any, /** * Disable specific clock time. * @param {number} clockValue The value to check. diff --git a/packages/x-date-pickers/src/TimeClock/TimeClock.tsx b/packages/x-date-pickers/src/TimeClock/TimeClock.tsx index e074df8b0a51..d4addb49bba1 100644 --- a/packages/x-date-pickers/src/TimeClock/TimeClock.tsx +++ b/packages/x-date-pickers/src/TimeClock/TimeClock.tsx @@ -21,6 +21,7 @@ import { Clock, ClockProps } from './Clock'; import { TimeClockProps } from './TimeClock.types'; import { getHourNumbers, getMinutesNumbers } from './ClockNumbers'; import { uncapitalizeObjectKeys } from '../internals/utils/slots-migration'; +import { useClockReferenceDate } from '../internals/hooks/useClockReferenceDate'; const useUtilityClasses = (ownerState: TimeClockProps) => { const { classes } = ownerState; @@ -56,6 +57,8 @@ type TimeClockComponent = (( props: TimeClockProps & React.RefAttributes, ) => JSX.Element) & { propTypes?: any }; +const TIME_CLOCK_DEFAULT_VIEWS: TimeView[] = ['hours', 'minutes']; + /** * * API: @@ -84,6 +87,8 @@ export const TimeClock = React.forwardRef(function TimeClock { setValue(newValue); @@ -134,13 +145,8 @@ export const TimeClock = React.forwardRef(function TimeClock value || utils.setSeconds(utils.setMinutes(utils.setHours(now, 0), 0), 0), - [value, now, utils], - ); - const { meridiemMode, handleMeridiemChange } = useMeridiemMode( - selectedTimeOrMidnight, + valueOrReferenceDate, ampm, setValueAndGoToNextView, ); @@ -183,16 +189,16 @@ export const TimeClock = React.forwardRef(function TimeClock { const valueWithMeridiem = convertValueToMeridiem(hourValue, meridiemMode, ampm); setValueAndGoToNextView( - utils.setHours(selectedTimeOrMidnight, valueWithMeridiem), + utils.setHours(valueOrReferenceDate, valueWithMeridiem), isFinish, ); }; return { onChange: handleHoursChange, - viewValue: utils.getHours(selectedTimeOrMidnight), + viewValue: utils.getHours(valueOrReferenceDate), children: getHourNumbers({ value, utils, @@ -283,9 +289,9 @@ export const TimeClock = React.forwardRef(function TimeClock { - setValueAndGoToNextView(utils.setMinutes(selectedTimeOrMidnight, minuteValue), isFinish); + setValueAndGoToNextView(utils.setMinutes(valueOrReferenceDate, minuteValue), isFinish); }; return { @@ -303,9 +309,9 @@ export const TimeClock = React.forwardRef(function TimeClock { - setValueAndGoToNextView(utils.setSeconds(selectedTimeOrMidnight, secondValue), isFinish); + setValueAndGoToNextView(utils.setSeconds(valueOrReferenceDate, secondValue), isFinish); }; return { @@ -335,7 +341,7 @@ export const TimeClock = React.forwardRef(function TimeClock defaultValue?: TDate | null; /** * The date used to generate the new value when both `value` and `defaultValue` are empty. - * @default The closest valid year using the validation props, except callbacks such as `shouldDisableDate`. + * @default The closest valid year using the validation props, except callbacks such as `shouldDisableYear`. */ referenceDate?: TDate; /** diff --git a/packages/x-date-pickers/src/internals/hooks/useClockReferenceDate.ts b/packages/x-date-pickers/src/internals/hooks/useClockReferenceDate.ts new file mode 100644 index 000000000000..4d8916b24cc3 --- /dev/null +++ b/packages/x-date-pickers/src/internals/hooks/useClockReferenceDate.ts @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { MuiPickersAdapter } from '../../models'; +import { singleItemValueManager } from '../utils/valueManagers'; +import { getTodayDate } from '../utils/date-utils'; +import { SECTION_TYPE_GRANULARITY } from '../utils/getDefaultReferenceDate'; + +export const useClockReferenceDate = ({ + value, + referenceDate: referenceDateProp, + utils, + props, +}: { + value: TDate; + referenceDate: TDate | undefined; + utils: MuiPickersAdapter; + props: TProps; +}) => { + const referenceDate = React.useMemo( + () => + singleItemValueManager.getInitialReferenceValue({ + value, + utils, + props, + referenceDate: referenceDateProp, + granularity: SECTION_TYPE_GRANULARITY.day, + getTodayDate: () => getTodayDate(utils, 'date'), + }), + [], // eslint-disable-line react-hooks/exhaustive-deps + ); + + return value ?? referenceDate; +}; diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts index 8780e9039de8..b92e0057a18e 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts @@ -45,7 +45,8 @@ export interface PickerValueManager { * @param {TValue} params.value The value provided by the user. * @param {GetDefaultReferenceDateProps} params.props The validation props needed to compute the reference value. * @param {MuiPickersAdapter} params.utils The adapter. - * @param {granularity} params.granularity The granularity of the selection possible on this component. + * @param {number} params.granularity The granularity of the selection possible on this component. + * @param {() => TDate} params.getTodayDate The reference date to use if not reference date is passed to the component. * @returns {TValue} The reference value to use for non-provided dates. */ getInitialReferenceValue: (params: { @@ -54,6 +55,7 @@ export interface PickerValueManager { props: GetDefaultReferenceDateProps; utils: MuiPickersAdapter; granularity: number; + getTodayDate?: () => TDate; }) => TValue; /** * Method parsing the input value to replace all invalid dates by `null`. diff --git a/packages/x-date-pickers/src/internals/models/props/clock.ts b/packages/x-date-pickers/src/internals/models/props/clock.ts index d0f677856074..80a12079b078 100644 --- a/packages/x-date-pickers/src/internals/models/props/clock.ts +++ b/packages/x-date-pickers/src/internals/models/props/clock.ts @@ -37,7 +37,7 @@ export interface BaseClockProps defaultValue?: TDate | null; /** * Callback fired when the value changes. - * @template TDate + * @template TDate, TView * @param {TDate | null} value The new value. * @param {PickerSelectionState | undefined} selectionState Indicates if the date selection is complete. * @param {TView | undefined} selectedView Indicates the view in which the selection has been made. @@ -57,6 +57,11 @@ export interface BaseClockProps * @default false */ readOnly?: boolean; + /** + * The date used to generate the new value when both `value` and `defaultValue` are empty. + * @default The closest valid time using the validation props, except callbacks such as `shouldDisableTime`. + */ + referenceDate?: TDate; } export interface DesktopOnlyTimePickerProps diff --git a/packages/x-date-pickers/src/internals/utils/getDefaultReferenceDate.ts b/packages/x-date-pickers/src/internals/utils/getDefaultReferenceDate.ts index a4ec06f0648c..efe7f409211a 100644 --- a/packages/x-date-pickers/src/internals/utils/getDefaultReferenceDate.ts +++ b/packages/x-date-pickers/src/internals/utils/getDefaultReferenceDate.ts @@ -1,6 +1,6 @@ import { createIsAfterIgnoreDatePart } from './time-utils'; import { mergeDateAndTime, getTodayDate } from './date-utils'; -import { FieldSection, MuiPickersAdapter } from '../../models'; +import { DateOrTimeView, FieldSection, MuiPickersAdapter } from '../../models'; export interface GetDefaultReferenceDateProps { maxDate?: TDate; @@ -21,12 +21,10 @@ export const SECTION_TYPE_GRANULARITY = { }; export const getSectionTypeGranularity = (sections: FieldSection[]) => - Math.max( - ...sections.map( - (section) => - SECTION_TYPE_GRANULARITY[section.type as keyof typeof SECTION_TYPE_GRANULARITY] ?? 1, - ), - ); + Math.max(...sections.map((section) => SECTION_TYPE_GRANULARITY[section.type] ?? 1)); + +export const getViewsGranularity = (views: readonly DateOrTimeView[]) => + Math.max(...views.map((view) => SECTION_TYPE_GRANULARITY[view] ?? 1)); const roundDate = (utils: MuiPickersAdapter, granularity: number, date: TDate) => { if (granularity === SECTION_TYPE_GRANULARITY.year) { @@ -58,12 +56,16 @@ export const getDefaultReferenceDate = ({ props, utils, granularity, + getTodayDate: inGetTodayDate, }: { props: GetDefaultReferenceDateProps; utils: MuiPickersAdapter; granularity: number; + getTodayDate?: () => TDate; }) => { - let referenceDate = roundDate(utils, granularity, getTodayDate(utils)); + let referenceDate = inGetTodayDate + ? inGetTodayDate() + : roundDate(utils, granularity, getTodayDate(utils)); if (props.minDate != null && utils.isAfterDay(props.minDate, referenceDate)) { referenceDate = roundDate(utils, granularity, props.minDate); From b55ac2d87c23158c14b37d752d153bca305d73ad Mon Sep 17 00:00:00 2001 From: delangle Date: Mon, 19 Jun 2023 14:51:44 +0200 Subject: [PATCH 2/6] Fix --- .../src/internals/utils/getDefaultReferenceDate.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/x-date-pickers/src/internals/utils/getDefaultReferenceDate.ts b/packages/x-date-pickers/src/internals/utils/getDefaultReferenceDate.ts index efe7f409211a..603a15952ebf 100644 --- a/packages/x-date-pickers/src/internals/utils/getDefaultReferenceDate.ts +++ b/packages/x-date-pickers/src/internals/utils/getDefaultReferenceDate.ts @@ -21,7 +21,12 @@ export const SECTION_TYPE_GRANULARITY = { }; export const getSectionTypeGranularity = (sections: FieldSection[]) => - Math.max(...sections.map((section) => SECTION_TYPE_GRANULARITY[section.type] ?? 1)); + Math.max( + ...sections.map( + (section) => + SECTION_TYPE_GRANULARITY[section.type as keyof typeof SECTION_TYPE_GRANULARITY] ?? 1, + ), + ); export const getViewsGranularity = (views: readonly DateOrTimeView[]) => Math.max(...views.map((view) => SECTION_TYPE_GRANULARITY[view] ?? 1)); From 6199f60c63af0718947e24861fc09cb780d31acd Mon Sep 17 00:00:00 2001 From: delangle Date: Tue, 20 Jun 2023 13:04:46 +0200 Subject: [PATCH 3/6] Write tests --- .../src/AdapterDayjs/AdapterDayjs.ts | 4 + .../DateCalendar/tests/DateCalendar.test.tsx | 34 ++++---- .../DigitalClock/tests/DigitalClock.test.tsx | 68 +++++++++++++++ .../tests/describes.DigitalClock.test.tsx | 8 +- .../MultiSectionDigitalClock.tsx | 48 ++++++----- .../tests/MultiSectionDigitalClock.test.tsx | 86 +++++++++++++++++++ ...escribes.MultiSectionDigitalClock.test.tsx | 17 +--- .../TimeClock/{ => tests}/TimeClock.test.tsx | 62 +++++++++++++ .../tests/describes.TimeClock.test.tsx | 25 ++---- .../internals/hooks/useClockReferenceDate.ts | 2 +- test/utils/pickers-utils.tsx | 7 +- test/utils/pickers/viewHandlers.ts | 58 +++++++++++++ 12 files changed, 336 insertions(+), 83 deletions(-) create mode 100644 packages/x-date-pickers/src/DigitalClock/tests/DigitalClock.test.tsx create mode 100644 packages/x-date-pickers/src/MultiSectionDigitalClock/tests/MultiSectionDigitalClock.test.tsx rename packages/x-date-pickers/src/TimeClock/{ => tests}/TimeClock.test.tsx (89%) create mode 100644 test/utils/pickers/viewHandlers.ts diff --git a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts index 07d42f216559..556a4d9396ed 100644 --- a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts +++ b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts @@ -272,6 +272,10 @@ export class AdapterDayjs implements MuiPickersAdapter { let parsedValue: Dayjs; + if (timezone === 'date') { + console.trace(); + } + if (timezone === 'UTC') { parsedValue = this.createUTCDate(value); } else if (timezone === 'system' || (timezone === 'default' && !this.hasTimezonePlugin())) { diff --git a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx index c718e6203e94..acd462cec353 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx @@ -174,7 +174,7 @@ describe('', () => { userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 2)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2018, 0, 2)); }); it('should use `referenceDate` when no value defined', () => { @@ -190,7 +190,7 @@ describe('', () => { userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 2, 12, 30)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2018, 0, 2, 12, 30)); }); it('should not use `referenceDate` when a value is defined', () => { @@ -207,7 +207,7 @@ describe('', () => { userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2019, 0, 2, 12, 20)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 0, 2, 12, 20)); }); it('should not use `referenceDate` when a defaultValue is defined', () => { @@ -224,7 +224,7 @@ describe('', () => { userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2019, 0, 2, 12, 20)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 0, 2, 12, 20)); }); it('should keep the time of the currently provided date', () => { @@ -241,7 +241,7 @@ describe('', () => { userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime( + expect(onChange.lastCall.firstArg).toEqualDateTime( adapterToUse.date(new Date(2018, 0, 2, 11, 11, 11)), ); }); @@ -304,7 +304,7 @@ describe('', () => { fireEvent.click(april); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2019, 3, 6)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 3, 6)); }); it('should respect minDate when selecting closest enabled date', () => { @@ -324,7 +324,7 @@ describe('', () => { fireEvent.click(april); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2019, 3, 7)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 3, 7)); }); it('should respect maxDate when selecting closest enabled date', () => { @@ -344,7 +344,7 @@ describe('', () => { fireEvent.click(april); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2019, 3, 22)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 3, 22)); }); it('should go to next view without changing the date when no date of the new month is enabled', () => { @@ -384,7 +384,7 @@ describe('', () => { fireEvent.click(april); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2018, 3, 1, 12, 30)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2018, 3, 1, 12, 30)); }); it('should not use `referenceDate` when a value is defined', () => { @@ -404,7 +404,7 @@ describe('', () => { fireEvent.click(april); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2019, 3, 1, 12, 20)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 3, 1, 12, 20)); }); it('should not use `referenceDate` when a defaultValue is defined', () => { @@ -424,7 +424,7 @@ describe('', () => { fireEvent.click(april); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2019, 3, 1, 12, 20)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 3, 1, 12, 20)); }); }); @@ -454,7 +454,7 @@ describe('', () => { fireEvent.click(year2022); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2022, 4, 1)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2022, 4, 1)); }); it('should respect minDate when selecting closest enabled date', () => { @@ -474,7 +474,7 @@ describe('', () => { fireEvent.click(year2017); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2017, 4, 12)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2017, 4, 12)); }); it('should respect maxDate when selecting closest enabled date', () => { @@ -494,7 +494,7 @@ describe('', () => { fireEvent.click(year2022); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2022, 2, 31)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2022, 2, 31)); }); it('should go to next view without changing the date when no date of the new year is enabled', () => { @@ -559,7 +559,7 @@ describe('', () => { fireEvent.click(year2022); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2022, 0, 1, 12, 30)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2022, 0, 1, 12, 30)); }); it('should not use `referenceDate` when a value is defined', () => { @@ -579,7 +579,7 @@ describe('', () => { fireEvent.click(year2022); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2022, 0, 1, 12, 20)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2022, 0, 1, 12, 20)); }); it('should not use `referenceDate` when a defaultValue is defined', () => { @@ -599,7 +599,7 @@ describe('', () => { fireEvent.click(year2022); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2022, 0, 1, 12, 20)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2022, 0, 1, 12, 20)); }); }); diff --git a/packages/x-date-pickers/src/DigitalClock/tests/DigitalClock.test.tsx b/packages/x-date-pickers/src/DigitalClock/tests/DigitalClock.test.tsx new file mode 100644 index 000000000000..50c1c600d42b --- /dev/null +++ b/packages/x-date-pickers/src/DigitalClock/tests/DigitalClock.test.tsx @@ -0,0 +1,68 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { spy } from 'sinon'; +import { DigitalClock } from '@mui/x-date-pickers/DigitalClock'; +import { adapterToUse, createPickerRenderer } from 'test/utils/pickers-utils'; +import { digitalClockHandler } from 'test/utils/pickers/viewHandlers'; + +describe('', () => { + const { render } = createPickerRenderer(); + + describe('Reference date', () => { + it('should use `referenceDate` when no value defined', () => { + const onChange = spy(); + + render( + , + ); + + digitalClockHandler.setViewValue( + adapterToUse, + adapterToUse.setMinutes(adapterToUse.setHours(adapterToUse.date(), 15), 30), + ); + expect(onChange.callCount).to.equal(1); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2018, 0, 1, 15, 30)); + }); + + it('should not use `referenceDate` when a value is defined', () => { + const onChange = spy(); + + render( + , + ); + + digitalClockHandler.setViewValue( + adapterToUse, + adapterToUse.setMinutes(adapterToUse.setHours(adapterToUse.date(), 15), 30), + ); + expect(onChange.callCount).to.equal(1); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 0, 1, 15, 30)); + }); + + it('should not use `referenceDate` when a defaultValue is defined', () => { + const onChange = spy(); + + render( + , + ); + + digitalClockHandler.setViewValue( + adapterToUse, + adapterToUse.setMinutes(adapterToUse.setHours(adapterToUse.date(), 15), 30), + ); + expect(onChange.callCount).to.equal(1); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 0, 1, 15, 30)); + }); + }); +}); diff --git a/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx b/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx index ecc047d8aea4..d2e281199d80 100644 --- a/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx +++ b/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx @@ -5,6 +5,7 @@ import { describeValue } from '@mui/x-date-pickers/tests/describeValue'; import { createPickerRenderer, adapterToUse, wrapPickerMount } from 'test/utils/pickers-utils'; import { DigitalClock } from '@mui/x-date-pickers/DigitalClock'; import { expect } from 'chai'; +import { digitalClockHandler } from 'test/utils/pickers/viewHandlers'; describe(' - Describes', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); @@ -63,12 +64,7 @@ describe(' - Describes', () => { }, setNewValue: (value) => { const newValue = adapterToUse.addMinutes(adapterToUse.addHours(value, 1), 30); - const hasMeridiem = adapterToUse.is12HourCycleInCurrentLocale(); - const formattedLabel = adapterToUse.format( - newValue, - hasMeridiem ? 'fullTime12h' : 'fullTime24h', - ); - userEvent.mousePress(screen.getByRole('option', { name: formattedLabel })); + digitalClockHandler.setViewValue(adapterToUse, newValue); return newValue; }, diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx index 690424ed686e..f2b54eba6f7d 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx @@ -21,6 +21,7 @@ import { TimeStepOptions, TimeView } from '../models'; import { TimeViewWithMeridiem } from '../internals/models'; import { useControlledValueWithTimezone } from '../internals/hooks/useValueWithTimezone'; import { singleItemValueManager } from '../internals/utils/valueManagers'; +import { useClockReferenceDate } from '@mui/x-date-pickers/internals/hooks/useClockReferenceDate'; const useUtilityClasses = (ownerState: MultiSectionDigitalClockProps) => { const { classes } = ownerState; @@ -65,6 +66,8 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi slots, slotProps, value: valueProp, + defaultValue, + referenceDate: referenceDateProp, disableIgnoringDatePartForTimeValidation = false, maxTime, minTime, @@ -74,7 +77,6 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi shouldDisableClock, shouldDisableTime, onChange, - defaultValue, view: inView, views: inViews = ['hours', 'minutes'], openTo, @@ -115,6 +117,14 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi [inTimeSteps], ); + const valueOrReferenceDate = useClockReferenceDate({ + value, + referenceDate: referenceDateProp, + utils, + props, + timezone, + }); + const handleValueChange = useEventCallback( ( newValue: TDate | null, @@ -140,17 +150,12 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi onFocusedViewChange, }); - const selectedTimeOrMidnight = React.useMemo( - () => value || utils.setSeconds(utils.setMinutes(utils.setHours(now, 0), 0), 0), - [value, now, utils], - ); - const handleMeridiemValueChange = useEventCallback((newValue: TDate | null) => { setValueAndGoToView(newValue, null, 'meridiem'); }); const { meridiemMode, handleMeridiemChange } = useMeridiemMode( - selectedTimeOrMidnight, + valueOrReferenceDate, ampm, handleMeridiemValueChange, 'finish', @@ -194,16 +199,16 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi if (shouldDisableTime) { switch (viewType) { case 'hours': - return !shouldDisableTime(utils.setHours(selectedTimeOrMidnight, timeValue), 'hours'); + return !shouldDisableTime(utils.setHours(valueOrReferenceDate, timeValue), 'hours'); case 'minutes': return !shouldDisableTime( - utils.setMinutes(selectedTimeOrMidnight, timeValue), + utils.setMinutes(valueOrReferenceDate, timeValue), 'minutes', ); case 'seconds': return !shouldDisableTime( - utils.setSeconds(selectedTimeOrMidnight, timeValue), + utils.setSeconds(valueOrReferenceDate, timeValue), 'seconds', ); @@ -218,7 +223,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi switch (viewType) { case 'hours': { const valueWithMeridiem = convertValueToMeridiem(rawValue, meridiemMode, ampm); - const dateWithNewHours = utils.setHours(selectedTimeOrMidnight, valueWithMeridiem); + const dateWithNewHours = utils.setHours(valueOrReferenceDate, valueWithMeridiem); const start = utils.setSeconds(utils.setMinutes(dateWithNewHours, 0), 0); const end = utils.setSeconds(utils.setMinutes(dateWithNewHours, 59), 59); @@ -226,7 +231,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi } case 'minutes': { - const dateWithNewMinutes = utils.setMinutes(selectedTimeOrMidnight, rawValue); + const dateWithNewMinutes = utils.setMinutes(valueOrReferenceDate, rawValue); const start = utils.setSeconds(dateWithNewMinutes, 0); const end = utils.setSeconds(dateWithNewMinutes, 59); @@ -234,7 +239,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi } case 'seconds': { - const dateWithNewSeconds = utils.setSeconds(selectedTimeOrMidnight, rawValue); + const dateWithNewSeconds = utils.setSeconds(valueOrReferenceDate, rawValue); const start = dateWithNewSeconds; const end = dateWithNewSeconds; @@ -247,7 +252,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi }, [ ampm, - selectedTimeOrMidnight, + valueOrReferenceDate, disableIgnoringDatePartForTimeValidation, maxTime, meridiemMode, @@ -278,10 +283,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi return { onChange: (hours) => { const valueWithMeridiem = convertValueToMeridiem(hours, meridiemMode, ampm); - handleSectionChange( - 'hours', - utils.setHours(selectedTimeOrMidnight, valueWithMeridiem), - ); + handleSectionChange('hours', utils.setHours(valueOrReferenceDate, valueWithMeridiem)); }, items: getHourSectionOptions({ now, @@ -298,10 +300,10 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi case 'minutes': { return { onChange: (minutes) => { - handleSectionChange('minutes', utils.setMinutes(selectedTimeOrMidnight, minutes)); + handleSectionChange('minutes', utils.setMinutes(valueOrReferenceDate, minutes)); }, items: getTimeSectionOptions({ - value: utils.getMinutes(selectedTimeOrMidnight), + value: utils.getMinutes(valueOrReferenceDate), isDisabled: (minutes) => disabled || isTimeDisabled(minutes, 'minutes'), resolveLabel: (minutes) => utils.format(utils.setMinutes(now, minutes), 'minutes'), timeStep: timeSteps.minutes, @@ -314,10 +316,10 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi case 'seconds': { return { onChange: (seconds) => { - handleSectionChange('seconds', utils.setSeconds(selectedTimeOrMidnight, seconds)); + handleSectionChange('seconds', utils.setSeconds(valueOrReferenceDate, seconds)); }, items: getTimeSectionOptions({ - value: utils.getSeconds(selectedTimeOrMidnight), + value: utils.getSeconds(valueOrReferenceDate), isDisabled: (seconds) => disabled || isTimeDisabled(seconds, 'seconds'), resolveLabel: (seconds) => utils.format(utils.setSeconds(now, seconds), 'seconds'), timeStep: timeSteps.seconds, @@ -366,7 +368,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi localeText.secondsClockNumberText, meridiemMode, handleSectionChange, - selectedTimeOrMidnight, + valueOrReferenceDate, disabled, isTimeDisabled, handleMeridiemChange, diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/MultiSectionDigitalClock.test.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/MultiSectionDigitalClock.test.tsx new file mode 100644 index 000000000000..158296fab656 --- /dev/null +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/MultiSectionDigitalClock.test.tsx @@ -0,0 +1,86 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { spy } from 'sinon'; +import { + MultiSectionDigitalClock, + MultiSectionDigitalClockProps, +} from '@mui/x-date-pickers/MultiSectionDigitalClock'; +import { adapterToUse, createPickerRenderer } from 'test/utils/pickers-utils'; +import { multiSectionDigitalClockHandler } from 'test/utils/pickers/viewHandlers'; + +describe('', () => { + const { render } = createPickerRenderer(); + + describe('Reference date', () => { + it('should use `referenceDate` when no value defined', () => { + const onChange = spy(); + + render( + , + ); + + multiSectionDigitalClockHandler.setViewValue( + adapterToUse, + adapterToUse.setMinutes(adapterToUse.setHours(adapterToUse.date(), 15), 30), + ); + expect(onChange.callCount).to.equal(3); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2018, 0, 1, 15, 30)); + }); + + it('should not use `referenceDate` when a value is defined', () => { + const onChange = spy(); + + const ControlledMultiSectionDigitalClock = (props: MultiSectionDigitalClockProps) => { + const [value, setValue] = React.useState(props.value); + + return ( + { + setValue(newValue); + props.onChange?.(newValue); + }} + /> + ); + }; + + render( + , + ); + + multiSectionDigitalClockHandler.setViewValue( + adapterToUse, + adapterToUse.setMinutes(adapterToUse.setHours(adapterToUse.date(), 15), 30), + ); + expect(onChange.callCount).to.equal(3); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 0, 1, 15, 30)); + }); + + it('should not use `referenceDate` when a defaultValue is defined', () => { + const onChange = spy(); + + render( + , + ); + + multiSectionDigitalClockHandler.setViewValue( + adapterToUse, + adapterToUse.setMinutes(adapterToUse.setHours(adapterToUse.date(), 15), 30), + ); + expect(onChange.callCount).to.equal(3); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 0, 1, 15, 30)); + }); + }); +}); diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx index 205470e0d858..34a3e43c8cf1 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx @@ -5,6 +5,7 @@ import { describeValue } from '@mui/x-date-pickers/tests/describeValue'; import { createPickerRenderer, adapterToUse, wrapPickerMount } from 'test/utils/pickers-utils'; import { MultiSectionDigitalClock } from '@mui/x-date-pickers/MultiSectionDigitalClock'; import { expect } from 'chai'; +import { multiSectionDigitalClockHandler } from 'test/utils/pickers/viewHandlers'; describe(' - Describes', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); @@ -69,21 +70,7 @@ describe(' - Describes', () => { }, setNewValue: (value) => { const newValue = adapterToUse.addMinutes(adapterToUse.addHours(value, 1), 5); - const hasMeridiem = adapterToUse.is12HourCycleInCurrentLocale(); - const hoursLabel = parseInt( - adapterToUse.format(newValue, hasMeridiem ? 'hours12h' : 'hours24h'), - 10, - ); - const minutesLabel = adapterToUse.getMinutes(newValue).toString(); - userEvent.mousePress(screen.getByRole('option', { name: `${hoursLabel} hours` })); - userEvent.mousePress(screen.getByRole('option', { name: `${minutesLabel} minutes` })); - if (hasMeridiem) { - userEvent.mousePress( - screen.getByRole('option', { - name: adapterToUse.getMeridiemText(adapterToUse.getHours(newValue) >= 12 ? 'pm' : 'am'), - }), - ); - } + multiSectionDigitalClockHandler.setViewValue(adapterToUse, newValue); return newValue; }, diff --git a/packages/x-date-pickers/src/TimeClock/TimeClock.test.tsx b/packages/x-date-pickers/src/TimeClock/tests/TimeClock.test.tsx similarity index 89% rename from packages/x-date-pickers/src/TimeClock/TimeClock.test.tsx rename to packages/x-date-pickers/src/TimeClock/tests/TimeClock.test.tsx index 8c7bf56c4d7f..119656c14948 100644 --- a/packages/x-date-pickers/src/TimeClock/TimeClock.test.tsx +++ b/packages/x-date-pickers/src/TimeClock/tests/TimeClock.test.tsx @@ -10,6 +10,7 @@ import { } from '@mui/monorepo/test/utils'; import { TimeClock } from '@mui/x-date-pickers/TimeClock'; import { adapterToUse, createPickerRenderer } from 'test/utils/pickers-utils'; +import { timeClockHandler } from 'test/utils/pickers/viewHandlers'; describe('', () => { const { render } = createPickerRenderer(); @@ -514,4 +515,65 @@ describe('', () => { expect(adapterToUse.getSeconds(newDate)).to.equal(0); }); }); + + describe('Reference date', () => { + it('should use `referenceDate` when no value defined', () => { + const onChange = spy(); + + render( + , + ); + + timeClockHandler.setViewValue( + adapterToUse, + adapterToUse.setHours(adapterToUse.date(), 3), + 'hours', + ); + expect(onChange.callCount).to.equal(2); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2018, 0, 1, 15, 30)); + }); + + it('should not use `referenceDate` when a value is defined', () => { + const onChange = spy(); + + render( + , + ); + + timeClockHandler.setViewValue( + adapterToUse, + adapterToUse.setHours(adapterToUse.date(), 3), + 'hours', + ); + expect(onChange.callCount).to.equal(2); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 0, 1, 15, 20)); + }); + + it('should not use `referenceDate` when a defaultValue is defined', () => { + const onChange = spy(); + + render( + , + ); + + timeClockHandler.setViewValue( + adapterToUse, + adapterToUse.setHours(adapterToUse.date(), 3), + 'hours', + ); + expect(onChange.callCount).to.equal(2); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 0, 1, 15, 20)); + }); + }); }); diff --git a/packages/x-date-pickers/src/TimeClock/tests/describes.TimeClock.test.tsx b/packages/x-date-pickers/src/TimeClock/tests/describes.TimeClock.test.tsx index 12fb9ce5cf07..b26813026831 100644 --- a/packages/x-date-pickers/src/TimeClock/tests/describes.TimeClock.test.tsx +++ b/packages/x-date-pickers/src/TimeClock/tests/describes.TimeClock.test.tsx @@ -1,18 +1,14 @@ import * as React from 'react'; import { expect } from 'chai'; -import { describeConformance, fireTouchChangedEvent, screen } from '@mui/monorepo/test/utils'; +import { describeConformance, screen } from '@mui/monorepo/test/utils'; import { describeValue } from '@mui/x-date-pickers/tests/describeValue'; import { clockPointerClasses, TimeClock, timeClockClasses as classes, } from '@mui/x-date-pickers/TimeClock'; -import { - adapterToUse, - wrapPickerMount, - createPickerRenderer, - getClockTouchEvent, -} from 'test/utils/pickers-utils'; +import { adapterToUse, wrapPickerMount, createPickerRenderer } from 'test/utils/pickers-utils'; +import { timeClockHandler } from 'test/utils/pickers/viewHandlers'; describe(' - Describes', () => { const { render, clock } = createPickerRenderer(); @@ -56,18 +52,9 @@ describe(' - Describes', () => { }, setNewValue: (value) => { const newValue = adapterToUse.addMinutes(adapterToUse.addHours(value, 1), 5); - const hasMeridiem = adapterToUse.is12HourCycleInCurrentLocale(); - // change hours - const hourClockEvent = getClockTouchEvent( - adapterToUse.getHours(newValue), - hasMeridiem ? '12hours' : '24hours', - ); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', hourClockEvent); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchend', hourClockEvent); - // change minutes - const minutesClockEvent = getClockTouchEvent(adapterToUse.getMinutes(newValue), 'minutes'); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', minutesClockEvent); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchend', minutesClockEvent); + + timeClockHandler.setViewValue(adapterToUse, newValue, 'hours'); + timeClockHandler.setViewValue(adapterToUse, newValue, 'minutes'); return newValue; }, diff --git a/packages/x-date-pickers/src/internals/hooks/useClockReferenceDate.ts b/packages/x-date-pickers/src/internals/hooks/useClockReferenceDate.ts index 4f24cd9d5fbf..b7c93125ea96 100644 --- a/packages/x-date-pickers/src/internals/hooks/useClockReferenceDate.ts +++ b/packages/x-date-pickers/src/internals/hooks/useClockReferenceDate.ts @@ -26,7 +26,7 @@ export const useClockReferenceDate = ({ referenceDate: referenceDateProp, granularity: SECTION_TYPE_GRANULARITY.day, timezone, - getTodayDate: () => getTodayDate(utils, 'date'), + getTodayDate: () => getTodayDate(utils, timezone, 'date'), }), [], // eslint-disable-line react-hooks/exhaustive-deps ); diff --git a/test/utils/pickers-utils.tsx b/test/utils/pickers-utils.tsx index cef275cf578f..3fcb05118ce7 100644 --- a/test/utils/pickers-utils.tsx +++ b/test/utils/pickers-utils.tsx @@ -211,7 +211,10 @@ export const getClockMouseEvent = ( return event; }; -export const getClockTouchEvent = (value: number, view: 'minutes' | '12hours' | '24hours') => { +export const getClockTouchEvent = ( + value: number | string, + view: 'minutes' | '12hours' | '24hours', +) => { // TODO: Handle 24 hours clock if (view === '24hours') { throw new Error('Do not support 24 hours clock yet'); @@ -224,7 +227,7 @@ export const getClockTouchEvent = (value: number, view: 'minutes' | '12hours' | itemCount = 12; } - const angle = Math.PI / 2 - (Math.PI * 2 * value) / itemCount; + const angle = Math.PI / 2 - (Math.PI * 2 * Number(value)) / itemCount; const clientX = Math.round(((1 + Math.cos(angle)) * CLOCK_WIDTH) / 2); const clientY = Math.round(((1 - Math.sin(angle)) * CLOCK_WIDTH) / 2); diff --git a/test/utils/pickers/viewHandlers.ts b/test/utils/pickers/viewHandlers.ts new file mode 100644 index 000000000000..9ec3e39d2fbd --- /dev/null +++ b/test/utils/pickers/viewHandlers.ts @@ -0,0 +1,58 @@ +import { fireTouchChangedEvent, userEvent, screen } from '@mui/monorepo/test/utils'; +import { getClockTouchEvent } from 'test/utils/pickers-utils'; +import { MuiPickersAdapter, TimeView } from '@mui/x-date-pickers/models'; + +type TDate = any; + +interface ViewHandler { + setViewValue: (utils: MuiPickersAdapter, viewValue: TDate, view?: TView) => void; +} + +export const timeClockHandler: ViewHandler = { + setViewValue: (adapter, value, view) => { + const hasMeridiem = adapter.is12HourCycleInCurrentLocale(); + + let valueInt; + let clockView; + + if (view === 'hours') { + valueInt = adapter.getHours(value); + clockView = hasMeridiem ? '12hours' : '24hours'; + } else if (view === 'minutes') { + valueInt = adapter.getMinutes(value); + clockView = 'minutes'; + } else { + throw new Error('View not supported'); + } + + const hourClockEvent = getClockTouchEvent(valueInt, clockView); + + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', hourClockEvent); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchend', hourClockEvent); + }, +}; + +export const digitalClockHandler: ViewHandler = { + setViewValue: (adapter, value) => { + const hasMeridiem = adapter.is12HourCycleInCurrentLocale(); + const formattedLabel = adapter.format(value, hasMeridiem ? 'fullTime12h' : 'fullTime24h'); + userEvent.mousePress(screen.getByRole('option', { name: formattedLabel })); + }, +}; + +export const multiSectionDigitalClockHandler: ViewHandler = { + setViewValue: (adapter, value) => { + const hasMeridiem = adapter.is12HourCycleInCurrentLocale(); + const hoursLabel = parseInt(adapter.format(value, hasMeridiem ? 'hours12h' : 'hours24h'), 10); + const minutesLabel = adapter.getMinutes(value).toString(); + userEvent.mousePress(screen.getByRole('option', { name: `${hoursLabel} hours` })); + userEvent.mousePress(screen.getByRole('option', { name: `${minutesLabel} minutes` })); + if (hasMeridiem) { + userEvent.mousePress( + screen.getByRole('option', { + name: adapter.getMeridiemText(adapter.getHours(value) >= 12 ? 'pm' : 'am'), + }), + ); + } + }, +}; From be0e5a0085d5ca3446938ece314a5b7acefaa7da Mon Sep 17 00:00:00 2001 From: delangle Date: Tue, 20 Jun 2023 13:20:05 +0200 Subject: [PATCH 4/6] Fix --- .../src/DigitalClock/tests/describes.DigitalClock.test.tsx | 2 +- .../src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx | 2 +- .../tests/MultiSectionDigitalClock.test.tsx | 4 ++-- .../tests/describes.MultiSectionDigitalClock.test.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx b/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx index d2e281199d80..a9fe62d31073 100644 --- a/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx +++ b/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { screen, userEvent, describeConformance } from '@mui/monorepo/test/utils'; +import { screen, describeConformance } from '@mui/monorepo/test/utils'; import { describeValidation } from '@mui/x-date-pickers/tests/describeValidation'; import { describeValue } from '@mui/x-date-pickers/tests/describeValue'; import { createPickerRenderer, adapterToUse, wrapPickerMount } from 'test/utils/pickers-utils'; diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx index f2b54eba6f7d..3eccb3ad95d2 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx @@ -21,7 +21,7 @@ import { TimeStepOptions, TimeView } from '../models'; import { TimeViewWithMeridiem } from '../internals/models'; import { useControlledValueWithTimezone } from '../internals/hooks/useValueWithTimezone'; import { singleItemValueManager } from '../internals/utils/valueManagers'; -import { useClockReferenceDate } from '@mui/x-date-pickers/internals/hooks/useClockReferenceDate'; +import { useClockReferenceDate } from '../internals/hooks/useClockReferenceDate'; const useUtilityClasses = (ownerState: MultiSectionDigitalClockProps) => { const { classes } = ownerState; diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/MultiSectionDigitalClock.test.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/MultiSectionDigitalClock.test.tsx index 158296fab656..0306f761d54a 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/MultiSectionDigitalClock.test.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/MultiSectionDigitalClock.test.tsx @@ -33,7 +33,7 @@ describe('', () => { it('should not use `referenceDate` when a value is defined', () => { const onChange = spy(); - const ControlledMultiSectionDigitalClock = (props: MultiSectionDigitalClockProps) => { + function ControlledMultiSectionDigitalClock(props: MultiSectionDigitalClockProps) { const [value, setValue] = React.useState(props.value); return ( @@ -46,7 +46,7 @@ describe('', () => { }} /> ); - }; + } render( Date: Tue, 20 Jun 2023 13:38:05 +0200 Subject: [PATCH 5/6] Fix --- packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts index 556a4d9396ed..0f6f73c6ba70 100644 --- a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts +++ b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts @@ -271,11 +271,6 @@ export class AdapterDayjs implements MuiPickersAdapter { } let parsedValue: Dayjs; - - if (timezone === 'date') { - console.trace(); - } - if (timezone === 'UTC') { parsedValue = this.createUTCDate(value); } else if (timezone === 'system' || (timezone === 'default' && !this.hasTimezonePlugin())) { From d4570bc39e88b40fc6118e180ec86500c573308f Mon Sep 17 00:00:00 2001 From: flavien Date: Thu, 29 Jun 2023 15:03:58 +0200 Subject: [PATCH 6/6] Code review: Lukas --- .../x-date-pickers/src/internals/hooks/useClockReferenceDate.ts | 2 +- .../src/internals/hooks/usePicker/usePickerValue.types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/x-date-pickers/src/internals/hooks/useClockReferenceDate.ts b/packages/x-date-pickers/src/internals/hooks/useClockReferenceDate.ts index b7c93125ea96..f7f4062bad21 100644 --- a/packages/x-date-pickers/src/internals/hooks/useClockReferenceDate.ts +++ b/packages/x-date-pickers/src/internals/hooks/useClockReferenceDate.ts @@ -27,7 +27,7 @@ export const useClockReferenceDate = ({ granularity: SECTION_TYPE_GRANULARITY.day, timezone, getTodayDate: () => getTodayDate(utils, timezone, 'date'), - }), + }), // We only want to compute the reference date on mount. [], // eslint-disable-line react-hooks/exhaustive-deps ); diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts index f4a07ce6aff0..93510ad80c05 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts @@ -54,7 +54,7 @@ export interface PickerValueManager { * @param {MuiPickersAdapter} params.utils The adapter. * @param {number} params.granularity The granularity of the selection possible on this component. * @param {PickersTimezone} params.timezone The current timezone. - * @param {() => TDate} params.getTodayDate The reference date to use if not reference date is passed to the component. + * @param {() => TDate} params.getTodayDate The reference date to use if no reference date is passed to the component. * @returns {TValue} The reference value to use for non-provided dates. */ getInitialReferenceValue: (params: {