diff --git a/packages/react-router/test/base/src/App.tsx b/packages/react-router/test/base/src/App.tsx index b90eccc0df..426b6df34d 100644 --- a/packages/react-router/test/base/src/App.tsx +++ b/packages/react-router/test/base/src/App.tsx @@ -51,6 +51,7 @@ import IonRoutePropsTest from './pages/ion-route-props/IonRouteProps'; import PrefixMatchWildcard from './pages/prefix-match-wildcard/PrefixMatchWildcard'; import StaleViewCleanup from './pages/stale-view-cleanup/StaleViewCleanup'; import IndexParamPriority from './pages/index-param-priority/IndexParamPriority'; +import IndexRouteReuse from './pages/index-route-reuse/IndexRouteReuse'; setupIonicReact(); @@ -91,6 +92,7 @@ const App: React.FC = () => { } /> } /> } /> + } /> diff --git a/packages/react-router/test/base/src/pages/Main.tsx b/packages/react-router/test/base/src/pages/Main.tsx index e064f29743..d8bbbcfeab 100644 --- a/packages/react-router/test/base/src/pages/Main.tsx +++ b/packages/react-router/test/base/src/pages/Main.tsx @@ -98,6 +98,9 @@ const Main: React.FC = () => { Index Param Priority + + Index Route Reuse + diff --git a/packages/react-router/test/base/src/pages/index-route-reuse/IndexRouteReuse.tsx b/packages/react-router/test/base/src/pages/index-route-reuse/IndexRouteReuse.tsx new file mode 100644 index 0000000000..e4791af088 --- /dev/null +++ b/packages/react-router/test/base/src/pages/index-route-reuse/IndexRouteReuse.tsx @@ -0,0 +1,180 @@ +import { + IonContent, + IonHeader, + IonPage, + IonTitle, + IonToolbar, + IonRouterOutlet, + IonTabs, + IonTabBar, + IonTabButton, + IonIcon, + IonLabel, + IonButton, + IonBackButton, + IonButtons, +} from '@ionic/react'; +import { triangle, ellipse, square } from 'ionicons/icons'; +import React from 'react'; +import { Route, Navigate } from 'react-router-dom'; + +/** + * Test page for index route reuse across tabs with nested outlets. + * + * Each tab has its own nested IonRouterOutlet with an index route that + * renders distinct content. This tests that switching between tabs shows + * the correct index route content and doesn't incorrectly reuse a view + * item from another tab's index route. + */ + +// Tab 1 nested outlet with its own index route +const Tab1Content: React.FC = () => { + return ( + + + + Tab 1 Home + + + + Tab 1 Index Route Content + + Go to Tab 1 Detail + + + + ); +}; + +const Tab1Detail: React.FC = () => { + return ( + + + + + + + Tab 1 Detail + + + + Tab 1 Detail Content + + + ); +}; + +const Tab1Outlet: React.FC = () => { + return ( + + + } /> + } /> + + + ); +}; + +// Tab 2 nested outlet with its own index route +const Tab2Content: React.FC = () => { + return ( + + + + Tab 2 Home + + + + Tab 2 Index Route Content + + Go to Tab 2 Detail + + + + ); +}; + +const Tab2Detail: React.FC = () => { + return ( + + + + + + + Tab 2 Detail + + + + Tab 2 Detail Content + + + ); +}; + +const Tab2Outlet: React.FC = () => { + return ( + + + } /> + } /> + + + ); +}; + +// Tab 3 nested outlet with its own index route +const Tab3Content: React.FC = () => { + return ( + + + + Tab 3 Home + + + + Tab 3 Index Route Content + + + ); +}; + +const Tab3Outlet: React.FC = () => { + return ( + + + } /> + + + ); +}; + +// Main tabs component +const IndexRouteReuse: React.FC = () => { + return ( + + + } /> + } /> + } /> + } /> + + + + + Tab 1 + + + + Tab 2 + + + + Tab 3 + + + + ); +}; + +export default IndexRouteReuse; diff --git a/packages/react-router/test/base/tests/e2e/specs/index-route-reuse.cy.js b/packages/react-router/test/base/tests/e2e/specs/index-route-reuse.cy.js new file mode 100644 index 0000000000..fb4358b263 --- /dev/null +++ b/packages/react-router/test/base/tests/e2e/specs/index-route-reuse.cy.js @@ -0,0 +1,113 @@ +const port = 3000; + +/** + * Tests for index route reuse across tabs with nested outlets. + * + * Validates that switching between tabs where each has its own nested + * IonRouterOutlet with an index route correctly shows the right content. + * This tests whether createViewItem's index route reuse check + * (existingIsIndexRoute && newIsIndexRoute) causes issues when multiple + * outlets each have their own index routes. + */ +describe('Index Route Reuse - Nested Outlet Index Routes', () => { + it('should show tab1 index content by default', () => { + cy.visit(`http://localhost:${port}/index-route-reuse`); + cy.ionPageVisible('irr-tab1-home'); + cy.get('[data-testid="irr-tab1-home-content"]').should('be.visible'); + cy.get('[data-testid="irr-tab1-home-content"]').should('contain', 'Tab 1 Index Route Content'); + }); + + it('should show tab2 index content when switching to tab2', () => { + cy.visit(`http://localhost:${port}/index-route-reuse/tab1`); + cy.ionPageVisible('irr-tab1-home'); + + // Switch to Tab 2 + cy.ionTabClick('Tab 2'); + cy.ionPageVisible('irr-tab2-home'); + cy.get('[data-testid="irr-tab2-home-content"]').should('be.visible'); + cy.get('[data-testid="irr-tab2-home-content"]').should('contain', 'Tab 2 Index Route Content'); + + // Verify URL changed to tab2 + cy.url().should('include', '/index-route-reuse/tab2'); + }); + + it('should show tab3 index content when switching to tab3', () => { + cy.visit(`http://localhost:${port}/index-route-reuse/tab1`); + cy.ionPageVisible('irr-tab1-home'); + + // Switch to Tab 3 + cy.ionTabClick('Tab 3'); + cy.ionPageVisible('irr-tab3-home'); + cy.get('[data-testid="irr-tab3-home-content"]').should('be.visible'); + cy.get('[data-testid="irr-tab3-home-content"]').should('contain', 'Tab 3 Index Route Content'); + + // Verify URL changed to tab3 + cy.url().should('include', '/index-route-reuse/tab3'); + }); + + it('should correctly show each tab index when cycling through all tabs', () => { + cy.visit(`http://localhost:${port}/index-route-reuse/tab1`); + cy.ionPageVisible('irr-tab1-home'); + cy.get('[data-testid="irr-tab1-home-content"]').should('be.visible'); + + // Tab 1 -> Tab 2 + cy.ionTabClick('Tab 2'); + cy.ionPageVisible('irr-tab2-home'); + cy.get('[data-testid="irr-tab2-home-content"]').should('be.visible'); + cy.get('[data-testid="irr-tab2-home-content"]').should('contain', 'Tab 2 Index Route Content'); + + // Tab 2 -> Tab 3 + cy.ionTabClick('Tab 3'); + cy.ionPageVisible('irr-tab3-home'); + cy.get('[data-testid="irr-tab3-home-content"]').should('be.visible'); + cy.get('[data-testid="irr-tab3-home-content"]').should('contain', 'Tab 3 Index Route Content'); + + // Tab 3 -> Tab 1 (back to start) + cy.ionTabClick('Tab 1'); + cy.ionPageVisible('irr-tab1-home'); + cy.get('[data-testid="irr-tab1-home-content"]').should('be.visible'); + cy.get('[data-testid="irr-tab1-home-content"]').should('contain', 'Tab 1 Index Route Content'); + }); + + it('should preserve tab1 detail navigation and return correctly', () => { + cy.visit(`http://localhost:${port}/index-route-reuse/tab1`); + cy.ionPageVisible('irr-tab1-home'); + + // Navigate to detail page + cy.get('#irr-tab1-detail-btn').click(); + cy.ionPageVisible('irr-tab1-detail'); + cy.get('[data-testid="irr-tab1-detail-content"]').should('be.visible'); + + // Switch to Tab 2 + cy.ionTabClick('Tab 2'); + cy.ionPageVisible('irr-tab2-home'); + cy.get('[data-testid="irr-tab2-home-content"]').should('be.visible'); + + // Switch back to Tab 1 - should show detail (preserved history) + cy.ionTabClick('Tab 1'); + cy.ionPageVisible('irr-tab1-detail'); + cy.get('[data-testid="irr-tab1-detail-content"]').should('be.visible'); + }); + + it('should show correct content after rapid tab switching', () => { + cy.visit(`http://localhost:${port}/index-route-reuse/tab1`); + cy.ionPageVisible('irr-tab1-home'); + + // Rapid switching: Tab1 -> Tab2 -> Tab3 -> Tab2 -> Tab1 + cy.ionTabClick('Tab 2'); + cy.ionPageVisible('irr-tab2-home'); + + cy.ionTabClick('Tab 3'); + cy.ionPageVisible('irr-tab3-home'); + + cy.ionTabClick('Tab 2'); + cy.ionPageVisible('irr-tab2-home'); + cy.get('[data-testid="irr-tab2-home-content"]').should('be.visible'); + cy.get('[data-testid="irr-tab2-home-content"]').should('contain', 'Tab 2 Index Route Content'); + + cy.ionTabClick('Tab 1'); + cy.ionPageVisible('irr-tab1-home'); + cy.get('[data-testid="irr-tab1-home-content"]').should('be.visible'); + cy.get('[data-testid="irr-tab1-home-content"]').should('contain', 'Tab 1 Index Route Content'); + }); +});