Compare commits
22 Commits
v6.3.3-nig
...
FW-388-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d629ba9f1 | ||
|
|
57105d54ea | ||
|
|
9f0b30e460 | ||
|
|
bb005956ea | ||
|
|
1f7fc8f05c | ||
|
|
e9306f8ad9 | ||
|
|
c6db6c7822 | ||
|
|
5e8a75a643 | ||
|
|
4c30a80b7e | ||
|
|
1504b8888d | ||
|
|
bbbd37bc49 | ||
|
|
c0f4381745 | ||
|
|
b364d58710 | ||
|
|
8ac3b8b575 | ||
|
|
1320948b24 | ||
|
|
a7e15babf3 | ||
|
|
3745083b7c | ||
|
|
c3882d8b21 | ||
|
|
a5b45821cd | ||
|
|
53caa22212 | ||
|
|
6bbb372980 | ||
|
|
0fa68cfa61 |
@@ -37,7 +37,7 @@ runs:
|
||||
shell: bash
|
||||
working-directory: ./angular/test
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
run: npm install --legacy-peer-deps
|
||||
shell: bash
|
||||
working-directory: ./angular/test/build/${{ inputs.app }}
|
||||
- name: Sync Built Changes
|
||||
|
||||
2
.github/workflows/build.yml
vendored
@@ -145,7 +145,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
apps: [ng12, ng13, ng14]
|
||||
apps: [ng12, ng13, ng14, ng15]
|
||||
needs: [build-angular, build-angular-server]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
15
CHANGELOG.md
@@ -3,7 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [6.3.3-nightly.20221019](https://github.com/ionic-team/ionic-framework/compare/v6.3.2...v6.3.3-nightly.20221019) (2022-10-19)
|
||||
## [6.3.4](https://github.com/ionic-team/ionic-framework/compare/v6.3.3...v6.3.4) (2022-11-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** account for allowed values when setting default date ([#26093](https://github.com/ionic-team/ionic-framework/issues/26093)) ([3745083](https://github.com/ionic-team/ionic-framework/commit/3745083b7cdf8651d1c7f5f1a0214b487ea286ca)), closes [#24722](https://github.com/ionic-team/ionic-framework/issues/24722)
|
||||
* **modal, popover:** remove trigger click listeners when overlay is unmounted ([#26167](https://github.com/ionic-team/ionic-framework/issues/26167)) ([1320948](https://github.com/ionic-team/ionic-framework/commit/1320948b245be3defe8610b9f049e781a4903a6e))
|
||||
* **modal:** buttons are highlighted with VoiceOver ([#26180](https://github.com/ionic-team/ionic-framework/issues/26180)) ([1504b88](https://github.com/ionic-team/ionic-framework/commit/1504b8888d6f79ea382935ed52b104b9f841d150)), closes [#26156](https://github.com/ionic-team/ionic-framework/issues/26156)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.3.3](https://github.com/ionic-team/ionic-framework/compare/v6.3.2...v6.3.3) (2022-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [6.3.3-nightly.20221019](https://github.com/ionic-team/ionic/compare/v6.3.2...v6.3.3-nightly.20221019) (2022-10-19)
|
||||
## [6.3.4](https://github.com/ionic-team/ionic/compare/v6.3.3...v6.3.4) (2022-11-02)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.3.3](https://github.com/ionic-team/ionic/compare/v6.3.2...v6.3.3) (2022-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
18
angular/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "6.3.3-nightly.20221019",
|
||||
"version": "6.3.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/angular",
|
||||
"version": "6.3.3-nightly.20221019",
|
||||
"version": "6.3.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "6.3.3-nightly.20221019",
|
||||
"@ionic/core": "^6.3.4",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
@@ -1023,9 +1023,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "6.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
|
||||
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
|
||||
"version": "6.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.4.tgz",
|
||||
"integrity": "sha512-3EnKqFdon7Im8JNvuPoq/iGyYK7hskdb9u5hlQE0fHU/9Q0DvFVh17QTq1sQjXntoJovfRhaPJhG+kvZK3mVAQ==",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.18.0",
|
||||
"ionicons": "^6.0.3",
|
||||
@@ -7951,9 +7951,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "6.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
|
||||
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
|
||||
"version": "6.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.4.tgz",
|
||||
"integrity": "sha512-3EnKqFdon7Im8JNvuPoq/iGyYK7hskdb9u5hlQE0fHU/9Q0DvFVh17QTq1sQjXntoJovfRhaPJhG+kvZK3mVAQ==",
|
||||
"requires": {
|
||||
"@stencil/core": "^2.18.0",
|
||||
"ionicons": "^6.0.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "6.3.3-nightly.20221019",
|
||||
"version": "6.3.4",
|
||||
"description": "Angular specific wrappers for @ionic/core",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -45,7 +45,7 @@
|
||||
"validate": "npm i && npm run lint && npm run test && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/core": "6.3.3-nightly.20221019",
|
||||
"@ionic/core": "^6.3.4",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
|
||||
@@ -30,7 +30,7 @@ export class FormComponent {
|
||||
formControl.markAsTouched();
|
||||
}
|
||||
|
||||
onSubmit(_ev) {
|
||||
onSubmit() {
|
||||
this.submitted = 'true';
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export class FormComponent {
|
||||
formControl.markAsTouched();
|
||||
}
|
||||
|
||||
onSubmit(_ev) {
|
||||
onSubmit() {
|
||||
this.submitted = 'true';
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
describe('Routing with Standalone Components', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/version-test/standalone');
|
||||
});
|
||||
|
||||
it('should render the component', () => {
|
||||
cy.get('ion-content').contains('This is a standalone component rendered from a route.');
|
||||
});
|
||||
});
|
||||
27969
angular/test/apps/ng15/package-lock.json
generated
Normal file
68
angular/test/apps/ng15/package.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "ionic-angular-test-app",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"sync:build": "sh scripts/build-ionic.sh",
|
||||
"sync": "sh scripts/sync.sh",
|
||||
"build": "ng build --configuration production --no-progress",
|
||||
"lint": "ng lint",
|
||||
"postinstall": "ngcc",
|
||||
"serve:ssr": "node dist/test-app/server/main.js",
|
||||
"build:ssr": "ng build --prod && ng run test-app:server:production",
|
||||
"dev:ssr": "ng run test-app:serve-ssr",
|
||||
"prerender": "ng run test-app:prerender",
|
||||
"cy.open": "cypress open",
|
||||
"cy.run": "cypress run",
|
||||
"test": "concurrently \"npm run start -- --configuration production\" \"wait-on http-get://localhost:4200 && npm run cy.run\" --kill-others --success first",
|
||||
"test.watch": "concurrently \"npm run start\" \"wait-on http-get://localhost:4200 && npm run cy.open\" --kill-others --success first"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^15.0.0-rc.1",
|
||||
"@angular/common": "^15.0.0-rc.1",
|
||||
"@angular/compiler": "^15.0.0-rc.1",
|
||||
"@angular/core": "^15.0.0-rc.1",
|
||||
"@angular/forms": "^15.0.0-rc.1",
|
||||
"@angular/platform-browser": "^15.0.0-rc.1",
|
||||
"@angular/platform-browser-dynamic": "^15.0.0-rc.1",
|
||||
"@angular/platform-server": "^15.0.0-rc.1",
|
||||
"@angular/router": "^15.0.0-rc.1",
|
||||
"@ionic/angular": "^6.1.15",
|
||||
"@ionic/angular-server": "^6.1.15",
|
||||
"@nguniversal/express-engine": "^14.0.3",
|
||||
"angular-in-memory-web-api": "^0.11.0",
|
||||
"core-js": "^2.6.11",
|
||||
"express": "^4.15.2",
|
||||
"rxjs": "~7.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"typescript-eslint-language-service": "^4.1.5",
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^15.0.0-rc.1",
|
||||
"@angular-eslint/builder": "^14.0.2",
|
||||
"@angular-eslint/eslint-plugin": "^14.0.2",
|
||||
"@angular-eslint/eslint-plugin-template": "^14.0.2",
|
||||
"@angular-eslint/schematics": "^14.0.2",
|
||||
"@angular-eslint/template-parser": "^14.0.2",
|
||||
"@angular/cli": "^15.0.0-rc.1",
|
||||
"@angular/compiler-cli": "^15.0.0-rc.1",
|
||||
"@angular/language-service": "^15.0.0-rc.1",
|
||||
"@nguniversal/builders": "^15.0.0-next.0",
|
||||
"@types/express": "^4.17.7",
|
||||
"@types/node": "^12.12.54",
|
||||
"@typescript-eslint/eslint-plugin": "4.28.2",
|
||||
"@typescript-eslint/parser": "4.28.2",
|
||||
"concurrently": "^6.0.0",
|
||||
"cypress": "^10.2.0",
|
||||
"eslint": "^7.26.0",
|
||||
"ts-loader": "^6.2.2",
|
||||
"ts-node": "^8.3.0",
|
||||
"typescript": "~4.8.4",
|
||||
"wait-on": "^5.2.1",
|
||||
"webpack": "^5.61.0",
|
||||
"webpack-cli": "^4.9.2"
|
||||
}
|
||||
}
|
||||
3
angular/test/apps/ng15/src/app/app.component.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<ion-app>
|
||||
<ion-router-outlet [environmentInjector]="environmentInjector"></ion-router-outlet>
|
||||
</ion-app>
|
||||
11
angular/test/apps/ng15/src/app/app.component.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Component, EnvironmentInjector } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
|
||||
constructor(public environmentInjector: EnvironmentInjector) { }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<ion-content>
|
||||
<p>This is a standalone component rendered from a route.</p>
|
||||
<ion-button routerLink="/">Return home</ion-button>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-standalone',
|
||||
templateUrl: './standalone.component.html',
|
||||
standalone: true,
|
||||
imports: [IonicModule, RouterModule]
|
||||
})
|
||||
export class StandaloneComponent { }
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: 'standalone',
|
||||
loadComponent: () => import('./standalone/standalone.component').then(m => m.StandaloneComponent)
|
||||
}
|
||||
])
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class VersionTestRoutingModule { }
|
||||
35
angular/test/apps/ng15/tsconfig.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/out-tsc",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "es2020",
|
||||
"emitDecoratorMetadata": true,
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
"lib": ["es2020", "dom"],
|
||||
"plugins": [
|
||||
{
|
||||
"name": "typescript-eslint-language-service"
|
||||
}
|
||||
],
|
||||
"useDefineForClassFields": false
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
28
angular/test/base/e2e/src/output-target.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
describe('Angular Output Target', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/output-target');
|
||||
});
|
||||
|
||||
it('should emit one event per emission', () => {
|
||||
/**
|
||||
* Angular @Output() events aren't actual DOM events,
|
||||
* instead they are an "EventEmitter" (RxJS Subject)
|
||||
* that emits a change.
|
||||
*
|
||||
* In the Angular output target, we manually create
|
||||
* a RxJS Subject (EventEmitter) for each DOM event,
|
||||
* so that developers can use the same binding syntax
|
||||
* and have the expected behavior that Angular events
|
||||
* do not bubble up the DOM tree.
|
||||
*
|
||||
* We additionally "trick" Angular by creating
|
||||
* @Output decorated properties for each DOM event
|
||||
* on the component proxy class and manually clearing
|
||||
* the decorated metadata. This allows Angular to
|
||||
* not add its own event listener and cause duplicate
|
||||
* event emissions for the web component events.
|
||||
*/
|
||||
cy.get('#ionChangeInput').type('a');
|
||||
cy.get('#ionChangeEmittedCount').should('have.text', '1');
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@ import { Component } from '@angular/core';
|
||||
templateUrl: './accordion-modal.component.html',
|
||||
})
|
||||
export class AccordionModalComponent {
|
||||
modal: HTMLIonModalElement;
|
||||
modal!: HTMLIonModalElement;
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,10 @@ const routes: Routes = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'output-target',
|
||||
loadChildren: () => import('./output-target/output-target.module').then(m => m.OutputTargetModule)
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<form [formGroup]="profileForm" (ngSubmit)="onSubmit($event)">
|
||||
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
|
||||
<ion-list>
|
||||
|
||||
<ion-item>
|
||||
|
||||
@@ -27,10 +27,12 @@ export class FormComponent {
|
||||
|
||||
setTouched() {
|
||||
const formControl = this.profileForm.get('input');
|
||||
formControl.markAsTouched();
|
||||
if (formControl) {
|
||||
formControl.markAsTouched();
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit(_ev) {
|
||||
onSubmit() {
|
||||
this.submitted = 'true';
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ import { Component } from '@angular/core';
|
||||
})
|
||||
export class InputsComponent {
|
||||
|
||||
datetime = '1994-03-15';
|
||||
input = 'some text';
|
||||
datetime? = '1994-03-15';
|
||||
input? = 'some text';
|
||||
checkbox = true;
|
||||
toggle = true;
|
||||
select = 'nes';
|
||||
range = 10;
|
||||
select? = 'nes';
|
||||
range? = 10;
|
||||
changes = 0;
|
||||
|
||||
setValues() {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ModalController, NavParams, IonNav, ViewWillLeave, ViewDidEnter, ViewDi
|
||||
})
|
||||
export class ModalExampleComponent implements OnInit, ViewWillLeave, ViewDidEnter, ViewWillLeave, ViewDidLeave {
|
||||
|
||||
@Input() value: string;
|
||||
@Input() value?: string;
|
||||
|
||||
form = new UntypedFormGroup({
|
||||
select: new UntypedFormControl([])
|
||||
@@ -21,7 +21,7 @@ export class ModalExampleComponent implements OnInit, ViewWillLeave, ViewDidEnte
|
||||
willLeave = 0;
|
||||
didLeave = 0;
|
||||
|
||||
modal: HTMLElement;
|
||||
modal!: HTMLElement;
|
||||
|
||||
constructor(
|
||||
private modalCtrl: ModalController,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
|
||||
import { OutputTargetComponent } from "./output-target.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: "",
|
||||
component: OutputTargetComponent
|
||||
}
|
||||
])
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class OutputTargetRoutingModule { }
|
||||
@@ -0,0 +1,11 @@
|
||||
<ion-content>
|
||||
<p>This test template verifies key behaviors in the <code>@stencil/angular-output-target</code>.</p>
|
||||
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-label>Events are emitted once</ion-label>
|
||||
<ion-input id="ionChangeInput" type="text" (ionChange)="onIonChange()"></ion-input>
|
||||
<p id="ionChangeEmittedCount">{{ ionChangeEmittedCount }}</p>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-output-target",
|
||||
templateUrl: "./output-target.component.html",
|
||||
})
|
||||
export class OutputTargetComponent {
|
||||
|
||||
ionChangeEmittedCount = 0;
|
||||
|
||||
onIonChange() {
|
||||
this.ionChangeEmittedCount++;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { IonicModule } from "@ionic/angular";
|
||||
|
||||
import { OutputTargetRoutingModule } from "./output-target-routing.module";
|
||||
import { OutputTargetComponent } from "./output-target.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [IonicModule, OutputTargetRoutingModule],
|
||||
declarations: [OutputTargetComponent]
|
||||
})
|
||||
export class OutputTargetModule { }
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { IonPopover } from "@ionic/angular";
|
||||
|
||||
/**
|
||||
* Validates that inline popovers will correctly display
|
||||
* dynamic contents that are updated after the modal is
|
||||
@@ -13,7 +15,7 @@ export class PopoverInlineComponent {
|
||||
|
||||
items: string[] = [];
|
||||
|
||||
openPopover(popover: HTMLIonPopoverElement) {
|
||||
openPopover(popover: IonPopover) {
|
||||
popover.present();
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -16,9 +16,9 @@ export class ProvidersComponent {
|
||||
isResumed = false;
|
||||
isPaused = false;
|
||||
isResized = false;
|
||||
isTesting: boolean = undefined;
|
||||
isDesktop: boolean = undefined;
|
||||
isMobile: boolean = undefined;
|
||||
isTesting?: boolean = undefined;
|
||||
isDesktop?: boolean = undefined;
|
||||
isMobile?: boolean = undefined;
|
||||
keyboardHeight = 0;
|
||||
queryParams = '';
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export class RouterLinkPageComponent implements OnInit, ViewWillLeave, ViewDidEn
|
||||
didEnter = 0;
|
||||
willLeave = 0;
|
||||
didLeave = 0;
|
||||
canGoBack: boolean = null;
|
||||
canGoBack: boolean | null | undefined = null;
|
||||
|
||||
constructor(
|
||||
private ionRouterOutlet: IonRouterOutlet
|
||||
|
||||
@@ -6,11 +6,11 @@ import { IonSlides } from '@ionic/angular';
|
||||
templateUrl: './slides.component.html',
|
||||
})
|
||||
export class SlidesComponent implements AfterViewInit {
|
||||
@ViewChild(IonSlides, { static: true }) slides: IonSlides;
|
||||
@ViewChild(IonSlides, { static: true }) slides!: IonSlides;
|
||||
|
||||
slideIndex = 0;
|
||||
slideIndex2 = 0;
|
||||
slidesData = [];
|
||||
slidesData: string[] = [];
|
||||
|
||||
constructor() { }
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { NavController } from '@ionic/angular';
|
||||
templateUrl: './tabs-tab1-nested.component.html',
|
||||
})
|
||||
export class TabsTab1NestedComponent implements OnInit {
|
||||
id = '';
|
||||
id: string | null = '';
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
public navCtrl: NavController
|
||||
@@ -18,6 +18,9 @@ export class TabsTab1NestedComponent implements OnInit {
|
||||
}
|
||||
|
||||
next() {
|
||||
if (this.id === null) {
|
||||
return '1';
|
||||
}
|
||||
return parseInt(this.id, 10) + 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ import { IonTabBar } from '@ionic/angular';
|
||||
export class TabsComponent {
|
||||
tabsDidChangeCounter = 0;
|
||||
tabsDidChangeEvent = '';
|
||||
tabsDidChangeSelectedTab = '';
|
||||
tabsDidChangeSelectedTab? = '';
|
||||
|
||||
tabsWillChangeCounter = 0;
|
||||
tabsWillChangeEvent = '';
|
||||
tabsWillChangeSelectedTab = '';
|
||||
tabsWillChangeSelectedTab? = '';
|
||||
|
||||
@ViewChild(IonTabBar) tabBar: IonTabBar;
|
||||
@ViewChild(IonTabBar) tabBar!: IonTabBar;
|
||||
|
||||
tabChanged(ev: { tab: string }) {
|
||||
console.log('ionTabsDidChange', this.tabBar.selectedTab);
|
||||
|
||||
@@ -7,11 +7,11 @@ import { IonTabs, IonButton, IonSlides, IonSlide } from '@ionic/angular';
|
||||
})
|
||||
export class ViewChildComponent implements AfterViewInit {
|
||||
|
||||
@ViewChild(IonSlides, { static: true }) slides: IonSlides;
|
||||
@ViewChild(IonButton, { static: true }) button: IonButton;
|
||||
@ViewChild(IonTabs, { static: true }) tabs: IonTabs;
|
||||
@ViewChild('div', { static: true }) div: ElementRef;
|
||||
@ViewChild('slide', { static: true }) slide: IonSlide;
|
||||
@ViewChild(IonSlides, { static: true }) slides!: IonSlides;
|
||||
@ViewChild(IonButton, { static: true }) button!: IonButton;
|
||||
@ViewChild(IonTabs, { static: true }) tabs!: IonTabs;
|
||||
@ViewChild('div', { static: true }) div!: ElementRef;
|
||||
@ViewChild('slide', { static: true }) slide!: IonSlide;
|
||||
|
||||
ngAfterViewInit() {
|
||||
const loaded = !!(this.slides && this.button && this.tabs && this.div && this.slide);
|
||||
|
||||
@@ -14,7 +14,7 @@ export class VirtualScrollDetailComponent implements OnInit, ViewWillEnter, View
|
||||
willLeave = 0;
|
||||
didLeave = 0;
|
||||
|
||||
itemNu = 'none';
|
||||
itemNu: string | null = 'none';
|
||||
|
||||
constructor(private route: ActivatedRoute) {}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Component, OnInit, NgZone, Input } from '@angular/core';
|
||||
})
|
||||
export class VirtualScrollInnerComponent implements OnInit {
|
||||
|
||||
@Input() value: string;
|
||||
@Input() value?: string;
|
||||
onInit = 0;
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { IonVirtualScroll } from '@ionic/angular';
|
||||
})
|
||||
export class VirtualScrollComponent {
|
||||
|
||||
@ViewChild(IonVirtualScroll, { static: true }) virtualScroll: IonVirtualScroll;
|
||||
@ViewChild(IonVirtualScroll, { static: true }) virtualScroll!: IonVirtualScroll;
|
||||
|
||||
items = Array.from({length: 100}, (_, i) => ({ name: `${i}`, checked: true}));
|
||||
|
||||
@@ -18,12 +18,14 @@ export class VirtualScrollComponent {
|
||||
if ((index % 10) === 0) {
|
||||
return `Header ${index}`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
myFooterFn: HeaderFn = (_, index) => {
|
||||
if ((index % 5) === 0) {
|
||||
return `Footer ${index}`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
addItems() {
|
||||
|
||||
@@ -3,7 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [6.3.3-nightly.20221019](https://github.com/ionic-team/ionic/compare/v6.3.2...v6.3.3-nightly.20221019) (2022-10-19)
|
||||
## [6.3.4](https://github.com/ionic-team/ionic/compare/v6.3.3...v6.3.4) (2022-11-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** account for allowed values when setting default date ([#26093](https://github.com/ionic-team/ionic/issues/26093)) ([3745083](https://github.com/ionic-team/ionic/commit/3745083b7cdf8651d1c7f5f1a0214b487ea286ca)), closes [#24722](https://github.com/ionic-team/ionic/issues/24722)
|
||||
* **modal, popover:** remove trigger click listeners when overlay is unmounted ([#26167](https://github.com/ionic-team/ionic/issues/26167)) ([1320948](https://github.com/ionic-team/ionic/commit/1320948b245be3defe8610b9f049e781a4903a6e))
|
||||
* **modal:** buttons are highlighted with VoiceOver ([#26180](https://github.com/ionic-team/ionic/issues/26180)) ([1504b88](https://github.com/ionic-team/ionic/commit/1504b8888d6f79ea382935ed52b104b9f841d150)), closes [#26156](https://github.com/ionic-team/ionic/issues/26156)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.3.3](https://github.com/ionic-team/ionic/compare/v6.3.2...v6.3.3) (2022-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
4
core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "6.3.3-nightly.20221019",
|
||||
"version": "6.3.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/core",
|
||||
"version": "6.3.3-nightly.20221019",
|
||||
"version": "6.3.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.18.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "6.3.3-nightly.20221019",
|
||||
"version": "6.3.4",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||
import type { PlaywrightTestConfig, PlaywrightTestOptions, PlaywrightWorkerOptions, Project } from '@playwright/test';
|
||||
import { devices, expect } from '@playwright/test';
|
||||
|
||||
import { matchers } from './src/utils/test/playwright';
|
||||
|
||||
expect.extend(matchers);
|
||||
|
||||
const projects = [
|
||||
const projects: Project<PlaywrightTestOptions, PlaywrightWorkerOptions>[] = [
|
||||
{
|
||||
/**
|
||||
* This is really just desktop Firefox
|
||||
@@ -24,10 +24,6 @@ const projects = [
|
||||
width: 393,
|
||||
height: 727
|
||||
},
|
||||
screen: {
|
||||
width: 393,
|
||||
height: 851
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -49,7 +45,7 @@ const projects = [
|
||||
const modes = ['ios', 'md'];
|
||||
|
||||
const generateProjects = () => {
|
||||
const projectsWithMetadata = [];
|
||||
const projectsWithMetadata: Project<PlaywrightTestOptions, PlaywrightWorkerOptions>[] = [];
|
||||
|
||||
modes.forEach(mode => {
|
||||
projects.forEach(project => {
|
||||
@@ -105,9 +101,6 @@ const config: PlaywrightTestConfig = {
|
||||
use: {
|
||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||
actionTimeout: 0,
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://localhost:3000',
|
||||
|
||||
/**
|
||||
* All failed tests should create
|
||||
* a trace file for easier debugging.
|
||||
|
||||
@@ -17,6 +17,7 @@ test.describe('datetime-button: switching to correct view', () => {
|
||||
expect(datetime).toHaveJSProperty('presentation', 'date-time');
|
||||
|
||||
await page.locator('#date-button').click();
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(datetime).toHaveJSProperty('presentation', 'date');
|
||||
});
|
||||
@@ -25,6 +26,7 @@ test.describe('datetime-button: switching to correct view', () => {
|
||||
expect(datetime).toHaveJSProperty('presentation', 'date-time');
|
||||
|
||||
await page.locator('#time-button').click();
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(datetime).toHaveJSProperty('presentation', 'time');
|
||||
});
|
||||
|
||||
@@ -36,6 +36,7 @@ import { is24Hour, isLocaleDayPeriodRTL, isMonthFirstLocale, getNumDaysInMonth }
|
||||
import {
|
||||
calculateHourFromAMPM,
|
||||
convertDataToISO,
|
||||
getClosestValidDate,
|
||||
getEndOfWeek,
|
||||
getNextDay,
|
||||
getNextMonth,
|
||||
@@ -96,7 +97,8 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
private minParts?: any;
|
||||
private maxParts?: any;
|
||||
private todayParts = parseDate(getToday());
|
||||
private todayParts!: DatetimeParts;
|
||||
private defaultParts!: DatetimeParts;
|
||||
|
||||
private prevPresentation: string | null = null;
|
||||
|
||||
@@ -559,13 +561,13 @@ export class Datetime implements ComponentInterface {
|
||||
* may not be set. This function works
|
||||
* by returning the first selected date in
|
||||
* "activePartsClone" and then falling back to
|
||||
* today's DatetimeParts if no active date is selected.
|
||||
* defaultParts if no active date is selected.
|
||||
*/
|
||||
private getDefaultPart = (): DatetimeParts => {
|
||||
const { activePartsClone, todayParts } = this;
|
||||
private getActivePartsWithFallback = () => {
|
||||
const { activePartsClone, defaultParts } = this;
|
||||
|
||||
const firstPart = Array.isArray(activePartsClone) ? activePartsClone[0] : activePartsClone;
|
||||
return firstPart ?? todayParts;
|
||||
return firstPart ?? defaultParts;
|
||||
};
|
||||
|
||||
private closeParentOverlay = () => {
|
||||
@@ -780,24 +782,24 @@ export class Datetime implements ComponentInterface {
|
||||
};
|
||||
|
||||
private processMinParts = () => {
|
||||
const { min, todayParts } = this;
|
||||
const { min, defaultParts } = this;
|
||||
if (min === undefined) {
|
||||
this.minParts = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
this.minParts = parseMinParts(min, todayParts);
|
||||
this.minParts = parseMinParts(min, defaultParts);
|
||||
};
|
||||
|
||||
private processMaxParts = () => {
|
||||
const { max, todayParts } = this;
|
||||
const { max, defaultParts } = this;
|
||||
|
||||
if (max === undefined) {
|
||||
this.maxParts = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
this.maxParts = parseMaxParts(max, todayParts);
|
||||
this.maxParts = parseMaxParts(max, defaultParts);
|
||||
};
|
||||
|
||||
private initializeCalendarListener = () => {
|
||||
@@ -1158,7 +1160,7 @@ export class Datetime implements ComponentInterface {
|
||||
* TODO FW-2646 remove value !== ''
|
||||
*/
|
||||
const hasValue = value !== '' && value !== null && value !== undefined;
|
||||
let valueToProcess = parseDate(hasValue ? value : getToday());
|
||||
let valueToProcess = hasValue ? parseDate(value) : this.defaultParts;
|
||||
|
||||
const { minParts, maxParts, multiple } = this;
|
||||
if (!multiple && Array.isArray(value)) {
|
||||
@@ -1236,12 +1238,16 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
this.processMinParts();
|
||||
this.processMaxParts();
|
||||
const hourValues = (this.parsedHourValues = convertToArrayOfNumbers(this.hourValues));
|
||||
const minuteValues = (this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues));
|
||||
const monthValues = (this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues));
|
||||
const yearValues = (this.parsedYearValues = convertToArrayOfNumbers(this.yearValues));
|
||||
const dayValues = (this.parsedDayValues = convertToArrayOfNumbers(this.dayValues));
|
||||
|
||||
const todayParts = (this.todayParts = parseDate(getToday()));
|
||||
this.defaultParts = getClosestValidDate(todayParts, monthValues, dayValues, yearValues, hourValues, minuteValues);
|
||||
this.processValue(this.value);
|
||||
this.parsedHourValues = convertToArrayOfNumbers(this.hourValues);
|
||||
this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues);
|
||||
this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues);
|
||||
this.parsedYearValues = convertToArrayOfNumbers(this.yearValues);
|
||||
this.parsedDayValues = convertToArrayOfNumbers(this.dayValues);
|
||||
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
@@ -1396,9 +1402,9 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
|
||||
private renderCombinedDatePickerColumn() {
|
||||
const { workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;
|
||||
const { defaultParts, workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;
|
||||
|
||||
const activePart = this.getDefaultPart();
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
/**
|
||||
* By default, generate a range of 3 months:
|
||||
@@ -1464,12 +1470,12 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
/**
|
||||
* If we have selected a day already, then default the column
|
||||
* to that value. Otherwise, default it to today.
|
||||
* to that value. Otherwise, set it to the default date.
|
||||
*/
|
||||
const todayString =
|
||||
workingParts.day !== null
|
||||
? `${workingParts.year}-${workingParts.month}-${workingParts.day}`
|
||||
: `${todayParts.year}-${todayParts.month}-${todayParts.day}`;
|
||||
: `${defaultParts.year}-${defaultParts.month}-${defaultParts.day}`;
|
||||
|
||||
return (
|
||||
<ion-picker-column-internal
|
||||
@@ -1555,7 +1561,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
const shouldRenderYears = forcePresentation !== 'month' && forcePresentation !== 'time';
|
||||
const years = shouldRenderYears
|
||||
? getYearColumnData(this.locale, this.todayParts, this.minParts, this.maxParts, this.parsedYearValues)
|
||||
? getYearColumnData(this.locale, this.defaultParts, this.minParts, this.maxParts, this.parsedYearValues)
|
||||
: [];
|
||||
|
||||
/**
|
||||
@@ -1588,14 +1594,14 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
const { workingParts } = this;
|
||||
|
||||
const activePart = this.getDefaultPart();
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
return (
|
||||
<ion-picker-column-internal
|
||||
class="day-column"
|
||||
color={this.color}
|
||||
items={days}
|
||||
value={(workingParts.day !== null ? workingParts.day : this.todayParts.day) ?? undefined}
|
||||
value={(workingParts.day !== null ? workingParts.day : this.defaultParts.day) ?? undefined}
|
||||
onIonChange={(ev: CustomEvent) => {
|
||||
// TODO(FW-1823) Remove this when iOS 14 support is dropped.
|
||||
// Due to a Safari 14 issue we need to destroy
|
||||
@@ -1632,7 +1638,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
const { workingParts } = this;
|
||||
|
||||
const activePart = this.getDefaultPart();
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
return (
|
||||
<ion-picker-column-internal
|
||||
@@ -1675,7 +1681,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
const { workingParts } = this;
|
||||
|
||||
const activePart = this.getDefaultPart();
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
return (
|
||||
<ion-picker-column-internal
|
||||
@@ -1739,7 +1745,7 @@ export class Datetime implements ComponentInterface {
|
||||
const { workingParts } = this;
|
||||
if (hoursData.length === 0) return [];
|
||||
|
||||
const activePart = this.getDefaultPart();
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
return (
|
||||
<ion-picker-column-internal
|
||||
@@ -1767,7 +1773,7 @@ export class Datetime implements ComponentInterface {
|
||||
const { workingParts } = this;
|
||||
if (minutesData.length === 0) return [];
|
||||
|
||||
const activePart = this.getDefaultPart();
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
return (
|
||||
<ion-picker-column-internal
|
||||
@@ -1797,7 +1803,7 @@ export class Datetime implements ComponentInterface {
|
||||
return [];
|
||||
}
|
||||
|
||||
const activePart = this.getDefaultPart();
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
const isDayPeriodRTL = isLocaleDayPeriodRTL(this.locale);
|
||||
|
||||
return (
|
||||
@@ -1911,7 +1917,7 @@ export class Datetime implements ComponentInterface {
|
||||
// can free-scroll the calendar.
|
||||
const isWorkingMonth = this.workingParts.month === month && this.workingParts.year === year;
|
||||
|
||||
const activePart = this.getDefaultPart();
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -2041,7 +2047,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
private renderTimeOverlay() {
|
||||
const use24Hour = is24Hour(this.locale, this.hourCycle);
|
||||
const activePart = this.getDefaultPart();
|
||||
const activePart = this.getActivePartsWithFallback();
|
||||
|
||||
return [
|
||||
<div class="time-header">{this.renderTimeLabel()}</div>,
|
||||
@@ -2122,7 +2128,7 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
} else {
|
||||
// for exactly 1 day selected (multiple set or not), show a formatted version of that
|
||||
headerText = getMonthAndDay(this.locale, this.getDefaultPart());
|
||||
headerText = getMonthAndDay(this.locale, this.getActivePartsWithFallback());
|
||||
}
|
||||
|
||||
return headerText;
|
||||
|
||||
@@ -148,6 +148,19 @@ test.describe('datetime: prefer wheel', () => {
|
||||
presentation="date"
|
||||
prefer-wheel="true"
|
||||
></ion-datetime>
|
||||
|
||||
<script>
|
||||
const mockToday = '2022-10-10T16:22';
|
||||
Date = class extends Date {
|
||||
constructor(...args) {
|
||||
if (args.length === 0) {
|
||||
super(mockToday)
|
||||
} else {
|
||||
super(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
`);
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
@@ -2,6 +2,10 @@ import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('datetime: values', () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
skip.mode('md');
|
||||
});
|
||||
test('should render correct days', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-datetime locale="en-US" presentation="date" day-values="1,2,3"></ion-datetime>
|
||||
@@ -49,6 +53,105 @@ test.describe('datetime: values', () => {
|
||||
const items = page.locator('ion-picker-column-internal:nth-of-type(2) .picker-item:not(.picker-item-empty)');
|
||||
await expect(items).toHaveText(['01', '02', '03']);
|
||||
});
|
||||
test('should adjust default parts for allowed hour and minute values', async ({ page }) => {
|
||||
/**
|
||||
* Mock today's date for testing.
|
||||
* Playwright does not support this natively
|
||||
* so we extend the native Date interface: https://github.com/microsoft/playwright/issues/6347
|
||||
*/
|
||||
await page.setContent(`
|
||||
<ion-datetime presentation="time" locale="en-US" hour-values="02" minute-values="0,15,30,45"></ion-datetime>
|
||||
|
||||
<script>
|
||||
const mockToday = '2022-10-10T16:22';
|
||||
Date = class extends Date {
|
||||
constructor(...args) {
|
||||
if (args.length === 0) {
|
||||
super(mockToday)
|
||||
} else {
|
||||
super(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
`);
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
const minuteItems = page.locator('ion-picker-column-internal:nth-of-type(2) .picker-item:not(.picker-item-empty)');
|
||||
await expect(minuteItems).toHaveText(['00', '15', '30', '45']);
|
||||
await expect(minuteItems.nth(1)).toHaveClass(/picker-item-active/);
|
||||
|
||||
const hourItems = page.locator('ion-picker-column-internal:nth-of-type(1) .picker-item:not(.picker-item-empty)');
|
||||
await expect(hourItems).toHaveText(['2']);
|
||||
await expect(hourItems.nth(0)).toHaveClass(/picker-item-active/);
|
||||
|
||||
/**
|
||||
* Since the allowed hour is 2AM, the time period
|
||||
* should switch from PM to AM.
|
||||
*/
|
||||
const ampmItems = page.locator('ion-picker-column-internal:nth-of-type(3) .picker-item:not(.picker-item-empty)');
|
||||
await expect(ampmItems).toHaveText(['AM', 'PM']);
|
||||
await expect(ampmItems.nth(0)).toHaveClass(/picker-item-active/);
|
||||
});
|
||||
test('should adjust default parts month for allowed month values', async ({ page }) => {
|
||||
/**
|
||||
* Mock today's date for testing.
|
||||
* Playwright does not support this natively
|
||||
* so we extend the native Date interface: https://github.com/microsoft/playwright/issues/6347
|
||||
*/
|
||||
await page.setContent(`
|
||||
<ion-datetime prefer-wheel="true" presentation="date" locale="en-US" month-values="01" hour-values="02" minute-values="0,15,30,45"></ion-datetime>
|
||||
|
||||
<script>
|
||||
const mockToday = '2022-10-10T16:22';
|
||||
Date = class extends Date {
|
||||
constructor(...args) {
|
||||
if (args.length === 0) {
|
||||
super(mockToday)
|
||||
} else {
|
||||
super(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
`);
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
const monthItems = page.locator('.month-column .picker-item:not(.picker-item-empty)');
|
||||
await expect(monthItems).toHaveText(['January']);
|
||||
await expect(monthItems.nth(0)).toHaveClass(/picker-item-active/);
|
||||
});
|
||||
test('today date highlight should persist even if disallowed from dayValues', async ({ page }) => {
|
||||
/**
|
||||
* Mock today's date for testing.
|
||||
* Playwright does not support this natively
|
||||
* so we extend the native Date interface: https://github.com/microsoft/playwright/issues/6347
|
||||
*/
|
||||
await page.setContent(`
|
||||
<ion-datetime day-values="9" presentation="date" locale="en-US"></ion-datetime>
|
||||
|
||||
<script>
|
||||
const mockToday = '2022-10-10T16:22';
|
||||
Date = class extends Date {
|
||||
constructor(...args) {
|
||||
if (args.length === 0) {
|
||||
super(mockToday)
|
||||
} else {
|
||||
super(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
`);
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
const todayButton = page.locator('.calendar-day[data-day="10"][data-month="10"][data-year="2022"]');
|
||||
|
||||
await expect(todayButton).toHaveClass(/calendar-day-today/);
|
||||
});
|
||||
});
|
||||
|
||||
test('setting value to empty string should treat it as having no date', async ({ page, skip }) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { DatetimeParts } from '../datetime-interface';
|
||||
|
||||
import { isSameDay } from './comparison';
|
||||
import { getNumDaysInMonth } from './helpers';
|
||||
import { parseAmPm } from './parse';
|
||||
|
||||
const twoDigit = (val: number | undefined): string => {
|
||||
return ('0' + (val !== undefined ? Math.abs(val) : '0')).slice(-2);
|
||||
@@ -429,3 +430,83 @@ export const validateParts = (
|
||||
|
||||
return partsCopy;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the closest date to refParts
|
||||
* that also meets the constraints of
|
||||
* the *Values params.
|
||||
* @param refParts The reference date
|
||||
* @param monthValues The allowed month values
|
||||
* @param dayValues The allowed day (of the month) values
|
||||
* @param yearValues The allowed year values
|
||||
* @param hourValues The allowed hour values
|
||||
* @param minuteValues The allowed minute values
|
||||
*/
|
||||
export const getClosestValidDate = (
|
||||
refParts: DatetimeParts,
|
||||
monthValues?: number[],
|
||||
dayValues?: number[],
|
||||
yearValues?: number[],
|
||||
hourValues?: number[],
|
||||
minuteValues?: number[]
|
||||
) => {
|
||||
const { hour, minute, day, month, year } = refParts;
|
||||
const copyParts = { ...refParts, dayOfWeek: undefined };
|
||||
|
||||
if (monthValues !== undefined) {
|
||||
copyParts.month = findClosestValue(month, monthValues);
|
||||
}
|
||||
|
||||
// Day is nullable but cannot be undefined
|
||||
if (day !== null && dayValues !== undefined) {
|
||||
copyParts.day = findClosestValue(day, dayValues);
|
||||
}
|
||||
|
||||
if (yearValues !== undefined) {
|
||||
copyParts.year = findClosestValue(year, yearValues);
|
||||
}
|
||||
|
||||
if (hour !== undefined && hourValues !== undefined) {
|
||||
copyParts.hour = findClosestValue(hour, hourValues);
|
||||
copyParts.ampm = parseAmPm(copyParts.hour);
|
||||
}
|
||||
|
||||
if (minute !== undefined && minuteValues !== undefined) {
|
||||
copyParts.minute = findClosestValue(minute, minuteValues);
|
||||
}
|
||||
|
||||
return copyParts;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the value in "values" that is
|
||||
* numerically closest to "reference".
|
||||
* This function assumes that "values" is
|
||||
* already sorted in ascending order.
|
||||
* @param reference The reference number to use
|
||||
* when finding the closest value
|
||||
* @param values The allowed values that will be
|
||||
* searched to find the closest value to "reference"
|
||||
*/
|
||||
const findClosestValue = (reference: number, values: number[]) => {
|
||||
let closestValue = values[0];
|
||||
let rank = Math.abs(closestValue - reference);
|
||||
|
||||
for (let i = 1; i < values.length; i++) {
|
||||
const value = values[i];
|
||||
/**
|
||||
* This code prioritizes the first
|
||||
* closest result. Given two values
|
||||
* with the same distance from reference,
|
||||
* this code will prioritize the smaller of
|
||||
* the two values.
|
||||
*/
|
||||
const valueRank = Math.abs(value - reference);
|
||||
if (valueRank < rank) {
|
||||
closestValue = value;
|
||||
rank = valueRank;
|
||||
}
|
||||
}
|
||||
|
||||
return closestValue;
|
||||
};
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('footer: basic', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/footer/test/basic?ionic:_testing=true',
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
57
core/src/components/footer/test/basic/footer.e2e.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('footer: basic', () => {
|
||||
test.describe('footer: rendering', () => {
|
||||
test('should not have visual regressions with basic footer', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-footer>
|
||||
<ion-toolbar>
|
||||
<ion-title>Footer - Default</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
`);
|
||||
|
||||
const footer = page.locator('ion-footer');
|
||||
expect(await footer.screenshot()).toMatchSnapshot(`footer-diff-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('footer: feature rendering', () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
});
|
||||
|
||||
test('should not have visual regressions with no border', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-footer class="ion-no-border">
|
||||
<ion-toolbar>
|
||||
<ion-title>Footer - No Border</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
`);
|
||||
|
||||
const footer = page.locator('ion-footer');
|
||||
expect(await footer.screenshot()).toMatchSnapshot(`footer-no-border-diff-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
|
||||
test('should not have visual regressions with translucent footer', async ({ page, skip }) => {
|
||||
skip.mode('md', 'Translucent effect is only available in iOS mode.');
|
||||
skip.browser('firefox', 'Firefox has some issues rendering translucent effects on Linux.');
|
||||
|
||||
await page.setContent(`
|
||||
<ion-footer translucent="true">
|
||||
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0">
|
||||
<img style="transform: rotate(145deg) scale(1.5)" src="/src/components/footer/test/img.jpg" />
|
||||
</div>
|
||||
<ion-toolbar>
|
||||
<ion-title>Footer - Translucent</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
`);
|
||||
|
||||
const footer = page.locator('ion-footer');
|
||||
expect(await footer.screenshot()).toMatchSnapshot(`footer-translucent-diff-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 53 KiB |
@@ -15,23 +15,14 @@
|
||||
</head>
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-content>
|
||||
<br />
|
||||
|
||||
<ion-footer>
|
||||
<ion-toolbar>
|
||||
<ion-title>Footer - Default</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
|
||||
<br />
|
||||
|
||||
<ion-footer class="ion-no-border">
|
||||
<ion-toolbar>
|
||||
<ion-title>Footer - No Border</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
<ion-content class="ion-padding">
|
||||
<h1>Content</h1>
|
||||
</ion-content>
|
||||
<ion-footer>
|
||||
<ion-toolbar>
|
||||
<ion-title>Footer</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { E2EPage } from '@stencil/core/testing';
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
import { scrollToBottom } from '@utils/test';
|
||||
|
||||
describe('footer: fade: iOS', () => {
|
||||
let page: E2EPage;
|
||||
|
||||
beforeEach(async () => {
|
||||
page = await newE2EPage({
|
||||
url: '/src/components/footer/test/fade?ionic:_testing=true&ionic:mode=ios',
|
||||
});
|
||||
});
|
||||
|
||||
it('should match existing visual screenshots', async () => {
|
||||
const compares = [];
|
||||
|
||||
compares.push(await page.compareScreenshot('footer: blurred'));
|
||||
|
||||
await scrollToBottom(page, 'ion-content');
|
||||
|
||||
compares.push(await page.compareScreenshot('footer: not blurred'));
|
||||
|
||||
for (const compare of compares) {
|
||||
expect(compare).toMatchScreenshot();
|
||||
}
|
||||
});
|
||||
});
|
||||
24
core/src/components/footer/test/fade/footer.e2e.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('footer: fade', () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
});
|
||||
|
||||
test('should not have visual regressions with fade footer', async ({ page, skip }) => {
|
||||
skip.mode('md', 'Translucent effect is only available in iOS mode.');
|
||||
skip.browser('firefox', 'Firefox has some issues rendering translucent effects on Linux.');
|
||||
|
||||
await page.goto('/src/components/footer/test/fade');
|
||||
|
||||
const footer = page.locator('ion-footer');
|
||||
expect(await footer.screenshot()).toMatchSnapshot(`footer-fade-blurred-diff-${page.getSnapshotSettings()}.png`);
|
||||
|
||||
const content = page.locator('ion-content');
|
||||
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(await footer.screenshot()).toMatchSnapshot(`footer-fade-not-blurred-diff-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 49 KiB |
@@ -12,78 +12,73 @@
|
||||
<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>
|
||||
<style>
|
||||
.red {
|
||||
background-color: #ea445a;
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: #76d672;
|
||||
}
|
||||
|
||||
.blue {
|
||||
background-color: #3478f6;
|
||||
}
|
||||
|
||||
.yellow {
|
||||
background-color: #ffff80;
|
||||
}
|
||||
|
||||
.pink {
|
||||
background-color: #ff6b86;
|
||||
}
|
||||
|
||||
.purple {
|
||||
background-color: #7e34f6;
|
||||
}
|
||||
|
||||
.black {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.orange {
|
||||
background-color: #f69234;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ion-app>
|
||||
<div class="ion-page">
|
||||
<ion-header translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-title>Mailboxes</ion-title>
|
||||
<ion-title>Header</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">Mailboxes</ion-title>
|
||||
<ion-title size="large">Header</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<div class="grid ion-padding">
|
||||
<div class="grid-item red"></div>
|
||||
<div class="grid-item green"></div>
|
||||
<div class="grid-item blue"></div>
|
||||
<div class="grid-item yellow"></div>
|
||||
<div class="grid-item pink"></div>
|
||||
<div class="grid-item purple"></div>
|
||||
<div class="grid-item black"></div>
|
||||
<div class="grid-item orange"></div>
|
||||
<div class="ion-padding">
|
||||
<h1>Content</h1>
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas blandit, justo vitae rhoncus porttitor,
|
||||
massa elit molestie nisl, ut tincidunt orci enim ac ante. Vestibulum tincidunt dignissim elit. Morbi
|
||||
cursus hendrerit turpis, ut egestas tortor pulvinar sit amet. Interdum et malesuada fames ac ante ipsum
|
||||
primis in faucibus. Phasellus faucibus consequat purus vel mollis. Ut ultricies elit nunc. Quisque
|
||||
ultrices turpis vel augue auctor accumsan. Donec bibendum at nisi vel finibus. Fusce id imperdiet odio.
|
||||
Morbi orci ipsum, imperdiet ut iaculis sit amet, suscipit vulputate felis. Nullam arcu leo, volutpat eu
|
||||
porttitor sed, fringilla et arcu. Pellentesque ac libero sapien. Quisque id dui velit. Mauris et pretium
|
||||
tortor.
|
||||
</p>
|
||||
<p>
|
||||
Ut ultricies id augue vel aliquam. Etiam ornare finibus nisl, nec egestas urna. Nam pellentesque libero
|
||||
nec justo tristique lacinia. In sit amet gravida metus, ac tincidunt mauris. Fusce sit amet tempus turpis.
|
||||
Nulla ligula nunc, vestibulum quis quam in, feugiat aliquet nibh. Quisque in ante non nulla luctus gravida
|
||||
vel at lacus. Vivamus erat magna, elementum et dignissim nec, posuere eu ante. Praesent elementum, arcu
|
||||
scelerisque venenatis sodales, turpis nulla aliquam urna, id hendrerit est orci et purus. Duis sem ipsum,
|
||||
imperdiet eu elit id, tincidunt tempus sapien. Praesent tincidunt, sapien sed rhoncus euismod, lectus
|
||||
velit ornare nunc, dapibus varius turpis leo ut magna.
|
||||
</p>
|
||||
<p>
|
||||
Nam quis quam id ante mattis pulvinar non sed mauris. Donec tempor sed nulla at semper. Vivamus ac nunc
|
||||
bibendum, ullamcorper lacus quis, ornare massa. Cras gravida nibh risus, id sollicitudin eros ultricies
|
||||
non. Integer velit massa, suscipit tincidunt rhoncus ut, lacinia et nisl. Maecenas volutpat ipsum blandit
|
||||
sollicitudin lobortis. Suspendisse potenti. Cras non mi non arcu varius dapibus. Suspendisse maximus eget
|
||||
justo a lobortis. Donec nulla ipsum, efficitur eget velit eget, varius rutrum quam. Nulla metus risus,
|
||||
accumsan a tellus ac, faucibus blandit quam. Donec luctus, nisl ac ultricies ornare, nunc elit finibus
|
||||
magna, id elementum ante urna congue ex. Cras condimentum nisi sollicitudin tortor vestibulum luctus.
|
||||
Curabitur non ipsum et ex vestibulum congue.
|
||||
</p>
|
||||
<p>
|
||||
Maecenas rhoncus elit ut consectetur faucibus. Etiam sed sem sed mauris condimentum viverra sit amet at
|
||||
nibh. Mauris bibendum at purus a cursus. Suspendisse potenti. Donec vel lacus ac odio euismod lacinia id
|
||||
in urna. Donec commodo ipsum augue, at bibendum ex convallis suscipit. Nulla ac rhoncus odio. Aenean
|
||||
elementum est nec arcu ultricies dignissim.
|
||||
</p>
|
||||
<p>
|
||||
Sed tincidunt bibendum massa, egestas bibendum est imperdiet vitae. Fusce dignissim consectetur ante a
|
||||
fermentum. Morbi suscipit turpis sapien. Suspendisse eleifend sapien eget nunc mattis mattis. Phasellus
|
||||
rhoncus sodales libero a imperdiet. Nam in vulputate lectus. Proin accumsan enim non nibh sagittis
|
||||
ultricies. Nullam vitae ultricies nunc. Nullam ultrices dolor nec vehicula posuere.
|
||||
</p>
|
||||
</div>
|
||||
</ion-content>
|
||||
<ion-footer collapse="fade" translucent="true">
|
||||
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0">
|
||||
<img src="/src/components/footer/test/img.jpg" />
|
||||
</div>
|
||||
<ion-toolbar>
|
||||
<ion-title>Updated Just Now</ion-title>
|
||||
<ion-title>Footer</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
</div>
|
||||
|
||||
BIN
core/src/components/footer/test/img.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
@@ -1,32 +0,0 @@
|
||||
import type { E2EPage } from '@stencil/core/testing';
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
import { scrollToBottom } from '@utils/test';
|
||||
|
||||
/**
|
||||
* This test suite verifies that the fade effect for iOS is working correctly
|
||||
* when the `ion-footer` is using a custom scroll target with the `.ion-content-scroll-host`
|
||||
* selector.
|
||||
*/
|
||||
describe('footer: fade with custom scroll target: iOS', () => {
|
||||
let page: E2EPage;
|
||||
|
||||
beforeEach(async () => {
|
||||
page = await newE2EPage({
|
||||
url: '/src/components/footer/test/scroll-target?ionic:_testing=true&ionic:mode=ios',
|
||||
});
|
||||
});
|
||||
|
||||
it('should match existing visual screenshots', async () => {
|
||||
const compares = [];
|
||||
|
||||
compares.push(await page.compareScreenshot('footer: blurred'));
|
||||
|
||||
await scrollToBottom(page, '#scroll-target');
|
||||
|
||||
compares.push(await page.compareScreenshot('footer: not blurred'));
|
||||
|
||||
for (const compare of compares) {
|
||||
expect(compare).toMatchScreenshot();
|
||||
}
|
||||
});
|
||||
});
|
||||
33
core/src/components/footer/test/scroll-target/footer.e2e.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('footer: scroll-target', () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
});
|
||||
|
||||
/**
|
||||
* This test suite verifies that the fade effect for iOS is working correctly
|
||||
* when the `ion-footer` is using a custom scroll target with the `.ion-content-scroll-host`
|
||||
* selector.
|
||||
*/
|
||||
test('should not have visual regressions with custom scroll target footer', async ({ page, skip }) => {
|
||||
skip.mode('md', 'Translucent effect is only available in iOS mode.');
|
||||
skip.browser('firefox', 'Firefox has some issues rendering translucent effects on Linux.');
|
||||
|
||||
await page.goto('/src/components/footer/test/scroll-target');
|
||||
|
||||
const footer = page.locator('ion-footer');
|
||||
expect(await footer.screenshot()).toMatchSnapshot(
|
||||
`footer-fade-scroll-target-blurred-diff-${page.getSnapshotSettings()}.png`
|
||||
);
|
||||
|
||||
const scrollTarget = page.locator('#scroll-target');
|
||||
await scrollTarget.evaluate((el: HTMLDivElement) => (el.scrollTop = el.scrollHeight));
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(await footer.screenshot()).toMatchSnapshot(
|
||||
`footer-fade-scroll-target-not-blurred-diff-${page.getSnapshotSettings()}.png`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 49 KiB |
@@ -2,7 +2,7 @@
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Footer - Fade (custom scroll host)</title>
|
||||
<title>Footer - Scroll Target</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
@@ -12,49 +12,8 @@
|
||||
<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>
|
||||
|
||||
<style>
|
||||
.red {
|
||||
background-color: #ea445a;
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: #76d672;
|
||||
}
|
||||
|
||||
.blue {
|
||||
background-color: #3478f6;
|
||||
}
|
||||
|
||||
.yellow {
|
||||
background-color: #ffff80;
|
||||
}
|
||||
|
||||
.pink {
|
||||
background-color: #ff6b86;
|
||||
}
|
||||
|
||||
.purple {
|
||||
background-color: #7e34f6;
|
||||
}
|
||||
|
||||
.black {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.orange {
|
||||
background-color: #f69234;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
#scroll-target {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -68,32 +27,67 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<div class="ion-page">
|
||||
<ion-header translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-title>Mailboxes</ion-title>
|
||||
<ion-title>Header</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content fullscreen="true" scroll-y="false">
|
||||
<div id="scroll-target" class="ion-content-scroll-host">
|
||||
<div class="grid ion-padding">
|
||||
<div class="grid-item red"></div>
|
||||
<div class="grid-item green"></div>
|
||||
<div class="grid-item blue"></div>
|
||||
<div class="grid-item yellow"></div>
|
||||
<div class="grid-item pink"></div>
|
||||
<div class="grid-item purple"></div>
|
||||
<div class="grid-item black"></div>
|
||||
<div class="grid-item orange"></div>
|
||||
</div>
|
||||
<h1>Content</h1>
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas blandit, justo vitae rhoncus porttitor,
|
||||
massa elit molestie nisl, ut tincidunt orci enim ac ante. Vestibulum tincidunt dignissim elit. Morbi
|
||||
cursus hendrerit turpis, ut egestas tortor pulvinar sit amet. Interdum et malesuada fames ac ante ipsum
|
||||
primis in faucibus. Phasellus faucibus consequat purus vel mollis. Ut ultricies elit nunc. Quisque
|
||||
ultrices turpis vel augue auctor accumsan. Donec bibendum at nisi vel finibus. Fusce id imperdiet odio.
|
||||
Morbi orci ipsum, imperdiet ut iaculis sit amet, suscipit vulputate felis. Nullam arcu leo, volutpat eu
|
||||
porttitor sed, fringilla et arcu. Pellentesque ac libero sapien. Quisque id dui velit. Mauris et pretium
|
||||
tortor.
|
||||
</p>
|
||||
<p>
|
||||
Ut ultricies id augue vel aliquam. Etiam ornare finibus nisl, nec egestas urna. Nam pellentesque libero
|
||||
nec justo tristique lacinia. In sit amet gravida metus, ac tincidunt mauris. Fusce sit amet tempus turpis.
|
||||
Nulla ligula nunc, vestibulum quis quam in, feugiat aliquet nibh. Quisque in ante non nulla luctus gravida
|
||||
vel at lacus. Vivamus erat magna, elementum et dignissim nec, posuere eu ante. Praesent elementum, arcu
|
||||
scelerisque venenatis sodales, turpis nulla aliquam urna, id hendrerit est orci et purus. Duis sem ipsum,
|
||||
imperdiet eu elit id, tincidunt tempus sapien. Praesent tincidunt, sapien sed rhoncus euismod, lectus
|
||||
velit ornare nunc, dapibus varius turpis leo ut magna.
|
||||
</p>
|
||||
<p>
|
||||
Nam quis quam id ante mattis pulvinar non sed mauris. Donec tempor sed nulla at semper. Vivamus ac nunc
|
||||
bibendum, ullamcorper lacus quis, ornare massa. Cras gravida nibh risus, id sollicitudin eros ultricies
|
||||
non. Integer velit massa, suscipit tincidunt rhoncus ut, lacinia et nisl. Maecenas volutpat ipsum blandit
|
||||
sollicitudin lobortis. Suspendisse potenti. Cras non mi non arcu varius dapibus. Suspendisse maximus eget
|
||||
justo a lobortis. Donec nulla ipsum, efficitur eget velit eget, varius rutrum quam. Nulla metus risus,
|
||||
accumsan a tellus ac, faucibus blandit quam. Donec luctus, nisl ac ultricies ornare, nunc elit finibus
|
||||
magna, id elementum ante urna congue ex. Cras condimentum nisi sollicitudin tortor vestibulum luctus.
|
||||
Curabitur non ipsum et ex vestibulum congue.
|
||||
</p>
|
||||
<p>
|
||||
Maecenas rhoncus elit ut consectetur faucibus. Etiam sed sem sed mauris condimentum viverra sit amet at
|
||||
nibh. Mauris bibendum at purus a cursus. Suspendisse potenti. Donec vel lacus ac odio euismod lacinia id
|
||||
in urna. Donec commodo ipsum augue, at bibendum ex convallis suscipit. Nulla ac rhoncus odio. Aenean
|
||||
elementum est nec arcu ultricies dignissim.
|
||||
</p>
|
||||
<p>
|
||||
Sed tincidunt bibendum massa, egestas bibendum est imperdiet vitae. Fusce dignissim consectetur ante a
|
||||
fermentum. Morbi suscipit turpis sapien. Suspendisse eleifend sapien eget nunc mattis mattis. Phasellus
|
||||
rhoncus sodales libero a imperdiet. Nam in vulputate lectus. Proin accumsan enim non nibh sagittis
|
||||
ultricies. Nullam vitae ultricies nunc. Nullam ultrices dolor nec vehicula posuere.
|
||||
</p>
|
||||
</div>
|
||||
</ion-content>
|
||||
<ion-footer collapse="fade" translucent="true">
|
||||
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0">
|
||||
<img src="/src/components/footer/test/img.jpg" />
|
||||
</div>
|
||||
<ion-toolbar>
|
||||
<ion-title>Updated Just Now</ion-title>
|
||||
<ion-title>Footer</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
import { checkComponentModeClasses } from '@utils/test';
|
||||
|
||||
test('footer: translucent', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/footer/test/translucent?ionic:_testing=true',
|
||||
});
|
||||
|
||||
const globalMode = await page.evaluate(() => document.documentElement.getAttribute('mode'));
|
||||
await checkComponentModeClasses(await page.find('ion-footer'), globalMode!, 'footer-translucent');
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
@@ -1,171 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Header - Translucent</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>
|
||||
<ion-app>
|
||||
<ion-content fullscreen>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col size="6">
|
||||
<f class="red"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="green"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="blue"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="yellow"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="pink"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="purple"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="black"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="orange"></f>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae lobortis felis, eu sodales enim. Nam
|
||||
risus nibh, placerat at rutrum ac, vehicula vel velit. Lorem ipsum dolor sit amet, consectetur adipiscing
|
||||
elit. Vestibulum quis elementum ligula, ac aliquet nulla. Mauris non placerat mauris. Aenean dignissim lacinia
|
||||
porttitor. Praesent fringilla at est et ullamcorper. In ac ante ac massa porta venenatis ut id nibh. Fusce
|
||||
felis neque, aliquet in velit vitae, venenatis euismod libero. Donec vulputate, urna sed sagittis tempor, mi
|
||||
arcu tristique lacus, eget fringilla urna sem eget felis. Fusce dignissim lacus a scelerisque vehicula. Nulla
|
||||
nec enim nunc. Quisque nec dui eu nibh pulvinar bibendum quis ut nunc. Duis ex odio, sollicitudin ac mollis
|
||||
nec, fringilla non lacus. Maecenas sed tincidunt urna. Nunc feugiat maximus venenatis. Donec porttitor, felis
|
||||
eget porttitor tempor, quam nulla dapibus nisl, sit amet posuere sapien sapien malesuada tortor. Pellentesque
|
||||
habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque luctus, sapien nec
|
||||
tincidunt efficitur, nibh turpis faucibus felis, in sodales massa augue nec erat. Morbi sollicitudin nisi ex,
|
||||
et gravida nisi euismod eu. Suspendisse hendrerit dapibus orci, non viverra neque vestibulum id. Quisque vitae
|
||||
interdum ligula, quis consectetur nibh. Phasellus in mi at erat ultrices semper. Fusce sollicitudin at dolor
|
||||
ac lobortis. Morbi sit amet sem quis nulla pellentesque imperdiet. Nullam eu sem a enim maximus eleifend non
|
||||
vulputate leo. Proin quis congue lacus. Pellentesque placerat, quam at tempus pulvinar, nisl ligula tempor
|
||||
risus, quis pretium arcu odio et nulla. Nullam mollis consequat pharetra. Phasellus dictum velit sed purus
|
||||
mattis maximus. In molestie eget massa ut dignissim. In a interdum elit. In finibus nibh a mauris lobortis
|
||||
aliquet. Proin rutrum varius consequat. In mollis dapibus nisl, eu finibus urna viverra ac. Quisque
|
||||
scelerisque nisl eu suscipit consectetur.
|
||||
</p>
|
||||
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col size="6">
|
||||
<f class="red"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="green"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="blue"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="yellow"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="pink"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="purple"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="black"></f>
|
||||
</ion-col>
|
||||
<ion-col size="6">
|
||||
<f class="orange"></f>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-content>
|
||||
|
||||
<ion-footer translucent>
|
||||
<ion-toolbar>
|
||||
<ion-title>Footer - Translucent</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-title>Primary - Translucent</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="secondary">
|
||||
<ion-title>Secondary - Translucent</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="tertiary">
|
||||
<ion-title>Tertiary - Translucent</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="success">
|
||||
<ion-title>Success - Translucent</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="warning">
|
||||
<ion-title>Warning - Translucent</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="danger">
|
||||
<ion-title>Danger - Translucent</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="light">
|
||||
<ion-title>Light - Translucent</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar color="medium">
|
||||
<ion-title>Medium - Translucent</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
</ion-app>
|
||||
|
||||
<style>
|
||||
f {
|
||||
display: block;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.red {
|
||||
background-color: #ea445a;
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: #76d672;
|
||||
}
|
||||
|
||||
.blue {
|
||||
background-color: #3478f6;
|
||||
}
|
||||
|
||||
.yellow {
|
||||
background-color: #ffff80;
|
||||
}
|
||||
|
||||
.pink {
|
||||
background-color: #ff6b86;
|
||||
}
|
||||
|
||||
.purple {
|
||||
background-color: #7e34f6;
|
||||
}
|
||||
|
||||
.black {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.orange {
|
||||
background-color: #f69234;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@@ -12,6 +12,7 @@
|
||||
<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>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--ion-safe-area-bottom: 40px;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { AxePuppeteer } from '@axe-core/puppeteer';
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('header: axe', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/header/test/a11y?ionic:_testing=true',
|
||||
});
|
||||
|
||||
const results = await new AxePuppeteer(page).analyze();
|
||||
expect(results.violations.length).toEqual(0);
|
||||
});
|
||||
34
core/src/components/header/test/a11y/header.e2e.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import AxeBuilder from '@axe-core/playwright';
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('header: a11y', () => {
|
||||
test.beforeEach(async ({ skip }) => {
|
||||
skip.rtl();
|
||||
skip.mode('md');
|
||||
});
|
||||
|
||||
test('should not have accessibility violations', async ({ page }) => {
|
||||
await page.goto(`/src/components/header/test/a11y`);
|
||||
|
||||
const headers = page.locator('ion-header');
|
||||
await expect(headers.first()).toHaveAttribute('role', 'banner');
|
||||
await expect(headers.last()).toHaveAttribute('role', 'none');
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test('should allow for custom role', async ({ page }) => {
|
||||
/**
|
||||
* Note: This example should not be used in production.
|
||||
* This only serves to check that `role` can be customized.
|
||||
*/
|
||||
await page.setContent(`
|
||||
<ion-header role="heading"></ion-header>
|
||||
`);
|
||||
const header = page.locator('ion-header');
|
||||
|
||||
await expect(header).toHaveAttribute('role', 'heading');
|
||||
});
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('header: basic', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/header/test/basic?ionic:_testing=true',
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
78
core/src/components/header/test/basic/header.e2e.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('header: basic', () => {
|
||||
test.describe('header: rendering', () => {
|
||||
test('should not have visual regressions with basic header', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Header - Default</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
`);
|
||||
|
||||
const header = page.locator('ion-header');
|
||||
expect(await header.screenshot()).toMatchSnapshot(`header-diff-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('header: feature rendering', () => {
|
||||
test.beforeEach(({ skip }) => {
|
||||
skip.rtl();
|
||||
});
|
||||
|
||||
test('should not have visual regressions with no border', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-header class="ion-no-border">
|
||||
<ion-toolbar>
|
||||
<ion-title>Header - No Border</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
`);
|
||||
|
||||
const header = page.locator('ion-header');
|
||||
expect(await header.screenshot()).toMatchSnapshot(`header-no-border-diff-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
|
||||
test('should not have visual regressions with translucent header', async ({ page, skip }) => {
|
||||
skip.mode('md', 'Translucent effect is only available in iOS mode.');
|
||||
skip.browser('firefox', 'Firefox has some issues rendering translucent effects on Linux.');
|
||||
|
||||
await page.setContent(`
|
||||
<ion-header translucent="true">
|
||||
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0">
|
||||
<img style="transform: rotate(145deg) scale(1.5)" src="/src/components/header/test/img.jpg" />
|
||||
</div>
|
||||
<ion-toolbar>
|
||||
<ion-title>Header - Translucent</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
`);
|
||||
|
||||
const header = page.locator('ion-header');
|
||||
expect(await header.screenshot()).toMatchSnapshot(`header-translucent-diff-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
|
||||
test('should not have visual regressions with translucent header with color', async ({ page, skip }) => {
|
||||
skip.mode('md', 'Translucent effect is only available in iOS mode.');
|
||||
skip.browser('firefox', 'Firefox has some issues rendering translucent effects on Linux.');
|
||||
|
||||
await page.setContent(`
|
||||
<ion-header translucent="true">
|
||||
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0">
|
||||
<img style="transform: rotate(145deg) scale(1.5)" src="/src/components/header/test/img.jpg" />
|
||||
</div>
|
||||
<ion-toolbar color="tertiary">
|
||||
<ion-title>Header - Translucent</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
`);
|
||||
|
||||
const header = page.locator('ion-header');
|
||||
expect(await header.screenshot()).toMatchSnapshot(
|
||||
`header-translucent-color-diff-${page.getSnapshotSettings()}.png`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |