mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 10:41:13 +08:00
Merge branch 'main' into chore-update-from-main-2
This commit is contained in:
@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [8.3.0](https://github.com/ionic-team/ionic-framework/compare/v8.2.8...v8.3.0) (2024-09-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **react, vue, angular:** use tabs without router ([#29794](https://github.com/ionic-team/ionic-framework/issues/29794)) ([867066b](https://github.com/ionic-team/ionic-framework/commit/867066b4eba369de025f3c7c8ef33e7089cdf3bc)), closes [#25184](https://github.com/ionic-team/ionic-framework/issues/25184)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.2.8](https://github.com/ionic-team/ionic-framework/compare/v8.2.7...v8.2.8) (2024-09-05)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.2.7](https://github.com/ionic-team/ionic-framework/compare/v8.2.5...v8.2.7) (2024-08-13)
|
||||
|
||||
|
||||
|
@ -7,6 +7,8 @@ import {
|
||||
HostListener,
|
||||
Output,
|
||||
ViewChild,
|
||||
AfterViewInit,
|
||||
QueryList,
|
||||
} from '@angular/core';
|
||||
|
||||
import { NavController } from '../../providers/nav-controller';
|
||||
@ -17,14 +19,15 @@ import { StackDidChangeEvent, StackWillChangeEvent } from './stack-utils';
|
||||
selector: 'ion-tabs',
|
||||
})
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
export abstract class IonTabs implements AfterContentInit, AfterContentChecked {
|
||||
export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterContentChecked {
|
||||
/**
|
||||
* Note: These must be redeclared on each child class since it needs
|
||||
* access to generated components such as IonRouterOutlet and IonTabBar.
|
||||
*/
|
||||
abstract outlet: any;
|
||||
abstract tabBar: any;
|
||||
abstract tabBars: any;
|
||||
abstract tabBars: QueryList<any>;
|
||||
abstract tabs: QueryList<any>;
|
||||
|
||||
@ViewChild('tabsInner', { read: ElementRef, static: true }) tabsInner: ElementRef<HTMLDivElement>;
|
||||
|
||||
@ -39,8 +42,29 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked {
|
||||
|
||||
private tabBarSlot = 'bottom';
|
||||
|
||||
private hasTab = false;
|
||||
private selectedTab?: { tab: string };
|
||||
private leavingTab?: any;
|
||||
|
||||
constructor(private navCtrl: NavController) {}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
/**
|
||||
* Developers must pass at least one ion-tab
|
||||
* inside of ion-tabs if they want to use a
|
||||
* basic tab-based navigation without the
|
||||
* history stack or URL updates associated
|
||||
* with the router.
|
||||
*/
|
||||
const firstTab = this.tabs.length > 0 ? this.tabs.first : undefined;
|
||||
|
||||
if (firstTab) {
|
||||
this.hasTab = true;
|
||||
this.setActiveTab(firstTab.tab);
|
||||
this.tabSwitch();
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
this.detectSlotChanges();
|
||||
}
|
||||
@ -96,6 +120,19 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked {
|
||||
select(tabOrEvent: string | CustomEvent): Promise<boolean> | undefined {
|
||||
const isTabString = typeof tabOrEvent === 'string';
|
||||
const tab = isTabString ? tabOrEvent : (tabOrEvent as CustomEvent).detail.tab;
|
||||
|
||||
/**
|
||||
* If the tabs are not using the router, then
|
||||
* the tab switch logic is handled by the tabs
|
||||
* component itself.
|
||||
*/
|
||||
if (this.hasTab) {
|
||||
this.setActiveTab(tab);
|
||||
this.tabSwitch();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const alreadySelected = this.outlet.getActiveStackId() === tab;
|
||||
const tabRootUrl = `${this.outlet.tabsPrefix}/${tab}`;
|
||||
|
||||
@ -142,7 +179,46 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked {
|
||||
}
|
||||
}
|
||||
|
||||
private setActiveTab(tab: string): void {
|
||||
const tabs = this.tabs;
|
||||
const selectedTab = tabs.find((t: any) => t.tab === tab);
|
||||
|
||||
if (!selectedTab) {
|
||||
console.error(`[Ionic Error]: Tab with id: "${tab}" does not exist`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.leavingTab = this.selectedTab;
|
||||
this.selectedTab = selectedTab;
|
||||
|
||||
this.ionTabsWillChange.emit({ tab });
|
||||
|
||||
selectedTab.el.active = true;
|
||||
}
|
||||
|
||||
private tabSwitch(): void {
|
||||
const { selectedTab, leavingTab } = this;
|
||||
|
||||
if (this.tabBar && selectedTab) {
|
||||
this.tabBar.selectedTab = selectedTab.tab;
|
||||
}
|
||||
|
||||
if (leavingTab?.tab !== selectedTab?.tab) {
|
||||
if (leavingTab?.el) {
|
||||
leavingTab.el.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedTab) {
|
||||
this.ionTabsDidChange.emit({ tab: selectedTab.tab });
|
||||
}
|
||||
}
|
||||
|
||||
getSelected(): string | undefined {
|
||||
if (this.hasTab) {
|
||||
return this.selectedTab?.tab;
|
||||
}
|
||||
|
||||
return this.outlet.getActiveStackId();
|
||||
}
|
||||
|
||||
|
19
packages/angular/package-lock.json
generated
19
packages/angular/package-lock.json
generated
@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "8.2.7",
|
||||
"version": "8.3.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/angular",
|
||||
"version": "8.2.7",
|
||||
"version": "8.3.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "^8.2.7",
|
||||
"@ionic/core": "^8.3.0",
|
||||
"ionicons": "^7.0.0",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"tslib": "^2.3.0"
|
||||
@ -1398,9 +1398,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.2.7.tgz",
|
||||
"integrity": "sha512-pi7cGjgUcX3dMiku7mO/wh1mQ6yHsHZaUQKKor+fabmoL04YSRJCSJfqf9vImMwmegeon9xU6VPZuiJh2HV8YQ==",
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.0.tgz",
|
||||
"integrity": "sha512-Yv/LA2OOsvdelhvVYKTNL+0AduKXO74DTdQ3lqS/NN512/wCNf2CVt8J62oCs25XOttLAy8RflDUN8joT3bO7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.19.2",
|
||||
"ionicons": "^7.2.2",
|
||||
@ -9820,9 +9821,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.2.7.tgz",
|
||||
"integrity": "sha512-pi7cGjgUcX3dMiku7mO/wh1mQ6yHsHZaUQKKor+fabmoL04YSRJCSJfqf9vImMwmegeon9xU6VPZuiJh2HV8YQ==",
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.0.tgz",
|
||||
"integrity": "sha512-Yv/LA2OOsvdelhvVYKTNL+0AduKXO74DTdQ3lqS/NN512/wCNf2CVt8J62oCs25XOttLAy8RflDUN8joT3bO7A==",
|
||||
"requires": {
|
||||
"@stencil/core": "^4.19.2",
|
||||
"ionicons": "^7.2.2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "8.2.7",
|
||||
"version": "8.3.0",
|
||||
"description": "Angular specific wrappers for @ionic/core",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@ -47,7 +47,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/core": "^8.2.7",
|
||||
"@ionic/core": "^8.3.0",
|
||||
"ionicons": "^7.0.0",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"tslib": "^2.3.0"
|
||||
|
@ -20,7 +20,7 @@ export const appInitialize = (config: Config, doc: Document, zone: NgZone) => {
|
||||
|
||||
return applyPolyfills().then(() => {
|
||||
return defineCustomElements(win, {
|
||||
exclude: ['ion-tabs', 'ion-tab'],
|
||||
exclude: ['ion-tabs'],
|
||||
syncQueue: true,
|
||||
raf,
|
||||
jmp: (h: any) => zone.runOutsideAngular(h),
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, ContentChild, ContentChildren, ViewChild, QueryList } from '@angular/core';
|
||||
import { IonTabs as IonTabsBase } from '@ionic/angular/common';
|
||||
|
||||
import { IonTabBar } from '../proxies';
|
||||
import { IonTabBar, IonTab } from '../proxies';
|
||||
|
||||
import { IonRouterOutlet } from './ion-router-outlet';
|
||||
|
||||
@ -11,11 +11,13 @@ import { IonRouterOutlet } from './ion-router-outlet';
|
||||
<ng-content select="[slot=top]"></ng-content>
|
||||
<div class="tabs-inner" #tabsInner>
|
||||
<ion-router-outlet
|
||||
*ngIf="tabs.length === 0"
|
||||
#outlet
|
||||
tabs="true"
|
||||
(stackWillChange)="onStackWillChange($event)"
|
||||
(stackDidChange)="onStackDidChange($event)"
|
||||
></ion-router-outlet>
|
||||
<ng-content *ngIf="tabs.length > 0" select="ion-tab"></ng-content>
|
||||
</div>
|
||||
<ng-content></ng-content>
|
||||
`,
|
||||
@ -52,4 +54,5 @@ export class IonTabs extends IonTabsBase {
|
||||
|
||||
@ContentChild(IonTabBar, { static: false }) tabBar: IonTabBar | undefined;
|
||||
@ContentChildren(IonTabBar) tabBars: QueryList<IonTabBar>;
|
||||
@ContentChildren(IonTab) tabs: QueryList<IonTab>;
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ export const DIRECTIVES = [
|
||||
d.IonSkeletonText,
|
||||
d.IonSpinner,
|
||||
d.IonSplitPane,
|
||||
d.IonTab,
|
||||
d.IonTabBar,
|
||||
d.IonTabButton,
|
||||
d.IonText,
|
||||
|
@ -2154,6 +2154,29 @@ export declare interface IonSplitPane extends Components.IonSplitPane {
|
||||
}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['component', 'mode', 'tab', 'theme'],
|
||||
methods: ['setActive']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-tab',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['component', 'mode', 'tab', 'theme'],
|
||||
})
|
||||
export class IonTab {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export declare interface IonTab extends Components.IonTab {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['color', 'mode', 'selectedTab', 'theme', 'translucent']
|
||||
})
|
||||
|
@ -69,6 +69,7 @@ import { defineCustomElement as defineIonSelectOption } from '@ionic/core/compon
|
||||
import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js';
|
||||
import { defineCustomElement as defineIonSpinner } from '@ionic/core/components/ion-spinner.js';
|
||||
import { defineCustomElement as defineIonSplitPane } from '@ionic/core/components/ion-split-pane.js';
|
||||
import { defineCustomElement as defineIonTab } from '@ionic/core/components/ion-tab.js';
|
||||
import { defineCustomElement as defineIonTabBar } from '@ionic/core/components/ion-tab-bar.js';
|
||||
import { defineCustomElement as defineIonTabButton } from '@ionic/core/components/ion-tab-button.js';
|
||||
import { defineCustomElement as defineIonText } from '@ionic/core/components/ion-text.js';
|
||||
@ -1944,6 +1945,31 @@ export declare interface IonSplitPane extends Components.IonSplitPane {
|
||||
}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonTab,
|
||||
inputs: ['component', 'tab'],
|
||||
methods: ['setActive']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-tab',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['component', 'tab'],
|
||||
standalone: true
|
||||
})
|
||||
export class IonTab {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export declare interface IonTab extends Components.IonTab {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonTabBar,
|
||||
inputs: ['color', 'mode', 'selectedTab', 'theme', 'translucent']
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { NgIf } from '@angular/common';
|
||||
import { Component, ContentChild, ContentChildren, ViewChild, QueryList } from '@angular/core';
|
||||
import { IonTabs as IonTabsBase } from '@ionic/angular/common';
|
||||
|
||||
import { IonTabBar } from '../directives/proxies';
|
||||
import { IonTabBar, IonTab } from '../directives/proxies';
|
||||
|
||||
import { IonRouterOutlet } from './router-outlet';
|
||||
|
||||
@ -11,11 +12,13 @@ import { IonRouterOutlet } from './router-outlet';
|
||||
<ng-content select="[slot=top]"></ng-content>
|
||||
<div class="tabs-inner" #tabsInner>
|
||||
<ion-router-outlet
|
||||
*ngIf="tabs.length === 0"
|
||||
#outlet
|
||||
tabs="true"
|
||||
(stackWillChange)="onStackWillChange($event)"
|
||||
(stackDidChange)="onStackDidChange($event)"
|
||||
></ion-router-outlet>
|
||||
<ng-content *ngIf="tabs.length > 0" select="ion-tab"></ng-content>
|
||||
</div>
|
||||
<ng-content></ng-content>
|
||||
`,
|
||||
@ -46,7 +49,7 @@ import { IonRouterOutlet } from './router-outlet';
|
||||
}
|
||||
`,
|
||||
],
|
||||
imports: [IonRouterOutlet],
|
||||
imports: [IonRouterOutlet, NgIf],
|
||||
})
|
||||
// eslint-disable-next-line @angular-eslint/component-class-suffix
|
||||
export class IonTabs extends IonTabsBase {
|
||||
@ -54,4 +57,5 @@ export class IonTabs extends IonTabsBase {
|
||||
|
||||
@ContentChild(IonTabBar, { static: false }) tabBar: IonTabBar | undefined;
|
||||
@ContentChildren(IonTabBar) tabBars: QueryList<IonTabBar>;
|
||||
@ContentChildren(IonTab) tabs: QueryList<IonTab>;
|
||||
}
|
||||
|
@ -1,436 +1,462 @@
|
||||
describe('Tabs', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/lazy/tabs');
|
||||
})
|
||||
|
||||
describe('entry url - /tabs', () => {
|
||||
it('should redirect and load tab-account', () => {
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1']);
|
||||
testState(1, 'account');
|
||||
});
|
||||
|
||||
it('should navigate between tabs and ionChange events should be dispatched', () => {
|
||||
let tab = testTabTitle('Tab 1 - Page 1');
|
||||
tab.find('.segment-changed').should('have.text', 'false');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
tab = testTabTitle('Tab 2 - Page 1');
|
||||
tab.find('.segment-changed').should('have.text', 'false');
|
||||
});
|
||||
|
||||
describe('when navigating between tabs', () => {
|
||||
|
||||
it('should emit ionTabsWillChange before setting the selected tab', () => {
|
||||
cy.get('#ionTabsWillChangeCounter').should('have.text', '1');
|
||||
cy.get('#ionTabsWillChangeEvent').should('have.text', 'account');
|
||||
cy.get('#ionTabsWillChangeSelectedTab').should('have.text', '');
|
||||
|
||||
cy.get('#ionTabsDidChangeCounter').should('have.text', '1');
|
||||
cy.get('#ionTabsDidChangeEvent').should('have.text', 'account');
|
||||
cy.get('#ionTabsDidChangeSelectedTab').should('have.text', 'account');
|
||||
|
||||
describe('With IonRouterOutlet', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/lazy/tabs');
|
||||
})
|
||||
|
||||
describe('entry url - /tabs', () => {
|
||||
it('should redirect and load tab-account', () => {
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1']);
|
||||
testState(1, 'account');
|
||||
});
|
||||
|
||||
it('should navigate between tabs and ionChange events should be dispatched', () => {
|
||||
let tab = testTabTitle('Tab 1 - Page 1');
|
||||
tab.find('.segment-changed').should('have.text', 'false');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
|
||||
cy.get('#ionTabsWillChangeCounter').should('have.text', '2');
|
||||
cy.get('#ionTabsWillChangeEvent').should('have.text', 'contact');
|
||||
cy.get('#ionTabsWillChangeSelectedTab').should('have.text', 'account');
|
||||
|
||||
cy.get('#ionTabsDidChangeCounter').should('have.text', '2');
|
||||
cy.get('#ionTabsDidChangeEvent').should('have.text', 'contact');
|
||||
cy.get('#ionTabsDidChangeSelectedTab').should('have.text', 'contact');
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
it('should simulate stack + double tab click', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested']);
|
||||
testState(1, 'account');
|
||||
|
||||
// When you call find on tab above it changes the value of tab
|
||||
// so we need to redefine it
|
||||
tab = getSelectedTab();
|
||||
tab.find('ion-back-button').should('be.visible');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']);
|
||||
testState(2, 'contact');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']);
|
||||
testState(3, 'account');
|
||||
|
||||
tab = getSelectedTab();
|
||||
tab.find('ion-back-button').should('be.visible');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']);
|
||||
testState(3, 'account');
|
||||
});
|
||||
|
||||
it('should simulate stack + back button click', () => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testState(1, 'account');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
testState(2, 'contact');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testState(3, 'account');
|
||||
|
||||
cy.get('ion-back-button').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']);
|
||||
testState(3, 'account');
|
||||
});
|
||||
|
||||
it('should navigate deep then go home', () => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
cy.ionPageVisible('app-tabs-tab1-nested');
|
||||
cy.ionPageHidden('app-tabs-tab1');
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
|
||||
cy.get('#goto-next').click();
|
||||
cy.ionPageVisible('app-tabs-tab1-nested:last-of-type');
|
||||
cy.ionPageHidden('app-tabs-tab1-nested:first-of-type');
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (2)');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
cy.ionPageVisible('app-tabs-tab2');
|
||||
cy.ionPageHidden('app-tabs-tab1-nested:last-of-type');
|
||||
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
cy.ionPageVisible('app-tabs-tab1-nested:last-of-type');
|
||||
cy.ionPageHidden('app-tabs-tab2');
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (2)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', [
|
||||
'app-tabs-tab1',
|
||||
'app-tabs-tab1-nested',
|
||||
'app-tabs-tab1-nested',
|
||||
'app-tabs-tab2'
|
||||
]);
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
|
||||
/**
|
||||
* Wait for the leaving view to
|
||||
* be unmounted otherwise testTabTitle
|
||||
* may get the leaving view before it
|
||||
* is unmounted.
|
||||
*/
|
||||
cy.ionPageVisible('app-tabs-tab1');
|
||||
cy.ionPageDoesNotExist('app-tabs-tab1-nested');
|
||||
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', [
|
||||
'app-tabs-tab1',
|
||||
'app-tabs-tab2'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should switch tabs and go back', () => {
|
||||
cy.get('#tab-button-contact').click();
|
||||
const tab = testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
tab.find('#goto-tab1-page1').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']);
|
||||
});
|
||||
|
||||
it('should switch tabs and go to nested', () => {
|
||||
cy.get('#tab-button-contact').click();
|
||||
const tab = testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab2', 'app-tabs-tab1-nested']);
|
||||
});
|
||||
|
||||
it('should load lazy loaded tab', () => {
|
||||
cy.get('#tab-button-lazy').click();
|
||||
cy.ionPageVisible('app-tabs-tab3');
|
||||
testTabTitle('Tab 3 - Page 1');
|
||||
});
|
||||
|
||||
it('should use ion-back-button defaultHref', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-tab3-page2').click();
|
||||
testTabTitle('Tab 3 - Page 2');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3-nested']);
|
||||
|
||||
tab = getSelectedTab();
|
||||
tab.find('ion-back-button').click();
|
||||
testTabTitle('Tab 3 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3']);
|
||||
});
|
||||
|
||||
it('should preserve navigation extras when switching tabs', () => {
|
||||
const expectUrlToContain = 'search=hello#fragment';
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-nested-page1-with-query-params').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testUrlContains(expectUrlToContain);
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testUrlContains(expectUrlToContain);
|
||||
});
|
||||
|
||||
it('should set root when clicking on an active tab to navigate to the root', () => {
|
||||
const expectNestedTabUrlToContain = 'search=hello#fragment';
|
||||
cy.url().then(url => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-nested-page1-with-query-params').click();
|
||||
tab = testTabTitle('Tab 2 - Page 1');
|
||||
tab.find('.segment-changed').should('have.text', 'false');
|
||||
});
|
||||
|
||||
describe('when navigating between tabs', () => {
|
||||
|
||||
it('should emit ionTabsWillChange before setting the selected tab', () => {
|
||||
cy.get('#ionTabsWillChangeCounter').should('have.text', '1');
|
||||
cy.get('#ionTabsWillChangeEvent').should('have.text', 'account');
|
||||
cy.get('#ionTabsWillChangeSelectedTab').should('have.text', '');
|
||||
|
||||
cy.get('#ionTabsDidChangeCounter').should('have.text', '1');
|
||||
cy.get('#ionTabsDidChangeEvent').should('have.text', 'account');
|
||||
cy.get('#ionTabsDidChangeSelectedTab').should('have.text', 'account');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
|
||||
cy.get('#ionTabsWillChangeCounter').should('have.text', '2');
|
||||
cy.get('#ionTabsWillChangeEvent').should('have.text', 'contact');
|
||||
cy.get('#ionTabsWillChangeSelectedTab').should('have.text', 'account');
|
||||
|
||||
cy.get('#ionTabsDidChangeCounter').should('have.text', '2');
|
||||
cy.get('#ionTabsDidChangeEvent').should('have.text', 'contact');
|
||||
cy.get('#ionTabsDidChangeSelectedTab').should('have.text', 'contact');
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
it('should simulate stack + double tab click', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testUrlContains(expectNestedTabUrlToContain);
|
||||
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested']);
|
||||
testState(1, 'account');
|
||||
|
||||
// When you call find on tab above it changes the value of tab
|
||||
// so we need to redefine it
|
||||
tab = getSelectedTab();
|
||||
tab.find('ion-back-button').should('be.visible');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']);
|
||||
testState(2, 'contact');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']);
|
||||
testState(3, 'account');
|
||||
|
||||
tab = getSelectedTab();
|
||||
tab.find('ion-back-button').should('be.visible');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
testUrlEquals(url);
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']);
|
||||
testState(3, 'account');
|
||||
});
|
||||
|
||||
it('should simulate stack + back button click', () => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testState(1, 'account');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
testState(2, 'contact');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testState(3, 'account');
|
||||
|
||||
cy.get('ion-back-button').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']);
|
||||
testState(3, 'account');
|
||||
});
|
||||
|
||||
it('should navigate deep then go home', () => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
cy.ionPageVisible('app-tabs-tab1-nested');
|
||||
cy.ionPageHidden('app-tabs-tab1');
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
|
||||
cy.get('#goto-next').click();
|
||||
cy.ionPageVisible('app-tabs-tab1-nested:last-of-type');
|
||||
cy.ionPageHidden('app-tabs-tab1-nested:first-of-type');
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (2)');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
cy.ionPageVisible('app-tabs-tab2');
|
||||
cy.ionPageHidden('app-tabs-tab1-nested:last-of-type');
|
||||
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
cy.ionPageVisible('app-tabs-tab1-nested:last-of-type');
|
||||
cy.ionPageHidden('app-tabs-tab2');
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (2)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', [
|
||||
'app-tabs-tab1',
|
||||
'app-tabs-tab1-nested',
|
||||
'app-tabs-tab1-nested',
|
||||
'app-tabs-tab2'
|
||||
]);
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
|
||||
/**
|
||||
* Wait for the leaving view to
|
||||
* be unmounted otherwise testTabTitle
|
||||
* may get the leaving view before it
|
||||
* is unmounted.
|
||||
*/
|
||||
cy.ionPageVisible('app-tabs-tab1');
|
||||
cy.ionPageDoesNotExist('app-tabs-tab1-nested');
|
||||
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', [
|
||||
'app-tabs-tab1',
|
||||
'app-tabs-tab2'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should switch tabs and go back', () => {
|
||||
cy.get('#tab-button-contact').click();
|
||||
const tab = testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
tab.find('#goto-tab1-page1').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']);
|
||||
});
|
||||
|
||||
it('should switch tabs and go to nested', () => {
|
||||
cy.get('#tab-button-contact').click();
|
||||
const tab = testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab2', 'app-tabs-tab1-nested']);
|
||||
});
|
||||
|
||||
it('should load lazy loaded tab', () => {
|
||||
cy.get('#tab-button-lazy').click();
|
||||
cy.ionPageVisible('app-tabs-tab3');
|
||||
testTabTitle('Tab 3 - Page 1');
|
||||
});
|
||||
|
||||
it('should use ion-back-button defaultHref', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-tab3-page2').click();
|
||||
testTabTitle('Tab 3 - Page 2');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3-nested']);
|
||||
|
||||
tab = getSelectedTab();
|
||||
tab.find('ion-back-button').click();
|
||||
testTabTitle('Tab 3 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3']);
|
||||
});
|
||||
|
||||
it('should preserve navigation extras when switching tabs', () => {
|
||||
const expectUrlToContain = 'search=hello#fragment';
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-nested-page1-with-query-params').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testUrlContains(expectUrlToContain);
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testUrlContains(expectUrlToContain);
|
||||
});
|
||||
|
||||
it('should set root when clicking on an active tab to navigate to the root', () => {
|
||||
const expectNestedTabUrlToContain = 'search=hello#fragment';
|
||||
cy.url().then(url => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-nested-page1-with-query-params').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testUrlContains(expectNestedTabUrlToContain);
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
testUrlEquals(url);
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
describe('entry tab contains navigation extras', () => {
|
||||
const expectNestedTabUrlToContain = 'search=hello#fragment';
|
||||
const rootUrlParams = 'test=123#rootFragment';
|
||||
const rootUrl = `/lazy/tabs/account?${rootUrlParams}`;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit(rootUrl);
|
||||
})
|
||||
|
||||
it('should preserve root url navigation extras when clicking on an active tab to navigate to the root', () => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-nested-page1-with-query-params').click();
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testUrlContains(expectNestedTabUrlToContain);
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
testUrlContains(rootUrl);
|
||||
});
|
||||
|
||||
it('should preserve root url navigation extras when changing tabs', () => {
|
||||
getSelectedTab();
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
testUrlContains(rootUrl);
|
||||
});
|
||||
|
||||
it('should navigate deep then go home and preserve navigation extras', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
cy.ionPageVisible('app-tabs-tab1-nested');
|
||||
cy.ionPageHidden('app-tabs-tab1');
|
||||
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
|
||||
tab.find('#goto-next').click();
|
||||
cy.ionPageVisible('app-tabs-tab1-nested:last-of-type');
|
||||
cy.ionPageHidden('app-tabs-tab1-nested:first-of-type');
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (2)');
|
||||
|
||||
cy.ionTabClick('Tab Two');
|
||||
cy.ionPageVisible('app-tabs-tab2');
|
||||
cy.ionPageHidden('app-tabs-tab1-nested:last-of-type');
|
||||
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
cy.ionTabClick('Tab One');
|
||||
cy.ionPageVisible('app-tabs-tab1-nested:last-of-type');
|
||||
cy.ionPageHidden('app-tabs-tab2');
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (2)');
|
||||
|
||||
cy.ionTabClick('Tab One');
|
||||
/**
|
||||
* Wait for the leaving view to
|
||||
* be unmounted otherwise testTabTitle
|
||||
* may get the leaving view before it
|
||||
* is unmounted.
|
||||
*/
|
||||
cy.ionPageVisible('app-tabs-tab1');
|
||||
cy.ionPageDoesNotExist('app-tabs-tab1-nested');
|
||||
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
testUrlContains(rootUrl);
|
||||
});
|
||||
})
|
||||
|
||||
describe('entry url - /tabs/account', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/lazy/tabs/account');
|
||||
});
|
||||
it('should pop to previous view when leaving tabs outlet', () => {
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1');
|
||||
|
||||
cy.get('#goto-tab1-page2').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)');
|
||||
|
||||
cy.get('#goto-global').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Global Page');
|
||||
|
||||
cy.get('#goto-prev-pop').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)');
|
||||
|
||||
cy.get('#goto-prev').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1');
|
||||
|
||||
/**
|
||||
* Verifies that when entering the tabs outlet directly,
|
||||
* the navController.pop() method does not pop the previous view,
|
||||
* when you are at the root of the tabs outlet.
|
||||
*/
|
||||
cy.get('#goto-previous-page').click();
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('entry url - /', () => {
|
||||
it('should pop to the root outlet from the tabs outlet', () => {
|
||||
cy.visit('/lazy/');
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Test App');
|
||||
|
||||
cy.get('ion-item').contains('Tabs test').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1');
|
||||
|
||||
cy.get('#goto-tab1-page2').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)');
|
||||
|
||||
cy.get('#goto-global').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Global Page');
|
||||
|
||||
cy.get('#goto-prev-pop').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)');
|
||||
|
||||
cy.get('#goto-prev').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1');
|
||||
|
||||
cy.get('#goto-previous-page').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Test App');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('entry url - /tabs/account/nested/1', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/lazy/tabs/account/nested/1');
|
||||
})
|
||||
|
||||
it('should only display the back-button when there is a page in the stack', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('ion-back-button').should('not.be.visible');
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']);
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
tab = testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
tab.find('ion-back-button').should('be.visible');
|
||||
});
|
||||
|
||||
it('should not reuse the same page', () => {
|
||||
let tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
tab.find('#goto-next').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (2)');
|
||||
|
||||
tab.find('#goto-next').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (3)');
|
||||
|
||||
cy.testStack('ion-tabs ion-router-outlet', [
|
||||
'app-tabs-tab1-nested',
|
||||
'app-tabs-tab1-nested',
|
||||
'app-tabs-tab1-nested'
|
||||
]);
|
||||
|
||||
tab = getSelectedTab();
|
||||
tab.find('ion-back-button').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (2)');
|
||||
tab.find('ion-back-button').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
|
||||
tab.find('ion-back-button').should('not.be.visible');
|
||||
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']);
|
||||
});
|
||||
})
|
||||
|
||||
describe('entry url - /tabs/lazy', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/lazy/tabs/lazy');
|
||||
});
|
||||
|
||||
it('should not display the back-button if coming from a different stack', () => {
|
||||
let tab = testTabTitle('Tab 3 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3']);
|
||||
|
||||
tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3', 'app-tabs-tab1-nested']);
|
||||
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
tab.find('ion-back-button').should('not.be.visible');
|
||||
});
|
||||
})
|
||||
|
||||
describe('enter url - /tabs/contact/one', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/lazy/tabs/contact/one');
|
||||
});
|
||||
|
||||
it('should return to correct tab after going to page in different outlet', () => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-nested-page1').click();
|
||||
cy.testStack('app-nested-outlet ion-router-outlet', ['app-nested-outlet-page']);
|
||||
|
||||
const nestedOutlet = cy.get('app-nested-outlet');
|
||||
nestedOutlet.find('ion-back-button').click();
|
||||
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
describe('entry tab contains navigation extras', () => {
|
||||
const expectNestedTabUrlToContain = 'search=hello#fragment';
|
||||
const rootUrlParams = 'test=123#rootFragment';
|
||||
const rootUrl = `/lazy/tabs/account?${rootUrlParams}`;
|
||||
|
||||
describe('Without IonRouterOutlet', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit(rootUrl);
|
||||
cy.visit('/lazy/tabs-basic');
|
||||
})
|
||||
|
||||
it('should preserve root url navigation extras when clicking on an active tab to navigate to the root', () => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-nested-page1-with-query-params').click();
|
||||
it('should show correct tab when clicking the tab button', () => {
|
||||
cy.get('ion-tab[tab="tab1"]').should('be.visible');
|
||||
cy.get('ion-tab[tab="tab2"]').should('not.be.visible');
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testUrlContains(expectNestedTabUrlToContain);
|
||||
cy.get('ion-tab-button[tab="tab2"]').click();
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
testUrlContains(rootUrl);
|
||||
cy.get('ion-tab[tab="tab1"]').should('not.be.visible');
|
||||
cy.get('ion-tab[tab="tab2"]').should('be.visible');
|
||||
});
|
||||
|
||||
it('should preserve root url navigation extras when changing tabs', () => {
|
||||
getSelectedTab();
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
it('should not change the URL when clicking the tab button', () => {
|
||||
cy.url().should('include', '/tabs-basic');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.get('ion-tab-button[tab="tab2"]').click();
|
||||
|
||||
testUrlContains(rootUrl);
|
||||
});
|
||||
|
||||
it('should navigate deep then go home and preserve navigation extras', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
cy.ionPageVisible('app-tabs-tab1-nested');
|
||||
cy.ionPageHidden('app-tabs-tab1');
|
||||
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
|
||||
tab.find('#goto-next').click();
|
||||
cy.ionPageVisible('app-tabs-tab1-nested:last-of-type');
|
||||
cy.ionPageHidden('app-tabs-tab1-nested:first-of-type');
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (2)');
|
||||
|
||||
cy.ionTabClick('Tab Two');
|
||||
cy.ionPageVisible('app-tabs-tab2');
|
||||
cy.ionPageHidden('app-tabs-tab1-nested:last-of-type');
|
||||
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
cy.ionTabClick('Tab One');
|
||||
cy.ionPageVisible('app-tabs-tab1-nested:last-of-type');
|
||||
cy.ionPageHidden('app-tabs-tab2');
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (2)');
|
||||
|
||||
cy.ionTabClick('Tab One');
|
||||
/**
|
||||
* Wait for the leaving view to
|
||||
* be unmounted otherwise testTabTitle
|
||||
* may get the leaving view before it
|
||||
* is unmounted.
|
||||
*/
|
||||
cy.ionPageVisible('app-tabs-tab1');
|
||||
cy.ionPageDoesNotExist('app-tabs-tab1-nested');
|
||||
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
testUrlContains(rootUrl);
|
||||
});
|
||||
})
|
||||
|
||||
describe('entry url - /tabs/account', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/lazy/tabs/account');
|
||||
});
|
||||
it('should pop to previous view when leaving tabs outlet', () => {
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1');
|
||||
|
||||
cy.get('#goto-tab1-page2').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)');
|
||||
|
||||
cy.get('#goto-global').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Global Page');
|
||||
|
||||
cy.get('#goto-prev-pop').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)');
|
||||
|
||||
cy.get('#goto-prev').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1');
|
||||
|
||||
/**
|
||||
* Verifies that when entering the tabs outlet directly,
|
||||
* the navController.pop() method does not pop the previous view,
|
||||
* when you are at the root of the tabs outlet.
|
||||
*/
|
||||
cy.get('#goto-previous-page').click();
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('entry url - /', () => {
|
||||
it('should pop to the root outlet from the tabs outlet', () => {
|
||||
cy.visit('/lazy/');
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Test App');
|
||||
|
||||
cy.get('ion-item').contains('Tabs test').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1');
|
||||
|
||||
cy.get('#goto-tab1-page2').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)');
|
||||
|
||||
cy.get('#goto-global').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Global Page');
|
||||
|
||||
cy.get('#goto-prev-pop').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)');
|
||||
|
||||
cy.get('#goto-prev').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1');
|
||||
|
||||
cy.get('#goto-previous-page').click();
|
||||
|
||||
cy.get('ion-title').should('contain.text', 'Test App');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('entry url - /tabs/account/nested/1', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/lazy/tabs/account/nested/1');
|
||||
})
|
||||
|
||||
it('should only display the back-button when there is a page in the stack', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('ion-back-button').should('not.be.visible');
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']);
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
tab = testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
tab.find('ion-back-button').should('be.visible');
|
||||
});
|
||||
|
||||
it('should not reuse the same page', () => {
|
||||
let tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
tab.find('#goto-next').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (2)');
|
||||
|
||||
tab.find('#goto-next').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (3)');
|
||||
|
||||
cy.testStack('ion-tabs ion-router-outlet', [
|
||||
'app-tabs-tab1-nested',
|
||||
'app-tabs-tab1-nested',
|
||||
'app-tabs-tab1-nested'
|
||||
]);
|
||||
|
||||
tab = getSelectedTab();
|
||||
tab.find('ion-back-button').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (2)');
|
||||
tab.find('ion-back-button').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
|
||||
tab.find('ion-back-button').should('not.be.visible');
|
||||
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']);
|
||||
});
|
||||
})
|
||||
|
||||
describe('entry url - /tabs/lazy', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/lazy/tabs/lazy');
|
||||
});
|
||||
|
||||
it('should not display the back-button if coming from a different stack', () => {
|
||||
let tab = testTabTitle('Tab 3 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3']);
|
||||
|
||||
tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3', 'app-tabs-tab1-nested']);
|
||||
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
tab.find('ion-back-button').should('not.be.visible');
|
||||
});
|
||||
})
|
||||
|
||||
describe('enter url - /tabs/contact/one', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/lazy/tabs/contact/one');
|
||||
});
|
||||
|
||||
it('should return to correct tab after going to page in different outlet', () => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-nested-page1').click();
|
||||
cy.testStack('app-nested-outlet ion-router-outlet', ['app-nested-outlet-page']);
|
||||
|
||||
const nestedOutlet = cy.get('app-nested-outlet');
|
||||
nestedOutlet.find('ion-back-button').click();
|
||||
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
cy.url().should('include', '/tabs-basic');
|
||||
});
|
||||
})
|
||||
})
|
||||
|
@ -1,21 +1,47 @@
|
||||
describe('Tabs', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/standalone/tabs');
|
||||
describe('Without IonRouterOutlet', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/standalone/tabs');
|
||||
});
|
||||
|
||||
it('should redirect to the default tab', () => {
|
||||
cy.get('app-tab-one').should('be.visible');
|
||||
cy.contains('Tab 1');
|
||||
});
|
||||
|
||||
it('should render new content when switching tabs', () => {
|
||||
cy.get('#tab-button-tab-two').click();
|
||||
cy.get('app-tab-two').should('be.visible');
|
||||
cy.contains('Tab 2');
|
||||
});
|
||||
|
||||
// Issue: https://github.com/ionic-team/ionic-framework/issues/28417
|
||||
it('parentOutlet should be defined', () => {
|
||||
cy.get('#parent-outlet span').should('have.text', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
it('should redirect to the default tab', () => {
|
||||
cy.get('app-tab-one').should('be.visible');
|
||||
cy.contains('Tab 1');
|
||||
});
|
||||
describe('Without IonRouterOutlet', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/standalone/tabs-basic');
|
||||
})
|
||||
|
||||
it('should render new content when switching tabs', () => {
|
||||
cy.get('#tab-button-tab-two').click();
|
||||
cy.get('app-tab-two').should('be.visible');
|
||||
cy.contains('Tab 2');
|
||||
});
|
||||
it('should show correct tab when clicking the tab button', () => {
|
||||
cy.get('ion-tab[tab="tab1"]').should('be.visible');
|
||||
cy.get('ion-tab[tab="tab2"]').should('not.be.visible');
|
||||
|
||||
// Fixes https://github.com/ionic-team/ionic-framework/issues/28417
|
||||
it('parentOutlet should be defined', () => {
|
||||
cy.get('#parent-outlet span').should('have.text', 'true');
|
||||
cy.get('ion-tab-button[tab="tab2"]').click();
|
||||
|
||||
cy.get('ion-tab[tab="tab1"]').should('not.be.visible');
|
||||
cy.get('ion-tab[tab="tab2"]').should('be.visible');
|
||||
});
|
||||
|
||||
it('should not change the URL when clicking the tab button', () => {
|
||||
cy.url().should('include', '/tabs-basic');
|
||||
|
||||
cy.get('ion-tab-button[tab="tab2"]').click();
|
||||
|
||||
cy.url().should('include', '/tabs-basic');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -27,6 +27,7 @@ import { NavigationPage3Component } from '../navigation-page3/navigation-page3.c
|
||||
import { AlertComponent } from '../alert/alert.component';
|
||||
import { AccordionComponent } from '../accordion/accordion.component';
|
||||
import { AccordionModalComponent } from '../accordion/accordion-modal/accordion-modal.component';
|
||||
import { TabsBasicComponent } from '../tabs-basic/tabs-basic.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -51,7 +52,8 @@ import { AccordionModalComponent } from '../accordion/accordion-modal/accordion-
|
||||
NavigationPage3Component,
|
||||
AlertComponent,
|
||||
AccordionComponent,
|
||||
AccordionModalComponent
|
||||
AccordionModalComponent,
|
||||
TabsBasicComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
@ -18,6 +18,7 @@ import { NavigationPage2Component } from '../navigation-page2/navigation-page2.c
|
||||
import { NavigationPage3Component } from '../navigation-page3/navigation-page3.component';
|
||||
import { AlertComponent } from '../alert/alert.component';
|
||||
import { AccordionComponent } from '../accordion/accordion.component';
|
||||
import { TabsBasicComponent } from '../tabs-basic/tabs-basic.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
@ -65,6 +66,7 @@ export const routes: Routes = [
|
||||
path: 'tabs-slots',
|
||||
loadComponent: () => import('../tabs-slots.component').then(c => c.TabsSlotsComponent)
|
||||
},
|
||||
{ path: 'tabs-basic', component: TabsBasicComponent },
|
||||
{
|
||||
path: 'nested-outlet',
|
||||
component: NestedOutletComponent,
|
||||
|
@ -37,6 +37,11 @@
|
||||
Tabs test
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item routerLink="/lazy/tabs-basic">
|
||||
<ion-label>
|
||||
Basic Tabs test
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item routerLink="/lazy/nested-outlet/page">
|
||||
<ion-label>
|
||||
Nested ion-router-outlet
|
||||
|
@ -0,0 +1,5 @@
|
||||
#test {
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
left: 0;
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<ion-tabs (ionTabsWillChange)="onTabWillChange($event)" (ionTabsDidChange)="onTabDidChange($event)">
|
||||
<ion-tab-bar slot="bottom">
|
||||
<ion-tab-button tab="tab1">
|
||||
<ion-label>Tab One</ion-label>
|
||||
<ion-icon name="add"></ion-icon>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="tab2">
|
||||
<ion-label>Tab Two</ion-label>
|
||||
<ion-icon name="logo-ionic"></ion-icon>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="tab3">
|
||||
<ion-label>Tab Three</ion-label>
|
||||
<ion-icon name="save"></ion-icon>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
|
||||
<ion-tab tab="tab1">
|
||||
<ion-label>Tab 1 Content</ion-label>
|
||||
</ion-tab>
|
||||
<ion-tab tab="tab2">
|
||||
<ion-label>Tab 2 Content</ion-label>
|
||||
</ion-tab>
|
||||
<ion-tab tab="tab3">
|
||||
<ion-label>Tab 3 Content</ion-label>
|
||||
</ion-tab>
|
||||
</ion-tabs>
|
||||
|
||||
<div id="test">
|
||||
<ul>
|
||||
<li>
|
||||
ionTabsWillChange counter: <span id="ionTabsWillChangeCounter">{{ tabsWillChangeCounter }}</span>
|
||||
</li>
|
||||
<li>
|
||||
ionTabsWillChange event: <span id="ionTabsWillChangeEvent">{{ tabsWillChangeEvent }}</span>
|
||||
</li>
|
||||
<li>
|
||||
ionTabsWillChange selectedTab: <span id="ionTabsWillChangeSelectedTab">{{ tabsWillChangeSelectedTab }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
ionTabsDidChange counter: <span id="ionTabsDidChangeCounter">{{ tabsDidChangeCounter }}</span>
|
||||
</li>
|
||||
<li>
|
||||
ionTabsDidChange event: <span id="ionTabsDidChangeEvent">{{ tabsDidChangeEvent }}</span>
|
||||
</li>
|
||||
<li>
|
||||
ionTabsDidChange selectedTab: <span id="ionTabsDidChangeSelectedTab">{{ tabsDidChangeSelectedTab }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
@ -0,0 +1,35 @@
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { IonTabBar } from '@ionic/angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tabs-basic',
|
||||
templateUrl: './tabs-basic.component.html',
|
||||
styleUrls: ['./tabs-basic.component.css']
|
||||
})
|
||||
export class TabsBasicComponent {
|
||||
constructor() { }
|
||||
|
||||
tabsWillChangeCounter = 0;
|
||||
tabsWillChangeEvent = '';
|
||||
tabsWillChangeSelectedTab? = '';
|
||||
|
||||
tabsDidChangeCounter = 0;
|
||||
tabsDidChangeEvent = '';
|
||||
tabsDidChangeSelectedTab? = '';
|
||||
|
||||
@ViewChild(IonTabBar) tabBar!: IonTabBar;
|
||||
|
||||
onTabWillChange(ev: { tab: string }) {
|
||||
console.log('ionTabsWillChange', this.tabBar.selectedTab);
|
||||
this.tabsWillChangeCounter++;
|
||||
this.tabsWillChangeEvent = ev.tab;
|
||||
this.tabsWillChangeSelectedTab = this.tabBar.selectedTab;
|
||||
}
|
||||
|
||||
onTabDidChange(ev: { tab: string }) {
|
||||
console.log('ionTabsDidChange', this.tabBar.selectedTab);
|
||||
this.tabsDidChangeCounter++;
|
||||
this.tabsDidChangeEvent = ev.tab;
|
||||
this.tabsDidChangeSelectedTab = this.tabBar.selectedTab;
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ export const routes: Routes = [
|
||||
{ path: 'tab-three', loadComponent: () => import('../tabs/tab3.component').then(c => c.TabThreeComponent) }
|
||||
]
|
||||
},
|
||||
{ path: 'tabs-basic', loadComponent: () => import('../tabs-basic/tabs-basic.component').then(c => c.TabsBasicComponent) },
|
||||
{
|
||||
path: 'value-accessors',
|
||||
children: [
|
||||
|
@ -0,0 +1,5 @@
|
||||
#test {
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
left: 0;
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<ion-tabs (ionTabsWillChange)="onTabWillChange($event)" (ionTabsDidChange)="onTabDidChange($event)">
|
||||
<ion-tab-bar slot="bottom">
|
||||
<ion-tab-button tab="tab1">
|
||||
<ion-label>Tab One</ion-label>
|
||||
<ion-icon name="add"></ion-icon>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="tab2">
|
||||
<ion-label>Tab Two</ion-label>
|
||||
<ion-icon name="logo-ionic"></ion-icon>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="tab3">
|
||||
<ion-label>Tab Three</ion-label>
|
||||
<ion-icon name="save"></ion-icon>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
|
||||
<ion-tab tab="tab1">
|
||||
<ion-label>Tab 1 Content</ion-label>
|
||||
</ion-tab>
|
||||
<ion-tab tab="tab2">
|
||||
<ion-label>Tab 2 Content</ion-label>
|
||||
</ion-tab>
|
||||
<ion-tab tab="tab3">
|
||||
<ion-label>Tab 3 Content</ion-label>
|
||||
</ion-tab>
|
||||
</ion-tabs>
|
||||
|
||||
<div id="test">
|
||||
<ul>
|
||||
<li>
|
||||
ionTabsWillChange counter: <span id="ionTabsWillChangeCounter">{{ tabsWillChangeCounter }}</span>
|
||||
</li>
|
||||
<li>
|
||||
ionTabsWillChange event: <span id="ionTabsWillChangeEvent">{{ tabsWillChangeEvent }}</span>
|
||||
</li>
|
||||
<li>
|
||||
ionTabsWillChange selectedTab: <span id="ionTabsWillChangeSelectedTab">{{ tabsWillChangeSelectedTab }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
ionTabsDidChange counter: <span id="ionTabsDidChangeCounter">{{ tabsDidChangeCounter }}</span>
|
||||
</li>
|
||||
<li>
|
||||
ionTabsDidChange event: <span id="ionTabsDidChangeEvent">{{ tabsDidChangeEvent }}</span>
|
||||
</li>
|
||||
<li>
|
||||
ionTabsDidChange selectedTab: <span id="ionTabsDidChangeSelectedTab">{{ tabsDidChangeSelectedTab }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
@ -0,0 +1,39 @@
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { IonTabBar, IonTabButton, IonIcon, IonLabel, IonTabs, IonTab } from '@ionic/angular/standalone';
|
||||
import { addIcons } from 'ionicons';
|
||||
import { add, logoIonic, save } from 'ionicons/icons';
|
||||
|
||||
addIcons({ add, logoIonic, save });
|
||||
|
||||
@Component({
|
||||
selector: 'app-tabs-basic',
|
||||
templateUrl: './tabs-basic.component.html',
|
||||
styleUrls: ['./tabs-basic.component.css'],
|
||||
standalone: true,
|
||||
imports: [IonTabBar, IonTabButton, IonIcon, IonLabel, IonTabs, IonTab]
|
||||
})
|
||||
export class TabsBasicComponent {
|
||||
tabsDidChangeCounter = 0;
|
||||
tabsDidChangeEvent = '';
|
||||
tabsDidChangeSelectedTab? = '';
|
||||
|
||||
tabsWillChangeCounter = 0;
|
||||
tabsWillChangeEvent = '';
|
||||
tabsWillChangeSelectedTab? = '';
|
||||
|
||||
@ViewChild(IonTabBar) tabBar!: IonTabBar;
|
||||
|
||||
onTabWillChange(ev: { tab: string }) {
|
||||
console.log('ionTabsWillChange', this.tabBar.selectedTab);
|
||||
this.tabsWillChangeCounter++;
|
||||
this.tabsWillChangeEvent = ev.tab;
|
||||
this.tabsWillChangeSelectedTab = this.tabBar.selectedTab;
|
||||
}
|
||||
|
||||
onTabDidChange(ev: { tab: string }) {
|
||||
console.log('ionTabsDidChange', this.tabBar.selectedTab);
|
||||
this.tabsDidChangeCounter++;
|
||||
this.tabsDidChangeEvent = ev.tab;
|
||||
this.tabsDidChangeSelectedTab = this.tabBar.selectedTab;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user