chore(): sync with main

This commit is contained in:
Liam DeBeasi
2022-06-27 09:54:57 -04:00
391 changed files with 15762 additions and 40817 deletions

View File

@@ -360,15 +360,20 @@ ion-picker-column-internal {
:host .calendar-day:after {
@include border-radius(32px, 32px, 32px, 32px);
@include padding(4px, 4px, 4px, 4px);
position: absolute;
/**
* Explicit position values are required here
* as pseudo element positioning is incorrect
* in older implementations of css grid.
*
* TODO: FW-1720: Remove top/left styles when deprecating iOS 13 support
*/
@include position(50%, null, null, 50%);
position: absolute;
/* stylelint-disable-next-line property-disallowed-list */
top: 50%;
/* stylelint-disable-next-line property-disallowed-list */
left: 50%;
width: 32px;
height: 32px;

View File

@@ -11,6 +11,7 @@ import { isRTL } from '../../utils/rtl';
import { createColorClasses } from '../../utils/theme';
import type { PickerColumnItem } from '../picker-column-internal/picker-column-internal-interfaces';
import { warnIfValueOutOfBounds } from './utils/comparison';
import {
generateMonths,
getDaysOfMonth,
@@ -327,6 +328,8 @@ export class Datetime implements ComponentInterface {
*/
const valueDateParts = parseDate(this.value);
if (valueDateParts) {
warnIfValueOutOfBounds(valueDateParts, this.minParts, this.maxParts);
const { month, day, year, hour, minute } = valueDateParts;
const ampm = hour >= 12 ? 'pm' : 'am';
@@ -1099,7 +1102,11 @@ export class Datetime implements ComponentInterface {
private processValue = (value?: string | null) => {
this.highlightActiveParts = !!value;
const valueToProcess = parseDate(value || getToday());
const { month, day, year, hour, minute, tzOffset } = clampDate(valueToProcess, this.minParts, this.maxParts);
const { minParts, maxParts } = this;
warnIfValueOutOfBounds(valueToProcess, minParts, maxParts);
const { month, day, year, hour, minute, tzOffset } = clampDate(valueToProcess, minParts, maxParts);
const ampm = parseAmPm(hour!);
this.setWorkingParts({

View File

@@ -2,6 +2,35 @@ import { expect } from '@playwright/test';
import type { E2EPage } from '@utils/test/playwright';
import { test } from '@utils/test/playwright';
test.describe('datetime: closing time popover', () => {
test('it should not change months', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/25438',
});
await page.setContent(`
<ion-datetime></ion-datetime>
`);
const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
const ionPopoverDidDismiss = await page.spyOnEvent('ionPopoverDidDismiss');
const timeButton = page.locator('.time-body');
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
const currentMonthAndYear = await calendarMonthYear.evaluate((el: HTMLElement) => el.innerText);
await timeButton.click();
await ionPopoverDidPresent.next();
await page.keyboard.press('Escape');
await ionPopoverDidDismiss.next();
await page.waitForChanges();
await expect(calendarMonthYear).toHaveText(currentMonthAndYear);
});
});
test.describe('datetime: selecting a day', () => {
const testHighlight = async (page: E2EPage, datetimeID: string) => {
const today = new Date();
@@ -12,13 +41,13 @@ test.describe('datetime: selecting a day', () => {
`#${datetimeID} .calendar-day[data-day='${today.getDate()}'][data-month='${today.getMonth() + 1}']`
);
expect(todayBtn).toHaveClass(/calendar-day-today/);
expect(todayBtn).not.toHaveClass(/calendar-day-active/);
await expect(todayBtn).toHaveClass(/calendar-day-today/);
await expect(todayBtn).not.toHaveClass(/calendar-day-active/);
await todayBtn.click();
await page.waitForChanges();
expect(todayBtn).toHaveClass(/calendar-day-active/);
await expect(todayBtn).toHaveClass(/calendar-day-active/);
};
test('should not highlight a day until one is selected', async ({ page }) => {
@@ -35,13 +64,13 @@ test.describe('datetime: selecting a day', () => {
const activeDay = page.locator('ion-datetime .calendar-day-active');
expect(activeDay).toHaveText('25');
await expect(activeDay).toHaveText('25');
const dayBtn = page.locator('ion-datetime .calendar-day[data-day="13"][data-month="12"]');
await dayBtn.click();
await page.waitForChanges();
expect(activeDay).toHaveText('13');
await expect(activeDay).toHaveText('13');
});
});
@@ -96,10 +125,10 @@ test.describe('datetime: footer', () => {
await page.setContent('<ion-datetime value="2022-05-03" show-default-buttons="true"></ion-datetime>');
const cancelButton = page.locator('ion-datetime #cancel-button');
expect(cancelButton).toHaveText('Cancel');
await expect(cancelButton).toHaveText('Cancel');
const confirmButton = page.locator('ion-datetime #confirm-button');
expect(confirmButton).toHaveText('Done');
await expect(confirmButton).toHaveText('Done');
const datetime = page.locator('ion-datetime');
expect(await datetime.screenshot()).toMatchSnapshot(
@@ -110,7 +139,7 @@ test.describe('datetime: footer', () => {
await page.setContent('<ion-datetime value="2022-05-03" show-clear-button="true"></ion-datetime>');
const clearButton = page.locator('ion-datetime #clear-button');
expect(clearButton).toHaveText('Clear');
await expect(clearButton).toHaveText('Clear');
const datetime = page.locator('ion-datetime');
expect(await datetime.screenshot()).toMatchSnapshot(
@@ -123,13 +152,13 @@ test.describe('datetime: footer', () => {
);
const cancelButton = page.locator('ion-datetime #cancel-button');
expect(cancelButton).toHaveText('Cancel');
await expect(cancelButton).toHaveText('Cancel');
const confirmButton = page.locator('ion-datetime #confirm-button');
expect(confirmButton).toHaveText('Done');
await expect(confirmButton).toHaveText('Done');
const clearButton = page.locator('ion-datetime #clear-button');
expect(clearButton).toHaveText('Clear');
await expect(clearButton).toHaveText('Clear');
const datetime = page.locator('ion-datetime');
expect(await datetime.screenshot()).toMatchSnapshot(
@@ -146,7 +175,7 @@ test.describe('datetime: footer', () => {
`);
const customButton = page.locator('ion-datetime #custom-button');
expect(customButton).toBeVisible();
await expect(customButton).toBeVisible();
const datetime = page.locator('ion-datetime');
expect(await datetime.screenshot()).toMatchSnapshot(

View File

@@ -122,21 +122,21 @@ test.describe('datetime: disable dates', () => {
test('should disable a specific date', async ({ page }) => {
const disabledDay = queryAllDisabledDays(page, '#specificDate');
expect(disabledDay).toHaveText('10');
await expect(disabledDay).toHaveText('10');
});
test('should disable specific days of the week', async ({ page }) => {
const disabledDays = queryAllWorkingMonthDisabledDays(page, '#weekends');
expect(await disabledDays.count()).toEqual(10);
expect(disabledDays).toHaveText(['2', '3', '9', '10', '16', '17', '23', '24', '30', '31']);
await expect(disabledDays).toHaveText(['2', '3', '9', '10', '16', '17', '23', '24', '30', '31']);
});
test('should disable a range of dates', async ({ page }) => {
const disabledDays = queryAllDisabledDays(page, '#dateRange');
expect(await disabledDays.count()).toEqual(11);
expect(disabledDays).toHaveText(['10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20']);
await expect(disabledDays).toHaveText(['10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20']);
});
test('should disable a month', async ({ page }) => {

View File

@@ -106,7 +106,7 @@ test.describe('datetime: display', () => {
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
expect(calendarMonthYear).toHaveText('March 2022');
await expect(calendarMonthYear).toHaveText('March 2022');
// ensure it still works if presentation is changed more than once
await select.selectOption('date-time');
@@ -118,7 +118,7 @@ test.describe('datetime: display', () => {
await ionWorkingPartsDidChange.next();
expect(calendarMonthYear).toHaveText('February 2022');
await expect(calendarMonthYear).toHaveText('February 2022');
});
});
});

View File

@@ -8,7 +8,7 @@ test.describe('datetime: hour cycle', () => {
`);
const timeButton = page.locator('ion-datetime .time-body');
expect(timeButton).toHaveText('16:30');
await expect(timeButton).toHaveText('16:30');
});
test('should set the h12 hour cycle correctly', async ({ page }) => {
await page.setContent(`
@@ -16,6 +16,6 @@ test.describe('datetime: hour cycle', () => {
`);
const timeButton = page.locator('ion-datetime .time-body');
expect(timeButton).toHaveText('4:30 PM');
await expect(timeButton).toHaveText('4:30 PM');
});
});

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test';
import type { E2EPage } from '@utils/test/playwright';
import { test } from '@utils/test/playwright';
test.describe('datetime: minmax', () => {
@@ -32,8 +33,8 @@ test.describe('datetime: minmax', () => {
const prevButton = page.locator('ion-datetime .calendar-next-prev ion-button:nth-child(1)');
const nextButton = page.locator('ion-datetime .calendar-next-prev ion-button:nth-child(2)');
expect(nextButton).toBeEnabled();
expect(prevButton).toBeDisabled();
await expect(nextButton).toBeEnabled();
await expect(prevButton).toBeDisabled();
await page.evaluate('initDatetimeChangeEvent()');
@@ -44,18 +45,19 @@ test.describe('datetime: minmax', () => {
await monthDidChangeSpy.next();
expect(nextButton).toBeDisabled();
expect(prevButton).toBeEnabled();
await expect(nextButton).toBeDisabled();
await expect(prevButton).toBeEnabled();
});
test('datetime: minmax months disabled', async ({ page }) => {
await page.goto('/src/components/datetime/test/minmax');
const calendarMonths = page.locator('ion-datetime#inside .calendar-month');
await page.waitForSelector('.datetime-ready');
expect(calendarMonths.nth(0)).not.toHaveClass(/calendar-month-disabled/);
expect(calendarMonths.nth(1)).not.toHaveClass(/calendar-month-disabled/);
expect(calendarMonths.nth(2)).toHaveClass(/calendar-month-disabled/);
await expect(calendarMonths.nth(0)).not.toHaveClass(/calendar-month-disabled/);
await expect(calendarMonths.nth(1)).not.toHaveClass(/calendar-month-disabled/);
await expect(calendarMonths.nth(2)).toHaveClass(/calendar-month-disabled/);
});
test('datetime: minmax navigation disabled', async ({ page }) => {
@@ -64,19 +66,21 @@ test.describe('datetime: minmax', () => {
const navButtons = page.locator('ion-datetime#outside .calendar-next-prev ion-button');
expect(navButtons.nth(0)).toHaveAttribute('disabled', '');
expect(navButtons.nth(1)).toHaveAttribute('disabled', '');
await expect(navButtons.nth(0)).toHaveAttribute('disabled', '');
await expect(navButtons.nth(1)).toHaveAttribute('disabled', '');
});
test('datetime: min including day should not disable month', async ({ page }) => {
await page.goto('/src/components/datetime/test/minmax');
await page.waitForSelector('.datetime-ready');
const calendarMonths = page.locator('ion-datetime#min-with-day .calendar-month');
expect(calendarMonths.nth(0)).toHaveClass(/calendar-month-disabled/);
expect(calendarMonths.nth(1)).not.toHaveClass(/calendar-month-disabled/);
expect(calendarMonths.nth(2)).not.toHaveClass(/calendar-month-disabled/);
await expect(calendarMonths.nth(0)).toHaveClass(/calendar-month-disabled/);
await expect(calendarMonths.nth(1)).not.toHaveClass(/calendar-month-disabled/);
await expect(calendarMonths.nth(2)).not.toHaveClass(/calendar-month-disabled/);
});
test.describe('when the datetime does not have a value', () => {
test('all time values should be available for selection', async ({ page }) => {
/**
@@ -105,4 +109,29 @@ test.describe('datetime: minmax', () => {
expect(await minutes.count()).toBe(60);
});
});
test.describe('setting value outside bounds should show in-bounds month', () => {
const testDisplayedMonth = async (page: E2EPage, content: string) => {
await page.setContent(content);
await page.waitForSelector('.datetime-ready');
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
await expect(calendarMonthYear).toHaveText('June 2021');
};
test('when min is defined', async ({ page }) => {
await testDisplayedMonth(page, `<ion-datetime min="2021-06-01" value="2021-05-01"></ion-datetime>`);
});
test('when max is defined', async ({ page }) => {
await testDisplayedMonth(page, `<ion-datetime max="2021-06-30" value="2021-07-01"></ion-datetime>`);
});
test('when both min and max are defined', async ({ page }) => {
await testDisplayedMonth(
page,
`<ion-datetime min="2021-06-01" max="2021-06-30" value="2021-05-01"></ion-datetime>`
);
});
});
});

View File

@@ -9,16 +9,16 @@ test.describe('datetime: month-year picker', () => {
test('should hide the footer when picker is open', async ({ page }) => {
const datetimeFooter = page.locator('#date-time .datetime-footer');
expect(datetimeFooter).toBeVisible();
await expect(datetimeFooter).toBeVisible();
const pickerButton = page.locator('#date-time .calendar-month-year > ion-item');
await pickerButton.click();
await page.waitForChanges();
expect(datetimeFooter).not.toBeVisible();
await expect(datetimeFooter).not.toBeVisible();
});
test('should not hide the footer on month-year presentation', async ({ page }) => {
const monthyearFooter = page.locator('#month-year .datetime-footer');
expect(monthyearFooter).toBeVisible();
await expect(monthyearFooter).toBeVisible();
});
});

View File

@@ -13,7 +13,7 @@ test.describe('datetime: set-value', () => {
await page.waitForChanges();
const activeDate = page.locator('ion-datetime .calendar-day-active');
expect(activeDate).toHaveText('25');
await expect(activeDate).toHaveText('25');
});
test('should update the active time', async ({ page }) => {
const datetime = page.locator('ion-datetime');
@@ -22,6 +22,6 @@ test.describe('datetime: set-value', () => {
await page.waitForChanges();
const activeDate = page.locator('ion-datetime .time-body');
expect(activeDate).toHaveText('12:40 PM');
await expect(activeDate).toHaveText('12:40 PM');
});
});

View File

@@ -21,7 +21,7 @@ test.describe('datetime: sub-pixel width', () => {
await datetimeMonthDidChange.next();
const monthYear = page.locator('ion-datetime .calendar-month-year');
expect(monthYear).toHaveText('March 2022');
await expect(monthYear).toHaveText('March 2022');
});
test('should update the month when prev button is clicked', async ({ page }) => {
@@ -40,6 +40,6 @@ test.describe('datetime: sub-pixel width', () => {
await datetimeMonthDidChange.next();
const monthYear = page.locator('ion-datetime .calendar-month-year');
expect(monthYear).toHaveText('January 2022');
await expect(monthYear).toHaveText('January 2022');
});
});

View File

@@ -9,7 +9,7 @@ test.describe('datetime: time label', () => {
await page.waitForSelector('.datetime-ready');
const timeLabel = page.locator('ion-datetime .time-header');
expect(timeLabel).toHaveText('Time');
await expect(timeLabel).toHaveText('Time');
});
test('should not render a custom time label', async ({ page }) => {
await page.setContent(`
@@ -18,6 +18,6 @@ test.describe('datetime: time label', () => {
await page.waitForSelector('.datetime-ready');
const timeLabel = page.locator('ion-datetime .time-header');
expect(timeLabel).toHaveText('');
await expect(timeLabel).toHaveText('');
});
});

View File

@@ -15,7 +15,7 @@ test.describe('datetime: values', () => {
* so this ensures that dayValues is applying to all
* rendered months, not just the initial month.
*/
expect(items).toHaveText(['1', '2', '3', '1', '2', '3', '1', '2', '3']);
await expect(items).toHaveText(['1', '2', '3', '1', '2', '3', '1', '2', '3']);
});
test('should render correct months', async ({ page }) => {
await page.setContent(`
@@ -23,7 +23,7 @@ test.describe('datetime: values', () => {
`);
const items = page.locator('.month-column .picker-item:not(.picker-item-empty)');
expect(items).toHaveText(['May', 'June', 'October']);
await expect(items).toHaveText(['May', 'June', 'October']);
});
test('should render correct years', async ({ page }) => {
await page.setContent(`
@@ -31,7 +31,7 @@ test.describe('datetime: values', () => {
`);
const items = page.locator('.year-column .picker-item:not(.picker-item-empty)');
expect(items).toHaveText(['2022', '2021', '2020']);
await expect(items).toHaveText(['2022', '2021', '2020']);
});
test('should render correct hours', async ({ page }) => {
await page.setContent(`
@@ -39,7 +39,7 @@ test.describe('datetime: values', () => {
`);
const items = page.locator('ion-picker-column-internal:first-of-type .picker-item:not(.picker-item-empty)');
expect(items).toHaveText(['1', '2', '3']);
await expect(items).toHaveText(['1', '2', '3']);
});
test('should render correct minutes', async ({ page }) => {
await page.setContent(`
@@ -47,6 +47,6 @@ test.describe('datetime: values', () => {
`);
const items = page.locator('ion-picker-column-internal:nth-of-type(2) .picker-item:not(.picker-item-empty)');
expect(items).toHaveText(['01', '02', '03']);
await expect(items).toHaveText(['01', '02', '03']);
});
});

View File

@@ -33,7 +33,7 @@ test.describe('datetime: zoom in interactivity', () => {
await datetimeMonthDidChange.next();
const monthYear = page.locator('ion-datetime .calendar-month-year');
expect(monthYear).toHaveText('March 2022');
await expect(monthYear).toHaveText('March 2022');
});
test('should update the month when prev button is clicked', async ({ page }) => {
@@ -50,6 +50,6 @@ test.describe('datetime: zoom in interactivity', () => {
await datetimeMonthDidChange.next();
const monthYear = page.locator('ion-datetime .calendar-month-year');
expect(monthYear).toHaveText('January 2022');
await expect(monthYear).toHaveText('January 2022');
});
});

View File

@@ -33,7 +33,7 @@ test.describe('datetime: zoom out interactivity', () => {
await datetimeMonthDidChange.next();
const monthYear = page.locator('ion-datetime .calendar-month-year');
expect(monthYear).toHaveText('March 2022');
await expect(monthYear).toHaveText('March 2022');
});
test('should update the month when prev button is clicked', async ({ page }) => {
@@ -50,6 +50,6 @@ test.describe('datetime: zoom out interactivity', () => {
await datetimeMonthDidChange.next();
const monthYear = page.locator('ion-datetime .calendar-month-year');
expect(monthYear).toHaveText('January 2022');
await expect(monthYear).toHaveText('January 2022');
});
});

View File

@@ -1,3 +1,5 @@
import { printIonWarning } from '@utils/logging';
import type { DatetimeParts } from '../datetime-interface';
/**
@@ -36,3 +38,14 @@ export const isAfter = (baseParts: DatetimeParts, compareParts: DatetimeParts) =
baseParts.day > compareParts.day!)
);
};
export const warnIfValueOutOfBounds = (value: DatetimeParts, min: DatetimeParts, max: DatetimeParts) => {
if ((min && isBefore(value, min)) || (max && isAfter(value, max))) {
printIonWarning(
'The value provided to ion-datetime is out of bounds.\n\n' +
`Min: ${JSON.stringify(min)}\n` +
`Max: ${JSON.stringify(max)}\n` +
`Value: ${JSON.stringify(value)}`
);
}
};