From 0fd50b3215141e3a6aab720646149c6cc51cb919 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 20 Aug 2024 08:26:37 -0700 Subject: [PATCH 01/19] feat(angular): use tabs without router --- core/src/components/tabs/tabs.tsx | 12 ++++++++- core/stencil.config.ts | 1 - .../common/src/directives/navigation/tabs.ts | 5 +++- packages/angular/src/app-initialize.ts | 2 +- .../src/directives/navigation/ion-tabs.ts | 5 +++- .../angular/src/directives/proxies-list.ts | 1 + packages/angular/src/directives/proxies.ts | 23 ++++++++++++++++ .../standalone/src/directives/proxies.ts | 26 +++++++++++++++++++ 8 files changed, 70 insertions(+), 5 deletions(-) diff --git a/core/src/components/tabs/tabs.tsx b/core/src/components/tabs/tabs.tsx index 5b744db1ad9..77b577f6e57 100644 --- a/core/src/components/tabs/tabs.tsx +++ b/core/src/components/tabs/tabs.tsx @@ -43,7 +43,17 @@ export class Tabs implements NavOutlet { async componentWillLoad() { if (!this.useRouter) { - this.useRouter = !!document.querySelector('ion-router') && !this.el.closest('[no-router]'); + /** + * JavaScript andd StencilJS use `ion-router`, while + * the other frameworks use `ion-router-outlet`. + * + * If either component is present then tabs will not use + * a basic tab-based navigation. It will use the history + * stack or URL updates associated with the router. + */ + this.useRouter = + (!!this.el.querySelector('ion-router-outlet') || !!document.querySelector('ion-router')) && + !this.el.closest('[no-router]'); } if (!this.useRouter) { const tabs = this.tabs; diff --git a/core/stencil.config.ts b/core/stencil.config.ts index 7f7113c22b9..7177e88e692 100644 --- a/core/stencil.config.ts +++ b/core/stencil.config.ts @@ -26,7 +26,6 @@ const getAngularOutputTargets = () => { // tabs 'ion-tabs', - 'ion-tab', // auxiliar 'ion-picker-legacy-column', diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index 7906e890736..fed8d6d95b0 100644 --- a/packages/angular/common/src/directives/navigation/tabs.ts +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -7,6 +7,8 @@ import { HostListener, Output, ViewChild, + Input, + AfterViewInit, } from '@angular/core'; import { NavController } from '../../providers/nav-controller'; @@ -17,7 +19,7 @@ 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. @@ -25,6 +27,7 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked { abstract outlet: any; abstract tabBar: any; abstract tabBars: any; + abstract tabs: any; @ViewChild('tabsInner', { read: ElementRef, static: true }) tabsInner: ElementRef; diff --git a/packages/angular/src/app-initialize.ts b/packages/angular/src/app-initialize.ts index 69cf4030f43..7b1062f82a9 100644 --- a/packages/angular/src/app-initialize.ts +++ b/packages/angular/src/app-initialize.ts @@ -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), diff --git a/packages/angular/src/directives/navigation/ion-tabs.ts b/packages/angular/src/directives/navigation/ion-tabs.ts index 9b7737a5774..079cef509da 100644 --- a/packages/angular/src/directives/navigation/ion-tabs.ts +++ b/packages/angular/src/directives/navigation/ion-tabs.ts @@ -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';
+
`, @@ -52,4 +54,5 @@ export class IonTabs extends IonTabsBase { @ContentChild(IonTabBar, { static: false }) tabBar: IonTabBar | undefined; @ContentChildren(IonTabBar) tabBars: QueryList; + @ContentChildren(IonTab) tabs: QueryList; } diff --git a/packages/angular/src/directives/proxies-list.ts b/packages/angular/src/directives/proxies-list.ts index 8a0d9eb03ec..1874d0bfe2d 100644 --- a/packages/angular/src/directives/proxies-list.ts +++ b/packages/angular/src/directives/proxies-list.ts @@ -74,6 +74,7 @@ export const DIRECTIVES = [ d.IonSkeletonText, d.IonSpinner, d.IonSplitPane, + d.IonTab, d.IonTabBar, d.IonTabButton, d.IonText, diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index 8db34fa7b89..f448236a161 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -2148,6 +2148,29 @@ export declare interface IonSplitPane extends Components.IonSplitPane { } +@ProxyCmp({ + inputs: ['component', 'tab'], + methods: ['setActive'] +}) +@Component({ + selector: 'ion-tab', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['component', 'tab'], +}) +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', 'translucent'] }) diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index 0db2a471286..c84717dfd1c 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -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'; @@ -1939,6 +1940,31 @@ export declare interface IonSplitPane extends Components.IonSplitPane { } +@ProxyCmp({ + defineCustomElementFn: defineIonTab, + inputs: ['component', 'tab'], + methods: ['setActive'] +}) +@Component({ + selector: 'ion-tab', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // 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', 'translucent'] From 85c7426574e9bfee433dc162ed0ccca310795952 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 20 Aug 2024 11:49:19 -0700 Subject: [PATCH 02/19] refactor(tabs): add test page --- .../common/src/directives/navigation/tabs.ts | 7 ++++- .../src/directives/navigation/ion-tabs.ts | 4 +-- .../angular/standalone/src/navigation/tabs.ts | 3 +- .../base/src/app/lazy/app-lazy/app.module.ts | 4 ++- .../base/src/app/lazy/app-lazy/app.routes.ts | 2 ++ .../lazy/home-page/home-page.component.html | 5 ++++ .../lazy/tabs-basic/tabs-basic.component.html | 30 +++++++++++++++++++ .../lazy/tabs-basic/tabs-basic.component.ts | 9 ++++++ .../app/lazy/tabs-basic/tabs-basic.module.ts | 17 +++++++++++ 9 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html create mode 100644 packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts create mode 100644 packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.module.ts diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index fed8d6d95b0..cafbae54d9d 100644 --- a/packages/angular/common/src/directives/navigation/tabs.ts +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -7,7 +7,6 @@ import { HostListener, Output, ViewChild, - Input, AfterViewInit, } from '@angular/core'; @@ -44,6 +43,11 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC constructor(private navCtrl: NavController) {} + ngAfterViewInit(): void { + const tabs = this.tabs; + console.log('tabs.length', tabs.length); + } + ngAfterContentInit(): void { this.detectSlotChanges(); } @@ -99,6 +103,7 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC select(tabOrEvent: string | CustomEvent): Promise | undefined { const isTabString = typeof tabOrEvent === 'string'; const tab = isTabString ? tabOrEvent : (tabOrEvent as CustomEvent).detail.tab; + console.log('tab', tab); const alreadySelected = this.outlet.getActiveStackId() === tab; const tabRootUrl = `${this.outlet.tabsPrefix}/${tab}`; diff --git a/packages/angular/src/directives/navigation/ion-tabs.ts b/packages/angular/src/directives/navigation/ion-tabs.ts index 079cef509da..70c5541034b 100644 --- a/packages/angular/src/directives/navigation/ion-tabs.ts +++ b/packages/angular/src/directives/navigation/ion-tabs.ts @@ -11,13 +11,13 @@ import { IonRouterOutlet } from './ion-router-outlet';
- +
`, diff --git a/packages/angular/standalone/src/navigation/tabs.ts b/packages/angular/standalone/src/navigation/tabs.ts index 50c85f1593f..9a7a4accf46 100644 --- a/packages/angular/standalone/src/navigation/tabs.ts +++ b/packages/angular/standalone/src/navigation/tabs.ts @@ -1,7 +1,7 @@ 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'; @@ -54,4 +54,5 @@ export class IonTabs extends IonTabsBase { @ContentChild(IonTabBar, { static: false }) tabBar: IonTabBar | undefined; @ContentChildren(IonTabBar) tabBars: QueryList; + @ContentChildren(IonTab) tabs: QueryList; } diff --git a/packages/angular/test/base/src/app/lazy/app-lazy/app.module.ts b/packages/angular/test/base/src/app/lazy/app-lazy/app.module.ts index c7c911da16e..caf27670d2d 100644 --- a/packages/angular/test/base/src/app/lazy/app-lazy/app.module.ts +++ b/packages/angular/test/base/src/app/lazy/app-lazy/app.module.ts @@ -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, diff --git a/packages/angular/test/base/src/app/lazy/app-lazy/app.routes.ts b/packages/angular/test/base/src/app/lazy/app-lazy/app.routes.ts index 8f96df5270f..0e15ea2867d 100644 --- a/packages/angular/test/base/src/app/lazy/app-lazy/app.routes.ts +++ b/packages/angular/test/base/src/app/lazy/app-lazy/app.routes.ts @@ -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, diff --git a/packages/angular/test/base/src/app/lazy/home-page/home-page.component.html b/packages/angular/test/base/src/app/lazy/home-page/home-page.component.html index 3c78cea6951..7fdca9c50d8 100644 --- a/packages/angular/test/base/src/app/lazy/home-page/home-page.component.html +++ b/packages/angular/test/base/src/app/lazy/home-page/home-page.component.html @@ -37,6 +37,11 @@ Tabs test + + + Basic Tabs test + + Nested ion-router-outlet diff --git a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html new file mode 100644 index 00000000000..b0e0b4f82f9 --- /dev/null +++ b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html @@ -0,0 +1,30 @@ + + + + + Tab One + + + + + Tab Two + + + + + Tab Three + + + + + + Tab 1 Content + + + Tab 2 Content + + + Tab 3 Content + + + diff --git a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts new file mode 100644 index 00000000000..495e48025ec --- /dev/null +++ b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-tabs-basic', + templateUrl: 'tabs-basic.component.html', +}) +export class TabsBasicComponent { + constructor() { } +} \ No newline at end of file diff --git a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.module.ts b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.module.ts new file mode 100644 index 00000000000..b221e16b9e5 --- /dev/null +++ b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.module.ts @@ -0,0 +1,17 @@ +import { IonicModule } from '@ionic/angular'; +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { TabsBasicComponent } from './tabs-basic.component'; + + +@NgModule({ + imports: [ + IonicModule, + CommonModule, + ], + declarations: [ + TabsBasicComponent + ] +}) +export class TabsBasicPageModule {} From 6bf2480d7bf0302a01d93f82b34d6d26a2ee4d1c Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 20 Aug 2024 11:51:11 -0700 Subject: [PATCH 03/19] refactor(tabs): remove unneeded file --- .../app/lazy/tabs-basic/tabs-basic.module.ts | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.module.ts diff --git a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.module.ts b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.module.ts deleted file mode 100644 index b221e16b9e5..00000000000 --- a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { IonicModule } from '@ionic/angular'; -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { TabsBasicComponent } from './tabs-basic.component'; - - -@NgModule({ - imports: [ - IonicModule, - CommonModule, - ], - declarations: [ - TabsBasicComponent - ] -}) -export class TabsBasicPageModule {} From a9919cedb3114d871883d556f292de959e35ed67 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 20 Aug 2024 13:48:52 -0700 Subject: [PATCH 04/19] refactor(tabs): set active and tab switch --- .../common/src/directives/navigation/tabs.ts | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index cafbae54d9d..45f1f34fb0b 100644 --- a/packages/angular/common/src/directives/navigation/tabs.ts +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -8,9 +8,12 @@ import { Output, ViewChild, AfterViewInit, + // QueryList, } from '@angular/core'; +// import type { Components } from '@ionic/core/components'; import { NavController } from '../../providers/nav-controller'; +// import { IonTab } from '../../directives/proxies'; import { StackDidChangeEvent, StackWillChangeEvent } from './stack-utils'; @@ -26,6 +29,7 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC abstract outlet: any; abstract tabBar: any; abstract tabBars: any; + // abstract tabs: QueryList; abstract tabs: any; @ViewChild('tabsInner', { read: ElementRef, static: true }) tabsInner: ElementRef; @@ -41,11 +45,26 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC private tabBarSlot = 'bottom'; + private hasTab = false; + private selectedTab?: any; + private leavingTab?: any; + constructor(private navCtrl: NavController) {} ngAfterViewInit(): void { - const tabs = this.tabs; - console.log('tabs.length', tabs.length); + /** + * 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); + } } ngAfterContentInit(): void { @@ -103,7 +122,19 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC select(tabOrEvent: string | CustomEvent): Promise | undefined { const isTabString = typeof tabOrEvent === 'string'; const tab = isTabString ? tabOrEvent : (tabOrEvent as CustomEvent).detail.tab; - console.log('tab', 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}`; @@ -150,7 +181,45 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC } } + private setActiveTab(tab: string): void { + const tabs = this.tabs; + const selectedTab = tabs.find((t: any) => t.tab === tab); + + if (!selectedTab) { + console.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 = this.selectedTab; + const leavingTab = this.leavingTab; + + if (this.tabBar) { + this.tabBar.selectedTab = selectedTab.tab; + } + + if (leavingTab.tab !== selectedTab.tab) { + if (leavingTab) { + leavingTab.el.active = false; + } + } + + this.ionTabsDidChange.emit({ tab: selectedTab.tab }); + } + getSelected(): string | undefined { + if (this.hasTab) { + return this.selectedTab.tab; + } + return this.outlet.getActiveStackId(); } From 22306308e731de4cbb49a327db316b671b8bde05 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 20 Aug 2024 15:56:22 -0700 Subject: [PATCH 05/19] refactor(tabs): add events --- .../common/src/directives/navigation/tabs.ts | 3 +- .../lazy/tabs-basic/tabs-basic.component.css | 5 +++ .../lazy/tabs-basic/tabs-basic.component.html | 33 ++++++++++++++++--- .../lazy/tabs-basic/tabs-basic.component.ts | 32 ++++++++++++++++-- 4 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.css diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index 45f1f34fb0b..135c4d2c6dd 100644 --- a/packages/angular/common/src/directives/navigation/tabs.ts +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -64,6 +64,7 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC if (firstTab) { this.hasTab = true; this.setActiveTab(firstTab.tab); + this.tabSwitch(); } } @@ -206,7 +207,7 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC this.tabBar.selectedTab = selectedTab.tab; } - if (leavingTab.tab !== selectedTab.tab) { + if (leavingTab?.tab !== selectedTab.tab) { if (leavingTab) { leavingTab.el.active = false; } diff --git a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.css b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.css new file mode 100644 index 00000000000..f6f95679b81 --- /dev/null +++ b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.css @@ -0,0 +1,5 @@ +#test { + position: absolute; + bottom: 100px; + left: 0; +} diff --git a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html index b0e0b4f82f9..1fedf194697 100644 --- a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html +++ b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html @@ -1,5 +1,5 @@ - + Tab One @@ -17,14 +17,39 @@ - + Tab 1 Content - + Tab 2 Content - + Tab 3 Content + +
+
    +
  • + ionTabsWillChange counter: {{ tabsWillChangeCounter }} +
  • +
  • + ionTabsWillChange event: {{ tabsWillChangeEvent }} +
  • +
  • + ionTabsWillChange selectedTab: {{ tabsWillChangeSelectedTab }} +
  • +
+
    +
  • + ionTabsDidChange counter: {{ tabsDidChangeCounter }} +
  • +
  • + ionTabsDidChange event: {{ tabsDidChangeEvent }} +
  • +
  • + ionTabsDidChange selectedTab: {{ tabsDidChangeSelectedTab }} +
  • +
+
diff --git a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts index 495e48025ec..5ce22d40f64 100644 --- a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts +++ b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts @@ -1,9 +1,35 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; +import { IonTabBar } from '@ionic/angular'; @Component({ selector: 'app-tabs-basic', - templateUrl: 'tabs-basic.component.html', + templateUrl: './tabs-basic.component.html', + styleUrls: ['./tabs-basic.component.css'] }) export class TabsBasicComponent { constructor() { } -} \ No newline at end of file + + tabsWillChangeCounter = 0; + tabsWillChangeEvent = ''; + tabsWillChangeSelectedTab? = ''; + + tabsDidChangeCounter = 0; + tabsDidChangeEvent = ''; + tabsDidChangeSelectedTab? = ''; + + @ViewChild(IonTabBar) tabBar!: IonTabBar; + + onTabWillChange(ev: any) { + console.log('ionTabsWillChange', this.tabBar.selectedTab); + this.tabsWillChangeCounter++; + this.tabsWillChangeEvent = ev.tab; + this.tabsWillChangeSelectedTab = this.tabBar.selectedTab; + } + + onTabDidChange(ev: any) { + console.log('ionTabsDidChange', this.tabBar.selectedTab); + this.tabsDidChangeCounter++; + this.tabsDidChangeEvent = ev.tab; + this.tabsDidChangeSelectedTab = this.tabBar.selectedTab; + } +} From 5526f4df46c1ce8c21d803503b06a93dab73f7cb Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 20 Aug 2024 16:06:46 -0700 Subject: [PATCH 06/19] test(tabs): add for no router --- .../test/base/e2e/src/lazy/tabs.spec.ts | 846 +++++++++--------- 1 file changed, 436 insertions(+), 410 deletions(-) diff --git a/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts b/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts index e7c4b51cfbf..238ad104a56 100644 --- a/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts +++ b/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts @@ -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', () => { + 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)'); + 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(); + 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'; - cy.url().then(url => { + 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'); - - 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); + + 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); + }); }) - - 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 - /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 - /', () => { + 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'); + + + 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'); + }); }) - - 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', () => { + describe('Without IonRouterOutlet', () => { beforeEach(() => { - cy.visit('/lazy/tabs/lazy'); - }); + cy.visit('/lazy/tabs-basic'); + }) - 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']); + it('should show correct tab when clicking the tab button', () => { + cy.get('ion-tab[data-pageid=tab1]').should('be.visible'); + cy.get('ion-tab[data-pageid=tab2]').should('not.be.visible'); - tab = getSelectedTab(); - tab.find('#goto-tab1-page2').click(); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3', 'app-tabs-tab1-nested']); + cy.get('ion-tab-button[data-tab=tab2]').click(); - 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'); + cy.get('ion-tab[data-pageid=tab1]').should('not.be.visible'); + cy.get('ion-tab[data-pageid=tab2]').should('be.visible'); }); - 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']); + it('should not change the URL when clicking the tab button', () => { + cy.url().should('include', '/tabs-basic'); - const nestedOutlet = cy.get('app-nested-outlet'); - nestedOutlet.find('ion-back-button').click(); + cy.get('ion-tab-button[data-tab=tab2]').click(); - testTabTitle('Tab 2 - Page 1'); + cy.url().should('include', '/tabs-basic'); }); }) }) From 2c21f0e24313aeb02288fe72c3ea18cd5e5854d7 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 20 Aug 2024 16:19:01 -0700 Subject: [PATCH 07/19] refactor(tabs): cleanup --- packages/angular/common/src/directives/navigation/tabs.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index 135c4d2c6dd..e7083d512b4 100644 --- a/packages/angular/common/src/directives/navigation/tabs.ts +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -8,12 +8,9 @@ import { Output, ViewChild, AfterViewInit, - // QueryList, } from '@angular/core'; -// import type { Components } from '@ionic/core/components'; import { NavController } from '../../providers/nav-controller'; -// import { IonTab } from '../../directives/proxies'; import { StackDidChangeEvent, StackWillChangeEvent } from './stack-utils'; @@ -29,7 +26,6 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC abstract outlet: any; abstract tabBar: any; abstract tabBars: any; - // abstract tabs: QueryList; abstract tabs: any; @ViewChild('tabsInner', { read: ElementRef, static: true }) tabsInner: ElementRef; From af0f2a7d0164a9d2b660dafcb6bb636aa828c547 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 20 Aug 2024 16:53:06 -0700 Subject: [PATCH 08/19] refactor(tabs): add standalone --- .../angular/standalone/src/navigation/tabs.ts | 5 +- .../lazy/tabs-basic/tabs-basic.component.html | 104 +++++++++--------- .../lazy/tabs-basic/tabs-basic.component.ts | 4 +- .../standalone/app-standalone/app.routes.ts | 1 + .../tabs-basic/tabs-basic.component.css | 5 + .../tabs-basic/tabs-basic.component.html | 53 +++++++++ .../tabs-basic/tabs-basic.component.ts | 39 +++++++ 7 files changed, 155 insertions(+), 56 deletions(-) create mode 100644 packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.css create mode 100644 packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.html create mode 100644 packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.ts diff --git a/packages/angular/standalone/src/navigation/tabs.ts b/packages/angular/standalone/src/navigation/tabs.ts index 9a7a4accf46..3951362f4d2 100644 --- a/packages/angular/standalone/src/navigation/tabs.ts +++ b/packages/angular/standalone/src/navigation/tabs.ts @@ -1,3 +1,4 @@ +import { NgIf } from '@angular/common'; import { Component, ContentChild, ContentChildren, ViewChild, QueryList } from '@angular/core'; import { IonTabs as IonTabsBase } from '@ionic/angular/common'; @@ -11,11 +12,13 @@ import { IonRouterOutlet } from './router-outlet';
+
`, @@ -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 { diff --git a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html index 1fedf194697..e000d6932f7 100644 --- a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html +++ b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html @@ -1,55 +1,53 @@ - - - - - Tab One - - - - - Tab Two - - - - - Tab Three - - - + + + + Tab One + + - - Tab 1 Content - - - Tab 2 Content - - - Tab 3 Content - - + + Tab Two + + -
-
    -
  • - ionTabsWillChange counter: {{ tabsWillChangeCounter }} -
  • -
  • - ionTabsWillChange event: {{ tabsWillChangeEvent }} -
  • -
  • - ionTabsWillChange selectedTab: {{ tabsWillChangeSelectedTab }} -
  • -
-
    -
  • - ionTabsDidChange counter: {{ tabsDidChangeCounter }} -
  • -
  • - ionTabsDidChange event: {{ tabsDidChangeEvent }} -
  • -
  • - ionTabsDidChange selectedTab: {{ tabsDidChangeSelectedTab }} -
  • -
-
-
+ + Tab Three + + + + + + Tab 1 Content + + + Tab 2 Content + + + Tab 3 Content + + + +
+
    +
  • + ionTabsWillChange counter: {{ tabsWillChangeCounter }} +
  • +
  • + ionTabsWillChange event: {{ tabsWillChangeEvent }} +
  • +
  • + ionTabsWillChange selectedTab: {{ tabsWillChangeSelectedTab }} +
  • +
+
    +
  • + ionTabsDidChange counter: {{ tabsDidChangeCounter }} +
  • +
  • + ionTabsDidChange event: {{ tabsDidChangeEvent }} +
  • +
  • + ionTabsDidChange selectedTab: {{ tabsDidChangeSelectedTab }} +
  • +
+
diff --git a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts index 5ce22d40f64..371dbb8a794 100644 --- a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts +++ b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts @@ -19,14 +19,14 @@ export class TabsBasicComponent { @ViewChild(IonTabBar) tabBar!: IonTabBar; - onTabWillChange(ev: any) { + onTabWillChange(ev: { tab: string }) { console.log('ionTabsWillChange', this.tabBar.selectedTab); this.tabsWillChangeCounter++; this.tabsWillChangeEvent = ev.tab; this.tabsWillChangeSelectedTab = this.tabBar.selectedTab; } - onTabDidChange(ev: any) { + onTabDidChange(ev: { tab: string }) { console.log('ionTabsDidChange', this.tabBar.selectedTab); this.tabsDidChangeCounter++; this.tabsDidChangeEvent = ev.tab; diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index 68213d8375e..40afc7a68de 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -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: [ diff --git a/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.css b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.css new file mode 100644 index 00000000000..f6f95679b81 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.css @@ -0,0 +1,5 @@ +#test { + position: absolute; + bottom: 100px; + left: 0; +} diff --git a/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.html b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.html new file mode 100644 index 00000000000..ede62957341 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.html @@ -0,0 +1,53 @@ + + + + Tab One + + + + + Tab Two + + + + + Tab Three + + + + + + Tab 1 Content + + + Tab 2 Content + + + Tab 3 Content + + + +
+
    +
  • + ionTabsWillChange counter: {{ tabsWillChangeCounter }} +
  • +
  • + ionTabsWillChange event: {{ tabsWillChangeEvent }} +
  • +
  • + ionTabsWillChange selectedTab: {{ tabsWillChangeSelectedTab }} +
  • +
+
    +
  • + ionTabsDidChange counter: {{ tabsDidChangeCounter }} +
  • +
  • + ionTabsDidChange event: {{ tabsDidChangeEvent }} +
  • +
  • + ionTabsDidChange selectedTab: {{ tabsDidChangeSelectedTab }} +
  • +
+
\ No newline at end of file diff --git a/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.ts b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.ts new file mode 100644 index 00000000000..d66a5924df0 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.ts @@ -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; + } +} From f67a2c24c268e2d738b2be2cb1518e184c8eca73 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 20 Aug 2024 16:57:41 -0700 Subject: [PATCH 09/19] test(tabs): fix lazy --- packages/angular/test/base/e2e/src/lazy/tabs.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts b/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts index 238ad104a56..ac31d74060b 100644 --- a/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts +++ b/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts @@ -442,19 +442,19 @@ describe('Tabs', () => { }) it('should show correct tab when clicking the tab button', () => { - cy.get('ion-tab[data-pageid=tab1]').should('be.visible'); - cy.get('ion-tab[data-pageid=tab2]').should('not.be.visible'); + cy.get('ion-tab[tab="tab1"]').should('be.visible'); + cy.get('ion-tab[tab="tab2"]').should('not.be.visible'); - cy.get('ion-tab-button[data-tab=tab2]').click(); + cy.get('ion-tab-button[tab="tab2"]').click(); - cy.get('ion-tab[data-pageid=tab1]').should('not.be.visible'); - cy.get('ion-tab[data-pageid=tab2]').should('be.visible'); + 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[data-tab=tab2]').click(); + cy.get('ion-tab-button[tab="tab2"]').click(); cy.url().should('include', '/tabs-basic'); }); From 25693e619b9e93f79888e11e228bb26604bb2f61 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 20 Aug 2024 17:01:47 -0700 Subject: [PATCH 10/19] test(tabs): add standalone --- .../test/base/e2e/src/standalone/tabs.spec.ts | 54 ++++++++++++++----- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts b/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts index bb0901a1127..ef13313d72e 100644 --- a/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts +++ b/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts @@ -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'); + }); + + // Fixes 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'); + + 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(); - // 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.url().should('include', '/tabs-basic'); + }); }); }); From 81edeb44ff7f52e036411ccd20108abd6329e108 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 20 Aug 2024 17:14:17 -0700 Subject: [PATCH 11/19] refactor(tabs): add new line --- .../src/app/standalone/tabs-basic/tabs-basic.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.html b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.html index ede62957341..e000d6932f7 100644 --- a/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.html +++ b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.html @@ -50,4 +50,4 @@ ionTabsDidChange selectedTab: {{ tabsDidChangeSelectedTab }} - \ No newline at end of file + From 0cc44927a45ae7e593810acb052d24da1512e29a Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 21 Aug 2024 08:53:28 -0700 Subject: [PATCH 12/19] refactor(tabs): use ng-content --- packages/angular/src/directives/navigation/ion-tabs.ts | 2 +- packages/angular/standalone/src/navigation/tabs.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/angular/src/directives/navigation/ion-tabs.ts b/packages/angular/src/directives/navigation/ion-tabs.ts index 70c5541034b..efdd8967d51 100644 --- a/packages/angular/src/directives/navigation/ion-tabs.ts +++ b/packages/angular/src/directives/navigation/ion-tabs.ts @@ -17,7 +17,7 @@ import { IonRouterOutlet } from './ion-router-outlet'; (stackWillChange)="onStackWillChange($event)" (stackDidChange)="onStackDidChange($event)" > - + `, diff --git a/packages/angular/standalone/src/navigation/tabs.ts b/packages/angular/standalone/src/navigation/tabs.ts index 3951362f4d2..16e5696eb47 100644 --- a/packages/angular/standalone/src/navigation/tabs.ts +++ b/packages/angular/standalone/src/navigation/tabs.ts @@ -18,7 +18,7 @@ import { IonRouterOutlet } from './router-outlet'; (stackWillChange)="onStackWillChange($event)" (stackDidChange)="onStackDidChange($event)" > - + `, From 99d70171fb1a626954832fe19c6f36e398d5e195 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 22 Aug 2024 13:47:53 -0700 Subject: [PATCH 13/19] refactor(tabs): update selectedTab type --- .../angular/common/src/directives/navigation/tabs.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index e7083d512b4..ab407f652ff 100644 --- a/packages/angular/common/src/directives/navigation/tabs.ts +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -42,7 +42,7 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC private tabBarSlot = 'bottom'; private hasTab = false; - private selectedTab?: any; + private selectedTab?: { tab: string }; private leavingTab?: any; constructor(private navCtrl: NavController) {} @@ -199,22 +199,24 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC const selectedTab = this.selectedTab; const leavingTab = this.leavingTab; - if (this.tabBar) { + if (this.tabBar && selectedTab) { this.tabBar.selectedTab = selectedTab.tab; } - if (leavingTab?.tab !== selectedTab.tab) { + if (leavingTab?.tab !== selectedTab?.tab) { if (leavingTab) { leavingTab.el.active = false; } } - this.ionTabsDidChange.emit({ tab: selectedTab.tab }); + if (selectedTab) { + this.ionTabsDidChange.emit({ tab: selectedTab.tab }); + } } getSelected(): string | undefined { if (this.hasTab) { - return this.selectedTab.tab; + return this.selectedTab?.tab; } return this.outlet.getActiveStackId(); From 6e9f1a735506d0e9e2129eaab4055ce2a90ca787 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 22 Aug 2024 13:49:44 -0700 Subject: [PATCH 14/19] refactor(tabs): update leavingTab check --- packages/angular/common/src/directives/navigation/tabs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index ab407f652ff..a00a7bc7f35 100644 --- a/packages/angular/common/src/directives/navigation/tabs.ts +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -204,7 +204,7 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC } if (leavingTab?.tab !== selectedTab?.tab) { - if (leavingTab) { + if (leavingTab?.el) { leavingTab.el.active = false; } } From 81193e1fc6e93e6eb682a9d963eb6526c44cd2aa Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 22 Aug 2024 13:51:42 -0700 Subject: [PATCH 15/19] refactor(tabs): minimize lines --- packages/angular/common/src/directives/navigation/tabs.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index a00a7bc7f35..d4936079088 100644 --- a/packages/angular/common/src/directives/navigation/tabs.ts +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -196,8 +196,7 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC } private tabSwitch(): void { - const selectedTab = this.selectedTab; - const leavingTab = this.leavingTab; + const { selectedTab, leavingTab } = this; if (this.tabBar && selectedTab) { this.tabBar.selectedTab = selectedTab.tab; From 53630c7797e5d7765d17bcc1d11f87372468682f Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 22 Aug 2024 13:53:22 -0700 Subject: [PATCH 16/19] refactor(tabs): update error message Co-authored-by: Sean Perkins <13732623+sean-perkins@users.noreply.github.com> --- packages/angular/common/src/directives/navigation/tabs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index d4936079088..dee55cc0c2b 100644 --- a/packages/angular/common/src/directives/navigation/tabs.ts +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -183,7 +183,7 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC const selectedTab = tabs.find((t: any) => t.tab === tab); if (!selectedTab) { - console.error(`tab with id: "${tab}" does not exist`); + console.error(`[Ionic Error]: Tab with id: "${tab}" does not exist`); return; } From 622cb07027360f148dcd58e648e1ffc50f9806a0 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 22 Aug 2024 13:57:20 -0700 Subject: [PATCH 17/19] Update packages/angular/test/base/e2e/src/standalone/tabs.spec.ts Co-authored-by: Sean Perkins <13732623+sean-perkins@users.noreply.github.com> --- packages/angular/test/base/e2e/src/standalone/tabs.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts b/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts index ef13313d72e..f1798cbdabf 100644 --- a/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts +++ b/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts @@ -15,7 +15,7 @@ describe('Tabs', () => { cy.contains('Tab 2'); }); - // Fixes https://github.com/ionic-team/ionic-framework/issues/28417 + // Issue: https://github.com/ionic-team/ionic-framework/issues/28417 it('parentOutlet should be defined', () => { cy.get('#parent-outlet span').should('have.text', 'true'); }); From 1f8a4e38e7df518a6456ad639512930fbc456515 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 22 Aug 2024 13:59:43 -0700 Subject: [PATCH 18/19] refactor(tabs): update types --- packages/angular/common/src/directives/navigation/tabs.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index dee55cc0c2b..73e8c0cc777 100644 --- a/packages/angular/common/src/directives/navigation/tabs.ts +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -8,6 +8,7 @@ import { Output, ViewChild, AfterViewInit, + QueryList, } from '@angular/core'; import { NavController } from '../../providers/nav-controller'; @@ -25,8 +26,8 @@ export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterC */ abstract outlet: any; abstract tabBar: any; - abstract tabBars: any; - abstract tabs: any; + abstract tabBars: QueryList; + abstract tabs: QueryList; @ViewChild('tabsInner', { read: ElementRef, static: true }) tabsInner: ElementRef; From f6132ca59a65c55f285ed0aad7f5e45a8e1cdc91 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 22 Aug 2024 14:47:01 -0700 Subject: [PATCH 19/19] refactor(tabs): remove optional chains --- packages/angular/src/directives/navigation/ion-tabs.ts | 4 ++-- packages/angular/standalone/src/navigation/tabs.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/angular/src/directives/navigation/ion-tabs.ts b/packages/angular/src/directives/navigation/ion-tabs.ts index efdd8967d51..65819cde79f 100644 --- a/packages/angular/src/directives/navigation/ion-tabs.ts +++ b/packages/angular/src/directives/navigation/ion-tabs.ts @@ -11,13 +11,13 @@ import { IonRouterOutlet } from './ion-router-outlet';
- +
`, diff --git a/packages/angular/standalone/src/navigation/tabs.ts b/packages/angular/standalone/src/navigation/tabs.ts index 16e5696eb47..61cbd12d088 100644 --- a/packages/angular/standalone/src/navigation/tabs.ts +++ b/packages/angular/standalone/src/navigation/tabs.ts @@ -12,13 +12,13 @@ import { IonRouterOutlet } from './router-outlet';
- +
`,