chore(): sync with main
57
.github/CODEOWNERS
vendored
@ -13,3 +13,60 @@
|
||||
|
||||
# Global owners
|
||||
* @ionic-team/framework
|
||||
|
||||
# Frameworks
|
||||
|
||||
## Angular
|
||||
|
||||
/angular/ @sean-perkins
|
||||
/packages/angular-server @sean-perkins
|
||||
/angular/test
|
||||
|
||||
## React
|
||||
|
||||
/packages/react/ @amandaejohnston
|
||||
/packages/react-router @amandaejohnston
|
||||
/packages/react/test-app/
|
||||
/packages/react-router/test-app/
|
||||
|
||||
## Vue
|
||||
|
||||
/packages/vue/ @liamdebeasi
|
||||
/packages/vue-router/ @liamdebeasi
|
||||
/packages/vue/test/
|
||||
/packages/vue-router/__tests__
|
||||
|
||||
# Components
|
||||
|
||||
/core/src/components/accordion/ @liamdebeasi
|
||||
/core/src/components/accordion-group/ @liamdebeasi
|
||||
|
||||
/core/src/components/datetime/ @liamdebeasi @amandaejohnston @sean-perkins
|
||||
/core/src/components/datetime-button/ @liamdebeasi
|
||||
|
||||
/core/src/components/menu/ @amandaejohnston
|
||||
/core/src/components/menu-toggle/ @amandaejohnston
|
||||
|
||||
/core/src/components/nav/ @sean-perkins
|
||||
/core/src/components/nav-link/ @sean-perkins
|
||||
|
||||
/core/src/components/picker-internal/ @liamdebeasi
|
||||
/core/src/components/picker-column-internal/ @liamdebeasi
|
||||
|
||||
/core/src/components/refresher/ @liamdebeasi
|
||||
/core/src/components/refresher-content/ @liamdebeasi
|
||||
|
||||
# Codeowner should own the source, but everyone should own the tests
|
||||
/core/src/components/**/test/ @ionic-team/framework
|
||||
|
||||
# Utilities
|
||||
|
||||
/core/src/utils/animation/ @liamdebeasi
|
||||
/core/src/utils/content/ @sean-perkins
|
||||
/core/src/utils/gesture/ @liamdebeasi
|
||||
/core/src/utils/input-shims/ @liamdebeasi
|
||||
/core/src/utils/keyboard/ @liamdebeasi
|
||||
/core/src/utils/logging/ @amandaejohnston
|
||||
/core/src/utils/sanitization/ @liamdebeasi
|
||||
/core/src/utils/tap-click/ @liamdebeasi
|
||||
/core/src/utils/transition/ @liamdebeasi
|
||||
|
7
.github/workflows/stencil-eval.yml
vendored
@ -47,13 +47,6 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/workflows/actions/test-core-spec
|
||||
|
||||
test-core-e2e:
|
||||
needs: [build-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/workflows/actions/test-core-e2e
|
||||
|
||||
test-core-screenshot:
|
||||
strategy:
|
||||
# This ensures that all screenshot shard
|
||||
|
@ -85,7 +85,10 @@ const config: PlaywrightTestConfig = {
|
||||
},
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Fail fast on CI */
|
||||
maxFailures: process.env.CI ? 1 : 0,
|
||||
/* Flaky test should be either addressed or disabled until we can address them */
|
||||
retries: 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
|
@ -19,7 +19,7 @@ test.describe('accordion: states', () => {
|
||||
const accordionGroup = page.locator('ion-accordion-group');
|
||||
const accordion = page.locator('ion-accordion');
|
||||
|
||||
expect(accordion).toHaveJSProperty('readonly', false);
|
||||
await expect(accordion).toHaveJSProperty('readonly', false);
|
||||
|
||||
await accordionGroup.evaluate((el: HTMLIonAccordionGroupElement) => {
|
||||
el.readonly = true;
|
||||
@ -27,7 +27,7 @@ test.describe('accordion: states', () => {
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(accordion).toHaveJSProperty('readonly', true);
|
||||
await expect(accordion).toHaveJSProperty('readonly', true);
|
||||
});
|
||||
|
||||
test('should properly set disabled on child accordions', async ({ page }) => {
|
||||
@ -43,7 +43,7 @@ test.describe('accordion: states', () => {
|
||||
const accordionGroup = page.locator('ion-accordion-group');
|
||||
const accordion = page.locator('ion-accordion');
|
||||
|
||||
expect(accordion).toHaveJSProperty('disabled', false);
|
||||
await expect(accordion).toHaveJSProperty('disabled', false);
|
||||
|
||||
await accordionGroup.evaluate((el: HTMLIonAccordionGroupElement) => {
|
||||
el.disabled = true;
|
||||
@ -51,6 +51,6 @@ test.describe('accordion: states', () => {
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(accordion).toHaveJSProperty('disabled', true);
|
||||
await expect(accordion).toHaveJSProperty('disabled', true);
|
||||
});
|
||||
});
|
||||
|
@ -41,7 +41,7 @@ test.describe('action sheet: basic', () => {
|
||||
await actionSheetFixture.open('#basic');
|
||||
|
||||
const actionSheet = page.locator('ion-action-sheet');
|
||||
expect(actionSheet).toHaveAttribute('data-testid', 'basic-action-sheet');
|
||||
await expect(actionSheet).toHaveAttribute('data-testid', 'basic-action-sheet');
|
||||
});
|
||||
});
|
||||
test.describe('action sheet: variants', () => {
|
||||
@ -78,7 +78,7 @@ test.describe('action sheet: basic', () => {
|
||||
await actionSheetFixture.open('#customBackdrop');
|
||||
|
||||
const backdrop = page.locator('ion-action-sheet ion-backdrop');
|
||||
expect(backdrop).toHaveCSS('opacity', '1');
|
||||
await expect(backdrop).toHaveCSS('opacity', '1');
|
||||
});
|
||||
test('should open alert from action sheet', async ({ page, skip }) => {
|
||||
skip.rtl();
|
||||
@ -96,7 +96,7 @@ test.describe('action sheet: basic', () => {
|
||||
const actionSheet = page.locator('ion-action-sheet');
|
||||
await actionSheet.locator('ion-backdrop').click();
|
||||
|
||||
expect(actionSheet).toBeVisible();
|
||||
await expect(actionSheet).toBeVisible();
|
||||
});
|
||||
});
|
||||
test.describe('action sheet: focus trap', () => {
|
||||
@ -133,14 +133,14 @@ class ActionSheetFixture {
|
||||
await this.page.locator(selector).click();
|
||||
await ionActionSheetDidPresent.next();
|
||||
this.actionSheet = this.page.locator('ion-action-sheet');
|
||||
expect(this.actionSheet).toBeVisible();
|
||||
await expect(this.actionSheet).toBeVisible();
|
||||
}
|
||||
|
||||
async dismiss() {
|
||||
const ionActionSheetDidDismiss = await this.page.spyOnEvent('ionActionSheetDidDismiss');
|
||||
await this.actionSheet.evaluate((el: HTMLIonActionSheetElement) => el.dismiss());
|
||||
await ionActionSheetDidDismiss.next();
|
||||
expect(this.actionSheet).not.toBeVisible();
|
||||
await expect(this.actionSheet).not.toBeVisible();
|
||||
}
|
||||
|
||||
async screenshot(modifier: string) {
|
||||
|
@ -51,7 +51,7 @@ test.describe('alert: basic', () => {
|
||||
await page.goto(`/src/components/alert/test/basic`);
|
||||
|
||||
const alert = await openAlert(page, 'basic');
|
||||
expect(alert).toHaveAttribute('data-testid', 'basic-alert');
|
||||
await expect(alert).toHaveAttribute('data-testid', 'basic-alert');
|
||||
});
|
||||
|
||||
test('should dismiss when async handler resolves', async ({ page, skip }) => {
|
||||
|
@ -14,21 +14,21 @@ test.describe('datetime-button: switching to correct view', () => {
|
||||
});
|
||||
test('should switch to a date-only view when the date button is clicked', async ({ page }) => {
|
||||
const datetime = page.locator('ion-datetime');
|
||||
expect(datetime).toHaveJSProperty('presentation', 'date-time');
|
||||
await expect(datetime).toHaveJSProperty('presentation', 'date-time');
|
||||
|
||||
await page.locator('#date-button').click();
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(datetime).toHaveJSProperty('presentation', 'date');
|
||||
await expect(datetime).toHaveJSProperty('presentation', 'date');
|
||||
});
|
||||
test('should switch to a time-only view when the time button is clicked', async ({ page }) => {
|
||||
const datetime = page.locator('ion-datetime');
|
||||
expect(datetime).toHaveJSProperty('presentation', 'date-time');
|
||||
await expect(datetime).toHaveJSProperty('presentation', 'date-time');
|
||||
|
||||
await page.locator('#time-button').click();
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(datetime).toHaveJSProperty('presentation', 'time');
|
||||
await expect(datetime).toHaveJSProperty('presentation', 'time');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -68,38 +68,38 @@ test.describe('datetime-button: popover', () => {
|
||||
|
||||
await ionPopoverDidPresent.next();
|
||||
|
||||
expect(datetime).toBeVisible();
|
||||
await expect(datetime).toBeVisible();
|
||||
});
|
||||
test('should open the time popover', async ({ page }) => {
|
||||
await page.locator('#time-button').click();
|
||||
|
||||
await ionPopoverDidPresent.next();
|
||||
|
||||
expect(datetime).toBeVisible();
|
||||
await expect(datetime).toBeVisible();
|
||||
});
|
||||
test('should open the date popover then the time popover', async ({ page }) => {
|
||||
await page.locator('#date-button').click();
|
||||
await ionPopoverDidPresent.next();
|
||||
expect(datetime).toBeVisible();
|
||||
await expect(datetime).toBeVisible();
|
||||
|
||||
await popover.evaluate((el: HTMLIonPopoverElement) => el.dismiss());
|
||||
await ionPopoverDidDismiss.next();
|
||||
|
||||
await page.locator('#time-button').click();
|
||||
await ionPopoverDidPresent.next();
|
||||
expect(datetime).toBeVisible();
|
||||
await expect(datetime).toBeVisible();
|
||||
});
|
||||
test('should open the time popover then the date popover', async ({ page }) => {
|
||||
await page.locator('#time-button').click();
|
||||
await ionPopoverDidPresent.next();
|
||||
expect(datetime).toBeVisible();
|
||||
await expect(datetime).toBeVisible();
|
||||
|
||||
await popover.evaluate((el: HTMLIonPopoverElement) => el.dismiss());
|
||||
await ionPopoverDidDismiss.next();
|
||||
|
||||
await page.locator('#date-button').click();
|
||||
await ionPopoverDidPresent.next();
|
||||
expect(datetime).toBeVisible();
|
||||
await expect(datetime).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@ -130,37 +130,37 @@ test.describe('datetime-button: modal', () => {
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
expect(datetime).toBeVisible();
|
||||
await expect(datetime).toBeVisible();
|
||||
});
|
||||
test('should open the time modal', async ({ page }) => {
|
||||
await page.locator('#time-button').click();
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
expect(datetime).toBeVisible();
|
||||
await expect(datetime).toBeVisible();
|
||||
});
|
||||
test('should open the date modal then the time modal', async ({ page }) => {
|
||||
await page.locator('#date-button').click();
|
||||
await ionModalDidPresent.next();
|
||||
expect(datetime).toBeVisible();
|
||||
await expect(datetime).toBeVisible();
|
||||
|
||||
await modal.evaluate((el: HTMLIonModalElement) => el.dismiss());
|
||||
await ionModalDidDismiss.next();
|
||||
|
||||
await page.locator('#time-button').click();
|
||||
await ionModalDidPresent.next();
|
||||
expect(datetime).toBeVisible();
|
||||
await expect(datetime).toBeVisible();
|
||||
});
|
||||
test('should open the time modal then the date modal', async ({ page }) => {
|
||||
await page.locator('#time-button').click();
|
||||
await ionModalDidPresent.next();
|
||||
expect(datetime).toBeVisible();
|
||||
await expect(datetime).toBeVisible();
|
||||
|
||||
await modal.evaluate((el: HTMLIonModalElement) => el.dismiss());
|
||||
await ionModalDidDismiss.next();
|
||||
|
||||
await page.locator('#date-button').click();
|
||||
await ionModalDidPresent.next();
|
||||
expect(datetime).toBeVisible();
|
||||
await expect(datetime).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
@ -300,10 +300,13 @@ test.describe('datetime: visibility', () => {
|
||||
|
||||
await datetime.evaluate((el: HTMLIonDatetimeElement) => el.style.setProperty('display', 'none'));
|
||||
await expect(datetime).toBeHidden();
|
||||
await expect(datetime).not.toHaveClass(/datetime-ready/);
|
||||
|
||||
await datetime.evaluate((el: HTMLIonDatetimeElement) => el.style.removeProperty('display'));
|
||||
await expect(datetime).toBeVisible();
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
// month/year interface should be reset
|
||||
await expect(monthYearInterface).toBeHidden();
|
||||
});
|
||||
|
@ -214,8 +214,8 @@ test.describe('datetime: prefer wheel', () => {
|
||||
const monthValues = page.locator('.month-column .picker-item:not(.picker-item-empty)');
|
||||
const dayValues = page.locator('.day-column .picker-item:not(.picker-item-empty)');
|
||||
|
||||
expect(monthValues).toHaveText(['1月', '2月', '3月']);
|
||||
expect(dayValues).toHaveText(['1日', '2日', '3日']);
|
||||
await expect(monthValues).toHaveText(['1月', '2月', '3月']);
|
||||
await expect(dayValues).toHaveText(['1日', '2日', '3日']);
|
||||
});
|
||||
test('should render the columns according to locale - en-US', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
@ -334,7 +334,7 @@ test.describe('datetime: prefer wheel', () => {
|
||||
|
||||
const dateValues = page.locator('.date-column .picker-item:not(.picker-item-empty)');
|
||||
|
||||
expect(dateValues).toHaveText(['2月1日(火)', '2月2日(水)', '2月3日(木)']);
|
||||
await expect(dateValues).toHaveText(['2月1日(火)', '2月2日(水)', '2月3日(木)']);
|
||||
});
|
||||
test('should respect min and max bounds even across years', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
@ -495,7 +495,7 @@ test.describe('datetime: prefer wheel', () => {
|
||||
|
||||
const dateValues = page.locator('.date-column .picker-item:not(.picker-item-empty)');
|
||||
|
||||
expect(dateValues).toHaveText(['2月1日(火)', '2月2日(水)', '2月3日(木)']);
|
||||
await expect(dateValues).toHaveText(['2月1日(火)', '2月2日(水)', '2月3日(木)']);
|
||||
});
|
||||
test('should respect min and max bounds even across years', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
|
@ -159,9 +159,10 @@ class TimePickerFixture {
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto(`/src/components/datetime/test/presentation`);
|
||||
await this.page.locator('select').selectOption('time');
|
||||
await this.page.waitForSelector('.datetime-presentation-time');
|
||||
await this.page.setContent(`
|
||||
<ion-datetime presentation="time" value="2022-03-10T13:00:00"></ion-datetime>
|
||||
`);
|
||||
await this.page.waitForSelector('.datetime-ready');
|
||||
this.timePicker = this.page.locator('ion-datetime');
|
||||
}
|
||||
|
||||
|
@ -47,28 +47,28 @@ test.describe('fab: basic (functionality checks)', () => {
|
||||
const fab = page.locator('#fab1');
|
||||
const fabList = fab.locator('ion-fab-list');
|
||||
|
||||
expect(fabList).not.toHaveClass(/fab-list-active/);
|
||||
await expect(fabList).not.toHaveClass(/fab-list-active/);
|
||||
|
||||
// open fab
|
||||
await fab.click();
|
||||
await page.waitForChanges();
|
||||
expect(fabList).toHaveClass(/fab-list-active/);
|
||||
await expect(fabList).toHaveClass(/fab-list-active/);
|
||||
|
||||
// close fab
|
||||
await fab.click();
|
||||
await page.waitForChanges();
|
||||
expect(fabList).not.toHaveClass(/fab-list-active/);
|
||||
await expect(fabList).not.toHaveClass(/fab-list-active/);
|
||||
});
|
||||
|
||||
test('should not open when disabled', async ({ page }) => {
|
||||
const fab = page.locator('#fab2');
|
||||
const fabList = fab.locator('ion-fab-list');
|
||||
|
||||
expect(fabList).not.toHaveClass(/fab-list-active/);
|
||||
await expect(fabList).not.toHaveClass(/fab-list-active/);
|
||||
|
||||
// attempt to open fab
|
||||
await fab.click();
|
||||
await page.waitForChanges();
|
||||
expect(fabList).not.toHaveClass(/fab-list-active/);
|
||||
await expect(fabList).not.toHaveClass(/fab-list-active/);
|
||||
});
|
||||
});
|
||||
|
@ -40,7 +40,8 @@ test.describe('item-sliding: basic', () => {
|
||||
expect(await item.screenshot()).toMatchSnapshot(`item-sliding-gesture-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
|
||||
test('should not scroll when the item-sliding is swiped', async ({ page, skip }) => {
|
||||
// TODO FW-3006
|
||||
test.skip('should not scroll when the item-sliding is swiped', async ({ page, skip }) => {
|
||||
skip.browser('webkit', 'mouse.wheel is not available in WebKit');
|
||||
skip.rtl();
|
||||
|
||||
|
@ -2,7 +2,8 @@ import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('item-sliding: scroll-target', () => {
|
||||
test('should not scroll when the item-sliding is swiped in custom scroll target', async ({ page, skip }) => {
|
||||
// TODO FW-3006
|
||||
test.skip('should not scroll when the item-sliding is swiped in custom scroll target', async ({ page, skip }) => {
|
||||
skip.browser('webkit', 'mouse.wheel is not available in WebKit');
|
||||
skip.rtl();
|
||||
|
||||
|
@ -2,14 +2,25 @@ import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('list: inset', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/src/components/list/test/inset');
|
||||
});
|
||||
test.describe('list: visual regression tests', () => {
|
||||
test.describe('list: rendering', () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.setIonViewport();
|
||||
await page.setContent(`
|
||||
<ion-content color="primary">
|
||||
<div class="wrapper" style="display: flex">
|
||||
<ion-list inset="true" style="width: 100%">
|
||||
<ion-item>Pokémon Yellow</ion-item>
|
||||
<ion-item>Super Metroid</ion-item>
|
||||
<ion-item>Mega Man X</ion-item>
|
||||
<ion-item>The Legend of Zelda</ion-item>
|
||||
<ion-item lines="full">Halo</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
</ion-content>
|
||||
`);
|
||||
|
||||
expect(await page.screenshot()).toMatchSnapshot(`list-inset-diff-${page.getSnapshotSettings()}.png`);
|
||||
const listWrapper = page.locator('.wrapper');
|
||||
|
||||
expect(await listWrapper.screenshot()).toMatchSnapshot(`list-inset-diff-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 30 KiB |
@ -1,6 +1,5 @@
|
||||
import { GESTURE } from '@utils/overlays';
|
||||
|
||||
import type { Animation } from '../../../interface';
|
||||
import { GESTURE } from '../../../utils/overlays';
|
||||
|
||||
export const handleCanDismiss = async (el: HTMLIonModalElement, animation: Animation) => {
|
||||
/**
|
||||
|
@ -60,7 +60,7 @@ test.describe('picker-column-internal: disabled', () => {
|
||||
`);
|
||||
|
||||
const disabledItem = page.locator('ion-picker-column-internal .picker-item.picker-item-disabled');
|
||||
expect(disabledItem).not.toBeEnabled();
|
||||
await expect(disabledItem).not.toBeEnabled();
|
||||
});
|
||||
test('disabled picker item should not be considered active', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
@ -79,7 +79,7 @@ test.describe('picker-column-internal: disabled', () => {
|
||||
`);
|
||||
|
||||
const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]');
|
||||
expect(disabledItem).not.toHaveClass(/picker-item-active/);
|
||||
await expect(disabledItem).not.toHaveClass(/picker-item-active/);
|
||||
});
|
||||
test('setting the value to a disabled item should not cause that item to be active', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
@ -103,8 +103,8 @@ test.describe('picker-column-internal: disabled', () => {
|
||||
await page.waitForChanges();
|
||||
|
||||
const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]');
|
||||
expect(disabledItem).toHaveClass(/picker-item-disabled/);
|
||||
expect(disabledItem).not.toHaveClass(/picker-item-active/);
|
||||
await expect(disabledItem).toHaveClass(/picker-item-disabled/);
|
||||
await expect(disabledItem).not.toHaveClass(/picker-item-active/);
|
||||
});
|
||||
test('defaulting the value to a disabled item should not cause that item to be active', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
@ -124,7 +124,7 @@ test.describe('picker-column-internal: disabled', () => {
|
||||
`);
|
||||
|
||||
const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]');
|
||||
expect(disabledItem).toHaveClass(/picker-item-disabled/);
|
||||
expect(disabledItem).not.toHaveClass(/picker-item-active/);
|
||||
await expect(disabledItem).toHaveClass(/picker-item-disabled/);
|
||||
await expect(disabledItem).not.toHaveClass(/picker-item-active/);
|
||||
});
|
||||
});
|
||||
|
@ -47,13 +47,13 @@ test.describe('picker-internal', () => {
|
||||
|
||||
// Focus first column
|
||||
await page.keyboard.press('Tab');
|
||||
expect(firstColumn).toBeFocused();
|
||||
await expect(firstColumn).toBeFocused();
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
// Focus second column
|
||||
await page.keyboard.press('Tab');
|
||||
expect(secondColumn).toBeFocused();
|
||||
await expect(secondColumn).toBeFocused();
|
||||
});
|
||||
|
||||
test('tabbing should correctly move focus back', async ({ page }) => {
|
||||
@ -61,13 +61,13 @@ test.describe('picker-internal', () => {
|
||||
const secondColumn = page.locator('ion-picker-column-internal#second');
|
||||
|
||||
await secondColumn.focus();
|
||||
expect(secondColumn).toBeFocused();
|
||||
await expect(secondColumn).toBeFocused();
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
// Focus first column
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
expect(firstColumn).toBeFocused();
|
||||
await expect(firstColumn).toBeFocused();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -24,7 +24,7 @@ test.describe('radio-group', () => {
|
||||
expect(radio).toBeHidden();
|
||||
|
||||
// ensure radio group has the same value
|
||||
expect(radioGroup).toHaveJSProperty('value', 'two');
|
||||
await expect(radioGroup).toHaveJSProperty('value', 'two');
|
||||
|
||||
// clear the search so the radio appears
|
||||
await page.fill('ion-searchbar input', '');
|
||||
|
@ -12,6 +12,7 @@ test.describe('range: basic', () => {
|
||||
|
||||
/**
|
||||
* The mouse events are flaky on CI
|
||||
* TODO FW-2873
|
||||
*/
|
||||
test.fixme('should emit start/end events', async ({ page }, testInfo) => {
|
||||
await page.setContent(`<ion-range value="20"></ion-range>`);
|
||||
@ -57,7 +58,8 @@ test.describe('range: basic', () => {
|
||||
expect(rangeEnd).toHaveReceivedEventDetail({ value: 21 });
|
||||
});
|
||||
|
||||
test('should not scroll when the knob is swiped', async ({ page, skip }) => {
|
||||
// TODO FW-2873
|
||||
test.skip('should not scroll when the knob is swiped', async ({ page, skip }) => {
|
||||
skip.browser('webkit', 'mouse.wheel is not available in WebKit');
|
||||
skip.rtl();
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('range: scroll-target', () => {
|
||||
// TODO FW-2873
|
||||
test.describe.skip('range: scroll-target', () => {
|
||||
test('should not scroll when the knob is swiped in custom scroll target', async ({ page, skip }) => {
|
||||
skip.browser('webkit', 'mouse.wheel is not available in WebKit');
|
||||
skip.rtl();
|
||||
|
@ -1,28 +1,11 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('searchbar: basic', () => {
|
||||
test.describe('searchbar: cancel button', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/src/components/searchbar/test/basic`);
|
||||
});
|
||||
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.setIonViewport();
|
||||
/**
|
||||
* The searchbar test template is rendering an ion-searchbar
|
||||
* that is animated. This requires the searchbar to render,
|
||||
* before the cancel button can be positioned correctly.
|
||||
*
|
||||
* We wait for changes to ensure the searchbar has rendered
|
||||
* correctly before capturing the screenshot.
|
||||
*/
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(await page.screenshot({ animations: 'disabled' })).toMatchSnapshot(
|
||||
`searchbar-diff-${page.getSnapshotSettings()}.png`
|
||||
);
|
||||
});
|
||||
|
||||
test('should show cancel button on focus if show-cancel-button=focus', async ({ page }) => {
|
||||
const searchbar = page.locator('#basic');
|
||||
const cancelButton = searchbar.locator('.searchbar-cancel-button');
|
||||
@ -89,3 +72,76 @@ test.describe('searchbar: clear button', () => {
|
||||
await expect(nativeInput).toBeFocused();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('searchbar: rendering', () => {
|
||||
test('should render searchbar', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-searchbar></ion-searchbar>
|
||||
`);
|
||||
|
||||
const searchbar = page.locator('ion-searchbar');
|
||||
|
||||
expect(await searchbar.screenshot()).toMatchSnapshot(`searchbar-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
|
||||
test('should render cancel and clear buttons', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-searchbar show-cancel-button="always" show-clear-button="always"></ion-searchbar>
|
||||
`);
|
||||
|
||||
const searchbar = page.locator('ion-searchbar');
|
||||
|
||||
expect(await searchbar.screenshot()).toMatchSnapshot(`searchbar-buttons-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
|
||||
test('should render searchbar with color', async ({ page, skip }) => {
|
||||
skip.rtl();
|
||||
|
||||
await page.setContent(`
|
||||
<ion-searchbar color="danger" show-cancel-button="always" show-clear-button="always"></ion-searchbar>
|
||||
`);
|
||||
|
||||
const searchbar = page.locator('ion-searchbar');
|
||||
|
||||
expect(await searchbar.screenshot()).toMatchSnapshot(`searchbar-color-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
|
||||
test('should render disabled searchbar', async ({ page, skip }) => {
|
||||
skip.rtl();
|
||||
|
||||
await page.setContent(`
|
||||
<ion-searchbar disabled="true"></ion-searchbar>
|
||||
`);
|
||||
|
||||
const searchbar = page.locator('ion-searchbar');
|
||||
|
||||
expect(await searchbar.screenshot()).toMatchSnapshot(`searchbar-disabled-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
|
||||
test('should render custom search icon', async ({ page, skip }) => {
|
||||
skip.rtl();
|
||||
|
||||
await page.setContent(`
|
||||
<ion-searchbar search-icon="home"></ion-searchbar>
|
||||
`);
|
||||
|
||||
const icon = page.locator('ion-searchbar ion-icon.searchbar-search-icon');
|
||||
|
||||
expect(await icon.screenshot()).toMatchSnapshot(`searchbar-search-icon-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('searchbar: placeholder', () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
skip.mode('ios');
|
||||
});
|
||||
test('should set placeholder', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-searchbar placeholder="My Placeholder"></ion-searchbar>
|
||||
`);
|
||||
|
||||
const nativeInput = page.locator('ion-searchbar input');
|
||||
await expect(nativeInput).toHaveAttribute('placeholder', 'My Placeholder');
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 365 KiB |
Before Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 362 KiB |
Before Width: | Height: | Size: 362 KiB |
Before Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 360 KiB |
Before Width: | Height: | Size: 335 KiB |
Before Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 329 KiB |
Before Width: | Height: | Size: 333 KiB |
Before Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 328 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 645 B |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 553 B |
After Width: | Height: | Size: 1.9 KiB |
@ -11,30 +11,20 @@ test.describe('textarea: autogrow', () => {
|
||||
});
|
||||
|
||||
test('should grow when typing', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-app>
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-textarea auto-grow="true"></ion-textarea>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ion-app>`
|
||||
await page.setContent(`
|
||||
<ion-textarea auto-grow="true"></ion-textarea>
|
||||
`);
|
||||
|
||||
const ionTextarea = page.locator('ion-textarea');
|
||||
const nativeTextarea = ionTextarea.locator('textarea');
|
||||
|
||||
await nativeTextarea.type('Now, this is a story all about how');
|
||||
|
||||
expect(await ionTextarea.screenshot({})).toMatchSnapshot(
|
||||
`textarea-autogrow-initial-${page.getSnapshotSettings()}.png`
|
||||
);
|
||||
|
||||
const textarea = await page.waitForSelector('ion-textarea');
|
||||
|
||||
await textarea.click();
|
||||
|
||||
await textarea.type('Now, this is a story all about how');
|
||||
|
||||
await page.setIonViewport();
|
||||
|
||||
expect(await textarea.screenshot()).toMatchSnapshot(`textarea-autogrow-initial-${page.getSnapshotSettings()}.png`);
|
||||
|
||||
await textarea.type(
|
||||
await nativeTextarea.type(
|
||||
[
|
||||
`\nMy life got flipped-turned upside down`,
|
||||
`And I'd like to take a minute`,
|
||||
@ -43,7 +33,7 @@ test.describe('textarea: autogrow', () => {
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
expect(await textarea.screenshot()).toMatchSnapshot(`textarea-autogrow-after-${page.getSnapshotSettings()}.png`);
|
||||
expect(await ionTextarea.screenshot()).toMatchSnapshot(`textarea-autogrow-after-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
|
||||
test('should break long lines without white space', async ({ page }) => {
|
||||
@ -53,17 +43,13 @@ test.describe('textarea: autogrow', () => {
|
||||
});
|
||||
|
||||
await page.setContent(
|
||||
`<ion-app>
|
||||
<ion-content>
|
||||
<ion-textarea
|
||||
auto-grow="true"
|
||||
value="abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz">
|
||||
</ion-textarea>
|
||||
</ion-content>
|
||||
</ion-app>`
|
||||
`<ion-textarea
|
||||
auto-grow="true"
|
||||
value="abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz">
|
||||
</ion-textarea>`
|
||||
);
|
||||
|
||||
const textarea = await page.locator('ion-textarea');
|
||||
const textarea = page.locator('ion-textarea');
|
||||
|
||||
expect(await textarea.screenshot()).toMatchSnapshot(
|
||||
`textarea-autogrow-word-break-${page.getSnapshotSettings()}.png`
|
||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.2 KiB |
@ -1,6 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('tap click utility', () => {
|
||||
// TODO FW-3010
|
||||
test.describe.skip('tap click utility', () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
skip.mode('ios');
|
||||
});
|
||||
test('it should apply activated class when clicking element', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-app>
|
||||
@ -14,8 +20,9 @@ test.describe('tap click utility', () => {
|
||||
if (box) {
|
||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
await page.mouse.down();
|
||||
await page.waitForChanges();
|
||||
}
|
||||
|
||||
await page.waitForSelector('button.ion-activated');
|
||||
await expect(button).toHaveClass(/ion-activated/);
|
||||
});
|
||||
});
|
||||
|
@ -108,29 +108,6 @@ test.describe('overlays: focus', () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
});
|
||||
test('should not focus the overlay container if element inside of overlay is focused', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-button id="open-modal">Show Modal</ion-button>
|
||||
<ion-modal trigger="open-modal">
|
||||
<ion-content>
|
||||
<ion-input autofocus="true"></ion-input>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
`);
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const button = page.locator('ion-button');
|
||||
const input = page.locator('ion-input');
|
||||
|
||||
await button.click();
|
||||
await input.evaluate((el: HTMLIonInputElement) => el.setFocus());
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
await page.waitForChanges();
|
||||
|
||||
await expect(page.locator('ion-input input')).toBeFocused();
|
||||
});
|
||||
|
||||
test('should not select a hidden focusable element', async ({ page, browserName }) => {
|
||||
await page.setContent(`
|
||||
<style>
|
||||
|