diff --git a/core/src/utils/animation/test/animationbuilder/animation.e2e-legacy.ts b/core/src/utils/animation/test/animationbuilder/animation.e2e-legacy.ts deleted file mode 100644 index 5e556d8c1d..0000000000 --- a/core/src/utils/animation/test/animationbuilder/animation.e2e-legacy.ts +++ /dev/null @@ -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(); -}; diff --git a/core/src/utils/animation/test/animationbuilder/animation.e2e.ts b/core/src/utils/animation/test/animationbuilder/animation.e2e.ts new file mode 100644 index 0000000000..96ea221853 --- /dev/null +++ b/core/src/utils/animation/test/animationbuilder/animation.e2e.ts @@ -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(); +}; diff --git a/core/src/utils/animation/test/basic/animation.e2e-legacy.ts b/core/src/utils/animation/test/basic/animation.e2e-legacy.ts deleted file mode 100644 index 41ae95b366..0000000000 --- a/core/src/utils/animation/test/basic/animation.e2e-legacy.ts +++ /dev/null @@ -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(); -}; diff --git a/core/src/utils/animation/test/basic/animation.e2e.ts b/core/src/utils/animation/test/basic/animation.e2e.ts new file mode 100644 index 0000000000..959218b3b2 --- /dev/null +++ b/core/src/utils/animation/test/basic/animation.e2e.ts @@ -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(); +}; diff --git a/core/src/utils/animation/test/display/animation.e2e-legacy.ts b/core/src/utils/animation/test/display/animation.e2e.ts similarity index 55% rename from core/src/utils/animation/test/display/animation.e2e-legacy.ts rename to core/src/utils/animation/test/display/animation.e2e.ts index d5f2b1bd3b..2f123854aa 100644 --- a/core/src/utils/animation/test/display/animation.e2e-legacy.ts +++ b/core/src/utils/animation/test/display/animation.e2e.ts @@ -1,21 +1,18 @@ 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', config); + await testDisplay(page); + }); - test(`should resolve using web animations`, async ({ page }) => { - await page.goto('/src/utils/animation/test/display'); - await testDisplay(page); - }); - - test(`should resolve using css animations`, async ({ page }) => { - await page.goto('/src/utils/animation/test/display?ionic:_forceCSSAnimations=true'); - await testDisplay(page); + test(`should resolve using css animations`, async ({ page }) => { + await page.goto('/src/utils/animation/test/display?ionic:_forceCSSAnimations=true', config); + await testDisplay(page); + }); }); }); diff --git a/core/src/utils/animation/test/hooks/animation.e2e-legacy.ts b/core/src/utils/animation/test/hooks/animation.e2e.ts similarity index 73% rename from core/src/utils/animation/test/hooks/animation.e2e-legacy.ts rename to core/src/utils/animation/test/hooks/animation.e2e.ts index be714b04c0..47c386ef59 100644 --- a/core/src/utils/animation/test/hooks/animation.e2e-legacy.ts +++ b/core/src/utils/animation/test/hooks/animation.e2e.ts @@ -1,21 +1,18 @@ 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', config); + await testHooks(page); + }); - test(`should fire hooks using web animations`, async ({ page }) => { - await page.goto('/src/utils/animation/test/hooks'); - await testHooks(page); - }); - - test(`should fire hooks using css animations`, async ({ page }) => { - await page.goto('/src/utils/animation/test/hooks?ionic:_forceCSSAnimations=true'); - await testHooks(page); + test(`should fire hooks using css animations`, async ({ page }) => { + await page.goto('/src/utils/animation/test/hooks?ionic:_forceCSSAnimations=true', config); + await testHooks(page); + }); }); }); diff --git a/core/src/utils/animation/test/multiple/animation.e2e-legacy.ts b/core/src/utils/animation/test/multiple/animation.e2e.ts similarity index 60% rename from core/src/utils/animation/test/multiple/animation.e2e-legacy.ts rename to core/src/utils/animation/test/multiple/animation.e2e.ts index a2772e04ee..5db1fbe38b 100644 --- a/core/src/utils/animation/test/multiple/animation.e2e-legacy.ts +++ b/core/src/utils/animation/test/multiple/animation.e2e.ts @@ -1,24 +1,21 @@ 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', config); + await testMultiple(page); + }); - test(`should resolve grouped animations using web animations`, async ({ page }) => { - await page.goto('/src/utils/animation/test/multiple'); - await testMultiple(page); - }); - - /** - * 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 testMultiple(page); + /** + * 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', config); + await testMultiple(page); + }); }); }); diff --git a/core/src/utils/input-shims/hacks/test/scroll-assist.e2e-legacy.ts b/core/src/utils/input-shims/hacks/test/scroll-assist.e2e-legacy.ts deleted file mode 100644 index 693429e758..0000000000 --- a/core/src/utils/input-shims/hacks/test/scroll-assist.e2e-legacy.ts +++ /dev/null @@ -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'); - } -} diff --git a/core/src/utils/input-shims/hacks/test/scroll-assist.e2e.ts b/core/src/utils/input-shims/hacks/test/scroll-assist.e2e.ts new file mode 100644 index 0000000000..9edfc45e7d --- /dev/null +++ b/core/src/utils/input-shims/hacks/test/scroll-assist.e2e.ts @@ -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'); + } +} diff --git a/core/src/utils/tap-click/test/tap-click.e2e-legacy.ts b/core/src/utils/tap-click/test/tap-click.e2e-legacy.ts deleted file mode 100644 index e027eec7a9..0000000000 --- a/core/src/utils/tap-click/test/tap-click.e2e-legacy.ts +++ /dev/null @@ -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(` - - - - `); - - 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/); - }); -}); diff --git a/core/src/utils/tap-click/test/tap-click.e2e.ts b/core/src/utils/tap-click/test/tap-click.e2e.ts new file mode 100644 index 0000000000..1827e27d6a --- /dev/null +++ b/core/src/utils/tap-click/test/tap-click.e2e.ts @@ -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( + ` + + + + `, + 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/); + }); + }); +}); diff --git a/core/src/utils/test/framework-delegate/framework-delegate.e2e-legacy.ts b/core/src/utils/test/framework-delegate/framework-delegate.e2e-legacy.ts deleted file mode 100644 index 435f7246d0..0000000000 --- a/core/src/utils/test/framework-delegate/framework-delegate.e2e-legacy.ts +++ /dev/null @@ -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(); - }); -}); diff --git a/core/src/utils/test/framework-delegate/framework-delegate.e2e.ts b/core/src/utils/test/framework-delegate/framework-delegate.e2e.ts new file mode 100644 index 0000000000..32bc44cd1c --- /dev/null +++ b/core/src/utils/test/framework-delegate/framework-delegate.e2e.ts @@ -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(); + }); + }); +}); diff --git a/core/src/utils/test/overlays/overlays.e2e-legacy.ts b/core/src/utils/test/overlays/overlays.e2e-legacy.ts deleted file mode 100644 index 440bdaf897..0000000000 --- a/core/src/utils/test/overlays/overlays.e2e-legacy.ts +++ /dev/null @@ -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(` - - - Show Modal - - - - Visible Button - - - `); - - 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(` - Show Modal - - - Button - Active Button - - - `); - - 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(` - Show Modal - - - Button - Active Button - - - `); - - 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(); - }); -}); diff --git a/core/src/utils/test/overlays/overlays.e2e.ts b/core/src/utils/test/overlays/overlays.e2e.ts new file mode 100644 index 0000000000..5de399fb41 --- /dev/null +++ b/core/src/utils/test/overlays/overlays.e2e.ts @@ -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( + ` + + + Show Modal + + + + Visible Button + + + `, + 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( + ` + Show Modal + + + Button + Active Button + + + `, + 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( + ` + Show Modal + + + Button + Active Button + + + `, + 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(); + }); + }); +});