From a31fb9f8c48f28b3076bb9e1c936fc2920530301 Mon Sep 17 00:00:00 2001 From: Lukas Date: Thu, 29 Feb 2024 10:59:36 +0200 Subject: [PATCH] [DateTimeRangePicker] Fix validation behavior (#12243) --- .../date-pickers/date-time-range-picker.json | 3 + .../desktop-date-time-range-picker.json | 3 + .../mobile-date-range-picker.json | 12 +- .../mobile-date-time-range-picker.json | 15 +- .../DateTimeRangePickerToolbar.tsx | 7 +- .../src/DateTimeRangePicker/shared.tsx | 23 ++- .../describes.DateTimeRangePicker.test.tsx | 27 +++ .../tests/DesktopDateTimeRangePicker.test.tsx | 168 ++++++++++++++++++ ...cribes.DesktopDateTimeRangePicker.test.tsx | 157 ++++++++++++++++ ...scribes.MobileDateTimeRangePicker.test.tsx | 157 ++++++++++++++++ .../useMobileRangePicker.tsx | 5 +- .../MultiSectionDigitalClock.tsx | 7 +- .../MultiSectionDigitalClockSection.tsx | 8 +- test/utils/pickers/assertions.ts | 4 +- .../pickers/describePicker/describePicker.tsx | 31 +++- .../testDayViewRangeValidation.tsx | 72 +++++--- .../testControlledUnControlled.tsx | 30 ++-- .../describeValue/testPickerActionBar.tsx | 6 +- .../testPickerOpenCloseLifeCycle.tsx | 26 +-- test/utils/pickers/misc.ts | 6 + test/utils/pickers/openPicker.ts | 7 +- 21 files changed, 683 insertions(+), 91 deletions(-) create mode 100644 packages/x-date-pickers-pro/src/DateTimeRangePicker/tests/describes.DateTimeRangePicker.test.tsx create mode 100644 packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx create mode 100644 packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/describes.DesktopDateTimeRangePicker.test.tsx create mode 100644 packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx diff --git a/docs/pages/x/api/date-pickers/date-time-range-picker.json b/docs/pages/x/api/date-pickers/date-time-range-picker.json index d08722fb1fd1..6980f341feb1 100644 --- a/docs/pages/x/api/date-pickers/date-time-range-picker.json +++ b/docs/pages/x/api/date-pickers/date-time-range-picker.json @@ -367,7 +367,10 @@ } ], "classes": [], + "spread": false, + "themeDefaultProps": false, "muiName": "MuiDateTimeRangePicker", + "forwardsRefTo": "HTMLDivElement", "filename": "/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.tsx", "inheritance": null, "demos": "", diff --git a/docs/pages/x/api/date-pickers/desktop-date-time-range-picker.json b/docs/pages/x/api/date-pickers/desktop-date-time-range-picker.json index 537034267407..2ffc29c59437 100644 --- a/docs/pages/x/api/date-pickers/desktop-date-time-range-picker.json +++ b/docs/pages/x/api/date-pickers/desktop-date-time-range-picker.json @@ -345,7 +345,10 @@ { "name": "field", "description": "", "class": null } ], "classes": [], + "spread": false, + "themeDefaultProps": false, "muiName": "MuiDesktopDateTimeRangePicker", + "forwardsRefTo": "HTMLDivElement", "filename": "/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx", "inheritance": null, "demos": "", diff --git a/docs/pages/x/api/date-pickers/mobile-date-range-picker.json b/docs/pages/x/api/date-pickers/mobile-date-range-picker.json index 2c0481a80fd6..4e4b02c80de7 100644 --- a/docs/pages/x/api/date-pickers/mobile-date-range-picker.json +++ b/docs/pages/x/api/date-pickers/mobile-date-range-picker.json @@ -200,6 +200,12 @@ "default": "ArrowDropDown", "class": null }, + { + "name": "dialog", + "description": "Custom component for the dialog inside which the views are rendered on mobile.", + "default": "PickersModalDialogRoot", + "class": null + }, { "name": "actionBar", "description": "Custom component for the action bar, it is placed below the picker views.", @@ -245,12 +251,6 @@ "default": "IconButton", "class": null }, - { - "name": "dialog", - "description": "Custom component for the dialog inside which the views are rendered on mobile.", - "default": "PickersModalDialogRoot", - "class": null - }, { "name": "mobilePaper", "description": "Custom component for the paper rendered inside the mobile picker's Dialog.", diff --git a/docs/pages/x/api/date-pickers/mobile-date-time-range-picker.json b/docs/pages/x/api/date-pickers/mobile-date-time-range-picker.json index 4b62aadfa72e..21c66292a2a0 100644 --- a/docs/pages/x/api/date-pickers/mobile-date-time-range-picker.json +++ b/docs/pages/x/api/date-pickers/mobile-date-time-range-picker.json @@ -269,6 +269,12 @@ "default": "MenuItem from '@mui/material'", "class": null }, + { + "name": "dialog", + "description": "Custom component for the dialog inside which the views are rendered on mobile.", + "default": "PickersModalDialogRoot", + "class": null + }, { "name": "actionBar", "description": "Custom component for the action bar, it is placed below the picker views.", @@ -314,12 +320,6 @@ "default": "IconButton", "class": null }, - { - "name": "dialog", - "description": "Custom component for the dialog inside which the views are rendered on mobile.", - "default": "PickersModalDialogRoot", - "class": null - }, { "name": "mobilePaper", "description": "Custom component for the paper rendered inside the mobile picker's Dialog.", @@ -335,7 +335,10 @@ { "name": "field", "description": "", "class": null } ], "classes": [], + "spread": false, + "themeDefaultProps": false, "muiName": "MuiMobileDateTimeRangePicker", + "forwardsRefTo": "HTMLDivElement", "filename": "/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx", "inheritance": null, "demos": "", diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx index 858bdfa0336e..32fbf3ec18ba 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx @@ -133,7 +133,6 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker hidden, toolbarFormat, toolbarPlaceholder, - titleId, }; const localeText = useLocaleText(); @@ -182,6 +181,10 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker [onChange, onRangePositionChange, props.value, rangePosition, utils], ); + if (hidden) { + return null; + } + return ( @@ -210,6 +214,7 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker view={rangePosition === 'end' ? view : undefined} className={classes.endToolbar} onChange={handleOnChange} + titleId={titleId ? `${titleId}-end-toolbar` : undefined} {...commonToolbarProps} /> diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/shared.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/shared.tsx index cba6a59a1aa2..cbbaa6102347 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/shared.tsx +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/shared.tsx @@ -170,8 +170,27 @@ export function useDateTimeRangePickerDefaultizedProps< ampm, disableFuture: themeProps.disableFuture ?? false, disablePast: themeProps.disablePast ?? false, - minDate: applyDefaultDate(utils, themeProps.minDate, defaultDates.minDate), - maxDate: applyDefaultDate(utils, themeProps.maxDate, defaultDates.maxDate), + minDate: applyDefaultDate( + utils, + themeProps.minDateTime ?? themeProps.minDate, + defaultDates.minDate, + ), + maxDate: applyDefaultDate( + utils, + themeProps.maxDateTime ?? themeProps.maxDate, + defaultDates.maxDate, + ), + minTime: themeProps.minDateTime ?? themeProps.minTime, + maxTime: themeProps.maxDateTime ?? themeProps.maxTime, + disableIgnoringDatePartForTimeValidation: + themeProps.disableIgnoringDatePartForTimeValidation ?? + Boolean( + themeProps.minDateTime || + themeProps.maxDateTime || + // allow digital clocks to correctly check time validity: https://github.com/mui/mui-x/issues/12048 + themeProps.disablePast || + themeProps.disableFuture, + ), slots: { tabs: DateTimeRangePickerTabs, toolbar: DateTimeRangePickerToolbar, diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/tests/describes.DateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/tests/describes.DateTimeRangePicker.test.tsx new file mode 100644 index 000000000000..4778e65099b2 --- /dev/null +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/tests/describes.DateTimeRangePicker.test.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { createPickerRenderer, wrapPickerMount } from 'test/utils/pickers'; +import { describeConformance } from 'test/utils/describeConformance'; +import { DateTimeRangePicker } from '../DateTimeRangePicker'; + +describe(' - Describes', () => { + const { render } = createPickerRenderer({ clock: 'fake' }); + + describeConformance(, () => ({ + classes: {} as any, + render, + muiName: 'MuiDateTimeRangePicker', + wrapMount: wrapPickerMount, + refInstanceof: window.HTMLDivElement, + skip: [ + 'componentProp', + 'componentsProp', + 'themeDefaultProps', + 'themeStyleOverrides', + 'themeVariants', + 'mergeClassName', + 'propsSpread', + 'rootClass', + 'reactTestRenderer', + ], + })); +}); diff --git a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx new file mode 100644 index 000000000000..793d2a6682d9 --- /dev/null +++ b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx @@ -0,0 +1,168 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { screen } from '@mui-internal/test-utils'; +import { createPickerRenderer, adapterToUse } from 'test/utils/pickers'; +import { DesktopDateTimeRangePicker } from '../DesktopDateTimeRangePicker'; + +describe('', () => { + const { render } = createPickerRenderer({ + clock: 'fake', + clockConfig: new Date(2018, 0, 10, 10, 16, 0), + }); + + describe('disabled dates', () => { + it('should respect the "disablePast" prop', () => { + render(); + + expect(screen.getByRole('gridcell', { name: '8' })).to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '9' })).to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '10' })).not.to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '11' })).not.to.have.attribute('disabled'); + + expect(screen.getByRole('option', { name: '9 hours' })).to.have.attribute( + 'aria-disabled', + 'true', + ); + expect(screen.getByRole('option', { name: '10 hours' })).not.to.have.attribute( + 'aria-disabled', + ); + + expect(screen.getByRole('option', { name: '15 minutes' })).to.have.attribute( + 'aria-disabled', + 'true', + ); + }); + + // Asserts correct behavior: https://github.com/mui/mui-x/issues/12048 + it('should respect the "disablePast" prop combined with "referenceDate"', () => { + render( + , + ); + + expect(screen.getByRole('gridcell', { name: '8' })).to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '9' })).to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '10' })).not.to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '11' })).not.to.have.attribute('disabled'); + + expect(screen.getByRole('option', { name: '9 hours' })).not.to.have.attribute( + 'aria-disabled', + ); + expect(screen.getByRole('option', { name: '10 hours' })).not.to.have.attribute( + 'aria-disabled', + ); + + expect(screen.getByRole('option', { name: '15 minutes' })).not.to.have.attribute( + 'aria-disabled', + ); + }); + + it('should respect the "disableFuture" prop', () => { + render(); + + expect(screen.getByRole('gridcell', { name: '9' })).not.to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '10' })).not.to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '11' })).to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '12' })).to.have.attribute('disabled'); + + expect(screen.getByRole('option', { name: '10 hours' })).not.to.have.attribute( + 'aria-disabled', + ); + expect(screen.getByRole('option', { name: '11 hours' })).to.have.attribute( + 'aria-disabled', + 'true', + ); + }); + + // Asserts correct behavior: https://github.com/mui/mui-x/issues/12048 + it('should respect the "disableFuture" prop combined with "referenceDate"', () => { + render( + , + ); + + expect(screen.getByRole('gridcell', { name: '9' })).not.to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '10' })).not.to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '11' })).to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '12' })).to.have.attribute('disabled'); + + expect(screen.getByRole('option', { name: '10 hours' })).not.to.have.attribute( + 'aria-disabled', + ); + expect(screen.getByRole('option', { name: '11 hours' })).not.to.have.attribute( + 'aria-disabled', + ); + }); + + it('should respect the "minDateTime" prop', () => { + render( + , + ); + + expect(screen.getByRole('gridcell', { name: '8' })).to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '9' })).to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '10' })).not.to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '11' })).not.to.have.attribute('disabled'); + + expect(screen.getByRole('option', { name: '9 hours' })).to.have.attribute( + 'aria-disabled', + 'true', + ); + expect(screen.getByRole('option', { name: '10 hours' })).not.to.have.attribute( + 'aria-disabled', + ); + + expect(screen.getByRole('option', { name: '15 minutes' })).to.have.attribute( + 'aria-disabled', + 'true', + ); + expect(screen.getByRole('option', { name: '20 minutes' })).not.to.have.attribute( + 'aria-disabled', + ); + }); + + it('should respect the "maxDateTime" prop', () => { + render( + , + ); + + expect(screen.getByRole('gridcell', { name: '9' })).not.to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '10' })).not.to.have.attribute('disabled'); + expect(screen.getByRole('gridcell', { name: '11' })).to.have.attribute('disabled'); + + expect(screen.getByRole('option', { name: '10 hours' })).not.to.have.attribute( + 'aria-disabled', + ); + expect(screen.getByRole('option', { name: '11 hours' })).to.have.attribute( + 'aria-disabled', + 'true', + ); + + expect(screen.getByRole('option', { name: '15 minutes' })).not.to.have.attribute( + 'aria-disabled', + ); + expect(screen.getByRole('option', { name: '20 minutes' })).to.have.attribute( + 'aria-disabled', + 'true', + ); + }); + }); +}); diff --git a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/describes.DesktopDateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/describes.DesktopDateTimeRangePicker.test.tsx new file mode 100644 index 000000000000..9c29cbf35fcf --- /dev/null +++ b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/describes.DesktopDateTimeRangePicker.test.tsx @@ -0,0 +1,157 @@ +import * as React from 'react'; +import { describeConformance, screen, userEvent } from '@mui-internal/test-utils'; +import { + createPickerRenderer, + adapterToUse, + expectFieldValueV7, + describeValue, + describePicker, + describeRangeValidation, + wrapPickerMount, + getFieldSectionsContainer, +} from 'test/utils/pickers'; +import { DesktopDateTimeRangePicker } from '../DesktopDateTimeRangePicker'; + +describe(' - Describes', () => { + const { render, clock } = createPickerRenderer({ + clock: 'fake', + }); + + describePicker(DesktopDateTimeRangePicker, { + render, + fieldType: 'multi-input', + variant: 'desktop', + }); + + describeRangeValidation(DesktopDateTimeRangePicker, () => ({ + render, + clock, + views: ['day', 'hours', 'minutes'], + componentFamily: 'picker', + variant: 'desktop', + })); + + describeConformance(, () => ({ + classes: {} as any, + render, + muiName: 'MuiDesktopDateTimeRangePicker', + wrapMount: wrapPickerMount, + refInstanceof: window.HTMLDivElement, + skip: [ + 'componentProp', + 'componentsProp', + 'themeDefaultProps', + 'themeStyleOverrides', + 'themeVariants', + 'mergeClassName', + 'propsSpread', + 'rootClass', + 'reactTestRenderer', + ], + })); + + describeValue(DesktopDateTimeRangePicker, () => ({ + render, + componentFamily: 'picker', + type: 'date-time-range', + variant: 'desktop', + initialFocus: 'start', + clock, + values: [ + // initial start and end dates + [adapterToUse.date('2018-01-01T11:30:00'), adapterToUse.date('2018-01-04T11:45:00')], + // start and end dates after `setNewValue` + [adapterToUse.date('2018-01-02T12:35:00'), adapterToUse.date('2018-01-05T12:50:00')], + ], + emptyValue: [null, null], + assertRenderedValue: (expectedValues: any[]) => { + const hasMeridiem = adapterToUse.is12HourCycleInCurrentLocale(); + const expectedPlaceholder = hasMeridiem ? 'MM/DD/YYYY hh:mm aa' : 'MM/DD/YYYY hh:mm'; + + const startSectionsContainer = getFieldSectionsContainer(0); + const expectedStartValueStr = expectedValues[0] + ? adapterToUse.format( + expectedValues[0], + hasMeridiem ? 'keyboardDateTime12h' : 'keyboardDateTime24h', + ) + : expectedPlaceholder; + expectFieldValueV7(startSectionsContainer, expectedStartValueStr); + + const endSectionsContainer = getFieldSectionsContainer(1); + const expectedEndValueStr = expectedValues[1] + ? adapterToUse.format( + expectedValues[1], + hasMeridiem ? 'keyboardDateTime12h' : 'keyboardDateTime24h', + ) + : expectedPlaceholder; + expectFieldValueV7(endSectionsContainer, expectedEndValueStr); + }, + setNewValue: ( + value, + { isOpened, applySameValue, setEndDate = false, selectSection, pressKey }, + ) => { + let newValue: any[]; + if (applySameValue) { + newValue = value; + } else if (setEndDate) { + newValue = [ + value[0], + adapterToUse.addMinutes(adapterToUse.addHours(adapterToUse.addDays(value[1], 1), 1), 5), + ]; + } else { + newValue = [ + adapterToUse.addMinutes(adapterToUse.addHours(adapterToUse.addDays(value[0], 1), 1), 5), + value[1], + ]; + } + if (isOpened) { + userEvent.mousePress( + screen.getByRole('gridcell', { + name: adapterToUse.getDate(newValue[setEndDate ? 1 : 0]).toString(), + }), + ); + const hasMeridiem = adapterToUse.is12HourCycleInCurrentLocale(); + const hours = adapterToUse.format( + newValue[setEndDate ? 1 : 0], + hasMeridiem ? 'hours12h' : 'hours24h', + ); + const hoursNumber = adapterToUse.getHours(newValue[setEndDate ? 1 : 0]); + userEvent.mousePress(screen.getByRole('option', { name: `${parseInt(hours, 10)} hours` })); + userEvent.mousePress( + screen.getByRole('option', { + name: `${adapterToUse.getMinutes(newValue[setEndDate ? 1 : 0])} minutes`, + }), + ); + if (hasMeridiem) { + // meridiem is an extra view on `DesktopDateTimeRangePicker` + // we need to click it to finish selection + userEvent.mousePress( + screen.getByRole('option', { name: hoursNumber >= 12 ? 'PM' : 'AM' }), + ); + } + } else { + selectSection('day'); + pressKey(undefined, 'ArrowUp'); + + selectSection('hours'); + pressKey(undefined, 'ArrowUp'); + + selectSection('minutes'); + pressKey(undefined, 'PageUp'); // increment by 5 minutes + + const hasMeridiem = adapterToUse.is12HourCycleInCurrentLocale(); + if (hasMeridiem) { + selectSection('meridiem'); + const previousHours = adapterToUse.getHours(value[setEndDate ? 1 : 0]); + const newHours = adapterToUse.getHours(newValue[setEndDate ? 1 : 0]); + // update meridiem section if it changed + if ((previousHours < 12 && newHours >= 12) || (previousHours >= 12 && newHours < 12)) { + pressKey(undefined, 'ArrowUp'); + } + } + } + + return newValue; + }, + })); +}); diff --git a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx new file mode 100644 index 000000000000..8b7f439ca168 --- /dev/null +++ b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx @@ -0,0 +1,157 @@ +import * as React from 'react'; +import { describeConformance, fireEvent, screen, userEvent } from '@mui-internal/test-utils'; +import { + createPickerRenderer, + adapterToUse, + expectFieldValueV7, + describeValue, + describePicker, + describeRangeValidation, + wrapPickerMount, + getFieldSectionsContainer, + openPicker, +} from 'test/utils/pickers'; +import { MobileDateTimeRangePicker } from '../MobileDateTimeRangePicker'; + +describe(' - Describes', () => { + const { render, clock } = createPickerRenderer({ + clock: 'fake', + }); + + describePicker(MobileDateTimeRangePicker, { + render, + fieldType: 'multi-input', + variant: 'mobile', + }); + + describeRangeValidation(MobileDateTimeRangePicker, () => ({ + render, + clock, + views: ['day', 'hours', 'minutes'], + componentFamily: 'picker', + variant: 'mobile', + })); + + describeConformance(, () => ({ + classes: {} as any, + render, + muiName: 'MuiMobileDateTimeRangePicker', + wrapMount: wrapPickerMount, + refInstanceof: window.HTMLDivElement, + skip: [ + 'componentProp', + 'componentsProp', + 'themeDefaultProps', + 'themeStyleOverrides', + 'themeVariants', + 'mergeClassName', + 'propsSpread', + 'rootClass', + 'reactTestRenderer', + ], + })); + + describeValue(MobileDateTimeRangePicker, () => ({ + render, + componentFamily: 'picker', + type: 'date-time-range', + variant: 'mobile', + initialFocus: 'start', + clock, + values: [ + // initial start and end dates + [adapterToUse.date('2018-01-01T11:30:00'), adapterToUse.date('2018-01-04T11:45:00')], + // start and end dates after `setNewValue` + [adapterToUse.date('2018-01-02T12:35:00'), adapterToUse.date('2018-01-05T12:50:00')], + ], + emptyValue: [null, null], + assertRenderedValue: (expectedValues: any[]) => { + const hasMeridiem = adapterToUse.is12HourCycleInCurrentLocale(); + const expectedPlaceholder = hasMeridiem ? 'MM/DD/YYYY hh:mm aa' : 'MM/DD/YYYY hh:mm'; + + const startSectionsContainer = getFieldSectionsContainer(0); + const expectedStartValueStr = expectedValues[0] + ? adapterToUse.format( + expectedValues[0], + hasMeridiem ? 'keyboardDateTime12h' : 'keyboardDateTime24h', + ) + : expectedPlaceholder; + expectFieldValueV7(startSectionsContainer, expectedStartValueStr); + + const endSectionsContainer = getFieldSectionsContainer(1); + const expectedEndValueStr = expectedValues[1] + ? adapterToUse.format( + expectedValues[1], + hasMeridiem ? 'keyboardDateTime12h' : 'keyboardDateTime24h', + ) + : expectedPlaceholder; + expectFieldValueV7(endSectionsContainer, expectedEndValueStr); + }, + setNewValue: (value, { isOpened, applySameValue, setEndDate = false }) => { + if (!isOpened) { + openPicker({ + type: 'date-time-range', + variant: 'mobile', + initialFocus: setEndDate ? 'end' : 'start', + }); + } + let newValue: any[]; + if (applySameValue) { + newValue = value; + } else if (setEndDate) { + newValue = [ + value[0], + adapterToUse.addMinutes(adapterToUse.addHours(adapterToUse.addDays(value[1], 1), 1), 5), + ]; + } else { + newValue = [ + adapterToUse.addMinutes(adapterToUse.addHours(adapterToUse.addDays(value[0], 1), 1), 5), + value[1], + ]; + } + + // if we want to set the end date, we firstly need to switch to end date "range position" + if (setEndDate) { + userEvent.mousePress( + screen.getByRole('button', { name: adapterToUse.format(value[1], 'shortDate') }), + ); + } + + userEvent.mousePress( + screen.getByRole('gridcell', { + name: adapterToUse.getDate(newValue[setEndDate ? 1 : 0]).toString(), + }), + ); + const hasMeridiem = adapterToUse.is12HourCycleInCurrentLocale(); + const hours = adapterToUse.format( + newValue[setEndDate ? 1 : 0], + hasMeridiem ? 'hours12h' : 'hours24h', + ); + const hoursNumber = adapterToUse.getHours(newValue[setEndDate ? 1 : 0]); + userEvent.mousePress(screen.getByRole('option', { name: `${parseInt(hours, 10)} hours` })); + userEvent.mousePress( + screen.getByRole('option', { + name: `${adapterToUse.getMinutes(newValue[setEndDate ? 1 : 0])} minutes`, + }), + ); + if (hasMeridiem) { + // meridiem is an extra view on `MobileDateTimeRangePicker` + // we need to click it to finish selection + userEvent.mousePress(screen.getByRole('option', { name: hoursNumber >= 12 ? 'PM' : 'AM' })); + } + // Close the picker + if (!isOpened) { + // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target + fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); + clock.runToLast(); + } else { + // return to the start date view in case we'd like to repeat the selection process + userEvent.mousePress( + screen.getByRole('button', { name: adapterToUse.format(newValue[0], 'shortDate') }), + ); + } + + return newValue; + }, + })); +}); diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx b/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx index b0095be48249..e0016ea48423 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx @@ -196,7 +196,10 @@ export const useMobileRangePicker = < ...contextLocaleText, ...localeText, }; - let labelledById = labelId; + let labelledById = + pickerParams.valueType === 'date-time' + ? `${labelId}-start-toolbar ${labelId}-end-toolbar` + : labelId; if (isToolbarHidden) { const labels: string[] = []; if (fieldType === 'multi-input') { diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx index 3aae2c8dcb26..5c309e3f02e4 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx @@ -292,7 +292,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi value, ampm, utils, - isDisabled: (hours) => disabled || isTimeDisabled(hours, 'hours'), + isDisabled: (hours) => isTimeDisabled(hours, 'hours'), timeStep: timeSteps.hours, resolveAriaLabel: localeText.hoursClockNumberText, valueOrReferenceDate, @@ -312,7 +312,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi items: getTimeSectionOptions({ value: utils.getMinutes(valueOrReferenceDate), utils, - isDisabled: (minutes) => disabled || isTimeDisabled(minutes, 'minutes'), + isDisabled: (minutes) => isTimeDisabled(minutes, 'minutes'), resolveLabel: (minutes) => utils.format(utils.setMinutes(now, minutes), 'minutes'), timeStep: timeSteps.minutes, hasValue: !!value, @@ -333,7 +333,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi items: getTimeSectionOptions({ value: utils.getSeconds(valueOrReferenceDate), utils, - isDisabled: (seconds) => disabled || isTimeDisabled(seconds, 'seconds'), + isDisabled: (seconds) => isTimeDisabled(seconds, 'seconds'), resolveLabel: (seconds) => utils.format(utils.setSeconds(now, seconds), 'seconds'), timeStep: timeSteps.seconds, hasValue: !!value, @@ -384,7 +384,6 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi meridiemMode, setValueAndGoToNextView, valueOrReferenceDate, - disabled, isTimeDisabled, handleMeridiemChange, ], diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClockSection.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClockSection.tsx index ced796ed21fa..ced944954913 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClockSection.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClockSection.tsx @@ -187,7 +187,9 @@ export const MultiSectionDigitalClockSection = React.forwardRef( {...other} > {items.map((option, index) => { - if (skipDisabled && option.isDisabled?.(option.value)) { + const isItemDisabled = option.isDisabled?.(option.value); + const isDisabled = disabled || isItemDisabled; + if (skipDisabled && isDisabled) { return null; } const isSelected = option.isSelected(option.value); @@ -198,11 +200,11 @@ export const MultiSectionDigitalClockSection = React.forwardRef( key={option.label} onClick={() => !readOnly && onChange(option.value)} selected={isSelected} - disabled={disabled || option.isDisabled?.(option.value)} + disabled={isDisabled} disableRipple={readOnly} role="option" // aria-readonly is not supported here and does not have any effect - aria-disabled={readOnly} + aria-disabled={readOnly || isDisabled || undefined} aria-label={option.ariaLabel} aria-selected={isSelected} tabIndex={tabIndex} diff --git a/test/utils/pickers/assertions.ts b/test/utils/pickers/assertions.ts index e9ce9c7288d8..b66dd0ee095e 100644 --- a/test/utils/pickers/assertions.ts +++ b/test/utils/pickers/assertions.ts @@ -30,11 +30,11 @@ export const expectFieldPlaceholderV6 = ( }; export function expectPickerChangeHandlerValue( - type: 'date' | 'date-time' | 'time' | 'date-range', + type: 'date' | 'date-time' | 'time' | 'date-range' | 'date-time-range', spyCallback: SinonSpy, expectedValue: any, ) { - if (type === 'date-range') { + if (['date-range', 'date-time-range'].includes(type)) { spyCallback.lastCall.firstArg.forEach((value, index) => { expect(value).to.deep.equal(expectedValue[index]); }); diff --git a/test/utils/pickers/describePicker/describePicker.tsx b/test/utils/pickers/describePicker/describePicker.tsx index 7752ded4ed3f..aee9affd8606 100644 --- a/test/utils/pickers/describePicker/describePicker.tsx +++ b/test/utils/pickers/describePicker/describePicker.tsx @@ -134,23 +134,33 @@ function innerDescribePicker(ElementToTest: React.ElementType, options: Describe this.skip(); } - render(); + render( + , + ); if (variant === 'desktop') { - expect(screen.queryByMuiTest('picker-toolbar')).to.equal(null); + expect(screen.queryByTestId('pickers-toolbar')).to.equal(null); } else { - expect(screen.getByMuiTest('picker-toolbar')).toBeVisible(); + expect(screen.getByTestId('pickers-toolbar')).toBeVisible(); } }); - it('should render toolbar when `hidden` is `false`', function test() { + it.skip('should render toolbar when `hidden` is `false`', function test() { if (hasNoView) { this.skip(); } - render(); + render( + , + ); - expect(screen.getByMuiTest('picker-toolbar')).toBeVisible(); + expect(screen.getByTestId('pickers-toolbar')).toBeVisible(); }); it('should not render toolbar when `hidden` is `true`', function test() { @@ -158,9 +168,14 @@ function innerDescribePicker(ElementToTest: React.ElementType, options: Describe this.skip(); } - render(); + render( + , + ); - expect(screen.queryByMuiTest('picker-toolbar')).to.equal(null); + expect(screen.queryByTestId('pickers-toolbar')).to.equal(null); }); }); } diff --git a/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx b/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx index 6d5e8ead8f97..fc1a3865ab93 100644 --- a/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx +++ b/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx @@ -3,17 +3,12 @@ import { expect } from 'chai'; import { screen } from '@mui-internal/test-utils'; import { adapterToUse } from 'test/utils/pickers'; -const isDisable = (el: HTMLElement) => el.getAttribute('disabled') !== null; +const isDisabled = (el: HTMLElement) => el.getAttribute('disabled') !== null; -const isFieldElement = (el: HTMLElement) => el.className.includes('MuiPickersInput'); - -const testDisabledDate = (day: string, expectedAnswer: boolean[], isDesktop: boolean) => { - expect( - screen - .getAllByText(day) - .filter((el) => !isFieldElement(el)) - .map(isDisable), - ).to.deep.equal(isDesktop ? expectedAnswer : expectedAnswer.slice(0, 1)); +const testDisabledDate = (day: string, expectedAnswer: boolean[], isSingleCalendar: boolean) => { + expect(screen.getAllByRole('gridcell', { name: day }).map(isDisabled)).to.deep.equal( + isSingleCalendar ? expectedAnswer.slice(0, 1) : expectedAnswer, + ); }; const testMonthSwitcherAreDisable = (areDisable: [boolean, boolean]) => { @@ -44,6 +39,7 @@ export function testDayViewRangeValidation(ElementToTest, getOptions) { } const isDesktop = variant === 'desktop'; + const includesTimeView = views.includes('hours'); const defaultProps = { referenceDate: adapterToUse.date('2018-03-12'), @@ -62,8 +58,8 @@ export function testDayViewRangeValidation(ElementToTest, getOptions) { />, ); - testDisabledDate('10', [false, true], isDesktop); - testDisabledDate('11', [true, true], isDesktop); + testDisabledDate('10', [false, true], !isDesktop || includesTimeView); + testDisabledDate('11', [true, true], !isDesktop || includesTimeView); }); it('should apply disablePast', function test() { @@ -80,14 +76,26 @@ export function testDayViewRangeValidation(ElementToTest, getOptions) { const tomorrow = adapterToUse.addDays(now, 1); const yesterday = adapterToUse.addDays(now, -1); - testDisabledDate(adapterToUse.format(now, 'dayOfMonth'), [false, false], isDesktop); - testDisabledDate(adapterToUse.format(tomorrow, 'dayOfMonth'), [false, false], isDesktop); + testDisabledDate( + adapterToUse.format(now, 'dayOfMonth'), + [false, false], + !isDesktop || includesTimeView, + ); + testDisabledDate( + adapterToUse.format(tomorrow, 'dayOfMonth'), + [false, false], + !isDesktop || includesTimeView, + ); if (!adapterToUse.isSameMonth(yesterday, tomorrow)) { setProps({ value: [yesterday, null] }); clock.runToLast(); } - testDisabledDate(adapterToUse.format(yesterday, 'dayOfMonth'), [true, false], isDesktop); + testDisabledDate( + adapterToUse.format(yesterday, 'dayOfMonth'), + [true, false], + !isDesktop || includesTimeView, + ); }); it('should apply disableFuture', function test() { @@ -104,14 +112,26 @@ export function testDayViewRangeValidation(ElementToTest, getOptions) { const tomorrow = adapterToUse.addDays(now, 1); const yesterday = adapterToUse.addDays(now, -1); - testDisabledDate(adapterToUse.format(now, 'dayOfMonth'), [false, true], isDesktop); - testDisabledDate(adapterToUse.format(tomorrow, 'dayOfMonth'), [true, true], isDesktop); + testDisabledDate( + adapterToUse.format(now, 'dayOfMonth'), + [false, true], + !isDesktop || includesTimeView, + ); + testDisabledDate( + adapterToUse.format(tomorrow, 'dayOfMonth'), + [true, true], + !isDesktop || includesTimeView, + ); if (!adapterToUse.isSameMonth(yesterday, tomorrow)) { setProps({ value: [yesterday, null] }); clock.runToLast(); } - testDisabledDate(adapterToUse.format(yesterday, 'dayOfMonth'), [false, true], isDesktop); + testDisabledDate( + adapterToUse.format(yesterday, 'dayOfMonth'), + [false, true], + !isDesktop || includesTimeView, + ); }); it('should apply minDate', function test() { @@ -125,10 +145,10 @@ export function testDayViewRangeValidation(ElementToTest, getOptions) { />, ); - testDisabledDate('1', [true, false], isDesktop); - testDisabledDate('3', [true, false], isDesktop); - testDisabledDate('4', [false, false], isDesktop); - testDisabledDate('15', [false, false], isDesktop); + testDisabledDate('1', [true, false], !isDesktop || includesTimeView); + testDisabledDate('3', [true, false], !isDesktop || includesTimeView); + testDisabledDate('4', [false, false], !isDesktop || includesTimeView); + testDisabledDate('15', [false, false], !isDesktop || includesTimeView); testMonthSwitcherAreDisable([true, false]); }); @@ -144,10 +164,10 @@ export function testDayViewRangeValidation(ElementToTest, getOptions) { />, ); - testDisabledDate('1', [false, true], isDesktop); - testDisabledDate('4', [false, true], isDesktop); - testDisabledDate('5', [true, true], isDesktop); - testDisabledDate('15', [true, true], isDesktop); + testDisabledDate('1', [false, true], !isDesktop || includesTimeView); + testDisabledDate('4', [false, true], !isDesktop || includesTimeView); + testDisabledDate('5', [true, true], !isDesktop || includesTimeView); + testDisabledDate('15', [true, true], !isDesktop || includesTimeView); testMonthSwitcherAreDisable([false, true]); }); diff --git a/test/utils/pickers/describeValue/testControlledUnControlled.tsx b/test/utils/pickers/describeValue/testControlledUnControlled.tsx index 63477036c9b6..493b9c66cce4 100644 --- a/test/utils/pickers/describeValue/testControlledUnControlled.tsx +++ b/test/utils/pickers/describeValue/testControlledUnControlled.tsx @@ -28,6 +28,9 @@ export const testControlledUnControlled: DescribeValueTestSuite = ( const params = pickerParams as DescribeValueOptions<'picker', any>; + const isRangeType = params.type === 'date-range' || params.type === 'date-time-range'; + const isDesktopRange = params.variant === 'desktop' && isRangeType; + describe('Controlled / uncontrolled value', () => { it('should render `props.defaultValue` if no `props.value` is passed', () => { renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: values[0] }); @@ -170,10 +173,7 @@ export const testControlledUnControlled: DescribeValueTestSuite = ( }); it('should have correct labelledby relationship when toolbar is shown', () => { - if ( - componentFamily !== 'picker' || - (params.variant === 'desktop' && params.type === 'date-range') - ) { + if (componentFamily !== 'picker' || isDesktopRange) { return; } @@ -184,14 +184,15 @@ export const testControlledUnControlled: DescribeValueTestSuite = ( localeText: { toolbarTitle: 'Test toolbar' }, }); - expect(screen.getByLabelText('Test toolbar')).to.have.attribute('role', 'dialog'); + if (params.variant === 'mobile' && params.type === 'date-time-range') { + expect(screen.getByLabelText('Start End')).to.have.attribute('role', 'dialog'); + } else { + expect(screen.getByLabelText('Test toolbar')).to.have.attribute('role', 'dialog'); + } }); it('should have correct labelledby relationship with provided label when toolbar is hidden', () => { - if ( - componentFamily !== 'picker' || - (params.variant === 'desktop' && params.type === 'date-range') - ) { + if (componentFamily !== 'picker' || isDesktopRange) { return; } @@ -199,7 +200,7 @@ export const testControlledUnControlled: DescribeValueTestSuite = ( enableAccessibleFieldDOMStructure: true, open: true, slotProps: { toolbar: { hidden: true } }, - ...(params.type === 'date-range' + ...(isRangeType ? { localeText: { start: 'test', @@ -213,10 +214,7 @@ export const testControlledUnControlled: DescribeValueTestSuite = ( }); it('should have correct labelledby relationship without label and hidden toolbar but external props', () => { - if ( - componentFamily !== 'picker' || - (params.variant === 'desktop' && params.type === 'date-range') - ) { + if (componentFamily !== 'picker' || isDesktopRange) { return; } @@ -226,7 +224,7 @@ export const testControlledUnControlled: DescribeValueTestSuite = ( = ( expect(fieldRoot).to.have.class(inputBaseClasses.error); expect(fieldRoot).to.have.attribute('aria-invalid', 'true'); - if (params.type === 'date-range' && !params.isSingleInput) { + if (isRangeType && !params.isSingleInput) { const fieldRootEnd = getFieldInputRoot(1); expect(fieldRootEnd).to.have.class(inputBaseClasses.error); expect(fieldRootEnd).to.have.attribute('aria-invalid', 'true'); diff --git a/test/utils/pickers/describeValue/testPickerActionBar.tsx b/test/utils/pickers/describeValue/testPickerActionBar.tsx index db94681f65c8..c58458a91776 100644 --- a/test/utils/pickers/describeValue/testPickerActionBar.tsx +++ b/test/utils/pickers/describeValue/testPickerActionBar.tsx @@ -27,6 +27,8 @@ export const testPickerActionBar: DescribeValueTestSuite = ( return; } + const isRangeType = pickerParams.type === 'date-range' || pickerParams.type === 'date-time-range'; + describe('Picker action bar', () => { describe('clear action', () => { it('should call onClose, onChange with empty value and onAccept with empty value', () => { @@ -105,7 +107,7 @@ export const testPickerActionBar: DescribeValueTestSuite = ( expect(onChange.callCount).to.equal( getExpectedOnChangeCount(componentFamily, pickerParams) + 1, ); - if (pickerParams.type === 'date-range') { + if (isRangeType) { values[0].forEach((value, index) => { expect(onChange.lastCall.args[0][index]).toEqualDateTime(value); }); @@ -245,7 +247,7 @@ export const testPickerActionBar: DescribeValueTestSuite = ( let startOfToday: any; if (pickerParams.type === 'date') { startOfToday = adapterToUse.startOfDay(adapterToUse.date()); - } else if (pickerParams.type === 'date-range') { + } else if (isRangeType) { startOfToday = [adapterToUse.date(), adapterToUse.date()]; } else { startOfToday = adapterToUse.date(); diff --git a/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx b/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx index fc0824b139e4..24ef112fc206 100644 --- a/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx +++ b/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx @@ -16,8 +16,8 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite return; } - const viewWrapperRole = - pickerParams.type === 'date-range' && pickerParams.variant === 'desktop' ? 'tooltip' : 'dialog'; + const isRangeType = pickerParams.type === 'date-range' || pickerParams.type === 'date-time-range'; + const viewWrapperRole = isRangeType && pickerParams.variant === 'desktop' ? 'tooltip' : 'dialog'; describe('Picker open / close lifecycle', () => { it('should not open on mount if `props.open` is false', () => { @@ -70,7 +70,7 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite // Change the value let newValue = setNewValue(values[0], { isOpened: true, selectSection, pressKey }); expect(onChange.callCount).to.equal(getExpectedOnChangeCount(componentFamily, pickerParams)); - if (pickerParams.type === 'date-range') { + if (isRangeType) { newValue = setNewValue(newValue, { isOpened: true, setEndDate: true, @@ -128,7 +128,7 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite // Change the value let newValue = setNewValue(values[0], { isOpened: true, selectSection, pressKey }); expect(onChange.callCount).to.equal(getExpectedOnChangeCount(componentFamily, pickerParams)); - if (pickerParams.type === 'date-range') { + if (isRangeType) { newValue = setNewValue(newValue, { isOpened: true, setEndDate: true, @@ -165,7 +165,7 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite // Change the value (same value) setNewValue(values[0], { isOpened: true, applySameValue: true, selectSection, pressKey }); - if (pickerParams.type === 'date-range') { + if (isRangeType) { setNewValue(values[0], { isOpened: true, applySameValue: true, @@ -202,7 +202,7 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite let newValue = setNewValue(values[0], { isOpened: true, selectSection, pressKey }); const initialChangeCount = getExpectedOnChangeCount(componentFamily, pickerParams); expect(onChange.callCount).to.equal(initialChangeCount); - if (pickerParams.type === 'date-range') { + if (isRangeType) { newValue = setNewValue(newValue, { isOpened: true, setEndDate: true, @@ -220,8 +220,12 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite // Change the value let newValueBis = setNewValue(newValue, { isOpened: true, selectSection, pressKey }); - if (pickerParams.type === 'date-range') { - expect(onChange.callCount).to.equal(3); + if (isRangeType) { + expect(onChange.callCount).to.equal( + initialChangeCount + + getExpectedOnChangeCount(componentFamily, pickerParams) * 2 - + (pickerParams.type === 'date-time-range' ? 1 : 0), + ); newValueBis = setNewValue(newValueBis, { isOpened: true, setEndDate: true, @@ -269,7 +273,7 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite userEvent.keyPress(document.activeElement!, { key: 'Escape' }); expect(onChange.callCount).to.equal(getExpectedOnChangeCount(componentFamily, pickerParams)); expect(onAccept.callCount).to.equal(1); - if (pickerParams.type === 'date-range') { + if (isRangeType) { newValue.forEach((value, index) => { expect(onChange.lastCall.args[0][index]).toEqualDateTime(value); }); @@ -281,7 +285,7 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite it('should call onClose when clicking outside of the picker without prior change', function test() { // TODO: Fix this test and enable it on mobile and date-range - if (pickerParams.variant === 'mobile' || pickerParams.type === 'date-range') { + if (pickerParams.variant === 'mobile' || isRangeType) { this.skip(); } @@ -310,7 +314,7 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite it('should call onClose and onAccept with the live value when clicking outside of the picker', function test() { // TODO: Fix this test and enable it on mobile and date-range - if (pickerParams.variant === 'mobile' || pickerParams.type === 'date-range') { + if (pickerParams.variant === 'mobile' || isRangeType) { this.skip(); } diff --git a/test/utils/pickers/misc.ts b/test/utils/pickers/misc.ts index 14afbe820874..9e189dc2c8dc 100644 --- a/test/utils/pickers/misc.ts +++ b/test/utils/pickers/misc.ts @@ -40,6 +40,12 @@ export const getExpectedOnChangeCount = ( params.variant === 'desktop' ? 'multi-section-digital-clock' : 'clock', ); } + if (componentFamily === 'picker' && params.type === 'date-time-range') { + return ( + getChangeCountForComponentFamily(componentFamily) + + getChangeCountForComponentFamily('multi-section-digital-clock') + ); + } if (componentFamily === 'clock') { // the `TimeClock` fires change for both touch move and touch end // but does not have meridiem control diff --git a/test/utils/pickers/openPicker.ts b/test/utils/pickers/openPicker.ts index a0726c9553f7..2de6210fca38 100644 --- a/test/utils/pickers/openPicker.ts +++ b/test/utils/pickers/openPicker.ts @@ -8,7 +8,7 @@ export type OpenPickerParams = variant: 'mobile' | 'desktop'; } | { - type: 'date-range'; + type: 'date-range' | 'date-time-range'; variant: 'mobile' | 'desktop'; initialFocus: 'start' | 'end'; /** @@ -18,11 +18,12 @@ export type OpenPickerParams = }; export const openPicker = (params: OpenPickerParams) => { + const isRangeType = params.type === 'date-range' || params.type === 'date-time-range'; const fieldSectionsContainer = getFieldSectionsContainer( - params.type === 'date-range' && !params.isSingleInput && params.initialFocus === 'end' ? 1 : 0, + isRangeType && !params.isSingleInput && params.initialFocus === 'end' ? 1 : 0, ); - if (params.type === 'date-range') { + if (isRangeType) { userEvent.mousePress(fieldSectionsContainer); if (params.isSingleInput && params.initialFocus === 'end') {