merge release-6.1.6
Release 6.1.6
13
.github/CONTRIBUTING.md
vendored
@ -140,12 +140,13 @@ Before creating a pull request, please read our requirements that explains the m
|
||||
|
||||
#### Modifying Documentation
|
||||
|
||||
1. Locate the `readme.md` file in the component's directory.
|
||||
2. Modify the documentation **above** the line that says `<!-- Auto Generated Below -->` in this file.
|
||||
3. To update any of the auto generated documentation below that line, make the relevant changes in the following places:
|
||||
- `Usage`: update the component's usage examples in the component's `usage/` directory
|
||||
- `Properties`, `Events`, or `Methods`: update the component's TypeScript file (`*.tsx`)
|
||||
- `CSS Custom Properties`: update the component's main Sass file (`*.scss`)
|
||||
- Changes to manually written documentation should be made in the `ionic-docs` repo: https://github.com/ionic-team/ionic-docs/tree/main/docs
|
||||
- In your `ionic-docs` PR, please add a link back to the related `ionic-framework` PR.
|
||||
- Changes to auto generated documentation should be made in the `ionic-framework` repo. These can be done in the same PR as your fix or feature.
|
||||
- Run `npm run build` and commit all updates to ensure your changes make it into the generated documentation.
|
||||
- `Usage`: update the component's usage examples in the component's `usage/` directory.
|
||||
- `Properties`, `Events`, or `Methods`: update the component's TypeScript file (`*.tsx`).
|
||||
- `CSS Custom Properties`: update the component's main Sass file (`*.scss`).
|
||||
|
||||
|
||||
#### Modifying Tests
|
||||
|
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -5,6 +5,7 @@
|
||||
Please check if your PR fulfills the following requirements:
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
- [ ] Docs have been reviewed and added / updated if needed (for bug fixes / features)
|
||||
- Some docs updates need to be made in the `ionic-docs` repo, in a separate PR. See the [contributing guide](https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#modifying-documentation) for details.
|
||||
- [ ] Build (`npm run build`) was run locally and any changes were pushed
|
||||
- [ ] Lint (`npm run lint`) has passed locally and any fixes were made for failures
|
||||
|
||||
|
13
CHANGELOG.md
@ -3,6 +3,19 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [6.1.6](https://github.com/ionic-team/ionic-framework/compare/v6.1.5...v6.1.6) (2022-05-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **loading:** spinner now respects —spinner-color ([#25261](https://github.com/ionic-team/ionic-framework/issues/25261)) ([65f4c74](https://github.com/ionic-team/ionic-framework/commit/65f4c742e7a5e5756f6f72dd853e38e885f90385)), closes [#25180](https://github.com/ionic-team/ionic-framework/issues/25180)
|
||||
* **modal:** reset breakpoint to initial breakpoint on present ([#25246](https://github.com/ionic-team/ionic-framework/issues/25246)) ([2557bf3](https://github.com/ionic-team/ionic-framework/commit/2557bf3c3eed9e33e89e07a8d73489da8d81bee3)), closes [#25245](https://github.com/ionic-team/ionic-framework/issues/25245)
|
||||
* **scroll-assist:** touch end events continue to bubble on inputs ([#25282](https://github.com/ionic-team/ionic-framework/issues/25282)) ([780f16d](https://github.com/ionic-team/ionic-framework/commit/780f16d9e04ee5aaaf91bb7c6ef15c72cc8aeb45)), closes [#25229](https://github.com/ionic-team/ionic-framework/issues/25229)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.1.5](https://github.com/ionic-team/ionic-framework/compare/v6.1.4...v6.1.5) (2022-05-11)
|
||||
|
||||
|
||||
|
@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [6.1.6](https://github.com/ionic-team/ionic/compare/v6.1.5...v6.1.6) (2022-05-18)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.1.5](https://github.com/ionic-team/ionic/compare/v6.1.4...v6.1.5) (2022-05-11)
|
||||
|
||||
|
||||
|
18
angular/package-lock.json
generated
@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "6.1.5",
|
||||
"version": "6.1.6",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/angular",
|
||||
"version": "6.1.5",
|
||||
"version": "6.1.6",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "^6.1.5",
|
||||
"@ionic/core": "^6.1.6",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
@ -1023,9 +1023,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "6.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.5.tgz",
|
||||
"integrity": "sha512-YEpFheFDGV7lifbYNqctcPXRPqEOKiDy5KgSPriFzrrPUbwrv/tnXHZq7hFVPCMUYFBS9QJts4r5FOYTqAfvtw==",
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz",
|
||||
"integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.14.2",
|
||||
"ionicons": "^6.0.0",
|
||||
@ -7951,9 +7951,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "6.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.5.tgz",
|
||||
"integrity": "sha512-YEpFheFDGV7lifbYNqctcPXRPqEOKiDy5KgSPriFzrrPUbwrv/tnXHZq7hFVPCMUYFBS9QJts4r5FOYTqAfvtw==",
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz",
|
||||
"integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==",
|
||||
"requires": {
|
||||
"@stencil/core": "^2.14.2",
|
||||
"ionicons": "^6.0.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "6.1.5",
|
||||
"version": "6.1.6",
|
||||
"description": "Angular specific wrappers for @ionic/core",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@ -44,7 +44,7 @@
|
||||
"validate": "npm i && npm run lint && npm run test && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/core": "^6.1.5",
|
||||
"@ionic/core": "^6.1.6",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
|
@ -3,6 +3,19 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [6.1.6](https://github.com/ionic-team/ionic/compare/v6.1.5...v6.1.6) (2022-05-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **loading:** spinner now respects —spinner-color ([#25261](https://github.com/ionic-team/ionic/issues/25261)) ([65f4c74](https://github.com/ionic-team/ionic/commit/65f4c742e7a5e5756f6f72dd853e38e885f90385)), closes [#25180](https://github.com/ionic-team/ionic/issues/25180)
|
||||
* **modal:** reset breakpoint to initial breakpoint on present ([#25246](https://github.com/ionic-team/ionic/issues/25246)) ([2557bf3](https://github.com/ionic-team/ionic/commit/2557bf3c3eed9e33e89e07a8d73489da8d81bee3)), closes [#25245](https://github.com/ionic-team/ionic/issues/25245)
|
||||
* **scroll-assist:** touch end events continue to bubble on inputs ([#25282](https://github.com/ionic-team/ionic/issues/25282)) ([780f16d](https://github.com/ionic-team/ionic/commit/780f16d9e04ee5aaaf91bb7c6ef15c72cc8aeb45)), closes [#25229](https://github.com/ionic-team/ionic/issues/25229)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.1.5](https://github.com/ionic-team/ionic/compare/v6.1.4...v6.1.5) (2022-05-11)
|
||||
|
||||
|
||||
|
4
core/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "6.1.5",
|
||||
"version": "6.1.6",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/core",
|
||||
"version": "6.1.5",
|
||||
"version": "6.1.6",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.14.2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "6.1.5",
|
||||
"version": "6.1.6",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
|
@ -0,0 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('breadcrumbs: basic', () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.goto(`/src/components/breadcrumbs/test/basic`);
|
||||
|
||||
await page.setIonViewport();
|
||||
|
||||
expect(await page.screenshot()).toMatchSnapshot(`breadcrumb-diff-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
});
|
After Width: | Height: | Size: 532 KiB |
After Width: | Height: | Size: 183 KiB |
After Width: | Height: | Size: 454 KiB |
After Width: | Height: | Size: 531 KiB |
After Width: | Height: | Size: 183 KiB |
After Width: | Height: | Size: 454 KiB |
After Width: | Height: | Size: 518 KiB |
After Width: | Height: | Size: 182 KiB |
After Width: | Height: | Size: 442 KiB |
After Width: | Height: | Size: 517 KiB |
After Width: | Height: | Size: 184 KiB |
After Width: | Height: | Size: 442 KiB |
@ -1,19 +0,0 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('breadcrumbs: basic', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/breadcrumbs/test/basic?ionic:_testing=true',
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
|
||||
test('breadcrumbs: basic-rtl', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/breadcrumbs/test/basic?ionic:_testing=true&rtl=true',
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
@ -0,0 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('breadcrumbs: collapsed', () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.goto(`/src/components/breadcrumbs/test/collapsed`);
|
||||
|
||||
await page.setIonViewport();
|
||||
|
||||
expect(await page.screenshot()).toMatchSnapshot(`breadcrumb-collapsed-diff-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
});
|
After Width: | Height: | Size: 295 KiB |
After Width: | Height: | Size: 99 KiB |
After Width: | Height: | Size: 247 KiB |
After Width: | Height: | Size: 292 KiB |
After Width: | Height: | Size: 99 KiB |
After Width: | Height: | Size: 246 KiB |
After Width: | Height: | Size: 293 KiB |
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 236 KiB |
After Width: | Height: | Size: 294 KiB |
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 236 KiB |
@ -1,10 +0,0 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('breadcrumbs: collapsed', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/breadcrumbs/test/collapsed?ionic:_testing=true',
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
@ -0,0 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('breadcrumbs: standalone', () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.goto(`/src/components/breadcrumbs/test/standalone`);
|
||||
|
||||
expect(await page.screenshot({ fullPage: true })).toMatchSnapshot(
|
||||
`breadcrumb-standalone-diff-${page.getSnapshotSettings()}.png`
|
||||
);
|
||||
});
|
||||
});
|
After Width: | Height: | Size: 211 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 218 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 218 KiB |
After Width: | Height: | Size: 226 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 217 KiB |
After Width: | Height: | Size: 226 KiB |
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 217 KiB |
@ -1,10 +0,0 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('breadcrumbs: standalone', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/breadcrumbs/test/standalone?ionic:_testing=true',
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
@ -67,11 +67,6 @@
|
||||
z-index: $z-index-overlay-wrapper;
|
||||
}
|
||||
|
||||
.spinner-lines,
|
||||
.spinner-lines-small,
|
||||
.spinner-bubbles,
|
||||
.spinner-circles,
|
||||
.spinner-crescent,
|
||||
.spinner-dots {
|
||||
ion-spinner {
|
||||
color: var(--spinner-color);
|
||||
}
|
||||
|
@ -56,7 +56,7 @@
|
||||
<ion-button
|
||||
id="custom-class-loading"
|
||||
expand="block"
|
||||
onclick="openLoading({duration: 5000,message: 'Please wait...', cssClass: 'custom-class custom-loading'})"
|
||||
onclick="openLoading({duration: 5000,message: 'Please wait...', spinner: 'lines-sharp', cssClass: 'custom-class custom-loading'})"
|
||||
>Show Loading with cssClass</ion-button
|
||||
>
|
||||
<ion-button id="backdrop-loading" expand="block" onclick="openLoading({backdropDismiss: true})"
|
||||
@ -112,15 +112,15 @@
|
||||
}
|
||||
|
||||
.custom-loading {
|
||||
--background: rgba(37, 210, 223, 0.8);
|
||||
--spinner-color: white;
|
||||
--background: rgba(255, 255, 255, 0.8);
|
||||
--spinner-color: #ea445a;
|
||||
|
||||
--height: 100%;
|
||||
--max-height: auto;
|
||||
--width: 100%;
|
||||
--max-width: auto;
|
||||
|
||||
color: white;
|
||||
color: #3478f6;
|
||||
}
|
||||
|
||||
f {
|
||||
|
@ -39,7 +39,7 @@ test.describe('loading: basic', () => {
|
||||
await runVisualTest(page, '#translucent-loading', 'translucent');
|
||||
});
|
||||
test('should open a loader with a custom class', async ({ page }) => {
|
||||
await runVisualTest(page, '#no-spinner-loading', 'no-spinner');
|
||||
await runVisualTest(page, '#custom-class-loading', 'custom-class');
|
||||
});
|
||||
test('should open a loader with html content', async ({ page }) => {
|
||||
await runVisualTest(page, '#html-content-loading', 'html-content');
|
||||
|
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 69 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 69 KiB |
@ -442,6 +442,12 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
await this.currentTransition;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the modal is presented multiple times (inline modals), we
|
||||
* need to reset the current breakpoint to the initial breakpoint.
|
||||
*/
|
||||
this.currentBreakpoint = this.initialBreakpoint;
|
||||
|
||||
const data = {
|
||||
...this.componentProps,
|
||||
modal: this.el,
|
||||
@ -668,7 +674,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
|
||||
enteringAnimation.forEach((ani) => ani.destroy());
|
||||
}
|
||||
|
||||
this.currentBreakpoint = undefined;
|
||||
this.currentTransition = undefined;
|
||||
this.animation = undefined;
|
||||
return dismissed;
|
||||
|
@ -43,8 +43,6 @@ test.describe('modal: focus trapping', () => {
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
// TODO FW-1436
|
||||
test.skip(browserName === 'firefox', 'Focus is flaky on Firefox');
|
||||
await page.goto('/src/components/modal/test/basic');
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
@ -133,4 +133,65 @@ test.describe('sheet modal: setting the breakpoint', () => {
|
||||
expect(ionBreakpointDidChange.events.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
test('it should reset the breakpoint value on dismiss', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/25245',
|
||||
});
|
||||
|
||||
await page.setContent(`
|
||||
<ion-content>
|
||||
<ion-button id="open-modal">Open</ion-button>
|
||||
<ion-modal trigger="open-modal" initial-breakpoint="0.25">
|
||||
<ion-content>
|
||||
<ion-button id="dismiss" onclick="modal.dismiss();">Dismiss</ion-button>
|
||||
<ion-button id="set-breakpoint">Set breakpoint</ion-button>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
</ion-content>
|
||||
<script>
|
||||
const modal = document.querySelector('ion-modal');
|
||||
const setBreakpointButton = document.querySelector('#set-breakpoint');
|
||||
|
||||
modal.breakpoints = [0.25, 0.5, 1];
|
||||
|
||||
setBreakpointButton.addEventListener('click', () => {
|
||||
modal.setCurrentBreakpoint(0.5);
|
||||
});
|
||||
</script>
|
||||
`);
|
||||
|
||||
const modal = page.locator('ion-modal');
|
||||
const dismissButton = page.locator('#dismiss');
|
||||
const openButton = page.locator('#open-modal');
|
||||
const setBreakpointButton = page.locator('#set-breakpoint');
|
||||
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
const ionBreakpointDidChange = await page.spyOnEvent('ionBreakpointDidChange');
|
||||
|
||||
await openButton.click();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await setBreakpointButton.click();
|
||||
await ionBreakpointDidChange.next();
|
||||
|
||||
await dismissButton.click();
|
||||
await ionModalDidDismiss.next();
|
||||
|
||||
await openButton.click();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const breakpoint = await modal.evaluate((el: HTMLIonModalElement) => el.getCurrentBreakpoint());
|
||||
|
||||
expect(breakpoint).toBe(0.25);
|
||||
|
||||
await setBreakpointButton.click();
|
||||
await ionBreakpointDidChange.next();
|
||||
|
||||
const updatedBreakpoint = await modal.evaluate((el: HTMLIonModalElement) => el.getCurrentBreakpoint());
|
||||
|
||||
expect(updatedBreakpoint).toBe(0.5);
|
||||
});
|
||||
});
|
||||
|
@ -14,7 +14,7 @@ test.describe('picker-internal', () => {
|
||||
|
||||
test.describe('within overlay:', () => {
|
||||
// TODO (FW-1397): Remove this test.skip when the issue is fixed.
|
||||
test.skip('Flaky test', 'Mobile Safari and Chrome on Linux renders the selected option incorrectly');
|
||||
test.skip(true, 'Mobile Safari and Chrome on Linux renders the selected option incorrectly');
|
||||
|
||||
test('popover: should not have visual regression', async ({ page }) => {
|
||||
await page.goto(`/src/components/picker-internal/test/basic`);
|
||||
|
@ -1,61 +0,0 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('popover - arrow side: top', async () => {
|
||||
await testPopover('top', false);
|
||||
});
|
||||
|
||||
test('popover - arrow side: right', async () => {
|
||||
await testPopover('right', false);
|
||||
});
|
||||
|
||||
test('popover - arrow side: bottom', async () => {
|
||||
await testPopover('bottom', false);
|
||||
});
|
||||
|
||||
test('popover - arrow side: left', async () => {
|
||||
await testPopover('left', false);
|
||||
});
|
||||
|
||||
test('popover - arrow side: start', async () => {
|
||||
await testPopover('start', false);
|
||||
});
|
||||
|
||||
test('popover - arrow side: end', async () => {
|
||||
await testPopover('end', false);
|
||||
});
|
||||
|
||||
test('popover - arrow side: start, rtl', async () => {
|
||||
await testPopover('start', true);
|
||||
});
|
||||
|
||||
test('popover - arrow side: end, rtl', async () => {
|
||||
await testPopover('end', true);
|
||||
});
|
||||
|
||||
const testPopover = async (side: string, isRTL = false) => {
|
||||
const rtl = isRTL ? '&rtl=true' : '';
|
||||
const page = await newE2EPage({ url: `/src/components/popover/test/arrow?ionic:_testing=true${rtl}` });
|
||||
|
||||
const POPOVER_CLASS = `${side}-popover`;
|
||||
const TRIGGER_ID = `${side}-trigger`;
|
||||
const screenshotCompares = [];
|
||||
|
||||
const trigger = await page.find(`#${TRIGGER_ID}`);
|
||||
|
||||
await page.evaluate((POPOVER_TRIGGER_ID) => {
|
||||
const popoverTrigger = document.querySelector(`#${POPOVER_TRIGGER_ID}`);
|
||||
popoverTrigger?.scrollIntoView({ block: 'center' });
|
||||
}, TRIGGER_ID);
|
||||
|
||||
trigger.click();
|
||||
|
||||
await page.waitForSelector(`.${POPOVER_CLASS}`);
|
||||
const popover = await page.find(`.${POPOVER_CLASS}`);
|
||||
await popover.waitForVisible();
|
||||
|
||||
screenshotCompares.push(await page.compareScreenshot());
|
||||
|
||||
for (const screenshotCompare of screenshotCompares) {
|
||||
expect(screenshotCompare).toMatchScreenshot();
|
||||
}
|
||||
};
|
@ -13,10 +13,13 @@
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<style>
|
||||
ion-app > ion-content {
|
||||
--background: #dddddd;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-row-gap: 20px;
|
||||
grid-row-gap: 80px;
|
||||
grid-column-gap: 20px;
|
||||
|
||||
padding: 200px;
|
||||
@ -54,42 +57,48 @@
|
||||
<div class="grid-item">
|
||||
<h2>Top</h2>
|
||||
<ion-button id="top-trigger">Click to Open</ion-button>
|
||||
<ion-popover class="top-popover" trigger="top-trigger" side="top" size="cover">
|
||||
<ion-popover show-backdrop="false" class="top-popover" trigger="top-trigger" side="top" size="cover">
|
||||
<ion-content class="ion-padding"> Hello World </ion-content>
|
||||
</ion-popover>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Right</h2>
|
||||
<ion-button id="right-trigger">Click to Open</ion-button>
|
||||
<ion-popover class="right-popover" trigger="right-trigger" side="right" size="cover">
|
||||
<ion-popover show-backdrop="false" class="right-popover" trigger="right-trigger" side="right" size="cover">
|
||||
<ion-content class="ion-padding"> Hello World </ion-content>
|
||||
</ion-popover>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Bottom</h2>
|
||||
<ion-button id="bottom-trigger">Click to Open</ion-button>
|
||||
<ion-popover class="bottom-popover" trigger="bottom-trigger" side="bottom" size="cover">
|
||||
<ion-popover
|
||||
show-backdrop="false"
|
||||
class="bottom-popover"
|
||||
trigger="bottom-trigger"
|
||||
side="bottom"
|
||||
size="cover"
|
||||
>
|
||||
<ion-content class="ion-padding"> Hello World </ion-content>
|
||||
</ion-popover>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Left</h2>
|
||||
<ion-button id="left-trigger">Click to Open</ion-button>
|
||||
<ion-popover class="left-popover" trigger="left-trigger" side="left" size="cover">
|
||||
<ion-popover show-backdrop="false" class="left-popover" trigger="left-trigger" side="left" size="cover">
|
||||
<ion-content class="ion-padding"> Hello World </ion-content>
|
||||
</ion-popover>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Start</h2>
|
||||
<ion-button id="start-trigger">Click to Open</ion-button>
|
||||
<ion-popover class="start-popover" trigger="start-trigger" side="start" size="cover">
|
||||
<ion-popover show-backdrop="false" class="start-popover" trigger="start-trigger" side="start" size="cover">
|
||||
<ion-content class="ion-padding"> Hello World </ion-content>
|
||||
</ion-popover>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>End</h2>
|
||||
<ion-button id="end-trigger">Click to Open</ion-button>
|
||||
<ion-popover class="end-popover" trigger="end-trigger" side="end" size="cover">
|
||||
<ion-popover show-backdrop="false" class="end-popover" trigger="end-trigger" side="end" size="cover">
|
||||
<ion-content class="ion-padding"> Hello World </ion-content>
|
||||
</ion-popover>
|
||||
</div>
|
||||
|
23
core/src/components/popover/test/arrow/popover.e2e.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test, Viewports } from '@utils/test/playwright';
|
||||
|
||||
import { openPopover } from '../test.utils';
|
||||
|
||||
test.describe('popover: arrow rendering', async () => {
|
||||
/**
|
||||
* The popovers have showBackdrop=false so we can open all of them at once
|
||||
* and massively cut down on screenshots taken. The content has its own
|
||||
* backdrop so you can still see the popovers.
|
||||
*/
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.goto('/src/components/popover/test/arrow');
|
||||
await page.setViewportSize(Viewports.tablet.portrait); // avoid extra-long viewport screenshots
|
||||
|
||||
const sides = ['top', 'right', 'bottom', 'left', 'start', 'end'];
|
||||
for (const side of sides) {
|
||||
await openPopover(page, `${side}-trigger`, true);
|
||||
}
|
||||
|
||||
expect(await page.screenshot()).toMatchSnapshot(`popover-arrow-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
});
|
After Width: | Height: | Size: 110 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 101 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 101 KiB |
@ -20,52 +20,6 @@
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-content class="ion-padding" id="content">
|
||||
<ion-button
|
||||
id="basic-popover"
|
||||
expand="block"
|
||||
onclick="presentPopover({ component: 'profile-page', event: event, htmlAttributes: { 'data-testid': 'basic-popover' } })"
|
||||
>
|
||||
Show Popover</ion-button
|
||||
>
|
||||
<ion-button
|
||||
id="translucent-popover"
|
||||
expand="block"
|
||||
onclick="presentPopover({ component: 'translucent-page', event: event, translucent: true })"
|
||||
>Show Translucent Popover</ion-button
|
||||
>
|
||||
<ion-button
|
||||
id="long-list-popover"
|
||||
expand="block"
|
||||
color="secondary"
|
||||
onclick="presentPopover({ component: 'list-page', event: event })"
|
||||
>Show Long List Popover</ion-button
|
||||
>
|
||||
<ion-button
|
||||
id="no-event-popover"
|
||||
expand="block"
|
||||
color="danger"
|
||||
onclick="presentPopover({ component: 'profile-page' })"
|
||||
>No Event Popover</ion-button
|
||||
>
|
||||
<ion-button
|
||||
id="custom-class-popover"
|
||||
expand="block"
|
||||
color="tertiary"
|
||||
onclick="presentPopover({ component: 'translucent-page', event: event, cssClass: 'my-custom-class' })"
|
||||
>Custom Class Popover</ion-button
|
||||
>
|
||||
<ion-button id="header-popover" expand="block" onclick="presentPopover({ component: 'header-page' })"
|
||||
>Popover With Header</ion-button
|
||||
>
|
||||
<ion-button
|
||||
id="translucent-header-popover"
|
||||
expand="block"
|
||||
onclick="presentPopover({ component: 'translucent-header-page' })"
|
||||
>Popover With Translucent Header</ion-button
|
||||
>
|
||||
</ion-content>
|
||||
|
||||
<ion-content class="ion-padding" id="content">
|
||||
<ion-button
|
||||
id="basic-popover"
|
||||
|
@ -1,142 +1,65 @@
|
||||
import type { E2EPage } from '@stencil/core/testing';
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
import { expect } from '@playwright/test';
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
import { testPopover } from '../test.utils';
|
||||
import { openPopover, screenshotPopover } from '../test.utils';
|
||||
|
||||
const DIRECTORY = 'basic';
|
||||
test.describe('popover: rendering', async () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
const buttonIDs = [
|
||||
'basic-popover',
|
||||
'translucent-popover',
|
||||
'long-list-popover',
|
||||
'no-event-popover',
|
||||
'custom-class-popover',
|
||||
'header-popover',
|
||||
'translucent-header-popover',
|
||||
];
|
||||
|
||||
/**
|
||||
* Focusing happens async inside of popover so we need
|
||||
* to wait for the requestAnimationFrame to fire.
|
||||
*/
|
||||
const expectActiveElementTextToEqual = async (page: E2EPage, textValue: string) => {
|
||||
await page.evaluate((text) => document.activeElement!.textContent === text, textValue);
|
||||
};
|
||||
|
||||
const getActiveElementSelectionStart = (page: E2EPage) => {
|
||||
return page.evaluate(() =>
|
||||
document.activeElement instanceof HTMLTextAreaElement ? document.activeElement.selectionStart : null
|
||||
);
|
||||
};
|
||||
|
||||
const getActiveElementScrollTop = (page: E2EPage) => {
|
||||
return page.evaluate(() => {
|
||||
// Returns the closest ion-textarea or active element
|
||||
const target = document.activeElement!.closest('ion-textarea') ?? document.activeElement;
|
||||
return target!.scrollTop;
|
||||
for (const id of buttonIDs) {
|
||||
await screenshotPopover(page, id, 'basic');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
test('popover: basic', async () => {
|
||||
await testPopover(DIRECTORY, '#basic-popover');
|
||||
});
|
||||
|
||||
test('popover: translucent', async () => {
|
||||
await testPopover(DIRECTORY, '#translucent-popover');
|
||||
test.describe('popover: htmlAttributes', async () => {
|
||||
test('should inherit attributes on host', async ({ page }) => {
|
||||
await page.goto('/src/components/popover/test/basic');
|
||||
await openPopover(page, 'basic-popover');
|
||||
|
||||
const alert = page.locator('ion-popover');
|
||||
expect(alert).toHaveAttribute('data-testid', 'basic-popover');
|
||||
});
|
||||
});
|
||||
|
||||
test('popover: long list', async () => {
|
||||
await testPopover(DIRECTORY, '#long-list-popover');
|
||||
});
|
||||
test.describe('popover: focus trap', async () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/src/components/popover/test/basic');
|
||||
});
|
||||
|
||||
test('popover: no event', async () => {
|
||||
await testPopover(DIRECTORY, '#no-event-popover');
|
||||
});
|
||||
|
||||
test('popover: custom class', async () => {
|
||||
await testPopover(DIRECTORY, '#custom-class-popover');
|
||||
});
|
||||
|
||||
test('popover: header', async () => {
|
||||
await testPopover(DIRECTORY, '#header-popover');
|
||||
});
|
||||
|
||||
test('popover: translucent header', async () => {
|
||||
await testPopover(DIRECTORY, '#translucent-header-popover');
|
||||
});
|
||||
|
||||
/**
|
||||
* RTL Tests
|
||||
*/
|
||||
|
||||
test('popover:rtl: basic', async () => {
|
||||
await testPopover(DIRECTORY, '#basic-popover', true, true);
|
||||
});
|
||||
|
||||
test('popover:rtl: translucent', async () => {
|
||||
await testPopover(DIRECTORY, '#translucent-popover', true, true);
|
||||
});
|
||||
|
||||
test('popover:rtl: long list', async () => {
|
||||
await testPopover(DIRECTORY, '#long-list-popover', true, true);
|
||||
});
|
||||
|
||||
test('popover:rtl: no event', async () => {
|
||||
await testPopover(DIRECTORY, '#no-event-popover', true, true);
|
||||
});
|
||||
|
||||
test('popover:rtl: custom class', async () => {
|
||||
await testPopover(DIRECTORY, '#custom-class-popover', true, true);
|
||||
});
|
||||
|
||||
test('popover:rtl: header', async () => {
|
||||
await testPopover(DIRECTORY, '#header-popover', true);
|
||||
});
|
||||
|
||||
test('popover:rtl: translucent header', async () => {
|
||||
await testPopover(DIRECTORY, '#translucent-header-popover', true);
|
||||
});
|
||||
|
||||
test('popover: htmlAttributes', async () => {
|
||||
const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' });
|
||||
|
||||
await page.click('#basic-popover');
|
||||
await page.waitForSelector('#basic-popover');
|
||||
|
||||
const alert = await page.find('ion-popover');
|
||||
|
||||
expect(alert).not.toBe(null);
|
||||
await alert.waitForVisible();
|
||||
|
||||
const attribute = await page.evaluate(() => document.querySelector('ion-popover')!.getAttribute('data-testid'));
|
||||
|
||||
expect(attribute).toEqual('basic-popover');
|
||||
});
|
||||
|
||||
describe('popover: focus trap', () => {
|
||||
it('should focus the first ion-item on ArrowDown', async () => {
|
||||
const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' });
|
||||
|
||||
await page.click('#basic-popover');
|
||||
|
||||
const popover = await page.find('ion-popover');
|
||||
|
||||
expect(popover).not.toBe(null);
|
||||
await popover.waitForVisible();
|
||||
test('should focus the first ion-item on ArrowDown', async ({ page }) => {
|
||||
await openPopover(page, 'basic-popover');
|
||||
|
||||
await page.keyboard.press('ArrowDown');
|
||||
|
||||
await expectActiveElementTextToEqual(page, 'Item 0');
|
||||
});
|
||||
|
||||
it('should work with ion-item children', async () => {
|
||||
const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' });
|
||||
|
||||
await page.click('#basic-popover');
|
||||
await page.waitForSelector('#basic-popover');
|
||||
|
||||
const popover = await page.find('ion-popover');
|
||||
|
||||
expect(popover).not.toBe(null);
|
||||
await popover.waitForVisible();
|
||||
test('should trap focus', async ({ page, browserName }) => {
|
||||
await openPopover(page, 'basic-popover');
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
await expectActiveElementTextToEqual(page, 'Item 0');
|
||||
|
||||
await page.keyboard.down('Shift');
|
||||
if (browserName === 'webkit') {
|
||||
await page.keyboard.down('Alt');
|
||||
}
|
||||
await page.keyboard.press('Tab');
|
||||
await page.keyboard.up('Shift');
|
||||
if (browserName === 'webkit') {
|
||||
await page.keyboard.up('Alt');
|
||||
}
|
||||
|
||||
await expectActiveElementTextToEqual(page, 'Item 3');
|
||||
|
||||
@ -161,19 +84,17 @@ describe('popover: focus trap', () => {
|
||||
await expectActiveElementTextToEqual(page, 'Item 3');
|
||||
});
|
||||
|
||||
it('should not override keyboard interactions for textarea elements', async () => {
|
||||
const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' });
|
||||
|
||||
await page.waitForSelector('#popover-with-textarea');
|
||||
await page.click('#popover-with-textarea');
|
||||
|
||||
const popover = await page.find('ion-popover');
|
||||
await popover.waitForVisible();
|
||||
|
||||
await page.waitForFunction('document.activeElement.tagName === "ION-POPOVER"');
|
||||
test('should not override keyboard interactions for textarea elements', async ({ page, browserName }) => {
|
||||
await openPopover(page, 'popover-with-textarea');
|
||||
await page.waitForFunction(() => document.activeElement?.tagName === 'ION-POPOVER');
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
// Checking within ion-textarea
|
||||
|
||||
// for Firefox, ion-textarea is focused first
|
||||
// need to tab again to get to native input
|
||||
if (browserName === 'firefox') {
|
||||
await page.keyboard.press('Tab');
|
||||
}
|
||||
|
||||
let activeElementTagName = await page.evaluate(() => document.activeElement!.tagName);
|
||||
let scrollTop = null;
|
||||
@ -235,3 +156,27 @@ describe('popover: focus trap', () => {
|
||||
expect(scrollTop).toBeGreaterThanOrEqual(previousScrollTop);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(FW-1424): convert these to Playwright assertions where possible
|
||||
|
||||
/**
|
||||
* Focusing happens async inside of popover so we need
|
||||
* to wait for the requestAnimationFrame to fire.
|
||||
*/
|
||||
const expectActiveElementTextToEqual = async (page: E2EPage, textValue: string) => {
|
||||
await page.evaluate((text) => document.activeElement!.textContent === text, textValue);
|
||||
};
|
||||
|
||||
const getActiveElementSelectionStart = (page: E2EPage) => {
|
||||
return page.evaluate(() =>
|
||||
document.activeElement instanceof HTMLTextAreaElement ? document.activeElement.selectionStart : null
|
||||
);
|
||||
};
|
||||
|
||||
const getActiveElementScrollTop = (page: E2EPage) => {
|
||||
return page.evaluate(() => {
|
||||
// Returns the closest ion-textarea or active element
|
||||
const target = document.activeElement!.closest('ion-textarea') ?? document.activeElement;
|
||||
return target!.scrollTop;
|
||||
});
|
||||
};
|
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 107 KiB |