test(angular): add navigation tests (#16494)

This commit is contained in:
Manu MA
2018-11-28 18:17:44 +01:00
committed by GitHub
parent e6f9d37301
commit 2d0e0bafea
30 changed files with 547 additions and 48 deletions

View File

@ -23,7 +23,7 @@ export class NavController {
if (ev instanceof NavigationStart) { if (ev instanceof NavigationStart) {
const id = (ev.restoredState) ? ev.restoredState.navigationId : ev.id; const id = (ev.restoredState) ? ev.restoredState.navigationId : ev.id;
this.guessDirection = id < this.lastNavId ? 'back' : 'forward'; this.guessDirection = id < this.lastNavId ? 'back' : 'forward';
this.lastNavId = id; this.lastNavId = this.guessDirection === 'forward' ? ev.id : id;
} }
}); });
@ -77,6 +77,7 @@ export class NavController {
if (this.direction === 'auto') { if (this.direction === 'auto') {
direction = this.guessDirection; direction = this.guessDirection;
console.log('guessed', direction);
animated = direction !== 'root'; animated = direction !== 'root';
} else { } else {
animated = this.animated; animated = this.animated;

View File

@ -14,10 +14,6 @@ Run `ng generate component component-name` to generate a new component. You can
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests ## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).

View File

@ -1,9 +1,10 @@
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
import { getProperty, setProperty } from './utils';
describe('input', () => { describe('inputs', () => {
beforeEach(async () => { beforeEach(async () => {
await browser.get('/inputs-test'); await browser.get('/inputs');
}); });
it('should have default values', async () => { it('should have default values', async () => {
@ -42,16 +43,3 @@ describe('input', () => {
expect(await element(by.css('#range-note')).getText()).toEqual('20'); expect(await element(by.css('#range-note')).getText()).toEqual('20');
}); });
}); });
function getProperty(selector: string, property: string) {
return browser.executeScript(`
return document.querySelector('${selector}')['${property}'];
`);
}
function setProperty(selector: string, property: string, value: any) {
const text = JSON.stringify(value);
return browser.executeScript(`
document.querySelector('${selector}')['${property}'] = ${text};
`);
}

View File

@ -0,0 +1,28 @@
import { browser, element, by } from 'protractor';
import { waitTime, getText } from './utils';
describe('modals', () => {
beforeEach(async () => {
await browser.get('/modals');
});
it('should open and close', async () => {
await element(by.css('#action-button')).click();
await waitTime(1000);
const modal = element(by.css('app-modal-example'));
expect(await modal.$('h2').getText()).toEqual('123');
expect(await getText('#onWillDismiss')).toEqual('false');
expect(await getText('#onDidDismiss')).toEqual('false');
await modal.$('#close-modal').click();
await waitTime(1000);
expect(await getText('#onWillDismiss')).toEqual('true');
expect(await getText('#onDidDismiss')).toEqual('true');
});
});

View File

@ -0,0 +1,123 @@
import { browser, element, by } from 'protractor';
import { waitTime, testStack, testLifeCycle } from './utils';
describe('router-link', () => {
beforeEach(async () => {
await browser.get('/router-link');
});
it('should have correct lifecycle counts', async () => {
await testLifeCycle('app-router-link', {
ionViewWillEnter: 1,
ionViewDidEnter: 1,
ionViewWillLeave: 0,
ionViewDidLeave: 0,
});
});
describe('forward', () => {
it('should go forward with ion-button[href]', async () => {
await element(by.css('#href')).click();
await testForward();
});
it('should go forward with ion-button[routerLink]', async () => {
await element(by.css('#routerLink')).click();
await testForward();
});
it('should go forward with a[routerLink]', async () => {
await element(by.css('#a')).click();
await testForward();
});
it('should go forward with button + navigateForward', async () => {
await element(by.css('#button-forward')).click();
await testForward();
});
});
describe('root', () => {
it('should go root with ion-button[href][routerDirection=root]', async () => {
await element(by.css('#href-root')).click();
await testRoot();
});
it('should go root with ion-button[routerLink][routerDirection=root]', async () => {
await element(by.css('#routerLink-root')).click();
await testRoot();
});
it('should go root with a[routerLink][routerDirection=root]', async () => {
await element(by.css('#a-root')).click();
await testRoot();
});
it('should go root with button + navigateRoot', async () => {
await element(by.css('#button-root')).click();
await testRoot();
});
});
describe('back', () => {
it('should go back with ion-button[href][routerDirection=back]', async () => {
await element(by.css('#href-back')).click();
await testBack();
});
it('should go back with ion-button[routerLink][routerDirection=back]', async () => {
await element(by.css('#routerLink-back')).click();
await testBack();
});
it('should go back with a[routerLink][routerDirection=back]', async () => {
await element(by.css('#a-back')).click();
await testBack();
});
it('should go back with button + navigateBack', async () => {
await element(by.css('#button-back')).click();
await testBack();
});
});
});
async function testForward() {
await waitTime(500);
await testStack('ion-router-outlet', ['app-router-link', 'app-router-link-page']);
await testLifeCycle('app-router-link', {
ionViewWillEnter: 1,
ionViewDidEnter: 1,
ionViewWillLeave: 1,
ionViewDidLeave: 1,
});
await testLifeCycle('app-router-link-page', {
ionViewWillEnter: 1,
ionViewDidEnter: 1,
ionViewWillLeave: 0,
ionViewDidLeave: 0,
});
}
async function testRoot() {
await waitTime(50);
await testStack('ion-router-outlet', ['app-router-link-page']);
await testLifeCycle('app-router-link-page', {
ionViewWillEnter: 1,
ionViewDidEnter: 1,
ionViewWillLeave: 0,
ionViewDidLeave: 0,
});
}
async function testBack() {
await waitTime(500);
await testStack('ion-router-outlet', ['app-router-link-page']);
await testLifeCycle('app-router-link-page', {
ionViewWillEnter: 1,
ionViewDidEnter: 1,
ionViewWillLeave: 0,
ionViewDidLeave: 0,
});
}

View File

@ -0,0 +1,50 @@
import { browser, by, element, ElementFinder } from 'protractor';
export function getProperty(selector: string, property: string) {
return browser.executeScript(`
return document.querySelector('${selector}')['${property}'];
`);
}
export function getText(selector: string) {
return browser.executeScript(`
return document.querySelector('${selector}').textContent;
`);
}
export function setProperty(selector: string, property: string, value: any) {
const text = JSON.stringify(value);
return browser.executeScript(`
document.querySelector('${selector}')['${property}'] = ${text};
`);
}
export function waitTime(time: number) {
return new Promise(resolve => {
setTimeout(resolve, time);
});
}
export interface LifeCycleCount {
ionViewWillEnter: number;
ionViewDidEnter: number;
ionViewWillLeave: number;
ionViewDidLeave: number;
}
export async function testLifeCycle(selector: string, expected: LifeCycleCount) {
const page = element(by.css(selector));
expect(await getText(`${selector} #ionViewWillEnter`)).toEqual(expected.ionViewWillEnter.toString());
expect(await getText(`${selector} #ionViewDidEnter`)).toEqual(expected.ionViewDidEnter.toString());
expect(await getText(`${selector} #ionViewWillLeave`)).toEqual(expected.ionViewWillLeave.toString());
expect(await getText(`${selector} #ionViewDidLeave`)).toEqual(expected.ionViewDidLeave.toString());
}
export async function testStack(selector: string, expected: string[]) {
const children = browser.executeScript(`
return Array.from(
document.querySelector('${selector}').children
).map(el => el.tagName.toLowerCase());
`);
expect(children).toEqual(expected);
}

View File

@ -4,10 +4,13 @@
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
"sync:build": "sh scripts/build-ionic.sh",
"sync": "sh scripts/sync.sh",
"build": "ng build", "build": "ng build",
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e" "e2e": "ng e2e",
"postinstall": "npm run sync:build"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {

View File

@ -0,0 +1,14 @@
# Build core
pushd ../../..
pushd core
npm run build
npm run link
popd
pushd angular
npm link @ionic/core
npm run build
popd
popd

View File

@ -0,0 +1,7 @@
# Copy angular dist
rm -rf node_modules/@ionic/angular/dist
cp -a ../../dist node_modules/@ionic/angular/dist
# Copy core dist
rm -rf node_modules/@ionic/core/dist
cp -a ../../../core/dist node_modules/@ionic/core/dist

View File

@ -1,12 +1,17 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import { InputsTestComponent } from './inputs-test/inputs-test.component'; import { InputsComponent } from './inputs/inputs.component';
import { ModalComponent } from './modal/modal.component';
import { RouterLinkComponent } from './router-link/router-link.component';
import { RouterLinkPageComponent } from './router-link-page/router-link-page.component';
import { HomePageComponent } from './home-page/home-page.component';
const routes: Routes = [ const routes: Routes = [
{ { path: '', component: HomePageComponent },
path: 'inputs-test', { path: 'inputs', component: InputsComponent },
component: InputsTestComponent { path: 'modals', component: ModalComponent },
} { path: 'router-link', component: RouterLinkComponent },
{ path: 'router-link-page', component: RouterLinkPageComponent }
]; ];
@NgModule({ @NgModule({

View File

@ -5,12 +5,22 @@ import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { IonicModule } from '@ionic/angular'; import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { InputsTestComponent } from './inputs-test/inputs-test.component'; import { InputsComponent } from './inputs/inputs.component';
import { ModalComponent } from './modal/modal.component';
import { ModalExampleComponent } from './modal-example/modal-example.component';
import { RouterLinkComponent } from './router-link/router-link.component';
import { RouterLinkPageComponent } from './router-link-page/router-link-page.component';
import { HomePageComponent } from './home-page/home-page.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
InputsTestComponent InputsComponent,
ModalComponent,
ModalExampleComponent,
RouterLinkComponent,
RouterLinkPageComponent,
HomePageComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -18,6 +28,9 @@ import { InputsTestComponent } from './inputs-test/inputs-test.component';
FormsModule, FormsModule,
IonicModule.forRoot(), IonicModule.forRoot(),
], ],
entryComponents: [
ModalExampleComponent
],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -0,0 +1,27 @@
<ion-header>
<ion-toolbar>
<ion-title>
Test App
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item href="/inputs">
<ion-label>
Inputs test
</ion-label>
</ion-item>
<ion-item href="/modals">
<ion-label>
Modals test
</ion-label>
</ion-item>
<ion-item href="/router-link">
<ion-label>
Router link test
</ion-label>
</ion-item>
</ion-list>
</ion-content>

View File

@ -0,0 +1,9 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-home-page',
templateUrl: './home-page.component.html',
})
export class HomePageComponent {
}

View File

@ -1,11 +1,10 @@
import { Component, OnInit } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-inputs-test', selector: 'app-inputs',
templateUrl: './inputs-test.component.html', templateUrl: './inputs.component.html',
styleUrls: ['./inputs-test.component.css']
}) })
export class InputsTestComponent { export class InputsComponent {
datetime: string; datetime: string;
input: string; input: string;

View File

@ -0,0 +1,20 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="closeModal()" id="close-modal">
Close
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Value</h1>
<h2>{{value}}</h2>
<p>ionViewWillEnter: <span id="ionViewWillEnter">{{willEnter}}</span></p>
<p>ionViewDidEnter: <span id="ionViewDidEnter">{{didEnter}}</span></p>
<p>ionViewWillLeave: <span id="ionViewWillLeave">{{willLeave}}</span></p>
<p>ionViewDidLeave: <span id="ionViewDidLeave">{{didLeave}}</span></p>
</ion-content>

View File

@ -0,0 +1,41 @@
import { Component, Input, NgZone } from '@angular/core';
import { ModalController } from '@ionic/angular';
@Component({
selector: 'app-modal-example',
templateUrl: './modal-example.component.html',
})
export class ModalExampleComponent {
@Input() value: string;
willEnter = 0;
didEnter = 0;
willLeave = 0;
didLeave = 0;
constructor(
private modalCtrl: ModalController
) {}
ionViewWillEnter() {
NgZone.assertInAngularZone();
this.willEnter++;
}
ionViewDidEnter() {
NgZone.assertInAngularZone();
this.didEnter++;
}
ionViewWillLeave() {
NgZone.assertInAngularZone();
this.willLeave++;
}
ionViewDidLeave() {
NgZone.assertInAngularZone();
this.didLeave++;
}
closeModal() {
this.modalCtrl.dismiss();
}
}

View File

@ -0,0 +1,16 @@
<ion-header>
<ion-toolbar>
<ion-title>
Modal test
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<ion-button button (click)="openModal()" id="action-button">Open Modal</ion-button>
<p>
onWillDismiss: <span id="onWillDismiss">{{onWillDismiss}}</span>
</p>
<p>
onDidDismiss: <span id="onDidDismiss">{{onDidDismiss}}</span>
</p>
</ion-content>

View File

@ -0,0 +1,38 @@
import { Component, OnInit, NgZone } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { ModalExampleComponent } from '../modal-example/modal-example.component';
@Component({
selector: 'app-modal',
templateUrl: './modal.component.html',
})
export class ModalComponent {
onWillDismiss = false;
onDidDismiss = false;
constructor(
private modalCtrl: ModalController
) { }
async openModal() {
const modal = await this.modalCtrl.create({
component: ModalExampleComponent,
componentProps: {
value: '123'
}
});
await modal.present();
modal.onWillDismiss().then(() => {
NgZone.assertInAngularZone();
this.onWillDismiss = true;
});
modal.onDidDismiss().then(() => {
NgZone.assertInAngularZone();
if (!this.onWillDismiss) {
throw new Error('onWillDismiss should be emited first');
}
this.onDidDismiss = true;
});
}
}

View File

@ -0,0 +1,15 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
router-link page
<p>ionViewWillEnter: <span id="ionViewWillEnter">{{willEnter}}</span></p>
<p>ionViewDidEnter: <span id="ionViewDidEnter">{{didEnter}}</span></p>
<p>ionViewWillLeave: <span id="ionViewWillLeave">{{willLeave}}</span></p>
<p>ionViewDidLeave: <span id="ionViewDidLeave">{{didLeave}}</span></p>
</ion-content>

View File

@ -0,0 +1,30 @@
import { Component, OnInit, NgZone } from '@angular/core';
@Component({
selector: 'app-router-link-page',
templateUrl: './router-link-page.component.html',
})
export class RouterLinkPageComponent {
willEnter = 0;
didEnter = 0;
willLeave = 0;
didLeave = 0;
ionViewWillEnter() {
NgZone.assertInAngularZone();
this.willEnter++;
}
ionViewDidEnter() {
NgZone.assertInAngularZone();
this.didEnter++;
}
ionViewWillLeave() {
NgZone.assertInAngularZone();
this.willLeave++;
}
ionViewDidLeave() {
NgZone.assertInAngularZone();
this.didLeave++;
}
}

View File

@ -0,0 +1,34 @@
<ion-header>
<ion-toolbar>
<ion-title>
Router Link Test
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<p>ionViewWillEnter: <span id="ionViewWillEnter">{{willEnter}}</span></p>
<p>ionViewDidEnter: <span id="ionViewDidEnter">{{didEnter}}</span></p>
<p>ionViewWillLeave: <span id="ionViewWillLeave">{{willLeave}}</span></p>
<p>ionViewDidLeave: <span id="ionViewDidLeave">{{didLeave}}</span></p>
<p>
<ion-button href="/router-link-page" expand="block" id="href">ion-button[href]</ion-button>
<ion-button href="/router-link-page" routerDirection="root" expand="block" id="href-root">ion-button[href] (direction:root)</ion-button>
<ion-button href="/router-link-page" routerDirection="back" expand="block" id="href-back">ion-button[href] (direction:back)</ion-button>
</p>
<p>
<ion-button routerLink="/router-link-page" expand="block" color="dark" id="routerLink">ion-button[routerLink]</ion-button>
<ion-button routerLink="/router-link-page" routerDirection="root" expand="block" color="dark" id="routerLink-root">ion-button[routerLink] (direction:root)</ion-button>
<ion-button routerLink="/router-link-page" routerDirection="back" expand="block" color="dark" id="routerLink-back">ion-button[routerLink] (direction:back)</ion-button>
</p>
<p><a routerLink="/router-link-page" id="a">a[routerLink]</a></p>
<p><a routerLink="/router-link-page" routerDirection="root" id="a-root">a[routerLink] (direction:root)</a></p>
<p><a routerLink="/router-link-page" routerDirection="back" id="a-back">a[routerLink] (direction:back)</a></p>
<p><button (click)="navigate()" id="button">navigate</button></p>
<p><button (click)="navigateForward()" id="button-forward">navigateForward</button></p>
<p><button (click)="navigateRoot()" id="button-root">navigateForward</button></p>
<p><button (click)="navigateBack()" id="button-back">navigateBack</button></p>
</ion-content>

View File

@ -0,0 +1,53 @@
import { Component, NgZone } from '@angular/core';
import { NavController } from '@ionic/angular';
import { Router } from '@angular/router';
@Component({
selector: 'app-router-link',
templateUrl: './router-link.component.html',
})
export class RouterLinkComponent {
willEnter = 0;
didEnter = 0;
willLeave = 0;
didLeave = 0;
constructor(
private navCtrl: NavController,
private router: Router
) {}
navigate() {
this.router.navigateByUrl('/router-link-page');
}
navigateForward() {
this.navCtrl.navigateForward('/router-link-page');
}
navigateBack() {
this.navCtrl.navigateBack('/router-link-page');
}
navigateRoot() {
this.navCtrl.navigateRoot('/router-link-page');
}
ionViewWillEnter() {
NgZone.assertInAngularZone();
this.willEnter++;
}
ionViewDidEnter() {
NgZone.assertInAngularZone();
this.didEnter++;
}
ionViewWillLeave() {
NgZone.assertInAngularZone();
this.willLeave++;
}
ionViewDidLeave() {
NgZone.assertInAngularZone();
this.didLeave++;
}
}

View File

@ -939,7 +939,6 @@ ion-select,event,ionBlur,void,true
ion-select,event,ionCancel,void,true ion-select,event,ionCancel,void,true
ion-select,event,ionChange,SelectInputChangeEvent,true ion-select,event,ionChange,SelectInputChangeEvent,true
ion-select,event,ionFocus,void,true ion-select,event,ionFocus,void,true
ion-select,css-prop,--color
ion-select,css-prop,--icon-color ion-select,css-prop,--icon-color
ion-select,css-prop,--padding-bottom ion-select,css-prop,--padding-bottom
ion-select,css-prop,--padding-end ion-select,css-prop,--padding-end

View File

@ -90,7 +90,6 @@ Type: `Promise<HTMLIonActionSheetElement | HTMLIonAlertElement | HTMLIonPopoverE
| Name | Description | | Name | Description |
| --------------------- | ------------------------------------ | | --------------------- | ------------------------------------ |
| `--color` | Color of the select text |
| `--icon-color` | Color of the select icon | | `--icon-color` | Color of the select icon |
| `--padding-bottom` | Bottom padding of the select | | `--padding-bottom` | Bottom padding of the select |
| `--padding-end` | End padding of the select | | `--padding-end` | End padding of the select |

View File

@ -5,7 +5,6 @@
// -------------------------------------------------- // --------------------------------------------------
:host { :host {
--color: #{$select-ios-text-color};
--icon-color: #{$select-ios-icon-color}; --icon-color: #{$select-ios-icon-color};
--padding-top: #{$select-ios-padding-top}; --padding-top: #{$select-ios-padding-top};
--padding-end: #{$select-ios-padding-end}; --padding-end: #{$select-ios-padding-end};

View File

@ -5,7 +5,6 @@
// -------------------------------------------------- // --------------------------------------------------
:host { :host {
--color: #{$select-md-text-color};
--icon-color: #{$select-md-icon-color}; --icon-color: #{$select-md-icon-color};
--padding-top: #{$select-md-padding-top}; --padding-top: #{$select-md-padding-top};
--padding-end: #{$select-md-padding-end}; --padding-end: #{$select-md-padding-end};

View File

@ -5,7 +5,6 @@
:host { :host {
/** /**
* @prop --color: Color of the select text
* @prop --icon-color: Color of the select icon * @prop --icon-color: Color of the select icon
* @prop --padding-top: Top padding of the select * @prop --padding-top: Top padding of the select
* @prop --padding-end: End padding of the select * @prop --padding-end: End padding of the select
@ -19,8 +18,6 @@
display: flex; display: flex;
position: relative; position: relative;
color: var(--color);
font-family: $font-family-base; font-family: $font-family-base;
overflow: hidden; overflow: hidden;
@ -88,7 +85,3 @@ button {
::slotted(ion-select-option) { ::slotted(ion-select-option) {
display: none; display: none;
} }
button:focus {
outline: none;
}

View File

@ -57,10 +57,10 @@
<ion-list> <ion-list>
<ion-list-header>Select - Custom Interface Options</ion-list-header> <ion-list-header>Select - Custom Interface Options</ion-list-header>
<ion-item> <ion-item color="dark">
<ion-label>Alert</ion-label> <ion-label>Alert</ion-label>
<ion-select id="customAlertSelect" interface="alert" multiple="true" placeholder="Select One"> <ion-select id="customAlertSelect" interface="alert" multiple="true" placeholder="Select One">
<ion-select-option value="bacon">Bacon</ion-select-option> <ion-select-option value="bacon" selected>Bacon</ion-select-option>
<ion-select-option value="olives">Black Olives</ion-select-option> <ion-select-option value="olives">Black Olives</ion-select-option>
<ion-select-option value="xcheese">Extra Cheese</ion-select-option> <ion-select-option value="xcheese">Extra Cheese</ion-select-option>
<ion-select-option value="peppers">Green Peppers</ion-select-option> <ion-select-option value="peppers">Green Peppers</ion-select-option>
@ -73,7 +73,7 @@
</ion-select> </ion-select>
</ion-item> </ion-item>
<ion-item> <ion-item color="dark">
<ion-label>Popover</ion-label> <ion-label>Popover</ion-label>
<ion-select id="customPopoverSelect" interface="popover" placeholder="Select One"> <ion-select id="customPopoverSelect" interface="popover" placeholder="Select One">
<ion-select-option value="brown">Brown</ion-select-option> <ion-select-option value="brown">Brown</ion-select-option>
@ -83,7 +83,7 @@
</ion-select> </ion-select>
</ion-item> </ion-item>
<ion-item> <ion-item color="dark">
<ion-label>Action Sheet</ion-label> <ion-label>Action Sheet</ion-label>
<ion-select id="customActionSheetSelect" interface="action-sheet" placeholder="Select One"> <ion-select id="customActionSheetSelect" interface="action-sheet" placeholder="Select One">
<ion-select-option value="red">Red</ion-select-option> <ion-select-option value="red">Red</ion-select-option>