test(menu): migrate to generators (#27330)
Issue number: N/A --------- <!-- Please refer to our contributing documentation for any questions on submitting a pull request, or let us know here if you need any help: https://ionicframework.com/docs/building/contributing --> <!-- Some docs updates need to be made in the `ionic-docs` repo, in a separate PR. See https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#modifying-documentation for details. --> <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Menu sliding tests use legacy syntax ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - Menu sliding tests use modern syntax ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->
@ -1,17 +0,0 @@
|
|||||||
import AxeBuilder from '@axe-core/playwright';
|
|
||||||
import { expect } from '@playwright/test';
|
|
||||||
import { test } from '@utils/test/playwright';
|
|
||||||
|
|
||||||
test.describe('menu-button: a11y', () => {
|
|
||||||
test.beforeEach(({ skip }) => {
|
|
||||||
skip.rtl();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not have accessibility violations', async ({ page }) => {
|
|
||||||
await page.goto('/src/components/menu-button/test/a11y');
|
|
||||||
|
|
||||||
const results = await new AxeBuilder({ page }).analyze();
|
|
||||||
|
|
||||||
expect(results.violations).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
15
core/src/components/menu-button/test/a11y/menu-button.e2e.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import AxeBuilder from '@axe-core/playwright';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { configs, test } from '@utils/test/playwright';
|
||||||
|
|
||||||
|
configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
|
test.describe(title('menu-button: a11y'), () => {
|
||||||
|
test('should not have accessibility violations', async ({ page }) => {
|
||||||
|
await page.goto('/src/components/menu-button/test/a11y', config);
|
||||||
|
|
||||||
|
const results = await new AxeBuilder({ page }).analyze();
|
||||||
|
|
||||||
|
expect(results.violations).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import { expect } from '@playwright/test';
|
|
||||||
import { test } from '@utils/test/playwright';
|
|
||||||
|
|
||||||
test.describe('menu-button: basic', () => {
|
|
||||||
test.beforeEach(({ skip }) => {
|
|
||||||
skip.rtl();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not have visual regressions', async ({ page }) => {
|
|
||||||
await page.goto(`/src/components/menu-button/test/basic`);
|
|
||||||
|
|
||||||
await page.setIonViewport();
|
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot(`menu-button-diff-${page.getSnapshotSettings()}.png`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { configs, test } from '@utils/test/playwright';
|
||||||
|
|
||||||
|
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||||
|
test.describe(title('menu-button: basic'), () => {
|
||||||
|
test('should not have visual regressions', async ({ page }) => {
|
||||||
|
await page.goto(`/src/components/menu-button/test/basic`, config);
|
||||||
|
|
||||||
|
await page.setIonViewport();
|
||||||
|
|
||||||
|
await expect(page).toHaveScreenshot(screenshot(`menu-button-diff`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@ -1,54 +0,0 @@
|
|||||||
import type { Locator } from '@playwright/test';
|
|
||||||
import { expect } from '@playwright/test';
|
|
||||||
import type { E2EPage } from '@utils/test/playwright';
|
|
||||||
import { test } from '@utils/test/playwright';
|
|
||||||
|
|
||||||
test.describe('menu-toggle: basic', () => {
|
|
||||||
test.beforeEach(async ({ page, skip }) => {
|
|
||||||
skip.rtl();
|
|
||||||
skip.mode('ios');
|
|
||||||
await page.goto(`/src/components/menu-toggle/test/basic`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should open selected menu by side', async ({ page }) => {
|
|
||||||
const startMenu = page.locator('[menu-id="start-menu"]');
|
|
||||||
const endMenu = page.locator('[menu-id="end-menu"]');
|
|
||||||
const menuToggle = page.locator('ion-menu-toggle');
|
|
||||||
|
|
||||||
// do this outside testMenu since passing params to eval callback is tricky due to execution context
|
|
||||||
await menuToggle.evaluate((el: HTMLIonMenuToggleElement) => (el.menu = 'start'));
|
|
||||||
await testMenu(page, startMenu);
|
|
||||||
|
|
||||||
await menuToggle.evaluate((el: HTMLIonMenuToggleElement) => (el.menu = 'end'));
|
|
||||||
await testMenu(page, endMenu);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should open selected menu by menu-id', async ({ page }) => {
|
|
||||||
const startMenu = page.locator('[menu-id="start-menu"]');
|
|
||||||
const endMenu = page.locator('[menu-id="end-menu"]');
|
|
||||||
const menuToggle = page.locator('ion-menu-toggle');
|
|
||||||
|
|
||||||
// do this outside testMenu since passing params to eval callback is tricky due to execution context
|
|
||||||
await menuToggle.evaluate((el: HTMLIonMenuToggleElement) => (el.menu = 'start-menu'));
|
|
||||||
await testMenu(page, startMenu);
|
|
||||||
|
|
||||||
await menuToggle.evaluate((el: HTMLIonMenuToggleElement) => (el.menu = 'end-menu'));
|
|
||||||
await testMenu(page, endMenu);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
async function testMenu(page: E2EPage, menu: Locator) {
|
|
||||||
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
|
||||||
const ionDidClose = await page.spyOnEvent('ionDidClose');
|
|
||||||
|
|
||||||
await page.click('ion-menu-toggle');
|
|
||||||
await ionDidOpen.next();
|
|
||||||
|
|
||||||
await expect(menu).toHaveClass(/show-menu/);
|
|
||||||
|
|
||||||
await menu.evaluate(async (el: HTMLIonMenuElement) => {
|
|
||||||
await el.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
await ionDidClose.next();
|
|
||||||
}
|
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import type { Locator } from '@playwright/test';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import type { E2EPage } from '@utils/test/playwright';
|
||||||
|
import { configs, test } from '@utils/test/playwright';
|
||||||
|
|
||||||
|
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
|
test.describe(title('menu-toggle: basic'), () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto(`/src/components/menu-toggle/test/basic`, config);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should open selected menu by side', async ({ page }) => {
|
||||||
|
const startMenu = page.locator('[menu-id="start-menu"]');
|
||||||
|
const endMenu = page.locator('[menu-id="end-menu"]');
|
||||||
|
const menuToggle = page.locator('ion-menu-toggle');
|
||||||
|
|
||||||
|
// do this outside testMenu since passing params to eval callback is tricky due to execution context
|
||||||
|
await menuToggle.evaluate((el: HTMLIonMenuToggleElement) => (el.menu = 'start'));
|
||||||
|
await testMenu(page, startMenu);
|
||||||
|
|
||||||
|
await menuToggle.evaluate((el: HTMLIonMenuToggleElement) => (el.menu = 'end'));
|
||||||
|
await testMenu(page, endMenu);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should open selected menu by menu-id', async ({ page }) => {
|
||||||
|
const startMenu = page.locator('[menu-id="start-menu"]');
|
||||||
|
const endMenu = page.locator('[menu-id="end-menu"]');
|
||||||
|
const menuToggle = page.locator('ion-menu-toggle');
|
||||||
|
|
||||||
|
// do this outside testMenu since passing params to eval callback is tricky due to execution context
|
||||||
|
await menuToggle.evaluate((el: HTMLIonMenuToggleElement) => (el.menu = 'start-menu'));
|
||||||
|
await testMenu(page, startMenu);
|
||||||
|
|
||||||
|
await menuToggle.evaluate((el: HTMLIonMenuToggleElement) => (el.menu = 'end-menu'));
|
||||||
|
await testMenu(page, endMenu);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function testMenu(page: E2EPage, menu: Locator) {
|
||||||
|
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
||||||
|
const ionDidClose = await page.spyOnEvent('ionDidClose');
|
||||||
|
|
||||||
|
await page.click('ion-menu-toggle');
|
||||||
|
await ionDidOpen.next();
|
||||||
|
|
||||||
|
await expect(menu).toHaveClass(/show-menu/);
|
||||||
|
|
||||||
|
await menu.evaluate(async (el: HTMLIonMenuElement) => {
|
||||||
|
await el.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
await ionDidClose.next();
|
||||||
|
}
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import AxeBuilder from '@axe-core/playwright';
|
|
||||||
import { expect } from '@playwright/test';
|
|
||||||
import { test } from '@utils/test/playwright';
|
|
||||||
|
|
||||||
test.describe('menu: a11y', () => {
|
|
||||||
test.beforeEach(async ({ skip }) => {
|
|
||||||
skip.rtl();
|
|
||||||
skip.mode('md');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('menu should not have accessibility violations', async ({ page }) => {
|
|
||||||
await page.goto(`/src/components/menu/test/a11y`);
|
|
||||||
|
|
||||||
const menu = page.locator('ion-menu');
|
|
||||||
const button = page.locator('#open-menu');
|
|
||||||
|
|
||||||
await button.click();
|
|
||||||
await expect(menu).toBeVisible();
|
|
||||||
|
|
||||||
await expect(menu).toHaveAttribute('role', 'navigation');
|
|
||||||
|
|
||||||
const heading = page.locator('ion-menu h1');
|
|
||||||
await expect(heading).toHaveText('Open Menu');
|
|
||||||
|
|
||||||
const results = await new AxeBuilder({ page }).analyze();
|
|
||||||
expect(results.violations).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
25
core/src/components/menu/test/a11y/menu.e2e.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import AxeBuilder from '@axe-core/playwright';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { configs, test } from '@utils/test/playwright';
|
||||||
|
|
||||||
|
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
|
test.describe(title('menu: a11y'), () => {
|
||||||
|
test('menu should not have accessibility violations', async ({ page }) => {
|
||||||
|
await page.goto(`/src/components/menu/test/a11y`, config);
|
||||||
|
|
||||||
|
const menu = page.locator('ion-menu');
|
||||||
|
const button = page.locator('#open-menu');
|
||||||
|
|
||||||
|
await button.click();
|
||||||
|
await expect(menu).toBeVisible();
|
||||||
|
|
||||||
|
await expect(menu).toHaveAttribute('role', 'navigation');
|
||||||
|
|
||||||
|
const heading = page.locator('ion-menu h1');
|
||||||
|
await expect(heading).toHaveText('Open Menu');
|
||||||
|
|
||||||
|
const results = await new AxeBuilder({ page }).analyze();
|
||||||
|
expect(results.violations).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,145 +0,0 @@
|
|||||||
import type { Locator } from '@playwright/test';
|
|
||||||
import { expect } from '@playwright/test';
|
|
||||||
import type { E2EPage } from '@utils/test/playwright';
|
|
||||||
import { test } from '@utils/test/playwright';
|
|
||||||
|
|
||||||
test.describe('menu: basic', () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await page.goto(`/src/components/menu/test/basic`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should open selected menu by side', async ({ page }) => {
|
|
||||||
const startMenu = page.locator('[menu-id="start-menu"]');
|
|
||||||
const customMenu = page.locator('[menu-id="custom-menu"]');
|
|
||||||
const endMenu = page.locator('[menu-id="end-menu"]');
|
|
||||||
|
|
||||||
await testMenu(page, startMenu, 'start');
|
|
||||||
await testMenu(page, customMenu, 'custom');
|
|
||||||
await testMenu(page, endMenu, 'end');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should trap focus', async ({ page, skip, browserName }) => {
|
|
||||||
skip.rtl('Trapping focus is not dependent on document direction');
|
|
||||||
skip.browser('firefox', 'Firefox incorrectly allows keyboard focus to move to ion-content');
|
|
||||||
// TODO (FW-2979)
|
|
||||||
skip.browser('webkit', 'Safari 16 only allows text fields and pop-up menus to be focused.');
|
|
||||||
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
|
||||||
|
|
||||||
await page.click('#open-start');
|
|
||||||
await ionDidOpen.next();
|
|
||||||
|
|
||||||
const button = page.locator('#start-menu-button');
|
|
||||||
|
|
||||||
if (browserName === 'webkit') {
|
|
||||||
await page.keyboard.down('Alt');
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.keyboard.press('Tab');
|
|
||||||
|
|
||||||
await expect(button).toBeFocused();
|
|
||||||
|
|
||||||
await page.keyboard.press('Tab');
|
|
||||||
|
|
||||||
if (browserName === 'webkit') {
|
|
||||||
await page.keyboard.up('Alt');
|
|
||||||
}
|
|
||||||
|
|
||||||
await expect(button).toBeFocused();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should preserve scroll position', async ({ page, skip }) => {
|
|
||||||
skip.rtl('Scroll position is not dependent on document direction');
|
|
||||||
skip.browser('firefox', 'Firefox does not preserve scroll position');
|
|
||||||
|
|
||||||
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
|
||||||
|
|
||||||
await page.click('#open-start');
|
|
||||||
await ionDidOpen.next();
|
|
||||||
|
|
||||||
await page.locator('#start-menu ion-content').evaluate(async (el: HTMLIonContentElement) => {
|
|
||||||
await el.scrollToPoint(0, 200);
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.locator('#start-menu').evaluate(async (el: HTMLIonMenuElement) => {
|
|
||||||
await el.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.click('#open-start');
|
|
||||||
await ionDidOpen.next();
|
|
||||||
|
|
||||||
const scrollTop = await page.locator('#start-menu ion-content').evaluate(async (el: HTMLIonContentElement) => {
|
|
||||||
const contentScrollEl = await el.getScrollElement();
|
|
||||||
return contentScrollEl.scrollTop;
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(scrollTop).toBe(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render on the correct side when side is changed dynamically', async ({ page, skip }) => {
|
|
||||||
test.info().annotations.push({
|
|
||||||
type: 'issue',
|
|
||||||
description: 'https://github.com/ionic-team/ionic-framework/issues/25601',
|
|
||||||
});
|
|
||||||
|
|
||||||
skip.mode('ios', 'Dynamic side changes are not mode dependent');
|
|
||||||
|
|
||||||
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
|
||||||
const ionDidClose = await page.spyOnEvent('ionDidClose');
|
|
||||||
|
|
||||||
await page.locator('[menu-id="start-menu"]').evaluate(async (el: HTMLIonMenuElement) => {
|
|
||||||
el.side = 'end';
|
|
||||||
});
|
|
||||||
await page.click('#open-start');
|
|
||||||
await ionDidOpen.next();
|
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot(`menu-basic-side-toggled-${page.getSnapshotSettings()}.png`);
|
|
||||||
|
|
||||||
await page.locator('[menu-id="start-menu"]').evaluate(async (el: HTMLIonMenuElement) => {
|
|
||||||
await el.close();
|
|
||||||
});
|
|
||||||
await ionDidClose.next();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render on the correct side when document direction is changed dynamically', async ({ page, skip }) => {
|
|
||||||
test.info().annotations.push({
|
|
||||||
type: 'issue',
|
|
||||||
description: 'https://github.com/ionic-team/ionic-framework/issues/25601',
|
|
||||||
});
|
|
||||||
|
|
||||||
skip.rtl('Document direction is not dependent on initial load');
|
|
||||||
skip.mode('ios', 'Dynamic side changes are not mode dependent');
|
|
||||||
|
|
||||||
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
|
||||||
const ionDidClose = await page.spyOnEvent('ionDidClose');
|
|
||||||
|
|
||||||
await page.evaluate(() => {
|
|
||||||
document.dir = 'rtl';
|
|
||||||
});
|
|
||||||
await page.click('#open-start');
|
|
||||||
await ionDidOpen.next();
|
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot(`menu-basic-doc-dir-toggled-${page.getSnapshotSettings()}.png`);
|
|
||||||
|
|
||||||
await page.locator('[menu-id="start-menu"]').evaluate(async (el: HTMLIonMenuElement) => {
|
|
||||||
await el.close();
|
|
||||||
});
|
|
||||||
await ionDidClose.next();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
async function testMenu(page: E2EPage, menu: Locator, menuId: string) {
|
|
||||||
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
|
||||||
const ionDidClose = await page.spyOnEvent('ionDidClose');
|
|
||||||
|
|
||||||
await page.click(`#open-${menuId}`);
|
|
||||||
await ionDidOpen.next();
|
|
||||||
|
|
||||||
await expect(menu).toHaveClass(/show-menu/);
|
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot(`menu-basic-${menuId}-${page.getSnapshotSettings()}.png`);
|
|
||||||
|
|
||||||
await menu.evaluate(async (el: HTMLIonMenuElement) => {
|
|
||||||
await el.close();
|
|
||||||
});
|
|
||||||
await ionDidClose.next();
|
|
||||||
}
|
|
||||||
158
core/src/components/menu/test/basic/menu.e2e.ts
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import type { Locator } from '@playwright/test';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import type { E2EPage, ScreenshotFn } from '@utils/test/playwright';
|
||||||
|
import { configs, test } from '@utils/test/playwright';
|
||||||
|
|
||||||
|
configs().forEach(({ title, config, screenshot }) => {
|
||||||
|
test.describe(title('menu: rendering'), () => {
|
||||||
|
test('should open selected menu by side', async ({ page }) => {
|
||||||
|
await page.goto(`/src/components/menu/test/basic`, config);
|
||||||
|
const startMenu = page.locator('[menu-id="start-menu"]');
|
||||||
|
const customMenu = page.locator('[menu-id="custom-menu"]');
|
||||||
|
const endMenu = page.locator('[menu-id="end-menu"]');
|
||||||
|
|
||||||
|
await testMenu(page, startMenu, 'start', screenshot);
|
||||||
|
await testMenu(page, customMenu, 'custom', screenshot);
|
||||||
|
await testMenu(page, endMenu, 'end', screenshot);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This behavior does not vary across modes/directions
|
||||||
|
*/
|
||||||
|
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
|
test.describe(title('menu: functionality'), () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto(`/src/components/menu/test/basic`, config);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should trap focus', async ({ page, skip, browserName }) => {
|
||||||
|
skip.browser('firefox', 'Firefox incorrectly allows keyboard focus to move to ion-content');
|
||||||
|
// TODO (FW-2979)
|
||||||
|
skip.browser('webkit', 'Safari 16 only allows text fields and pop-up menus to be focused.');
|
||||||
|
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
||||||
|
|
||||||
|
await page.click('#open-start');
|
||||||
|
await ionDidOpen.next();
|
||||||
|
|
||||||
|
const button = page.locator('#start-menu-button');
|
||||||
|
|
||||||
|
if (browserName === 'webkit') {
|
||||||
|
await page.keyboard.down('Alt');
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
|
||||||
|
await expect(button).toBeFocused();
|
||||||
|
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
|
||||||
|
if (browserName === 'webkit') {
|
||||||
|
await page.keyboard.up('Alt');
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(button).toBeFocused();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should preserve scroll position', async ({ page, skip }) => {
|
||||||
|
skip.browser('firefox', 'Firefox does not preserve scroll position');
|
||||||
|
|
||||||
|
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
||||||
|
|
||||||
|
await page.click('#open-start');
|
||||||
|
await ionDidOpen.next();
|
||||||
|
|
||||||
|
await page.locator('#start-menu ion-content').evaluate(async (el: HTMLIonContentElement) => {
|
||||||
|
await el.scrollToPoint(0, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.locator('#start-menu').evaluate(async (el: HTMLIonMenuElement) => {
|
||||||
|
await el.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.click('#open-start');
|
||||||
|
await ionDidOpen.next();
|
||||||
|
|
||||||
|
const scrollTop = await page.locator('#start-menu ion-content').evaluate(async (el: HTMLIonContentElement) => {
|
||||||
|
const contentScrollEl = await el.getScrollElement();
|
||||||
|
return contentScrollEl.scrollTop;
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(scrollTop).toBe(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This behavior does not vary across modes/directions.
|
||||||
|
*/
|
||||||
|
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||||
|
test.describe(title('menu: reactive side'), () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto(`/src/components/menu/test/basic`, config);
|
||||||
|
});
|
||||||
|
test('should render on the correct side when side is changed dynamically', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/ionic-team/ionic-framework/issues/25601',
|
||||||
|
});
|
||||||
|
|
||||||
|
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
||||||
|
const ionDidClose = await page.spyOnEvent('ionDidClose');
|
||||||
|
|
||||||
|
await page.locator('[menu-id="start-menu"]').evaluate(async (el: HTMLIonMenuElement) => {
|
||||||
|
el.side = 'end';
|
||||||
|
});
|
||||||
|
await page.click('#open-start');
|
||||||
|
await ionDidOpen.next();
|
||||||
|
|
||||||
|
await expect(page).toHaveScreenshot(screenshot(`menu-basic-side-toggled`));
|
||||||
|
|
||||||
|
await page.locator('[menu-id="start-menu"]').evaluate(async (el: HTMLIonMenuElement) => {
|
||||||
|
await el.close();
|
||||||
|
});
|
||||||
|
await ionDidClose.next();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render on the correct side when document direction is changed dynamically', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/ionic-team/ionic-framework/issues/25601',
|
||||||
|
});
|
||||||
|
|
||||||
|
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
||||||
|
const ionDidClose = await page.spyOnEvent('ionDidClose');
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.dir = 'rtl';
|
||||||
|
});
|
||||||
|
await page.click('#open-start');
|
||||||
|
await ionDidOpen.next();
|
||||||
|
|
||||||
|
await expect(page).toHaveScreenshot(screenshot(`menu-basic-doc-dir-toggled`));
|
||||||
|
|
||||||
|
await page.locator('[menu-id="start-menu"]').evaluate(async (el: HTMLIonMenuElement) => {
|
||||||
|
await el.close();
|
||||||
|
});
|
||||||
|
await ionDidClose.next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function testMenu(page: E2EPage, menu: Locator, menuId: string, screenshot: ScreenshotFn) {
|
||||||
|
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
||||||
|
const ionDidClose = await page.spyOnEvent('ionDidClose');
|
||||||
|
|
||||||
|
await page.click(`#open-${menuId}`);
|
||||||
|
await ionDidOpen.next();
|
||||||
|
|
||||||
|
await expect(menu).toHaveClass(/show-menu/);
|
||||||
|
|
||||||
|
await expect(page).toHaveScreenshot(screenshot(`menu-basic-${menuId}`));
|
||||||
|
|
||||||
|
await menu.evaluate(async (el: HTMLIonMenuElement) => {
|
||||||
|
await el.close();
|
||||||
|
});
|
||||||
|
await ionDidClose.next();
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
@ -1,89 +0,0 @@
|
|||||||
import { expect } from '@playwright/test';
|
|
||||||
import { test } from '@utils/test/playwright';
|
|
||||||
|
|
||||||
test.describe('menu: focus trap', () => {
|
|
||||||
test.beforeEach(async ({ page, skip }) => {
|
|
||||||
await page.goto(`/src/components/menu/test/focus-trap`);
|
|
||||||
|
|
||||||
skip.rtl('Trapping focus is not dependent on document direction');
|
|
||||||
skip.browser('firefox', 'Firefox incorrectly allows keyboard focus to move to ion-content');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should trap focus with overlays', async ({ page, browserName }) => {
|
|
||||||
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
|
||||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
|
||||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
|
||||||
|
|
||||||
await page.click('#open-menu-button');
|
|
||||||
await ionDidOpen.next();
|
|
||||||
|
|
||||||
const menu = page.locator('#menu');
|
|
||||||
await expect(menu).toBeFocused();
|
|
||||||
|
|
||||||
const openModalButton = page.locator('#open-modal-button');
|
|
||||||
await openModalButton.click();
|
|
||||||
await ionModalDidPresent.next();
|
|
||||||
|
|
||||||
const modal = page.locator('#modal');
|
|
||||||
await expect(modal).toBeFocused();
|
|
||||||
|
|
||||||
await modal.evaluate(async (el: HTMLIonModalElement) => {
|
|
||||||
await el.dismiss();
|
|
||||||
});
|
|
||||||
await ionModalDidDismiss.next();
|
|
||||||
|
|
||||||
// Safari focuses the body after the modal is dismissed
|
|
||||||
if (browserName !== 'webkit') {
|
|
||||||
await expect(openModalButton).toBeFocused();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should trap focus with content inside overlays', async ({ page }) => {
|
|
||||||
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
|
||||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
|
||||||
|
|
||||||
await page.click('#open-menu-button');
|
|
||||||
await ionDidOpen.next();
|
|
||||||
|
|
||||||
const menu = page.locator('#menu');
|
|
||||||
await expect(menu).toBeFocused();
|
|
||||||
|
|
||||||
await page.click('#open-modal-button');
|
|
||||||
await ionModalDidPresent.next();
|
|
||||||
|
|
||||||
const modal = page.locator('#modal');
|
|
||||||
await expect(modal).toBeFocused();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should work with swipe gestures after modal is dismissed', async ({ page }) => {
|
|
||||||
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
|
||||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
|
||||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
|
||||||
|
|
||||||
await page.click('#open-menu-button');
|
|
||||||
await ionDidOpen.next();
|
|
||||||
|
|
||||||
const menu = page.locator('#menu');
|
|
||||||
await expect(menu).toBeFocused();
|
|
||||||
|
|
||||||
await page.click('#open-modal-button');
|
|
||||||
await ionModalDidPresent.next();
|
|
||||||
|
|
||||||
const modal = page.locator('#modal');
|
|
||||||
|
|
||||||
await modal.evaluate(async (el: HTMLIonModalElement) => {
|
|
||||||
await el.dismiss();
|
|
||||||
});
|
|
||||||
await ionModalDidDismiss.next();
|
|
||||||
|
|
||||||
await page.mouse.move(30, 168);
|
|
||||||
await page.mouse.down();
|
|
||||||
|
|
||||||
await page.mouse.move(384, 168);
|
|
||||||
await page.mouse.up();
|
|
||||||
|
|
||||||
await page.waitForChanges();
|
|
||||||
|
|
||||||
await expect(menu).toHaveClass(/show-menu/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
93
core/src/components/menu/test/focus-trap/menu.e2e.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { configs, test } from '@utils/test/playwright';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trapping focus is not dependent on mode/direction
|
||||||
|
*/
|
||||||
|
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
|
test.describe(title('menu: focus trap'), () => {
|
||||||
|
test.beforeEach(async ({ page, skip }) => {
|
||||||
|
await page.goto(`/src/components/menu/test/focus-trap`, config);
|
||||||
|
|
||||||
|
skip.browser('firefox', 'Firefox incorrectly allows keyboard focus to move to ion-content');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should trap focus with overlays', async ({ page, browserName }) => {
|
||||||
|
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
||||||
|
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||||
|
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||||
|
|
||||||
|
await page.click('#open-menu-button');
|
||||||
|
await ionDidOpen.next();
|
||||||
|
|
||||||
|
const menu = page.locator('#menu');
|
||||||
|
await expect(menu).toBeFocused();
|
||||||
|
|
||||||
|
const openModalButton = page.locator('#open-modal-button');
|
||||||
|
await openModalButton.click();
|
||||||
|
await ionModalDidPresent.next();
|
||||||
|
|
||||||
|
const modal = page.locator('#modal');
|
||||||
|
await expect(modal).toBeFocused();
|
||||||
|
|
||||||
|
await modal.evaluate(async (el: HTMLIonModalElement) => {
|
||||||
|
await el.dismiss();
|
||||||
|
});
|
||||||
|
await ionModalDidDismiss.next();
|
||||||
|
|
||||||
|
// Safari focuses the body after the modal is dismissed
|
||||||
|
if (browserName !== 'webkit') {
|
||||||
|
await expect(openModalButton).toBeFocused();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should trap focus with content inside overlays', async ({ page }) => {
|
||||||
|
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
||||||
|
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||||
|
|
||||||
|
await page.click('#open-menu-button');
|
||||||
|
await ionDidOpen.next();
|
||||||
|
|
||||||
|
const menu = page.locator('#menu');
|
||||||
|
await expect(menu).toBeFocused();
|
||||||
|
|
||||||
|
await page.click('#open-modal-button');
|
||||||
|
await ionModalDidPresent.next();
|
||||||
|
|
||||||
|
const modal = page.locator('#modal');
|
||||||
|
await expect(modal).toBeFocused();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should work with swipe gestures after modal is dismissed', async ({ page }) => {
|
||||||
|
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
||||||
|
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||||
|
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||||
|
|
||||||
|
await page.click('#open-menu-button');
|
||||||
|
await ionDidOpen.next();
|
||||||
|
|
||||||
|
const menu = page.locator('#menu');
|
||||||
|
await expect(menu).toBeFocused();
|
||||||
|
|
||||||
|
await page.click('#open-modal-button');
|
||||||
|
await ionModalDidPresent.next();
|
||||||
|
|
||||||
|
const modal = page.locator('#modal');
|
||||||
|
|
||||||
|
await modal.evaluate(async (el: HTMLIonModalElement) => {
|
||||||
|
await el.dismiss();
|
||||||
|
});
|
||||||
|
await ionModalDidDismiss.next();
|
||||||
|
|
||||||
|
await page.mouse.move(30, 168);
|
||||||
|
await page.mouse.down();
|
||||||
|
|
||||||
|
await page.mouse.move(384, 168);
|
||||||
|
await page.mouse.up();
|
||||||
|
|
||||||
|
await page.waitForChanges();
|
||||||
|
|
||||||
|
await expect(menu).toHaveClass(/show-menu/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||