fix(datetime): arrow navigation respects min/max values (#25182)

Resolves #25073
This commit is contained in:
Sean Perkins
2022-05-02 13:01:25 -04:00
committed by GitHub
parent aa5e1b9621
commit 6946e09815
8 changed files with 143 additions and 11 deletions

View File

@ -0,0 +1,50 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('datetime: minmax', () => {
test('calendar arrow navigation should respect min/max values', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/25073',
});
await page.setContent(`
<ion-datetime min="2022-04-22" max="2022-05-21" value="2022-04-22T10:00:00"></ion-datetime>
<script>
const observer = new MutationObserver((mutationRecords) => {
if (mutationRecords) {
window.dispatchEvent(new CustomEvent('datetimeMonthDidChange'));
}
});
const initDatetimeChangeEvent = () => {
observer.observe(document.querySelector('ion-datetime').shadowRoot.querySelector('.calendar-body'), {
subtree: true,
childList: true
});
}
</script>
`);
await page.waitForSelector('.datetime-ready');
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 page.evaluate('initDatetimeChangeEvent()');
const monthDidChangeSpy = await page.spyOnEvent('datetimeMonthDidChange');
await nextButton.click();
await page.waitForChanges();
await monthDidChangeSpy.next();
expect(nextButton).toBeDisabled();
expect(prevButton).toBeEnabled();
});
});

View File

@ -18,7 +18,8 @@ export const isBefore = (baseParts: DatetimeParts, compareParts: DatetimeParts)
(baseParts.year === compareParts.year && baseParts.month < compareParts.month) ||
(baseParts.year === compareParts.year &&
baseParts.month === compareParts.month &&
baseParts.day! < compareParts.day!)
baseParts.day &&
baseParts.day < compareParts.day!)
);
};
@ -31,6 +32,7 @@ export const isAfter = (baseParts: DatetimeParts, compareParts: DatetimeParts) =
(baseParts.year === compareParts.year && baseParts.month > compareParts.month) ||
(baseParts.year === compareParts.year &&
baseParts.month === compareParts.month &&
baseParts.day! > compareParts.day!)
baseParts.day &&
baseParts.day > compareParts.day!)
);
};

View File

@ -140,7 +140,10 @@ export const isMonthDisabled = (
* previous navigation button is disabled.
*/
export const isPrevMonthDisabled = (refParts: DatetimeParts, minParts?: DatetimeParts, maxParts?: DatetimeParts) => {
const prevMonth = getPreviousMonth(refParts);
const prevMonth = {
...getPreviousMonth(refParts),
day: null,
};
return isMonthDisabled(prevMonth, {
minParts,
maxParts,
@ -152,7 +155,10 @@ export const isPrevMonthDisabled = (refParts: DatetimeParts, minParts?: Datetime
* determine if the next navigation button is disabled.
*/
export const isNextMonthDisabled = (refParts: DatetimeParts, maxParts?: DatetimeParts) => {
const nextMonth = getNextMonth(refParts);
const nextMonth = {
...getNextMonth(refParts),
day: null,
};
return isMonthDisabled(nextMonth, {
maxParts,
});

View File

@ -24,6 +24,18 @@ export const goto = async (page: Page, url: string, testInfo: TestInfo, original
const formattedUrl = `${splitUrl[0]}?ionic:_testing=${ionicTesting}&ionic:mode=${formattedMode}&rtl=${formattedRtl}`;
testInfo.annotations.push({
type: 'mode',
description: formattedMode,
});
if (rtl) {
testInfo.annotations.push({
type: 'rtl',
description: 'true',
});
}
const result = await Promise.all([
page.waitForFunction(() => (window as any).testAppLoaded === true, { timeout: 4750 }),
originalFn(formattedUrl),

View File

@ -3,3 +3,4 @@ export * from './goto';
export * from './get-snapshot-settings';
export * from './set-ion-viewport';
export * from './spy-on-event';
export * from './set-content';

View File

@ -0,0 +1,59 @@
import type { Page } from '@playwright/test';
/**
* Overwrites the default Playwright page.setContent method.
*
* Navigates to a blank page, sets the content, and waits for the
* Stencil components to be hydrated before proceeding with the test.
*
* @param page The Playwright page object.
* @param html The HTML content to set on the page.
*/
export const setContent = async (page: Page, html: string) => {
if (page.isClosed()) {
throw new Error('setContent unavailable: page is already closed');
}
const baseUrl = process.env.PLAYWRIGHT_TEST_BASE_URL;
const output = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
<link href="${baseUrl}/css/ionic.bundle.css" rel="stylesheet" />
<link href="${baseUrl}/scripts/testing/styles.css" rel="stylesheet" />
<script src="${baseUrl}/scripts/testing/scripts.js"></script>
<script type="module" src="${baseUrl}/dist/ionic/ionic.esm.js"></script>
</head>
<body>
${html}
</body>
</html>
`;
if (baseUrl) {
await page.route(baseUrl, (route) => {
if (route.request().url() === `${baseUrl}/`) {
/**
* Intercepts the empty page request and returns the
* HTML content that was passed in.
*/
route.fulfill({
status: 200,
contentType: 'text/html',
body: output,
});
} else {
// Allow all other requests to pass through
route.continue();
}
});
await page.goto(`${baseUrl}#`);
}
await page.waitForFunction(() => (window as any).testAppLoaded === true, { timeout: 4750 });
};

View File

@ -43,12 +43,6 @@ export interface E2EPage extends Page {
* we need to wait until the changes have been applied to the DOM.
*/
waitForChanges: (timeoutMs?: number) => Promise<void>;
/**
* Listens on the window for a specific event to be dispatched.
* Will wait a maximum of 5 seconds for the event to be dispatched.
*/
waitForCustomEvent: (eventName: string) => Promise<Page>;
/**
* Creates a new EventSpy and listens
* on the window for an event.

View File

@ -8,7 +8,14 @@ import type {
import { test as base } from '@playwright/test';
import { initPageEvents } from './page/event-spy';
import { getSnapshotSettings, goto as goToPage, setIonViewport, spyOnEvent, waitForChanges } from './page/utils';
import {
getSnapshotSettings,
goto as goToPage,
setContent,
setIonViewport,
spyOnEvent,
waitForChanges,
} from './page/utils';
import type { E2EPage } from './playwright-declarations';
type CustomTestArgs = PlaywrightTestArgs &
@ -28,6 +35,7 @@ export const test = base.extend<CustomFixtures>({
// Overridden Playwright methods
page.goto = (url: string) => goToPage(page, url, testInfo, originalGoto);
page.setContent = (html: string) => setContent(page, html);
// Custom Ionic methods
page.getSnapshotSettings = () => getSnapshotSettings(page, testInfo);
page.setIonViewport = () => setIonViewport(page);