mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-15 09:34:19 +08:00
test(utils): migrate to generators (#27383)
Issue number: N/A
---------
<!-- 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. -->
Animation, framework delegate, and overlay tests are using legacy syntax
## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->
- Animation, framework delegate, and overlay tests are using generator
syntax
513e850842
The overlay focus tests do not vary across modes, so I removed the extra
checks.
## 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. -->
This commit is contained in:
@ -1,43 +0,0 @@
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('animation: animationbuilder', async () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
});
|
||||
test('backwards-compatibility animation', async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/animationbuilder');
|
||||
await testNavigation(page);
|
||||
});
|
||||
|
||||
test('ios-transition web', async ({ page, skip }) => {
|
||||
skip.mode('md');
|
||||
|
||||
await page.goto('/src/utils/animation/test/animationbuilder');
|
||||
await testNavigation(page);
|
||||
});
|
||||
|
||||
test('ios-transition css', async ({ page, skip }) => {
|
||||
skip.mode('md');
|
||||
|
||||
await page.goto('/src/utils/animation/test/animationbuilder?ionic:_forceCSSAnimations=true');
|
||||
await testNavigation(page);
|
||||
});
|
||||
});
|
||||
|
||||
const testNavigation = async (page: E2EPage) => {
|
||||
const ionRouteDidChange = await page.spyOnEvent('ionRouteDidChange');
|
||||
|
||||
await page.click('page-root ion-button.next');
|
||||
await ionRouteDidChange.next();
|
||||
page.click('page-one ion-button.next');
|
||||
await ionRouteDidChange.next();
|
||||
page.click('page-two ion-button.next');
|
||||
await ionRouteDidChange.next();
|
||||
page.click('page-three ion-back-button');
|
||||
await ionRouteDidChange.next();
|
||||
page.click('page-two ion-back-button');
|
||||
await ionRouteDidChange.next();
|
||||
page.click('page-one ion-back-button');
|
||||
await ionRouteDidChange.next();
|
||||
};
|
@ -0,0 +1,38 @@
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('animation: animationbuilder'), async () => {
|
||||
test('backwards-compatibility animation', async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/animationbuilder', config);
|
||||
await testNavigation(page);
|
||||
});
|
||||
|
||||
test('ios-transition web', async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/animationbuilder', config);
|
||||
await testNavigation(page);
|
||||
});
|
||||
|
||||
test('ios-transition css', async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/animationbuilder?ionic:_forceCSSAnimations=true', config);
|
||||
await testNavigation(page);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const testNavigation = async (page: E2EPage) => {
|
||||
const ionRouteDidChange = await page.spyOnEvent('ionRouteDidChange');
|
||||
|
||||
await page.click('page-root ion-button.next');
|
||||
await ionRouteDidChange.next();
|
||||
page.click('page-one ion-button.next');
|
||||
await ionRouteDidChange.next();
|
||||
page.click('page-two ion-button.next');
|
||||
await ionRouteDidChange.next();
|
||||
page.click('page-three ion-back-button');
|
||||
await ionRouteDidChange.next();
|
||||
page.click('page-two ion-back-button');
|
||||
await ionRouteDidChange.next();
|
||||
page.click('page-one ion-back-button');
|
||||
await ionRouteDidChange.next();
|
||||
};
|
@ -1,27 +0,0 @@
|
||||
import { test } from '@utils/test/playwright';
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
|
||||
test.describe('animation: basic', async () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
skip.mode('ios');
|
||||
});
|
||||
|
||||
test(`should resolve using web animations`, async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/basic');
|
||||
await testPage(page);
|
||||
});
|
||||
|
||||
test(`should resolve using css animations`, async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/basic?ionic:_forceCSSAnimations=true');
|
||||
await testPage(page);
|
||||
});
|
||||
});
|
||||
|
||||
const testPage = async (page: E2EPage) => {
|
||||
const ionAnimationFinished = await page.spyOnEvent('ionAnimationFinished');
|
||||
|
||||
await page.click('.play');
|
||||
|
||||
await ionAnimationFinished.next();
|
||||
};
|
24
core/src/utils/animation/test/basic/animation.e2e.ts
Normal file
24
core/src/utils/animation/test/basic/animation.e2e.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('animation: basic'), async () => {
|
||||
test(`should resolve using web animations`, async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/basic', config);
|
||||
await testPage(page);
|
||||
});
|
||||
|
||||
test(`should resolve using css animations`, async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/basic?ionic:_forceCSSAnimations=true', config);
|
||||
await testPage(page);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const testPage = async (page: E2EPage) => {
|
||||
const ionAnimationFinished = await page.spyOnEvent('ionAnimationFinished');
|
||||
|
||||
await page.click('.play');
|
||||
|
||||
await ionAnimationFinished.next();
|
||||
};
|
@ -1,23 +1,20 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
|
||||
test.describe('animation: display', async () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
skip.mode('ios');
|
||||
});
|
||||
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('animation: display'), async () => {
|
||||
test(`should resolve using web animations`, async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/display');
|
||||
await page.goto('/src/utils/animation/test/display', config);
|
||||
await testDisplay(page);
|
||||
});
|
||||
|
||||
test(`should resolve using css animations`, async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/display?ionic:_forceCSSAnimations=true');
|
||||
await page.goto('/src/utils/animation/test/display?ionic:_forceCSSAnimations=true', config);
|
||||
await testDisplay(page);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const testDisplay = async (page: E2EPage) => {
|
||||
const ionAnimationFinished = await page.spyOnEvent('ionAnimationFinished');
|
@ -1,23 +1,20 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
|
||||
test.describe('animation: hooks', async () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
skip.mode('ios');
|
||||
});
|
||||
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('animation: hooks'), async () => {
|
||||
test(`should fire hooks using web animations`, async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/hooks');
|
||||
await page.goto('/src/utils/animation/test/hooks', config);
|
||||
await testHooks(page);
|
||||
});
|
||||
|
||||
test(`should fire hooks using css animations`, async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/hooks?ionic:_forceCSSAnimations=true');
|
||||
await page.goto('/src/utils/animation/test/hooks?ionic:_forceCSSAnimations=true', config);
|
||||
await testHooks(page);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const testHooks = async (page: E2EPage) => {
|
||||
const square = page.locator('.square-a');
|
@ -1,15 +1,11 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
|
||||
test.describe('animation: multiple', async () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
skip.mode('ios');
|
||||
});
|
||||
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('animation: multiple'), async () => {
|
||||
test(`should resolve grouped animations using web animations`, async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/multiple');
|
||||
await page.goto('/src/utils/animation/test/multiple', config);
|
||||
await testMultiple(page);
|
||||
});
|
||||
|
||||
@ -17,10 +13,11 @@ test.describe('animation: multiple', async () => {
|
||||
* CSS animations will occasionally resolve out of order, so we skip for now
|
||||
*/
|
||||
test.skip(`should resolve grouped animations using css animations`, async ({ page }) => {
|
||||
await page.goto('/src/utils/animation/test/multiple?ionic:_forceCSSAnimations=true');
|
||||
await page.goto('/src/utils/animation/test/multiple?ionic:_forceCSSAnimations=true', config);
|
||||
await testMultiple(page);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const testMultiple = async (page: E2EPage) => {
|
||||
const ionAnimationFinished = await page.spyOnEvent('ionAnimationFinished');
|
@ -1,180 +0,0 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import type { Locator } from '@playwright/test';
|
||||
import { KeyboardResize } from '@utils/native/keyboard';
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
const getScrollPosition = async (contentEl: Locator) => {
|
||||
return await contentEl.evaluate(async (el: HTMLIonContentElement) => {
|
||||
const scrollEl = await el.getScrollElement();
|
||||
|
||||
return scrollEl.scrollTop;
|
||||
});
|
||||
};
|
||||
|
||||
// TODO FW-3427
|
||||
test.describe.skip('scroll-assist', () => {
|
||||
let scrollAssistFixture: ScrollAssistFixture;
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
test.slow();
|
||||
skip.rtl();
|
||||
skip.mode('md', 'Scroll utils are only needed on iOS mode');
|
||||
skip.browser('firefox');
|
||||
skip.browser('chromium');
|
||||
|
||||
scrollAssistFixture = new ScrollAssistFixture(page);
|
||||
});
|
||||
|
||||
test.describe('scroll-assist: basic functionality', () => {
|
||||
test.beforeEach(async () => {
|
||||
await scrollAssistFixture.goto();
|
||||
});
|
||||
test('should not activate when input is above the keyboard', async () => {
|
||||
await scrollAssistFixture.expectNotToHaveScrollAssist(
|
||||
'#input-above-keyboard',
|
||||
'#input-above-keyboard input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
|
||||
test('should activate when input is below the keyboard', async () => {
|
||||
await scrollAssistFixture.expectToHaveScrollAssist(
|
||||
'#input-below-keyboard',
|
||||
'#input-below-keyboard input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
|
||||
test('should activate even when not explicitly tapping input', async () => {
|
||||
await scrollAssistFixture.expectToHaveScrollAssist(
|
||||
'#item-below-keyboard ion-label',
|
||||
'#input-below-keyboard input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
});
|
||||
test.describe('scroll-assist: scroll-padding', () => {
|
||||
test.describe('scroll-padding: browser/cordova', () => {
|
||||
test.beforeEach(async () => {
|
||||
await scrollAssistFixture.goto();
|
||||
});
|
||||
test('should add scroll padding for an input at the bottom of the scroll container', async () => {
|
||||
await scrollAssistFixture.expectToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
|
||||
test('should keep scroll padding even when switching between inputs', async () => {
|
||||
await scrollAssistFixture.expectToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
|
||||
await scrollAssistFixture.expectToHaveScrollPadding(
|
||||
'#textarea-outside-viewport',
|
||||
'#textarea-outside-viewport textarea:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
});
|
||||
test.describe('scroll-padding: webview resizing', () => {
|
||||
test('should add scroll padding when webview resizing is "none"', async () => {
|
||||
await scrollAssistFixture.goto(KeyboardResize.None);
|
||||
|
||||
await scrollAssistFixture.expectToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
test('should not add scroll padding when webview resizing is "body"', async () => {
|
||||
await scrollAssistFixture.goto(KeyboardResize.Body);
|
||||
|
||||
await scrollAssistFixture.expectNotToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
test('should not add scroll padding when webview resizing is "ionic"', async () => {
|
||||
await scrollAssistFixture.goto(KeyboardResize.Ionic);
|
||||
|
||||
await scrollAssistFixture.expectNotToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
test('should not add scroll padding when webview resizing is "native"', async () => {
|
||||
await scrollAssistFixture.goto(KeyboardResize.Native);
|
||||
|
||||
await scrollAssistFixture.expectNotToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ScrollAssistFixture {
|
||||
readonly page: E2EPage;
|
||||
private content!: Locator;
|
||||
|
||||
constructor(page: E2EPage) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
async goto(resizeMode?: KeyboardResize) {
|
||||
let url = `/src/utils/input-shims/hacks/test`;
|
||||
if (resizeMode !== undefined) {
|
||||
url += `?resizeMode=${resizeMode}`;
|
||||
}
|
||||
|
||||
await this.page.goto(url);
|
||||
|
||||
this.content = this.page.locator('ion-content');
|
||||
}
|
||||
|
||||
private async focusInput(interactiveSelector: string, inputSelector: string) {
|
||||
const { page } = this;
|
||||
const interactive = page.locator(interactiveSelector);
|
||||
const input = page.locator(inputSelector);
|
||||
|
||||
await interactive.click({ force: true });
|
||||
await expect(input).toBeFocused();
|
||||
await page.waitForChanges();
|
||||
}
|
||||
|
||||
private getScrollPosition() {
|
||||
const { content } = this;
|
||||
|
||||
return getScrollPosition(content);
|
||||
}
|
||||
|
||||
async expectNotToHaveScrollAssist(interactiveSelector: string, inputSelector: string) {
|
||||
await expect(await this.getScrollPosition()).toBe(0);
|
||||
|
||||
await this.focusInput(interactiveSelector, inputSelector);
|
||||
|
||||
await expect(await this.getScrollPosition()).toBe(0);
|
||||
}
|
||||
|
||||
async expectToHaveScrollAssist(interactiveSelector: string, inputSelector: string) {
|
||||
await expect(await this.getScrollPosition()).toBe(0);
|
||||
|
||||
await this.focusInput(interactiveSelector, inputSelector);
|
||||
|
||||
await expect(await this.getScrollPosition()).not.toBe(0);
|
||||
}
|
||||
|
||||
async expectToHaveScrollPadding(interactiveSelector: string, inputSelector: string) {
|
||||
const { content } = this;
|
||||
|
||||
await this.focusInput(interactiveSelector, inputSelector);
|
||||
|
||||
await expect(content).not.toHaveCSS('--keyboard-offset', '0px');
|
||||
}
|
||||
|
||||
async expectNotToHaveScrollPadding(interactiveSelector: string, inputSelector: string) {
|
||||
const { content } = this;
|
||||
|
||||
await this.focusInput(interactiveSelector, inputSelector);
|
||||
|
||||
await expect(content).toHaveCSS('--keyboard-offset', '0px');
|
||||
}
|
||||
}
|
180
core/src/utils/input-shims/hacks/test/scroll-assist.e2e.ts
Normal file
180
core/src/utils/input-shims/hacks/test/scroll-assist.e2e.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import type { Locator } from '@playwright/test';
|
||||
import { KeyboardResize } from '@utils/native/keyboard';
|
||||
import type { E2EPage, E2EPageOptions } from '@utils/test/playwright';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
const getScrollPosition = async (contentEl: Locator) => {
|
||||
return await contentEl.evaluate(async (el: HTMLIonContentElement) => {
|
||||
const scrollEl = await el.getScrollElement();
|
||||
|
||||
return scrollEl.scrollTop;
|
||||
});
|
||||
};
|
||||
|
||||
// TODO FW-3427
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('scroll-assist'), () => {
|
||||
let scrollAssistFixture: ScrollAssistFixture;
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
test.slow();
|
||||
skip.browser('firefox');
|
||||
skip.browser('chromium');
|
||||
|
||||
scrollAssistFixture = new ScrollAssistFixture(page);
|
||||
});
|
||||
|
||||
test.describe('scroll-assist: basic functionality', () => {
|
||||
test.beforeEach(async () => {
|
||||
await scrollAssistFixture.goto(config);
|
||||
});
|
||||
test('should not activate when input is above the keyboard', async () => {
|
||||
await scrollAssistFixture.expectNotToHaveScrollAssist(
|
||||
'#input-above-keyboard',
|
||||
'#input-above-keyboard input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
|
||||
test('should activate when input is below the keyboard', async () => {
|
||||
await scrollAssistFixture.expectToHaveScrollAssist(
|
||||
'#input-below-keyboard',
|
||||
'#input-below-keyboard input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
|
||||
test('should activate even when not explicitly tapping input', async () => {
|
||||
await scrollAssistFixture.expectToHaveScrollAssist(
|
||||
'#item-below-keyboard ion-label',
|
||||
'#input-below-keyboard input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
});
|
||||
test.describe('scroll-assist: scroll-padding', () => {
|
||||
test.describe('scroll-padding: browser/cordova', () => {
|
||||
test.beforeEach(async () => {
|
||||
await scrollAssistFixture.goto(config);
|
||||
});
|
||||
test('should add scroll padding for an input at the bottom of the scroll container', async () => {
|
||||
await scrollAssistFixture.expectToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
|
||||
test('should keep scroll padding even when switching between inputs', async () => {
|
||||
await scrollAssistFixture.expectToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
|
||||
await scrollAssistFixture.expectToHaveScrollPadding(
|
||||
'#textarea-outside-viewport',
|
||||
'#textarea-outside-viewport textarea:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
});
|
||||
test.describe('scroll-padding: webview resizing', () => {
|
||||
test('should add scroll padding when webview resizing is "none"', async () => {
|
||||
await scrollAssistFixture.goto(config, KeyboardResize.None);
|
||||
|
||||
await scrollAssistFixture.expectToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
test('should not add scroll padding when webview resizing is "body"', async () => {
|
||||
await scrollAssistFixture.goto(config, KeyboardResize.Body);
|
||||
|
||||
await scrollAssistFixture.expectNotToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
test('should not add scroll padding when webview resizing is "ionic"', async () => {
|
||||
await scrollAssistFixture.goto(config, KeyboardResize.Ionic);
|
||||
|
||||
await scrollAssistFixture.expectNotToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
test('should not add scroll padding when webview resizing is "native"', async () => {
|
||||
await scrollAssistFixture.goto(config, KeyboardResize.Native);
|
||||
|
||||
await scrollAssistFixture.expectNotToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ScrollAssistFixture {
|
||||
readonly page: E2EPage;
|
||||
private content!: Locator;
|
||||
|
||||
constructor(page: E2EPage) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
async goto(config: E2EPageOptions, resizeMode?: KeyboardResize) {
|
||||
let url = `/src/utils/input-shims/hacks/test`;
|
||||
if (resizeMode !== undefined) {
|
||||
url += `?resizeMode=${resizeMode}`;
|
||||
}
|
||||
|
||||
await this.page.goto(url, config);
|
||||
|
||||
this.content = this.page.locator('ion-content');
|
||||
}
|
||||
|
||||
private async focusInput(interactiveSelector: string, inputSelector: string) {
|
||||
const { page } = this;
|
||||
const interactive = page.locator(interactiveSelector);
|
||||
const input = page.locator(inputSelector);
|
||||
|
||||
await interactive.click({ force: true });
|
||||
await expect(input).toBeFocused();
|
||||
await page.waitForChanges();
|
||||
}
|
||||
|
||||
private getScrollPosition() {
|
||||
const { content } = this;
|
||||
|
||||
return getScrollPosition(content);
|
||||
}
|
||||
|
||||
async expectNotToHaveScrollAssist(interactiveSelector: string, inputSelector: string) {
|
||||
await expect(await this.getScrollPosition()).toBe(0);
|
||||
|
||||
await this.focusInput(interactiveSelector, inputSelector);
|
||||
|
||||
await expect(await this.getScrollPosition()).toBe(0);
|
||||
}
|
||||
|
||||
async expectToHaveScrollAssist(interactiveSelector: string, inputSelector: string) {
|
||||
await expect(await this.getScrollPosition()).toBe(0);
|
||||
|
||||
await this.focusInput(interactiveSelector, inputSelector);
|
||||
|
||||
await expect(await this.getScrollPosition()).not.toBe(0);
|
||||
}
|
||||
|
||||
async expectToHaveScrollPadding(interactiveSelector: string, inputSelector: string) {
|
||||
const { content } = this;
|
||||
|
||||
await this.focusInput(interactiveSelector, inputSelector);
|
||||
|
||||
await expect(content).not.toHaveCSS('--keyboard-offset', '0px');
|
||||
}
|
||||
|
||||
async expectNotToHaveScrollPadding(interactiveSelector: string, inputSelector: string) {
|
||||
const { content } = this;
|
||||
|
||||
await this.focusInput(interactiveSelector, inputSelector);
|
||||
|
||||
await expect(content).toHaveCSS('--keyboard-offset', '0px');
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
// 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>
|
||||
<button class="ion-activatable ion-activatable-instant">Click Me</button>
|
||||
</ion-app>
|
||||
`);
|
||||
|
||||
const button = page.locator('button');
|
||||
const box = await button.boundingBox()!;
|
||||
|
||||
if (box) {
|
||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
await page.mouse.down();
|
||||
await page.waitForChanges();
|
||||
}
|
||||
|
||||
await expect(button).toHaveClass(/ion-activated/);
|
||||
});
|
||||
});
|
30
core/src/utils/tap-click/test/tap-click.e2e.ts
Normal file
30
core/src/utils/tap-click/test/tap-click.e2e.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
// TODO FW-3010
|
||||
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('tap click utility'), () => {
|
||||
test('it should apply activated class when clicking element', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-app>
|
||||
<button class="ion-activatable ion-activatable-instant">Click Me</button>
|
||||
</ion-app>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const button = page.locator('button');
|
||||
const box = await button.boundingBox()!;
|
||||
|
||||
if (box) {
|
||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
await page.mouse.down();
|
||||
await page.waitForChanges();
|
||||
}
|
||||
|
||||
await expect(button).toHaveClass(/ion-activated/);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,40 +0,0 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('framework-delegate', () => {
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.rtl();
|
||||
skip.mode('ios');
|
||||
|
||||
await page.goto('/src/utils/test/framework-delegate');
|
||||
});
|
||||
test('should present modal already at ion-app root', async ({ page }) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#button-inline-root');
|
||||
|
||||
const modal = page.locator('#inline-root');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
|
||||
test('should present modal in content', async ({ page }) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#button-inline-content');
|
||||
|
||||
const modal = page.locator('#inline-content');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
|
||||
test('should present modal via controller', async ({ page }) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#button-controller');
|
||||
|
||||
const modal = page.locator('#controller');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
});
|
@ -0,0 +1,39 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('framework-delegate'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/src/utils/test/framework-delegate', config);
|
||||
});
|
||||
test('should present modal already at ion-app root', async ({ page }) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#button-inline-root');
|
||||
|
||||
const modal = page.locator('#inline-root');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
|
||||
test('should present modal in content', async ({ page }) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#button-inline-content');
|
||||
|
||||
const modal = page.locator('#inline-content');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
|
||||
test('should present modal via controller', async ({ page }) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#button-controller');
|
||||
|
||||
const modal = page.locator('#controller');
|
||||
await ionModalDidPresent.next();
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,254 +0,0 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('overlays: dismiss', () => {
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.rtl();
|
||||
|
||||
await page.goto('/src/utils/test/overlays');
|
||||
});
|
||||
test('hardware back button: should dismiss a presented overlay', async ({ page }) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#create-and-present');
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#modal-simulate');
|
||||
|
||||
await ionModalDidDismiss.next();
|
||||
});
|
||||
|
||||
test('hardware back button: should dismiss the presented overlay, even though another hidden modal was added last', async ({
|
||||
page,
|
||||
}) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#create-and-present');
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#modal-create');
|
||||
|
||||
const modals = page.locator('ion-modal');
|
||||
await expect(await modals.count()).toEqual(2);
|
||||
|
||||
await expect(await modals.nth(0)).not.toHaveClass(/overlay-hidden/);
|
||||
await expect(await modals.nth(1)).toHaveClass(/overlay-hidden/);
|
||||
|
||||
await page.click('#modal-simulate');
|
||||
|
||||
await ionModalDidDismiss.next();
|
||||
|
||||
await expect(await modals.count()).toEqual(1);
|
||||
await expect(await modals.nth(0)).toHaveClass(/overlay-hidden/);
|
||||
});
|
||||
|
||||
test('Esc: should dismiss a presented overlay', async ({ page }) => {
|
||||
const createAndPresentButton = page.locator('#create-and-present');
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await createAndPresentButton.click();
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await ionModalDidDismiss.next();
|
||||
});
|
||||
|
||||
test('Esc: should dismiss the presented overlay, even though another hidden modal was added last', async ({
|
||||
page,
|
||||
}) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#create-and-present');
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#modal-create');
|
||||
|
||||
const modals = page.locator('ion-modal');
|
||||
await expect(await modals.count()).toEqual(2);
|
||||
|
||||
await expect(await modals.nth(0)).not.toHaveClass(/overlay-hidden/);
|
||||
await expect(await modals.nth(1)).toHaveClass(/overlay-hidden/);
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await ionModalDidDismiss.next();
|
||||
|
||||
await expect(await modals.count()).toEqual(1);
|
||||
await expect(await modals.nth(0)).toHaveClass(/overlay-hidden/);
|
||||
});
|
||||
|
||||
test('overlays: Nested: should dismiss the top overlay', async ({ page }) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#create-nested');
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#dismiss-modal-nested-overlay');
|
||||
|
||||
await ionModalDidDismiss.next();
|
||||
|
||||
const modals = page.locator('ion-modal');
|
||||
expect(await modals.count()).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO FW-3536
|
||||
test.describe.skip('overlays: focus', () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
});
|
||||
|
||||
test('should not select a hidden focusable element', async ({ page, browserName }) => {
|
||||
await page.setContent(`
|
||||
<style>
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ion-button id="open-modal">Show Modal</ion-button>
|
||||
<ion-modal trigger="open-modal">
|
||||
<ion-content>
|
||||
<ion-button hidden id="hidden">Hidden Button</ion-button>
|
||||
<ion-button id="visible">Visible Button</ion-button>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
`);
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const presentButton = page.locator('ion-button#open-modal');
|
||||
const visibleButton = page.locator('ion-button#visible');
|
||||
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
|
||||
|
||||
await presentButton.click();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(visibleButton).toBeFocused();
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(visibleButton).toBeFocused();
|
||||
});
|
||||
|
||||
test('should not select a disabled focusable element', async ({ page, browserName }) => {
|
||||
await page.setContent(`
|
||||
<ion-button id="open-modal">Show Modal</ion-button>
|
||||
<ion-modal trigger="open-modal">
|
||||
<ion-content>
|
||||
<ion-button disabled="true" id="disabled">Button</ion-button>
|
||||
<ion-button id="active">Active Button</ion-button>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
`);
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const presentButton = page.locator('ion-button#open-modal');
|
||||
const activeButton = page.locator('ion-button#active');
|
||||
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
|
||||
|
||||
await presentButton.click();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(activeButton).toBeFocused();
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(activeButton).toBeFocused();
|
||||
});
|
||||
|
||||
test('should select a focusable element with disabled="false"', async ({ page, browserName }) => {
|
||||
await page.setContent(`
|
||||
<ion-button id="open-modal">Show Modal</ion-button>
|
||||
<ion-modal trigger="open-modal">
|
||||
<ion-content>
|
||||
<ion-button disabled="false" id="disabled-false">Button</ion-button>
|
||||
<ion-button id="active">Active Button</ion-button>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
`);
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const presentButton = page.locator('ion-button#open-modal');
|
||||
const disabledFalseButton = page.locator('ion-button#disabled-false');
|
||||
const activeButton = page.locator('ion-button#active');
|
||||
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
|
||||
|
||||
await presentButton.click();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(disabledFalseButton).toBeFocused();
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(activeButton).toBeFocused();
|
||||
|
||||
// Loop back to beginning of overlay
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(disabledFalseButton).toBeFocused();
|
||||
});
|
||||
|
||||
test('toast should not cause focus trapping', async ({ page }) => {
|
||||
await page.goto('/src/utils/test/overlays');
|
||||
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent');
|
||||
|
||||
await page.click('#create-and-present-toast');
|
||||
await ionToastDidPresent.next();
|
||||
|
||||
const input = page.locator('#root-input input');
|
||||
await input.click();
|
||||
|
||||
await expect(input).toBeFocused();
|
||||
});
|
||||
|
||||
test('toast should not cause focus trapping even when opened from a focus trapping overlay', async ({ page }) => {
|
||||
await page.goto('/src/utils/test/overlays');
|
||||
|
||||
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent');
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#create-and-present');
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#modal-toast');
|
||||
await ionToastDidPresent.next();
|
||||
|
||||
const modalInput = page.locator('.modal-input input');
|
||||
await modalInput.click();
|
||||
|
||||
await expect(modalInput).toBeFocused();
|
||||
});
|
||||
|
||||
test('focus trapping should only run on the top-most overlay', async ({ page }) => {
|
||||
await page.goto('/src/utils/test/overlays');
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#create-and-present');
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const modalInputZero = page.locator('.modal-0 .modal-input input');
|
||||
await modalInputZero.click();
|
||||
|
||||
await expect(modalInputZero).toBeFocused();
|
||||
|
||||
await page.click('#modal-create-and-present');
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const modalInputOne = page.locator('.modal-1 .modal-input input');
|
||||
await modalInputOne.click();
|
||||
|
||||
await expect(modalInputOne).toBeFocused();
|
||||
});
|
||||
});
|
262
core/src/utils/test/overlays/overlays.e2e.ts
Normal file
262
core/src/utils/test/overlays/overlays.e2e.ts
Normal file
@ -0,0 +1,262 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* This behavior does not vary across modes/directions.
|
||||
*/
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('overlays: dismiss'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/src/utils/test/overlays', config);
|
||||
});
|
||||
test('hardware back button: should dismiss a presented overlay', async ({ page }) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#create-and-present');
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#modal-simulate');
|
||||
|
||||
await ionModalDidDismiss.next();
|
||||
});
|
||||
|
||||
test('hardware back button: should dismiss the presented overlay, even though another hidden modal was added last', async ({
|
||||
page,
|
||||
}) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#create-and-present');
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#modal-create');
|
||||
|
||||
const modals = page.locator('ion-modal');
|
||||
await expect(await modals.count()).toEqual(2);
|
||||
|
||||
await expect(await modals.nth(0)).not.toHaveClass(/overlay-hidden/);
|
||||
await expect(await modals.nth(1)).toHaveClass(/overlay-hidden/);
|
||||
|
||||
await page.click('#modal-simulate');
|
||||
|
||||
await ionModalDidDismiss.next();
|
||||
|
||||
await expect(await modals.count()).toEqual(1);
|
||||
await expect(await modals.nth(0)).toHaveClass(/overlay-hidden/);
|
||||
});
|
||||
|
||||
test('Esc: should dismiss a presented overlay', async ({ page }) => {
|
||||
const createAndPresentButton = page.locator('#create-and-present');
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await createAndPresentButton.click();
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await ionModalDidDismiss.next();
|
||||
});
|
||||
|
||||
test('Esc: should dismiss the presented overlay, even though another hidden modal was added last', async ({
|
||||
page,
|
||||
}) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#create-and-present');
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#modal-create');
|
||||
|
||||
const modals = page.locator('ion-modal');
|
||||
await expect(await modals.count()).toEqual(2);
|
||||
|
||||
await expect(await modals.nth(0)).not.toHaveClass(/overlay-hidden/);
|
||||
await expect(await modals.nth(1)).toHaveClass(/overlay-hidden/);
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await ionModalDidDismiss.next();
|
||||
|
||||
await expect(await modals.count()).toEqual(1);
|
||||
await expect(await modals.nth(0)).toHaveClass(/overlay-hidden/);
|
||||
});
|
||||
|
||||
test('overlays: Nested: should dismiss the top overlay', async ({ page }) => {
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#create-nested');
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#dismiss-modal-nested-overlay');
|
||||
|
||||
await ionModalDidDismiss.next();
|
||||
|
||||
const modals = page.locator('ion-modal');
|
||||
expect(await modals.count()).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO FW-3536
|
||||
test.describe.skip(title('overlays: focus'), () => {
|
||||
test('should not select a hidden focusable element', async ({ page, browserName }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ion-button id="open-modal">Show Modal</ion-button>
|
||||
<ion-modal trigger="open-modal">
|
||||
<ion-content>
|
||||
<ion-button hidden id="hidden">Hidden Button</ion-button>
|
||||
<ion-button id="visible">Visible Button</ion-button>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const presentButton = page.locator('ion-button#open-modal');
|
||||
const visibleButton = page.locator('ion-button#visible');
|
||||
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
|
||||
|
||||
await presentButton.click();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(visibleButton).toBeFocused();
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(visibleButton).toBeFocused();
|
||||
});
|
||||
|
||||
test('should not select a disabled focusable element', async ({ page, browserName }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-button id="open-modal">Show Modal</ion-button>
|
||||
<ion-modal trigger="open-modal">
|
||||
<ion-content>
|
||||
<ion-button disabled="true" id="disabled">Button</ion-button>
|
||||
<ion-button id="active">Active Button</ion-button>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const presentButton = page.locator('ion-button#open-modal');
|
||||
const activeButton = page.locator('ion-button#active');
|
||||
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
|
||||
|
||||
await presentButton.click();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(activeButton).toBeFocused();
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(activeButton).toBeFocused();
|
||||
});
|
||||
|
||||
test('should select a focusable element with disabled="false"', async ({ page, browserName }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-button id="open-modal">Show Modal</ion-button>
|
||||
<ion-modal trigger="open-modal">
|
||||
<ion-content>
|
||||
<ion-button disabled="false" id="disabled-false">Button</ion-button>
|
||||
<ion-button id="active">Active Button</ion-button>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const presentButton = page.locator('ion-button#open-modal');
|
||||
const disabledFalseButton = page.locator('ion-button#disabled-false');
|
||||
const activeButton = page.locator('ion-button#active');
|
||||
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
|
||||
|
||||
await presentButton.click();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(disabledFalseButton).toBeFocused();
|
||||
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(activeButton).toBeFocused();
|
||||
|
||||
// Loop back to beginning of overlay
|
||||
await page.keyboard.press(tabKey);
|
||||
await expect(disabledFalseButton).toBeFocused();
|
||||
});
|
||||
|
||||
test('toast should not cause focus trapping', async ({ page }) => {
|
||||
await page.goto('/src/utils/test/overlays', config);
|
||||
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent');
|
||||
|
||||
await page.click('#create-and-present-toast');
|
||||
await ionToastDidPresent.next();
|
||||
|
||||
const input = page.locator('#root-input input');
|
||||
await input.click();
|
||||
|
||||
await expect(input).toBeFocused();
|
||||
});
|
||||
|
||||
test('toast should not cause focus trapping even when opened from a focus trapping overlay', async ({ page }) => {
|
||||
await page.goto('/src/utils/test/overlays', config);
|
||||
|
||||
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent');
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#create-and-present');
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#modal-toast');
|
||||
await ionToastDidPresent.next();
|
||||
|
||||
const modalInput = page.locator('.modal-input input');
|
||||
await modalInput.click();
|
||||
|
||||
await expect(modalInput).toBeFocused();
|
||||
});
|
||||
|
||||
test('focus trapping should only run on the top-most overlay', async ({ page }) => {
|
||||
await page.goto('/src/utils/test/overlays', config);
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#create-and-present');
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const modalInputZero = page.locator('.modal-0 .modal-input input');
|
||||
await modalInputZero.click();
|
||||
|
||||
await expect(modalInputZero).toBeFocused();
|
||||
|
||||
await page.click('#modal-create-and-present');
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const modalInputOne = page.locator('.modal-1 .modal-input input');
|
||||
await modalInputOne.click();
|
||||
|
||||
await expect(modalInputOne).toBeFocused();
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user