mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
test(utils): migrate utils e2e tests to playwright (#26377)
This commit is contained in:
56
core/src/components/accordion/test/accordion.e2e.ts
Normal file
56
core/src/components/accordion/test/accordion.e2e.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { test } from '@utils/test/playwright';
|
||||||
|
|
||||||
|
test.describe('accordion: states', () => {
|
||||||
|
test.beforeEach(({ skip }) => {
|
||||||
|
skip.rtl();
|
||||||
|
skip.mode('ios');
|
||||||
|
});
|
||||||
|
test('should properly set readonly on child accordions', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<ion-accordion-group animated="false">
|
||||||
|
<ion-accordion>
|
||||||
|
<ion-item slot="header">Label</ion-item>
|
||||||
|
<div slot="content">Content</div>
|
||||||
|
</ion-accordion>
|
||||||
|
</ion-accordion-group>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const accordionGroup = page.locator('ion-accordion-group');
|
||||||
|
const accordion = page.locator('ion-accordion');
|
||||||
|
|
||||||
|
expect(accordion).toHaveJSProperty('readonly', false);
|
||||||
|
|
||||||
|
await accordionGroup.evaluate((el: HTMLIonAccordionGroupElement) => {
|
||||||
|
el.readonly = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.waitForChanges();
|
||||||
|
|
||||||
|
expect(accordion).toHaveJSProperty('readonly', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should properly set disabled on child accordions', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<ion-accordion-group animated="false">
|
||||||
|
<ion-accordion>
|
||||||
|
<ion-item slot="header">Label</ion-item>
|
||||||
|
<div slot="content">Content</div>
|
||||||
|
</ion-accordion>
|
||||||
|
</ion-accordion-group>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const accordionGroup = page.locator('ion-accordion-group');
|
||||||
|
const accordion = page.locator('ion-accordion');
|
||||||
|
|
||||||
|
expect(accordion).toHaveJSProperty('disabled', false);
|
||||||
|
|
||||||
|
await accordionGroup.evaluate((el: HTMLIonAccordionGroupElement) => {
|
||||||
|
el.disabled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.waitForChanges();
|
||||||
|
|
||||||
|
expect(accordion).toHaveJSProperty('disabled', true);
|
||||||
|
});
|
||||||
|
});
|
@ -1,55 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
test('should properly set readonly on child accordions', async () => {
|
|
||||||
const page = await newE2EPage({
|
|
||||||
html: `
|
|
||||||
<ion-accordion-group animated="false">
|
|
||||||
<ion-accordion>
|
|
||||||
<ion-item slot="header">Label</ion-item>
|
|
||||||
<div slot="content">Content</div>
|
|
||||||
</ion-accordion>
|
|
||||||
</ion-accordion-group>
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const accordion = await page.find('ion-accordion');
|
|
||||||
const value = await accordion.getProperty('readonly');
|
|
||||||
|
|
||||||
expect(value).toBe(false);
|
|
||||||
|
|
||||||
await page.$eval('ion-accordion-group', (el: HTMLIonAccordionGroupElement) => {
|
|
||||||
el.readonly = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.waitForChanges();
|
|
||||||
|
|
||||||
const valueAgain = await accordion.getProperty('readonly');
|
|
||||||
expect(valueAgain).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should properly set disabled on child accordions', async () => {
|
|
||||||
const page = await newE2EPage({
|
|
||||||
html: `
|
|
||||||
<ion-accordion-group animated="false">
|
|
||||||
<ion-accordion>
|
|
||||||
<ion-item slot="header">Label</ion-item>
|
|
||||||
<div slot="content">Content</div>
|
|
||||||
</ion-accordion>
|
|
||||||
</ion-accordion-group>
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const accordion = await page.find('ion-accordion');
|
|
||||||
const value = await accordion.getProperty('disabled');
|
|
||||||
|
|
||||||
expect(value).toBe(false);
|
|
||||||
|
|
||||||
await page.$eval('ion-accordion-group', (el: HTMLIonAccordionGroupElement) => {
|
|
||||||
el.disabled = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.waitForChanges();
|
|
||||||
|
|
||||||
const valueAgain = await accordion.getProperty('disabled');
|
|
||||||
expect(valueAgain).toBe(true);
|
|
||||||
});
|
|
@ -1,10 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
test('label: basic', async () => {
|
|
||||||
const page = await newE2EPage({
|
|
||||||
url: '/src/components/label/test/basic?ionic:_testing=true',
|
|
||||||
});
|
|
||||||
|
|
||||||
const compare = await page.compareScreenshot();
|
|
||||||
expect(compare).toMatchScreenshot();
|
|
||||||
});
|
|
@ -1,38 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
import { generateE2EUrl } from '@utils/test';
|
|
||||||
|
|
||||||
export const testLoading = async (type: string, selector: string, rtl = false) => {
|
|
||||||
try {
|
|
||||||
const pageUrl = generateE2EUrl('loading', type, rtl);
|
|
||||||
|
|
||||||
const page = await newE2EPage({
|
|
||||||
url: pageUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const screenshotCompares = [];
|
|
||||||
|
|
||||||
await page.click(selector);
|
|
||||||
await page.waitForSelector(selector);
|
|
||||||
|
|
||||||
let loading = await page.find('ion-loading');
|
|
||||||
expect(loading).not.toBeNull();
|
|
||||||
|
|
||||||
await loading.waitForVisible();
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot());
|
|
||||||
|
|
||||||
await loading.callMethod('dismiss');
|
|
||||||
await loading.waitForNotVisible();
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot('dismiss'));
|
|
||||||
|
|
||||||
loading = await page.find('ion-loading');
|
|
||||||
expect(loading).toBeNull();
|
|
||||||
|
|
||||||
for (const screenshotCompare of screenshotCompares) {
|
|
||||||
expect(screenshotCompare).toMatchScreenshot();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,10 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
test('slides: basic', async () => {
|
|
||||||
const page = await newE2EPage({
|
|
||||||
url: '/src/components/slides/test/basic?ionic:_testing=true',
|
|
||||||
});
|
|
||||||
|
|
||||||
const compare = await page.compareScreenshot();
|
|
||||||
expect(compare).toMatchScreenshot();
|
|
||||||
});
|
|
@ -1,10 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
test('slides: image', async () => {
|
|
||||||
const page = await newE2EPage({
|
|
||||||
url: '/src/components/slides/test/image?ionic:_testing=true',
|
|
||||||
});
|
|
||||||
|
|
||||||
const compare = await page.compareScreenshot();
|
|
||||||
expect(compare).toMatchScreenshot();
|
|
||||||
});
|
|
@ -1,33 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
test('slides: prevent-default', async () => {
|
|
||||||
// For this specific test, _testing=false to import tap-click in app.tsx
|
|
||||||
const page = await newE2EPage({
|
|
||||||
url: '/src/components/slides/test/prevent-default?ionic:_testing=false',
|
|
||||||
});
|
|
||||||
|
|
||||||
const screenshotCompares = [];
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot());
|
|
||||||
|
|
||||||
const scroller = await page.find('#scrollDownButton');
|
|
||||||
const button = await page.find('#changeBackgroundButton');
|
|
||||||
const contentWithBackground = await page.find('#contentWithBackground');
|
|
||||||
|
|
||||||
await page.waitForTimeout(500);
|
|
||||||
|
|
||||||
await scroller.click();
|
|
||||||
await page.waitForTimeout(500);
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot('scroll down button'));
|
|
||||||
|
|
||||||
await button.click();
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot('change background'));
|
|
||||||
|
|
||||||
expect(contentWithBackground).toHaveClasses(['blueBackground']);
|
|
||||||
|
|
||||||
for (const screenshotCompare of screenshotCompares) {
|
|
||||||
expect(screenshotCompare).toMatchScreenshot();
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,10 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
test('slides: vertical', async () => {
|
|
||||||
const page = await newE2EPage({
|
|
||||||
url: '/src/components/slides/test/vertical?ionic:_testing=true',
|
|
||||||
});
|
|
||||||
|
|
||||||
const compare = await page.compareScreenshot();
|
|
||||||
expect(compare).toMatchScreenshot();
|
|
||||||
});
|
|
@ -1,42 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
import { generateE2EUrl } from '@utils/test';
|
|
||||||
|
|
||||||
export const testToast = async (type: string, selector: string, rtl = false) => {
|
|
||||||
try {
|
|
||||||
const pageUrl = generateE2EUrl('toast', type, rtl);
|
|
||||||
|
|
||||||
const page = await newE2EPage({
|
|
||||||
url: pageUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const screenshotCompares = [];
|
|
||||||
|
|
||||||
const button = await page.find(selector);
|
|
||||||
await button.waitForVisible();
|
|
||||||
await button.click();
|
|
||||||
|
|
||||||
await page.waitForTimeout(250);
|
|
||||||
|
|
||||||
let toast = await page.find('ion-toast');
|
|
||||||
await toast.waitForVisible();
|
|
||||||
|
|
||||||
expect(toast).not.toBe(null);
|
|
||||||
await toast.waitForVisible();
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot());
|
|
||||||
|
|
||||||
await toast.callMethod('dismiss');
|
|
||||||
await toast.waitForNotVisible();
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot('dismiss'));
|
|
||||||
|
|
||||||
toast = await page.find('ion-toast');
|
|
||||||
expect(toast).toBe(null);
|
|
||||||
|
|
||||||
for (const screenshotCompare of screenshotCompares) {
|
|
||||||
expect(screenshotCompare).toMatchScreenshot();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,11 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
test('virtual-scroll: basic', async () => {
|
|
||||||
const page = await newE2EPage({
|
|
||||||
url: '/src/components/virtual-scroll/test/basic?ionic:_testing=true',
|
|
||||||
});
|
|
||||||
await page.waitForTimeout(300);
|
|
||||||
|
|
||||||
const compare = await page.compareScreenshot();
|
|
||||||
expect(compare).toMatchScreenshot();
|
|
||||||
});
|
|
@ -1,11 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
test('virtual-scroll: cards', async () => {
|
|
||||||
const page = await newE2EPage({
|
|
||||||
url: '/src/components/virtual-scroll/test/cards?ionic:_testing=true',
|
|
||||||
});
|
|
||||||
await page.waitForTimeout(300);
|
|
||||||
|
|
||||||
const compare = await page.compareScreenshot();
|
|
||||||
expect(compare).toMatchScreenshot();
|
|
||||||
});
|
|
@ -1,10 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
test('themes: css-variables', async () => {
|
|
||||||
const page = await newE2EPage({
|
|
||||||
url: '/src/themes/test/css-variables?ionic:_testing=true',
|
|
||||||
});
|
|
||||||
|
|
||||||
const compare = await page.compareScreenshot();
|
|
||||||
expect(compare).toMatchScreenshot();
|
|
||||||
});
|
|
@ -0,0 +1,43 @@
|
|||||||
|
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();
|
||||||
|
};
|
@ -1,45 +0,0 @@
|
|||||||
import type { E2EPage } from '@stencil/core/testing';
|
|
||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
test('animation:backwards-compatibility animation', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/animation/test/animationbuilder' });
|
|
||||||
await testNavigation(page);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('animation:ios-transition web', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/animation/test/animationbuilder?ionic:mode=ios' });
|
|
||||||
await testNavigation(page);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('animation:ios-transition css', async () => {
|
|
||||||
const page = await newE2EPage({
|
|
||||||
url: '/src/utils/animation/test/animationbuilder?ionic:mode=ios&ionic:_forceCSSAnimations=true',
|
|
||||||
});
|
|
||||||
await testNavigation(page);
|
|
||||||
});
|
|
||||||
|
|
||||||
const testNavigation = async (page: E2EPage) => {
|
|
||||||
const screenshotCompares = [];
|
|
||||||
const ionRouteDidChange = await page.spyOnEvent('ionRouteDidChange');
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot());
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot('end navigation'));
|
|
||||||
|
|
||||||
for (const screenshotCompare of screenshotCompares) {
|
|
||||||
expect(screenshotCompare).toMatchScreenshot();
|
|
||||||
}
|
|
||||||
};
|
|
27
core/src/utils/animation/test/basic/animation.e2e.ts
Normal file
27
core/src/utils/animation/test/basic/animation.e2e.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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();
|
||||||
|
};
|
@ -1,59 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
import { listenForEvent, waitForFunctionTestContext } from '../../../test/utils';
|
|
||||||
|
|
||||||
test(`animation:web: basic`, async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/animation/test/basic' });
|
|
||||||
const screenshotCompares = [];
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot());
|
|
||||||
|
|
||||||
const ANIMATION_FINISHED = 'onIonAnimationFinished';
|
|
||||||
const animationFinishedCount: any = { count: 0 };
|
|
||||||
await page.exposeFunction(ANIMATION_FINISHED, () => {
|
|
||||||
animationFinishedCount.count += 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
const square = await page.$('.square-a');
|
|
||||||
await listenForEvent(page, 'ionAnimationFinished', square, ANIMATION_FINISHED);
|
|
||||||
|
|
||||||
await page.click('.play');
|
|
||||||
await page.waitForSelector('.play');
|
|
||||||
|
|
||||||
await waitForFunctionTestContext(
|
|
||||||
(payload: any) => {
|
|
||||||
return payload.animationFinishedCount.count === 1;
|
|
||||||
},
|
|
||||||
{ animationFinishedCount }
|
|
||||||
);
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot('end animation'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test(`animation:css: basic`, async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/animation/test/basic?ionic:_forceCSSAnimations=true' });
|
|
||||||
const screenshotCompares = [];
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot());
|
|
||||||
|
|
||||||
const ANIMATION_FINISHED = 'onIonAnimationFinished';
|
|
||||||
const animationFinishedCount: any = { count: 0 };
|
|
||||||
await page.exposeFunction(ANIMATION_FINISHED, () => {
|
|
||||||
animationFinishedCount.count += 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
const square = await page.$('.square-a');
|
|
||||||
await listenForEvent(page, 'ionAnimationFinished', square, ANIMATION_FINISHED);
|
|
||||||
|
|
||||||
await page.click('.play');
|
|
||||||
await page.waitForSelector('.play');
|
|
||||||
|
|
||||||
await waitForFunctionTestContext(
|
|
||||||
(payload: any) => {
|
|
||||||
return payload.animationFinishedCount.count === 1;
|
|
||||||
},
|
|
||||||
{ animationFinishedCount }
|
|
||||||
);
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot('end animation'));
|
|
||||||
});
|
|
@ -37,7 +37,7 @@
|
|||||||
])
|
])
|
||||||
.onFinish(() => {
|
.onFinish(() => {
|
||||||
const ev = new CustomEvent('ionAnimationFinished');
|
const ev = new CustomEvent('ionAnimationFinished');
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector('.play').addEventListener('click', () => {
|
document.querySelector('.play').addEventListener('click', () => {
|
||||||
|
37
core/src/utils/animation/test/display/animation.e2e.ts
Normal file
37
core/src/utils/animation/test/display/animation.e2e.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { 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');
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const testDisplay = async (page: E2EPage) => {
|
||||||
|
const ionAnimationFinished = await page.spyOnEvent('ionAnimationFinished');
|
||||||
|
|
||||||
|
await page.click('.play');
|
||||||
|
|
||||||
|
await ionAnimationFinished.next();
|
||||||
|
await expect(ionAnimationFinished).toHaveReceivedEventDetail('AnimationBFinished');
|
||||||
|
|
||||||
|
await ionAnimationFinished.next();
|
||||||
|
await expect(ionAnimationFinished).toHaveReceivedEventDetail('AnimationAFinished');
|
||||||
|
|
||||||
|
await ionAnimationFinished.next();
|
||||||
|
await expect(ionAnimationFinished).toHaveReceivedEventDetail('AnimationRootFinished');
|
||||||
|
|
||||||
|
await expect(ionAnimationFinished).toHaveReceivedEventTimes(3);
|
||||||
|
};
|
@ -1,42 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
import { listenForEvent, waitForFunctionTestContext } from '../../../test/utils';
|
|
||||||
|
|
||||||
test(`animation:web: display`, async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/animation/test/display' });
|
|
||||||
await runTest(page);
|
|
||||||
});
|
|
||||||
|
|
||||||
test(`animation:css: display`, async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/animation/test/display?ionic:_forceCSSAnimations=true' });
|
|
||||||
await runTest(page);
|
|
||||||
});
|
|
||||||
|
|
||||||
const runTest = async (page: any) => {
|
|
||||||
const screenshotCompares = [];
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot());
|
|
||||||
|
|
||||||
const ANIMATION_FINISHED = 'onIonAnimationFinished';
|
|
||||||
const animationStatus = [];
|
|
||||||
await page.exposeFunction(ANIMATION_FINISHED, (ev: any) => {
|
|
||||||
animationStatus.push(ev.detail);
|
|
||||||
});
|
|
||||||
|
|
||||||
const squareA = await page.$('.square-a');
|
|
||||||
await listenForEvent(page, 'ionAnimationFinished', squareA, ANIMATION_FINISHED);
|
|
||||||
|
|
||||||
await page.click('.play');
|
|
||||||
await page.waitForSelector('.play');
|
|
||||||
|
|
||||||
await waitForFunctionTestContext(
|
|
||||||
(payload: any) => {
|
|
||||||
return (
|
|
||||||
payload.animationStatus.join(', ') ===
|
|
||||||
['AnimationBFinished', 'AnimationAFinished', 'AnimationRootFinished'].join(', ')
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{ animationStatus }
|
|
||||||
);
|
|
||||||
screenshotCompares.push(await page.compareScreenshot('end animation'));
|
|
||||||
};
|
|
@ -43,7 +43,7 @@
|
|||||||
])
|
])
|
||||||
.onFinish(() => {
|
.onFinish(() => {
|
||||||
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationAFinished' });
|
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationAFinished' });
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
});
|
});
|
||||||
|
|
||||||
animationB
|
animationB
|
||||||
@ -58,12 +58,12 @@
|
|||||||
])
|
])
|
||||||
.onFinish(() => {
|
.onFinish(() => {
|
||||||
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationBFinished' });
|
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationBFinished' });
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
});
|
});
|
||||||
|
|
||||||
rootAnimation.addAnimation([animationA, animationB]).onFinish(() => {
|
rootAnimation.addAnimation([animationA, animationB]).onFinish(() => {
|
||||||
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationRootFinished' });
|
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationRootFinished' });
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector('.play').addEventListener('click', () => {
|
document.querySelector('.play').addEventListener('click', () => {
|
||||||
|
63
core/src/utils/animation/test/hooks/animation.e2e.ts
Normal file
63
core/src/utils/animation/test/hooks/animation.e2e.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { 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');
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const testHooks = async (page: E2EPage) => {
|
||||||
|
const square = page.locator('.square-a');
|
||||||
|
const ionAnimationFinished = await page.spyOnEvent('ionAnimationFinished');
|
||||||
|
const beforeRead = await page.spyOnEvent('beforeRead');
|
||||||
|
const beforeWrite = await page.spyOnEvent('beforeWrite');
|
||||||
|
const afterRead = await page.spyOnEvent('afterRead');
|
||||||
|
const afterWrite = await page.spyOnEvent('afterWrite');
|
||||||
|
|
||||||
|
// Test initial classes
|
||||||
|
await expect(square).toHaveClass(/hello-world/);
|
||||||
|
await expect(square).not.toHaveClass(/test-class/);
|
||||||
|
|
||||||
|
// Test initial styles
|
||||||
|
await expect(square).toHaveCSS('padding-bottom', '20px');
|
||||||
|
await expect(square).toHaveCSS('color', 'rgb(0, 0, 0)');
|
||||||
|
|
||||||
|
await page.click('.play');
|
||||||
|
|
||||||
|
// Test beforeRemoveClass and beforeAddClass
|
||||||
|
await expect(square).not.toHaveClass(/hello-world/);
|
||||||
|
await expect(square).toHaveClass(/test-class/);
|
||||||
|
|
||||||
|
// Test beforeStyles and beforeClearStyles
|
||||||
|
await expect(square).toHaveCSS('padding-bottom', '0px');
|
||||||
|
await expect(square).toHaveCSS('color', 'rgb(128, 0, 128)');
|
||||||
|
|
||||||
|
await beforeRead.next();
|
||||||
|
await beforeWrite.next();
|
||||||
|
|
||||||
|
await ionAnimationFinished.next();
|
||||||
|
|
||||||
|
await afterRead.next();
|
||||||
|
await afterWrite.next();
|
||||||
|
|
||||||
|
// Test afterRemoveClass and afterAddClass
|
||||||
|
await expect(square).toHaveClass(/hello-world/);
|
||||||
|
await expect(square).not.toHaveClass(/test-class/);
|
||||||
|
|
||||||
|
// Test afterStyles and afterClearStyles
|
||||||
|
await expect(square).toHaveCSS('padding-bottom', '20px');
|
||||||
|
await expect(square).toHaveCSS('color', 'rgb(0, 0, 0)');
|
||||||
|
};
|
@ -1,146 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
import { listenForEvent, waitForFunctionTestContext } from '../../../test/utils';
|
|
||||||
|
|
||||||
test(`animation:web: hooks`, async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/animation/test/hooks' });
|
|
||||||
const screenshotCompares = [];
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot());
|
|
||||||
|
|
||||||
const square = await page.$('.square-a');
|
|
||||||
|
|
||||||
const styles = await getStyles(page, '.square-a');
|
|
||||||
expect(styles.paddingBottom).toEqual('20px');
|
|
||||||
expect(styles.color).toEqual('rgb(0, 0, 0)');
|
|
||||||
|
|
||||||
const classList = await getClassList(square);
|
|
||||||
expect(classList.includes('hello-world')).toEqual(true);
|
|
||||||
expect(classList.includes('test-class')).toEqual(false);
|
|
||||||
|
|
||||||
await waitForEventToBeCalled('afterWrite', page, square, async () => {
|
|
||||||
await waitForEventToBeCalled('afterRead', page, square, async () => {
|
|
||||||
await waitForEventToBeCalled('ionAnimationFinished', page, square, async () => {
|
|
||||||
await waitForEventToBeCalled('beforeWrite', page, square, async () => {
|
|
||||||
await waitForEventToBeCalled('beforeRead', page, square, async () => {
|
|
||||||
await page.click('.play');
|
|
||||||
await page.waitForSelector('.play');
|
|
||||||
|
|
||||||
// Test beforeRemoveClass and beforeAddClass
|
|
||||||
const webClassListAgain = await getClassList(square);
|
|
||||||
expect(webClassListAgain.includes('hello-world')).toEqual(false);
|
|
||||||
expect(webClassListAgain.includes('test-class')).toEqual(true);
|
|
||||||
|
|
||||||
// Test beforeStyles and beforeClearStyles
|
|
||||||
const webStylesAgain = await getStyles(page, '.square-a');
|
|
||||||
expect(webStylesAgain.paddingBottom).toEqual('0px');
|
|
||||||
expect(webStylesAgain.color).toEqual('rgb(128, 0, 128)');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test afterRemoveClass and afterAddClass
|
|
||||||
const classListAgain = await getClassList(square);
|
|
||||||
expect(classListAgain.includes('hello-world')).toEqual(true);
|
|
||||||
expect(classListAgain.includes('test-class')).toEqual(false);
|
|
||||||
|
|
||||||
// Test afterStyles and afterClearStyles
|
|
||||||
const stylesAgain = await getStyles(page, '.square-a');
|
|
||||||
expect(stylesAgain.paddingBottom).toEqual('20px');
|
|
||||||
expect(stylesAgain.color).toEqual('rgb(0, 0, 0)');
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot('end animation'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test(`animation:css: hooks`, async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/animation/test/hooks?ionic:_forceCSSAnimations=true' });
|
|
||||||
const screenshotCompares = [];
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot());
|
|
||||||
|
|
||||||
const square = await page.$('.square-a');
|
|
||||||
|
|
||||||
const styles = await getStyles(page, '.square-a');
|
|
||||||
expect(styles.paddingBottom).toEqual('20px');
|
|
||||||
expect(styles.color).toEqual('rgb(0, 0, 0)');
|
|
||||||
|
|
||||||
const classList = await getClassList(square);
|
|
||||||
expect(classList.includes('hello-world')).toEqual(true);
|
|
||||||
expect(classList.includes('test-class')).toEqual(false);
|
|
||||||
|
|
||||||
await waitForEventToBeCalled('afterWrite', page, square, async () => {
|
|
||||||
await waitForEventToBeCalled('afterRead', page, square, async () => {
|
|
||||||
await waitForEventToBeCalled('ionAnimationFinished', page, square, async () => {
|
|
||||||
await waitForEventToBeCalled('beforeWrite', page, square, async () => {
|
|
||||||
await waitForEventToBeCalled('beforeRead', page, square, async () => {
|
|
||||||
await page.click('.play');
|
|
||||||
await page.waitForSelector('.play');
|
|
||||||
|
|
||||||
// Test beforeRemoveClass and beforeAddClass
|
|
||||||
const cssClassListAgain = await getClassList(square);
|
|
||||||
expect(cssClassListAgain.includes('hello-world')).toEqual(false);
|
|
||||||
expect(cssClassListAgain.includes('test-class')).toEqual(true);
|
|
||||||
|
|
||||||
// Test beforeStyles and beforeClearStyles
|
|
||||||
const cssStylesAgain = await getStyles(page, '.square-a');
|
|
||||||
expect(cssStylesAgain.paddingBottom).toEqual('0px');
|
|
||||||
expect(cssStylesAgain.color).toEqual('rgb(128, 0, 128)');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test afterRemoveClass and afterAddClass
|
|
||||||
const classListAgain = await getClassList(square);
|
|
||||||
expect(classListAgain.includes('hello-world')).toEqual(true);
|
|
||||||
expect(classListAgain.includes('test-class')).toEqual(false);
|
|
||||||
|
|
||||||
// Test afterStyles and afterClearStyles
|
|
||||||
const stylesAgain = await getStyles(page, '.square-a');
|
|
||||||
expect(stylesAgain.paddingBottom).toEqual('20px');
|
|
||||||
expect(stylesAgain.color).toEqual('rgb(0, 0, 0)');
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot('end animation'));
|
|
||||||
});
|
|
||||||
|
|
||||||
const waitForEventToBeCalled = async (eventName: string, page: any, el: HTMLElement, fn: any, num = 1) => {
|
|
||||||
const EVENT_FIRED = `on${eventName}`;
|
|
||||||
const eventFiredCount: any = { count: 0 };
|
|
||||||
await page.exposeFunction(EVENT_FIRED, () => {
|
|
||||||
eventFiredCount.count += 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
await listenForEvent(page, eventName, el, EVENT_FIRED);
|
|
||||||
|
|
||||||
if (fn) {
|
|
||||||
await fn();
|
|
||||||
}
|
|
||||||
|
|
||||||
await waitForFunctionTestContext(
|
|
||||||
(payload: any) => {
|
|
||||||
return payload.eventFiredCount.count === payload.num;
|
|
||||||
},
|
|
||||||
{ eventFiredCount, num }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStyles = async (page: any, selector: string) => {
|
|
||||||
return page.evaluate(
|
|
||||||
(payload: any) => {
|
|
||||||
const el = document.querySelector(payload.selector);
|
|
||||||
|
|
||||||
return JSON.parse(JSON.stringify(getComputedStyle(el)));
|
|
||||||
},
|
|
||||||
{ selector }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getClassList = async (el: HTMLElement) => {
|
|
||||||
const classListObject = await el.getProperty('classList');
|
|
||||||
const jsonValue = await classListObject.jsonValue();
|
|
||||||
|
|
||||||
return Object.values(jsonValue);
|
|
||||||
};
|
|
@ -34,19 +34,19 @@
|
|||||||
.beforeClearStyles(['padding-bottom'])
|
.beforeClearStyles(['padding-bottom'])
|
||||||
.beforeAddRead(() => {
|
.beforeAddRead(() => {
|
||||||
const ev = new CustomEvent('beforeRead');
|
const ev = new CustomEvent('beforeRead');
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
})
|
})
|
||||||
.beforeAddWrite(() => {
|
.beforeAddWrite(() => {
|
||||||
const ev = new CustomEvent('beforeWrite');
|
const ev = new CustomEvent('beforeWrite');
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
})
|
})
|
||||||
.afterAddRead(() => {
|
.afterAddRead(() => {
|
||||||
const ev = new CustomEvent('afterRead');
|
const ev = new CustomEvent('afterRead');
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
})
|
})
|
||||||
.afterAddWrite(() => {
|
.afterAddWrite(() => {
|
||||||
const ev = new CustomEvent('afterWrite');
|
const ev = new CustomEvent('afterWrite');
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
})
|
})
|
||||||
.afterAddClass(['hello-world'])
|
.afterAddClass(['hello-world'])
|
||||||
.afterRemoveClass(['test-class'])
|
.afterRemoveClass(['test-class'])
|
||||||
@ -60,7 +60,7 @@
|
|||||||
])
|
])
|
||||||
.onFinish(() => {
|
.onFinish(() => {
|
||||||
const ev = new CustomEvent('ionAnimationFinished');
|
const ev = new CustomEvent('ionAnimationFinished');
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector('.play').addEventListener('click', () => {
|
document.querySelector('.play').addEventListener('click', () => {
|
||||||
|
46
core/src/utils/animation/test/multiple/animation.e2e.ts
Normal file
46
core/src/utils/animation/test/multiple/animation.e2e.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { 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');
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should resolve grouped animations using web animations`, async ({ page }) => {
|
||||||
|
await page.goto('/src/utils/animation/test/multiple');
|
||||||
|
await testMultiple(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should resolve grouped animations using css animations`, async ({ page }) => {
|
||||||
|
await page.goto('/src/utils/animation/test/multiple?ionic:_forceCSSAnimations=true');
|
||||||
|
await testMultiple(page);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const testMultiple = async (page: E2EPage) => {
|
||||||
|
const ionAnimationFinished = await page.spyOnEvent('ionAnimationFinished');
|
||||||
|
|
||||||
|
await page.click('.play');
|
||||||
|
|
||||||
|
await ionAnimationFinished.next();
|
||||||
|
await expect(ionAnimationFinished).toHaveReceivedEventDetail('AnimationCSubBFinished');
|
||||||
|
|
||||||
|
await ionAnimationFinished.next();
|
||||||
|
await expect(ionAnimationFinished).toHaveReceivedEventDetail('AnimationBFinished');
|
||||||
|
|
||||||
|
await ionAnimationFinished.next();
|
||||||
|
await expect(ionAnimationFinished).toHaveReceivedEventDetail('AnimationCSubAFinished');
|
||||||
|
|
||||||
|
await ionAnimationFinished.next();
|
||||||
|
await expect(ionAnimationFinished).toHaveReceivedEventDetail('AnimationCFinished');
|
||||||
|
|
||||||
|
await ionAnimationFinished.next();
|
||||||
|
await expect(ionAnimationFinished).toHaveReceivedEventDetail('AnimationAFinished');
|
||||||
|
|
||||||
|
await ionAnimationFinished.next();
|
||||||
|
await expect(ionAnimationFinished).toHaveReceivedEventDetail('AnimationRootFinished');
|
||||||
|
|
||||||
|
await expect(ionAnimationFinished).toHaveReceivedEventTimes(6);
|
||||||
|
};
|
@ -1,77 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
import { listenForEvent, waitForFunctionTestContext } from '../../../test/utils';
|
|
||||||
|
|
||||||
test(`animation:web: multiple`, async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/animation/test/multiple' });
|
|
||||||
const screenshotCompares = [];
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot());
|
|
||||||
|
|
||||||
const ANIMATION_FINISHED = 'onIonAnimationFinished';
|
|
||||||
const animationStatus = [];
|
|
||||||
await page.exposeFunction(ANIMATION_FINISHED, (ev: any) => {
|
|
||||||
animationStatus.push(ev.detail);
|
|
||||||
});
|
|
||||||
|
|
||||||
const squareA = await page.$('.square-a');
|
|
||||||
await listenForEvent(page, 'ionAnimationFinished', squareA, ANIMATION_FINISHED);
|
|
||||||
|
|
||||||
await page.click('.play');
|
|
||||||
await page.waitForSelector('.play');
|
|
||||||
|
|
||||||
await waitForFunctionTestContext(
|
|
||||||
(payload: any) => {
|
|
||||||
return (
|
|
||||||
payload.animationStatus.join(', ') ===
|
|
||||||
[
|
|
||||||
'AnimationCSubBFinished',
|
|
||||||
'AnimationBFinished',
|
|
||||||
'AnimationCSubAFinished',
|
|
||||||
'AnimationCFinished',
|
|
||||||
'AnimationAFinished',
|
|
||||||
'AnimationRootFinished',
|
|
||||||
].join(', ')
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{ animationStatus }
|
|
||||||
);
|
|
||||||
screenshotCompares.push(await page.compareScreenshot('end animation'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test(`animation:css: multiple`, async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/animation/test/multiple?ionic:_forceCSSAnimations=true' });
|
|
||||||
const screenshotCompares = [];
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot());
|
|
||||||
|
|
||||||
const ANIMATION_FINISHED = 'onIonAnimationFinished';
|
|
||||||
const animationStatus = [];
|
|
||||||
await page.exposeFunction(ANIMATION_FINISHED, (ev: any) => {
|
|
||||||
animationStatus.push(ev.detail);
|
|
||||||
});
|
|
||||||
|
|
||||||
const squareA = await page.$('.square-a');
|
|
||||||
await listenForEvent(page, 'ionAnimationFinished', squareA, ANIMATION_FINISHED);
|
|
||||||
|
|
||||||
await page.click('.play');
|
|
||||||
await page.waitForSelector('.play');
|
|
||||||
|
|
||||||
await waitForFunctionTestContext(
|
|
||||||
(payload: any) => {
|
|
||||||
return (
|
|
||||||
payload.animationStatus.join(', ') ===
|
|
||||||
[
|
|
||||||
'AnimationCSubBFinished',
|
|
||||||
'AnimationBFinished',
|
|
||||||
'AnimationCSubAFinished',
|
|
||||||
'AnimationCFinished',
|
|
||||||
'AnimationAFinished',
|
|
||||||
'AnimationRootFinished',
|
|
||||||
].join(', ')
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{ animationStatus }
|
|
||||||
);
|
|
||||||
screenshotCompares.push(await page.compareScreenshot('end animation'));
|
|
||||||
});
|
|
@ -56,7 +56,7 @@
|
|||||||
})
|
})
|
||||||
.onFinish(() => {
|
.onFinish(() => {
|
||||||
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationAFinished' });
|
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationAFinished' });
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
});
|
});
|
||||||
|
|
||||||
animationB
|
animationB
|
||||||
@ -78,7 +78,7 @@
|
|||||||
})
|
})
|
||||||
.onFinish(() => {
|
.onFinish(() => {
|
||||||
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationBFinished' });
|
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationBFinished' });
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
});
|
});
|
||||||
|
|
||||||
animationC
|
animationC
|
||||||
@ -100,7 +100,7 @@
|
|||||||
})
|
})
|
||||||
.onFinish(() => {
|
.onFinish(() => {
|
||||||
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationCFinished' });
|
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationCFinished' });
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
});
|
});
|
||||||
|
|
||||||
animationCSubA
|
animationCSubA
|
||||||
@ -110,12 +110,12 @@
|
|||||||
.fromTo('color', 'red', 'blue')
|
.fromTo('color', 'red', 'blue')
|
||||||
.onFinish(() => {
|
.onFinish(() => {
|
||||||
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationCSubAFinished' });
|
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationCSubAFinished' });
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
});
|
});
|
||||||
|
|
||||||
animationCSubB.addElement(squareCSubText).onFinish(() => {
|
animationCSubB.addElement(squareCSubText).onFinish(() => {
|
||||||
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationCSubBFinished' });
|
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationCSubBFinished' });
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
});
|
});
|
||||||
|
|
||||||
animationC.addAnimation([animationCSubA, animationCSubB]);
|
animationC.addAnimation([animationCSubA, animationCSubB]);
|
||||||
@ -125,7 +125,7 @@
|
|||||||
.fill('none')
|
.fill('none')
|
||||||
.onFinish(() => {
|
.onFinish(() => {
|
||||||
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationRootFinished' });
|
const ev = new CustomEvent('ionAnimationFinished', { detail: 'AnimationRootFinished' });
|
||||||
squareA.dispatchEvent(ev);
|
window.dispatchEvent(ev);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector('.play').addEventListener('click', () => {
|
document.querySelector('.play').addEventListener('click', () => {
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
import { dragElementBy } from '@utils/test';
|
|
||||||
|
|
||||||
test('swipe to go back should complete', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/gesture/test?ionic:mode=ios' });
|
|
||||||
|
|
||||||
const nav = await page.find('ion-nav');
|
|
||||||
const ionNavDidChange = await nav.spyOnEvent('ionNavDidChange');
|
|
||||||
|
|
||||||
await page.click('.next');
|
|
||||||
await ionNavDidChange.next();
|
|
||||||
|
|
||||||
const content = await page.$('.page-two-content');
|
|
||||||
|
|
||||||
const width = await page.evaluate(() => window.innerWidth);
|
|
||||||
await dragElementBy(content, page, width, 0, { x: 25, y: 100 });
|
|
||||||
|
|
||||||
await ionNavDidChange.next();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('swipe to go back should complete in rtl', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/gesture/test?rtl=true&ionic:mode=ios' });
|
|
||||||
|
|
||||||
const nav = await page.find('ion-nav');
|
|
||||||
const ionNavDidChange = await nav.spyOnEvent('ionNavDidChange');
|
|
||||||
|
|
||||||
await page.click('.next');
|
|
||||||
await ionNavDidChange.next();
|
|
||||||
|
|
||||||
const width = await page.evaluate(() => window.innerWidth);
|
|
||||||
|
|
||||||
const content = await page.$('.page-two-content');
|
|
||||||
await dragElementBy(content, page, -width, 0, { x: width - 25, y: 100 });
|
|
||||||
|
|
||||||
await ionNavDidChange.next();
|
|
||||||
});
|
|
@ -1,34 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
test('framework-delegate: should present modal already at ion-app root', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/test/framework-delegate?ionic:_testing=true' });
|
|
||||||
|
|
||||||
const button = await page.find('#button-inline-root');
|
|
||||||
await button.click();
|
|
||||||
|
|
||||||
const modal = await page.find('#inline-root');
|
|
||||||
expect(modal).not.toBe(null);
|
|
||||||
await modal.waitForVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('framework-delegate: should present modal in content', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/test/framework-delegate?ionic:_testing=true' });
|
|
||||||
|
|
||||||
const button = await page.find('#button-inline-content');
|
|
||||||
await button.click();
|
|
||||||
|
|
||||||
const modal = await page.find('#inline-content');
|
|
||||||
expect(modal).not.toBe(null);
|
|
||||||
await modal.waitForVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('framework-delegate: should present modal via controller', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/test/framework-delegate?ionic:_testing=true' });
|
|
||||||
|
|
||||||
const button = await page.find('#button-controller');
|
|
||||||
await button.click();
|
|
||||||
|
|
||||||
const modal = await page.find('#controller');
|
|
||||||
expect(modal).not.toBe(null);
|
|
||||||
await modal.waitForVisible();
|
|
||||||
});
|
|
@ -0,0 +1,40 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
@ -9,6 +9,7 @@
|
|||||||
/>
|
/>
|
||||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||||
|
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { modalController, createAnimation } from '../../../../../dist/ionic/index.esm.js';
|
import { modalController, createAnimation } from '../../../../../dist/ionic/index.esm.js';
|
||||||
|
@ -1,169 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
import { checkComponentModeClasses, checkModeClasses } from '../utils';
|
|
||||||
|
|
||||||
// This test is to loop through all components that should have
|
|
||||||
// specific classes added and test them
|
|
||||||
test('component: modes', async () => {
|
|
||||||
const page = await newE2EPage({
|
|
||||||
url: '/src/utils/test/modes?ionic:_testing=true',
|
|
||||||
});
|
|
||||||
|
|
||||||
// First test: .button class
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// components that need to have the `button` class
|
|
||||||
// for use in styling by other components (`ion-buttons`)
|
|
||||||
// e.g. <ion-back-button class="button">
|
|
||||||
let tags = ['ion-button', 'ion-back-button', 'ion-menu-button'];
|
|
||||||
|
|
||||||
for (const tag of tags) {
|
|
||||||
const el = await page.find(tag);
|
|
||||||
expect(el).toHaveClass('button');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second test: .item class
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// components that need to have the `item` class
|
|
||||||
// for use in styling by other components
|
|
||||||
// e.g. <ion-item-divider class="item">
|
|
||||||
tags = ['ion-item', 'ion-item-divider', 'ion-item-group'];
|
|
||||||
|
|
||||||
for (const tag of tags) {
|
|
||||||
const el = await page.find(tag);
|
|
||||||
expect(el).toHaveClass('item');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Third test: .{component}-{mode} class
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// components that need to have their tag name
|
|
||||||
// + mode as a class for internal styling
|
|
||||||
// e.g. <ion-card-content class="card-content-md">
|
|
||||||
tags = [
|
|
||||||
'ion-card-content',
|
|
||||||
'ion-footer',
|
|
||||||
'ion-header',
|
|
||||||
'ion-infinite-scroll-content',
|
|
||||||
'ion-item-group',
|
|
||||||
'ion-item-options',
|
|
||||||
'ion-list',
|
|
||||||
'ion-picker',
|
|
||||||
'ion-refresher',
|
|
||||||
'ion-slides',
|
|
||||||
'ion-split-pane',
|
|
||||||
];
|
|
||||||
|
|
||||||
const globalMode = await page.evaluate(() => document.documentElement.getAttribute('mode'));
|
|
||||||
for (const tag of tags) {
|
|
||||||
const el = await page.find(tag);
|
|
||||||
await checkComponentModeClasses(el, globalMode!);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fourth test: .{mode} class
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// components that need to have the mode class
|
|
||||||
// added for external / user styling
|
|
||||||
// e.g. <ion-badge class="md">
|
|
||||||
tags = [
|
|
||||||
'ion-action-sheet',
|
|
||||||
'ion-alert',
|
|
||||||
'ion-app',
|
|
||||||
'ion-avatar',
|
|
||||||
'ion-back-button',
|
|
||||||
'ion-backdrop',
|
|
||||||
'ion-badge',
|
|
||||||
'ion-button',
|
|
||||||
'ion-buttons',
|
|
||||||
'ion-card-content',
|
|
||||||
'ion-card-header',
|
|
||||||
'ion-card-subtitle',
|
|
||||||
'ion-card-title',
|
|
||||||
'ion-card',
|
|
||||||
'ion-checkbox',
|
|
||||||
'ion-chip',
|
|
||||||
'ion-col',
|
|
||||||
'ion-content',
|
|
||||||
'ion-datetime',
|
|
||||||
'ion-fab',
|
|
||||||
'ion-fab-button',
|
|
||||||
'ion-fab-list',
|
|
||||||
'ion-footer',
|
|
||||||
'ion-grid',
|
|
||||||
'ion-header',
|
|
||||||
'ion-icon',
|
|
||||||
'ion-img',
|
|
||||||
'ion-infinite-scroll',
|
|
||||||
'ion-infinite-scroll-content',
|
|
||||||
'ion-input',
|
|
||||||
'ion-item',
|
|
||||||
'ion-item-divider',
|
|
||||||
'ion-item-group',
|
|
||||||
'ion-item-option',
|
|
||||||
'ion-item-options',
|
|
||||||
'ion-item-sliding',
|
|
||||||
'ion-label',
|
|
||||||
'ion-list',
|
|
||||||
'ion-list-header',
|
|
||||||
'ion-loading',
|
|
||||||
'ion-modal',
|
|
||||||
'ion-menu',
|
|
||||||
'ion-menu-button',
|
|
||||||
'ion-menu-toggle',
|
|
||||||
'ion-note',
|
|
||||||
'ion-picker',
|
|
||||||
'ion-picker-column',
|
|
||||||
'ion-popover',
|
|
||||||
'ion-progress-bar',
|
|
||||||
'ion-radio',
|
|
||||||
'ion-radio-group',
|
|
||||||
'ion-range',
|
|
||||||
'ion-refresher',
|
|
||||||
'ion-refresher-content',
|
|
||||||
'ion-reorder',
|
|
||||||
'ion-reorder-group',
|
|
||||||
'ion-ripple-effect',
|
|
||||||
'ion-router-link',
|
|
||||||
'ion-row',
|
|
||||||
'ion-searchbar',
|
|
||||||
'ion-segment',
|
|
||||||
'ion-segment-button',
|
|
||||||
'ion-select',
|
|
||||||
'ion-select-option',
|
|
||||||
'ion-select-popover',
|
|
||||||
'ion-skeleton-text',
|
|
||||||
'ion-slide',
|
|
||||||
'ion-slides',
|
|
||||||
'ion-spinner',
|
|
||||||
'ion-split-pane',
|
|
||||||
'ion-tab-bar',
|
|
||||||
'ion-tab-button',
|
|
||||||
'ion-text',
|
|
||||||
'ion-textarea',
|
|
||||||
'ion-thumbnail',
|
|
||||||
'ion-title',
|
|
||||||
'ion-toast',
|
|
||||||
'ion-toggle',
|
|
||||||
'ion-toolbar',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const tag of tags) {
|
|
||||||
await page.waitForSelector(tag);
|
|
||||||
const el = await page.find(tag);
|
|
||||||
await checkModeClasses(el, globalMode!);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fifth test: {mode} attribute on non-ionic ancestor element
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// non-ionic ancestor components with a mode attribute
|
|
||||||
// e.g. <p mode="foo">
|
|
||||||
const ancestorTags = ['p[mode]'];
|
|
||||||
const childTag = 'ion-label';
|
|
||||||
|
|
||||||
for (const tag of ancestorTags) {
|
|
||||||
await page.waitForSelector(tag);
|
|
||||||
const ancestor = await page.find(tag);
|
|
||||||
const mode = ancestor.getAttribute('mode');
|
|
||||||
const expectedMode = ['ios', 'md'].includes(mode) ? mode : globalMode!;
|
|
||||||
const el = await ancestor.find(childTag);
|
|
||||||
await checkModeClasses(el, expectedMode);
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,191 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" dir="ltr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>Components - Modes</title>
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
|
||||||
/>
|
|
||||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
|
||||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
|
||||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
|
||||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
|
||||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body onLoad="load()">
|
|
||||||
<ion-app>
|
|
||||||
<ion-split-pane content-id="main">
|
|
||||||
<ion-menu content-id="main"> </ion-menu>
|
|
||||||
|
|
||||||
<div class="ion-page" id="main">
|
|
||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-title>Components: Modes</ion-title>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-back-button></ion-back-button>
|
|
||||||
<ion-button></ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content>
|
|
||||||
<ion-action-sheet></ion-action-sheet>
|
|
||||||
<ion-alert></ion-alert>
|
|
||||||
<ion-avatar></ion-avatar>
|
|
||||||
<ion-backdrop></ion-backdrop>
|
|
||||||
<ion-badge></ion-badge>
|
|
||||||
<ion-button></ion-button>
|
|
||||||
<ion-card>
|
|
||||||
<ion-card-header>
|
|
||||||
<ion-card-subtitle></ion-card-subtitle>
|
|
||||||
<ion-card-title></ion-card-title>
|
|
||||||
</ion-card-header>
|
|
||||||
<ion-card-content></ion-card-content>
|
|
||||||
</ion-card>
|
|
||||||
|
|
||||||
<ion-checkbox></ion-checkbox>
|
|
||||||
<ion-chip></ion-chip>
|
|
||||||
<ion-col></ion-col>
|
|
||||||
<ion-datetime></ion-datetime>
|
|
||||||
<ion-fab-button></ion-fab-button>
|
|
||||||
<ion-fab-list></ion-fab-list>
|
|
||||||
<ion-fab></ion-fab>
|
|
||||||
<ion-footer></ion-footer>
|
|
||||||
<ion-grid></ion-grid>
|
|
||||||
<ion-icon name="star"></ion-icon>
|
|
||||||
<ion-img></ion-img>
|
|
||||||
<ion-infinite-scroll></ion-infinite-scroll>
|
|
||||||
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
|
||||||
<ion-input></ion-input>
|
|
||||||
<ion-item-divider></ion-item-divider>
|
|
||||||
<ion-item-group></ion-item-group>
|
|
||||||
<ion-item-option></ion-item-option>
|
|
||||||
<ion-item-options></ion-item-options>
|
|
||||||
<ion-item-sliding></ion-item-sliding>
|
|
||||||
<ion-item></ion-item>
|
|
||||||
<ion-label></ion-label>
|
|
||||||
<ion-list></ion-list>
|
|
||||||
<ion-list-header></ion-list-header>
|
|
||||||
<ion-loading></ion-loading>
|
|
||||||
<ion-menu-button></ion-menu-button>
|
|
||||||
<ion-menu-toggle></ion-menu-toggle>
|
|
||||||
<ion-modal></ion-modal>
|
|
||||||
<ion-note></ion-note>
|
|
||||||
<ion-picker></ion-picker>
|
|
||||||
<ion-popover></ion-popover>
|
|
||||||
<ion-progress-bar></ion-progress-bar>
|
|
||||||
<ion-radio-group></ion-radio-group>
|
|
||||||
<ion-radio></ion-radio>
|
|
||||||
<ion-range></ion-range>
|
|
||||||
<ion-refresher></ion-refresher>
|
|
||||||
<ion-refresher-content></ion-refresher-content>
|
|
||||||
<ion-reorder-group></ion-reorder-group>
|
|
||||||
<ion-reorder></ion-reorder>
|
|
||||||
<ion-ripple-effect></ion-ripple-effect>
|
|
||||||
<ion-router-link></ion-router-link>
|
|
||||||
<ion-row></ion-row>
|
|
||||||
<ion-searchbar></ion-searchbar>
|
|
||||||
<ion-segment-button></ion-segment-button>
|
|
||||||
<ion-segment></ion-segment>
|
|
||||||
<ion-select-option></ion-select-option>
|
|
||||||
<ion-select-popover></ion-select-popover>
|
|
||||||
<ion-select></ion-select>
|
|
||||||
<ion-skeleton-text></ion-skeleton-text>
|
|
||||||
<ion-slide></ion-slide>
|
|
||||||
<ion-slides></ion-slides>
|
|
||||||
<ion-spinner></ion-spinner>
|
|
||||||
<ion-tab-bar></ion-tab-bar>
|
|
||||||
<ion-tab-button></ion-tab-button>
|
|
||||||
<ion-text></ion-text>
|
|
||||||
<ion-textarea></ion-textarea>
|
|
||||||
<ion-thumbnail></ion-thumbnail>
|
|
||||||
<ion-title></ion-title>
|
|
||||||
<ion-toast></ion-toast>
|
|
||||||
<ion-toggle></ion-toggle>
|
|
||||||
<ion-toolbar></ion-toolbar>
|
|
||||||
<p mode="ios">
|
|
||||||
<ion-label></ion-label>
|
|
||||||
</p>
|
|
||||||
<p mode="md">
|
|
||||||
<ion-label></ion-label>
|
|
||||||
</p>
|
|
||||||
<p mode="foo">
|
|
||||||
<ion-label></ion-label>
|
|
||||||
</p>
|
|
||||||
</ion-content>
|
|
||||||
</div>
|
|
||||||
</ion-split-pane>
|
|
||||||
</ion-app>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
async function load() {
|
|
||||||
const actionSheet = document.querySelector('ion-action-sheet');
|
|
||||||
|
|
||||||
actionSheet.buttons = [
|
|
||||||
{
|
|
||||||
text: 'Delete',
|
|
||||||
role: 'destructive',
|
|
||||||
icon: 'trash',
|
|
||||||
handler: () => {
|
|
||||||
console.log('Delete clicked');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Share',
|
|
||||||
icon: 'share',
|
|
||||||
handler: () => {
|
|
||||||
console.log('Share clicked');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Cancel',
|
|
||||||
icon: 'close',
|
|
||||||
role: 'cancel',
|
|
||||||
handler: () => {
|
|
||||||
console.log('Cancel clicked');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
await actionSheet.present();
|
|
||||||
|
|
||||||
const picker = document.querySelector('ion-picker');
|
|
||||||
if (picker.componentOnReady) {
|
|
||||||
await picker.componentOnReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
picker.buttons = [{ text: 'Cancel', role: 'cancel' }, { text: 'Done' }];
|
|
||||||
|
|
||||||
picker.columns = [
|
|
||||||
{
|
|
||||||
name: 'month',
|
|
||||||
align: 'left',
|
|
||||||
selectedIndex: 0,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
text: 'June',
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'July',
|
|
||||||
value: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
prevSelected: 0,
|
|
||||||
selectedIndex: 0,
|
|
||||||
optionsWidth: '68px',
|
|
||||||
columnWidth: '200px',
|
|
||||||
prefixWidth: '100px',
|
|
||||||
suffixWidth: '100px',
|
|
||||||
prefix: undefined,
|
|
||||||
suffix: undefined,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
await picker.present();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,178 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
import { getActiveElementParent } from '../utils';
|
|
||||||
|
|
||||||
test('overlays: hardware back button: should dismiss a presented overlay', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' });
|
|
||||||
|
|
||||||
const createAndPresentButton = await page.find('#create-and-present');
|
|
||||||
|
|
||||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
|
||||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
|
||||||
|
|
||||||
await createAndPresentButton.click();
|
|
||||||
const modal = await page.find('ion-modal');
|
|
||||||
expect(modal).not.toBe(null);
|
|
||||||
|
|
||||||
await ionModalDidPresent.next();
|
|
||||||
|
|
||||||
const simulateButton = await modal.find('#modal-simulate');
|
|
||||||
expect(simulateButton).not.toBe(null);
|
|
||||||
|
|
||||||
await simulateButton.click();
|
|
||||||
|
|
||||||
await ionModalDidDismiss.next();
|
|
||||||
|
|
||||||
await page.waitForSelector('ion-modal', { hidden: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('overlays: hardware back button: should dismiss the presented overlay, even though another hidden modal was added last', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' });
|
|
||||||
|
|
||||||
const createAndPresentButton = await page.find('#create-and-present');
|
|
||||||
|
|
||||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
|
||||||
|
|
||||||
await createAndPresentButton.click();
|
|
||||||
const modal = await page.find('ion-modal');
|
|
||||||
expect(modal).not.toBe(null);
|
|
||||||
|
|
||||||
await ionModalDidPresent.next();
|
|
||||||
|
|
||||||
const createButton = await page.find('#modal-create');
|
|
||||||
await createButton.click();
|
|
||||||
|
|
||||||
const modals = await page.$$('ion-modal');
|
|
||||||
expect(modals.length).toEqual(2);
|
|
||||||
|
|
||||||
expect(await modals[0].evaluate((node) => node.classList.contains('overlay-hidden'))).toEqual(false);
|
|
||||||
expect(await modals[1].evaluate((node) => node.classList.contains('overlay-hidden'))).toEqual(true);
|
|
||||||
|
|
||||||
const simulateButton = await modal.find('#modal-simulate');
|
|
||||||
expect(simulateButton).not.toBe(null);
|
|
||||||
|
|
||||||
await simulateButton.click();
|
|
||||||
|
|
||||||
expect(await modals[0].evaluate((node) => node.classList.contains('overlay-hidden'))).toEqual(true);
|
|
||||||
expect(await modals[1].evaluate((node) => node.classList.contains('overlay-hidden'))).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('overlays: Esc: should dismiss a presented overlay', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' });
|
|
||||||
|
|
||||||
const createAndPresentButton = await page.find('#create-and-present');
|
|
||||||
|
|
||||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
|
||||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
|
||||||
|
|
||||||
await createAndPresentButton.click();
|
|
||||||
const modal = await page.find('ion-modal');
|
|
||||||
expect(modal).not.toBe(null);
|
|
||||||
|
|
||||||
await ionModalDidPresent.next();
|
|
||||||
|
|
||||||
await page.keyboard.press('Escape');
|
|
||||||
|
|
||||||
await ionModalDidDismiss.next();
|
|
||||||
|
|
||||||
await page.waitForSelector('ion-modal', { hidden: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('overlays: Esc: should dismiss the presented overlay, even though another hidden modal was added last', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' });
|
|
||||||
|
|
||||||
const createAndPresentButton = await page.find('#create-and-present');
|
|
||||||
|
|
||||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
|
||||||
|
|
||||||
await createAndPresentButton.click();
|
|
||||||
const modal = await page.find('ion-modal');
|
|
||||||
expect(modal).not.toBe(null);
|
|
||||||
|
|
||||||
await ionModalDidPresent.next();
|
|
||||||
|
|
||||||
const createButton = await page.find('#modal-create');
|
|
||||||
await createButton.click();
|
|
||||||
|
|
||||||
const modals = await page.$$('ion-modal');
|
|
||||||
expect(modals.length).toEqual(2);
|
|
||||||
|
|
||||||
await page.keyboard.press('Escape');
|
|
||||||
|
|
||||||
await page.waitForSelector('ion-modal#ion-overlay-1', { hidden: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('overlays: Nested: should dismiss the top overlay', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' });
|
|
||||||
|
|
||||||
const createNestedButton = await page.find('#create-nested');
|
|
||||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
|
||||||
|
|
||||||
await createNestedButton.click();
|
|
||||||
|
|
||||||
await ionModalDidPresent.next();
|
|
||||||
|
|
||||||
const modal = await page.find('ion-modal');
|
|
||||||
expect(modal).not.toBe(null);
|
|
||||||
|
|
||||||
const dismissNestedOverlayButton = await page.find('#dismiss-modal-nested-overlay');
|
|
||||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
|
||||||
|
|
||||||
await dismissNestedOverlayButton.click();
|
|
||||||
|
|
||||||
await ionModalDidDismiss.next();
|
|
||||||
|
|
||||||
const modals = await page.$$('ion-modal');
|
|
||||||
expect(modals.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('toast should not cause focus trapping', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' });
|
|
||||||
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent');
|
|
||||||
|
|
||||||
await page.click('#create-and-present-toast');
|
|
||||||
await ionToastDidPresent.next();
|
|
||||||
|
|
||||||
await page.click('#root-input');
|
|
||||||
|
|
||||||
const parentEl = await getActiveElementParent(page);
|
|
||||||
expect(parentEl.id).toEqual('root-input');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('toast should not cause focus trapping even when opened from a focus trapping overlay', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' });
|
|
||||||
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();
|
|
||||||
|
|
||||||
await page.click('.modal-input');
|
|
||||||
|
|
||||||
const parentEl = await getActiveElementParent(page);
|
|
||||||
expect(parentEl.className).toContain('modal-input-0');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('focus trapping should only run on the top-most overlay', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' });
|
|
||||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
|
||||||
|
|
||||||
await page.click('#create-and-present');
|
|
||||||
await ionModalDidPresent.next();
|
|
||||||
|
|
||||||
await page.click('.modal-0 .modal-input');
|
|
||||||
|
|
||||||
const parentEl = await getActiveElementParent(page);
|
|
||||||
expect(parentEl.className).toContain('modal-input-0');
|
|
||||||
|
|
||||||
await page.click('#modal-create-and-present');
|
|
||||||
await ionModalDidPresent.next();
|
|
||||||
|
|
||||||
await page.click('.modal-1 .modal-input');
|
|
||||||
|
|
||||||
const parentElAgain = await getActiveElementParent(page);
|
|
||||||
expect(parentElAgain.className).toContain('modal-input-1');
|
|
||||||
});
|
|
@ -9,6 +9,7 @@
|
|||||||
/>
|
/>
|
||||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||||
|
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { modalController, toastController, createAnimation } from '../../../../../dist/ionic/index.esm.js';
|
import { modalController, toastController, createAnimation } from '../../../../../dist/ionic/index.esm.js';
|
||||||
|
@ -1,6 +1,109 @@
|
|||||||
import { expect } from '@playwright/test';
|
import { expect } from '@playwright/test';
|
||||||
import { test } from '@utils/test/playwright';
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test.describe('overlays: focus', () => {
|
test.describe('overlays: focus', () => {
|
||||||
test.beforeEach(({ skip }) => {
|
test.beforeEach(({ skip }) => {
|
||||||
skip.rtl();
|
skip.rtl();
|
||||||
@ -116,4 +219,57 @@ test.describe('overlays: focus', () => {
|
|||||||
await page.keyboard.press(tabKey);
|
await page.keyboard.press(tabKey);
|
||||||
await expect(disabledFalseButton).toBeFocused();
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,248 +0,0 @@
|
|||||||
import type { E2EElement, E2EPage } from '@stencil/core/testing';
|
|
||||||
import type { ElementHandle, SerializableOrJSHandle } from 'puppeteer';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* page.evaluate can only return a serializable value,
|
|
||||||
* so it is not possible to return the full element.
|
|
||||||
* Instead, we return an object with some common
|
|
||||||
* properties that you may want to access in a test.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const getSerialElement = async (page: E2EPage, element: SerializableOrJSHandle) => {
|
|
||||||
return page.evaluate((el) => {
|
|
||||||
const { className, tagName, id } = el;
|
|
||||||
return {
|
|
||||||
className,
|
|
||||||
tagName,
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
}, element);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getActiveElementParent = async (page: E2EPage) => {
|
|
||||||
const activeElement = await page.evaluateHandle(() => document.activeElement!.parentElement);
|
|
||||||
return getSerialElement(page, activeElement);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getActiveElement = async (page: E2EPage) => {
|
|
||||||
const activeElement = await page.evaluateHandle(() => document.activeElement);
|
|
||||||
return getSerialElement(page, activeElement);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const generateE2EUrl = (component: string, type: string, rtl = false): string => {
|
|
||||||
let url = `/src/components/${component}/test/${type}?ionic:_testing=true`;
|
|
||||||
if (rtl) {
|
|
||||||
url = `${url}&rtl=true`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the value of a property on an element
|
|
||||||
*/
|
|
||||||
export const getElementProperty = async (element: any, property: string): Promise<string> => {
|
|
||||||
const getProperty = await element.getProperty(property);
|
|
||||||
if (!getProperty) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return getProperty.jsonValue();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listens for an event and fires a callback
|
|
||||||
* @param page - The Puppeteer `page` object
|
|
||||||
* @param eventType: string - The event name to listen for. ex: `ionPickerColChange`
|
|
||||||
* @param element: HTMLElement - An HTML element
|
|
||||||
* @param callbackName: string - The name of the callback function to
|
|
||||||
* call when the event is fired.
|
|
||||||
*
|
|
||||||
* Note: The callback function must be added using
|
|
||||||
* page.exposeFunction prior to calling this function.
|
|
||||||
*/
|
|
||||||
export const listenForEvent = async (
|
|
||||||
page: any,
|
|
||||||
eventType: string,
|
|
||||||
element: any,
|
|
||||||
callbackName: string
|
|
||||||
): Promise<any> => {
|
|
||||||
try {
|
|
||||||
return await page.evaluate(
|
|
||||||
(scopeEventType: string, scopeElement: any, scopeCallbackName: string) => {
|
|
||||||
scopeElement.addEventListener(scopeEventType, (e: any) => {
|
|
||||||
(window as any)[scopeCallbackName]({ detail: e.detail });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
eventType,
|
|
||||||
element,
|
|
||||||
callbackName
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drags an element by (x, y) pixels
|
|
||||||
* @param element: HTMLElement - The HTML Element to drag
|
|
||||||
* @param page - The Puppeteer 'page' object
|
|
||||||
* @param x: number - Amount to drag `element` by on the x-axis
|
|
||||||
* @param y: number - Amount to drag `element` by on the y-axis
|
|
||||||
* @param startCoordinates (optional) - Coordinates of where to start the drag
|
|
||||||
* gesture. If not provided, the drag gesture will start in the middle of the
|
|
||||||
* element.
|
|
||||||
*/
|
|
||||||
export const dragElementBy = async (
|
|
||||||
element: ElementHandle<Element>,
|
|
||||||
page: any,
|
|
||||||
x = 0,
|
|
||||||
y = 0,
|
|
||||||
startCoordinates?: { x: number; y: number }
|
|
||||||
): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const boundingBox = (await element.boundingBox())!;
|
|
||||||
|
|
||||||
const startX = startCoordinates?.x === undefined ? boundingBox.x + boundingBox.width / 2 : startCoordinates.x;
|
|
||||||
const startY = startCoordinates?.y === undefined ? boundingBox.y + boundingBox.height / 2 : startCoordinates.y;
|
|
||||||
|
|
||||||
const midX = startX + x / 2;
|
|
||||||
const midY = startY + y / 2;
|
|
||||||
|
|
||||||
const endX = startX + x;
|
|
||||||
const endY = startY + y;
|
|
||||||
|
|
||||||
await page.mouse.move(startX, startY);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(midX, midY);
|
|
||||||
await page.mouse.move(endX, endY);
|
|
||||||
await page.mouse.up();
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait for a function to return true
|
|
||||||
* This method runs in the context of the
|
|
||||||
* test whereas page.waitForFunction runs in
|
|
||||||
* the context of the browser
|
|
||||||
* @param fn - The function to run
|
|
||||||
* @param params: any - Any parameters that the fn needs
|
|
||||||
* @param interval: number - Interval to run setInterval on
|
|
||||||
*/
|
|
||||||
export const waitForFunctionTestContext = async (fn: any, params: any, interval = 16): Promise<any> => {
|
|
||||||
return new Promise<void>((resolve) => {
|
|
||||||
const intervalId = setInterval(() => {
|
|
||||||
if (fn(params)) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
}, interval);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pierce through shadow roots
|
|
||||||
* https://github.com/GoogleChrome/puppeteer/issues/858#issuecomment-359763824
|
|
||||||
*/
|
|
||||||
export const queryDeep = async (page: E2EPage, ...selectors: string[]): Promise<ElementHandle> => {
|
|
||||||
const shadowSelectorFn = (el: Element, selector: string): Element | null =>
|
|
||||||
el?.shadowRoot && el.shadowRoot.querySelector(selector);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-async-promise-executor
|
|
||||||
return new Promise(async (resolve) => {
|
|
||||||
const [firstSelector, ...restSelectors] = selectors;
|
|
||||||
let parentElement = await page.$(firstSelector);
|
|
||||||
|
|
||||||
for (const selector of restSelectors) {
|
|
||||||
parentElement = (await page.evaluateHandle(shadowSelectorFn, parentElement, selector)) as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parentElement) {
|
|
||||||
resolve(parentElement);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Given an element and optional selector, use the selector if
|
|
||||||
* it exists or get the node name of that element if not. Combine
|
|
||||||
* with the current mode to verify the correct classes exist.
|
|
||||||
*
|
|
||||||
* @param el: E2EElement - The element to verify classes on
|
|
||||||
* @param selector: string - A selector to use instead of the element tag name
|
|
||||||
* @param globalMode: string - the global mode as a fallback
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
* await checkComponentModeClasses(await page.find('ion-card-content'), globalMode)
|
|
||||||
* => expect(el).toHaveClass(`card-content-{mode}`);
|
|
||||||
*
|
|
||||||
* await checkComponentModeClasses(await page.find('ion-card-content'), globalMode, 'some-class')
|
|
||||||
* => expect(el).toHaveClass(`some-class-{mode}`);
|
|
||||||
*/
|
|
||||||
export const checkComponentModeClasses = async (el: E2EElement, globalMode: string, selector?: string) => {
|
|
||||||
// If passed a selector to use, use that, else grab the nodeName
|
|
||||||
// of the element and remove the ion prefix to get the class selector
|
|
||||||
const component = selector !== undefined ? selector : el.nodeName.toLowerCase().replace('ion-', '');
|
|
||||||
|
|
||||||
const mode = (await el.getProperty('mode')) || globalMode;
|
|
||||||
|
|
||||||
expect(el).toHaveClass(`${component}-${mode}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an element, get the mode and verify it exists as a class
|
|
||||||
*
|
|
||||||
* @param el: E2EElement - the element to verify the mode class on
|
|
||||||
* @param globalMode: string - the global mode as a fallback
|
|
||||||
*/
|
|
||||||
export const checkModeClasses = async (el: E2EElement, globalMode: string) => {
|
|
||||||
const mode = (await el.getProperty('mode')) || globalMode;
|
|
||||||
expect(el).toHaveClass(`${mode}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scrolls to a specific x/y coordinate within a scroll container. Supports custom
|
|
||||||
* method for `ion-content` implementations.
|
|
||||||
*
|
|
||||||
* @param page The Puppeteer page object
|
|
||||||
* @param selector The element to scroll within.
|
|
||||||
* @param x The x coordinate to scroll to.
|
|
||||||
* @param y The y coordinate to scroll to.
|
|
||||||
*/
|
|
||||||
export const scrollTo = async (page: E2EPage, selector: string, x: number, y: number) => {
|
|
||||||
await page.evaluate(async (selector) => {
|
|
||||||
const el = document.querySelector<HTMLElement>(selector);
|
|
||||||
if (el) {
|
|
||||||
if (el.tagName === 'ION-CONTENT') {
|
|
||||||
await (el as any).scrollToPoint(x, y);
|
|
||||||
} else {
|
|
||||||
el.scroll(x, y);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error(`Unable to find element with selector: ${selector}`);
|
|
||||||
}
|
|
||||||
}, selector);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scrolls to the bottom of a scroll container. Supports custom method for
|
|
||||||
* `ion-content` implementations.
|
|
||||||
*
|
|
||||||
* @param page The Puppeteer page object
|
|
||||||
* @param selector The element to scroll within.
|
|
||||||
*/
|
|
||||||
export const scrollToBottom = async (page: E2EPage, selector: string) => {
|
|
||||||
await page.evaluate(async (elSelector) => {
|
|
||||||
const el = document.querySelector<HTMLElement>(elSelector);
|
|
||||||
if (el) {
|
|
||||||
if (el.tagName === 'ION-CONTENT') {
|
|
||||||
await (el as any).scrollToBottom();
|
|
||||||
} else {
|
|
||||||
el.scrollTop = el.scrollHeight;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error(`Unable to find element with selector: ${elSelector}`);
|
|
||||||
}
|
|
||||||
}, selector);
|
|
||||||
};
|
|
Reference in New Issue
Block a user