fix(datetime): ensure that default month shown is always in bounds (#25351)

Resolves #25320
This commit is contained in:
Sean Perkins
2022-06-02 22:27:25 -04:00
committed by GitHub
parent 4195f7ee28
commit 866d4528ad
5 changed files with 97 additions and 10 deletions

View File

@ -37,7 +37,7 @@ import {
getPreviousYear,
getStartOfWeek,
} from './utils/manipulation';
import { convertToArrayOfNumbers, getPartsFromCalendarDay, parseDate } from './utils/parse';
import { clampDate, convertToArrayOfNumbers, getPartsFromCalendarDay, parseDate } from './utils/parse';
import {
getCalendarDayState,
isDayDisabled,
@ -472,7 +472,7 @@ export class Datetime implements ComponentInterface {
/**
* Resets the internal state of the datetime but does not update the value.
* Passing a valid ISO-8601 string will reset the state of the component to the provided date.
* If no value is provided, the internal state will be reset to today.
* If no value is provided, the internal state will be reset to the clamped value of the min, max and today.
*/
@Method()
async reset(startDate?: string) {
@ -1083,8 +1083,8 @@ export class Datetime implements ComponentInterface {
private processValue = (value?: string | null) => {
this.highlightActiveParts = !!value;
const valueToProcess = value || getToday();
const { month, day, year, hour, minute, tzOffset } = parseDate(valueToProcess);
const valueToProcess = parseDate(value || getToday());
const { month, day, year, hour, minute, tzOffset } = clampDate(valueToProcess, this.minParts, this.maxParts);
this.setWorkingParts({
month,
@ -1093,7 +1093,7 @@ export class Datetime implements ComponentInterface {
hour,
minute,
tzOffset,
ampm: hour >= 12 ? 'pm' : 'am',
ampm: hour! >= 12 ? 'pm' : 'am',
});
this.activeParts = {
@ -1103,7 +1103,7 @@ export class Datetime implements ComponentInterface {
hour,
minute,
tzOffset,
ampm: hour >= 12 ? 'pm' : 'am',
ampm: hour! >= 12 ? 'pm' : 'am',
};
};
@ -1676,8 +1676,8 @@ export class Datetime implements ComponentInterface {
const { hours, minutes, am, pm } = generateTime(
workingParts,
use24Hour ? 'h23' : 'h12',
this.minParts,
this.maxParts,
this.value ? this.minParts : undefined,
this.value ? this.maxParts : undefined,
this.parsedHourValues,
this.parsedMinuteValues
);

View File

@ -47,4 +47,33 @@ test.describe('datetime: minmax', () => {
expect(nextButton).toBeDisabled();
expect(prevButton).toBeEnabled();
});
test.describe('when the datetime does not have a value', () => {
test('all time values should be available for selection', async ({ page }) => {
/**
* When the datetime does not have an initial value and today falls outside of
* the specified min and max values, all times values should be available for selection.
*/
await page.setContent(`
<ion-datetime min="2022-04-22T04:10:00" max="2022-05-21T21:30:00"></ion-datetime>
`);
await page.waitForSelector('.datetime-ready');
const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
await page.click('.time-body');
await ionPopoverDidPresent.next();
const hours = page.locator(
'ion-popover ion-picker-column-internal:nth-child(1) .picker-item:not(.picker-item-empty)'
);
const minutes = page.locator(
'ion-popover ion-picker-column-internal:nth-child(2) .picker-item:not(.picker-item-empty)'
);
expect(await hours.count()).toBe(12);
expect(await minutes.count()).toBe(60);
});
});
});

View File

@ -1,4 +1,4 @@
import { getPartsFromCalendarDay } from '../utils/parse';
import { clampDate, getPartsFromCalendarDay } from '../utils/parse';
describe('getPartsFromCalendarDay()', () => {
it('should extract DatetimeParts from a calendar day element', () => {
@ -18,3 +18,46 @@ describe('getPartsFromCalendarDay()', () => {
});
// TODO: parseDate()
describe('clampDate()', () => {
const minParts = {
year: 2021,
month: 6,
day: 5,
};
const maxParts = {
year: 2021,
month: 8,
day: 19,
};
it('should return the max month when the value is greater than the max', () => {
const dateParts = {
year: 2022,
month: 5,
day: 24,
};
const value = clampDate(dateParts, minParts, maxParts);
expect(value).toStrictEqual(maxParts);
});
it('should return the min month when the value is less than the min', () => {
const dateParts = {
year: 2020,
month: 5,
day: 24,
};
const value = clampDate(dateParts, minParts, maxParts);
expect(value).toStrictEqual(minParts);
});
it('should return the value when the value is greater than the min and less than the max', () => {
const dateParts = {
year: 2021,
month: 7,
day: 10,
};
const value = clampDate(dateParts, minParts, maxParts);
expect(value).toStrictEqual(dateParts);
});
});

View File

@ -1,5 +1,7 @@
import type { DatetimeParts } from '../datetime-interface';
import { isAfter, isBefore } from './comparison';
const ISO_8601_REGEXP =
// eslint-disable-next-line no-useless-escape
/^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/;
@ -106,3 +108,16 @@ export const parseDate = (val: string | undefined | null): any | undefined => {
tzOffset,
};
};
export const clampDate = (
dateParts: DatetimeParts,
minParts?: DatetimeParts,
maxParts?: DatetimeParts
): DatetimeParts => {
if (minParts && isBefore(dateParts, minParts)) {
return minParts;
} else if (maxParts && isAfter(dateParts, maxParts)) {
return maxParts;
}
return dateParts;
};