mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
test(react-router): add coverage for index route reuse across nested outlet tabs
This commit is contained in:
@@ -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 = () => {
|
||||
<Route path="/prefix-match-wildcard/*" element={<PrefixMatchWildcard />} />
|
||||
<Route path="/stale-view-cleanup/*" element={<StaleViewCleanup />} />
|
||||
<Route path="/index-param-priority/*" element={<IndexParamPriority />} />
|
||||
<Route path="/index-route-reuse/*" element={<IndexRouteReuse />} />
|
||||
</IonRouterOutlet>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
|
||||
@@ -98,6 +98,9 @@ const Main: React.FC = () => {
|
||||
<IonItem routerLink="/index-param-priority">
|
||||
<IonLabel>Index Param Priority</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem routerLink="/index-route-reuse">
|
||||
<IonLabel>Index Route Reuse</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
|
||||
@@ -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 (
|
||||
<IonPage data-pageid="irr-tab1-home">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 1 Home</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div data-testid="irr-tab1-home-content">Tab 1 Index Route Content</div>
|
||||
<IonButton routerLink="/index-route-reuse/tab1/detail" id="irr-tab1-detail-btn">
|
||||
Go to Tab 1 Detail
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab1Detail: React.FC = () => {
|
||||
return (
|
||||
<IonPage data-pageid="irr-tab1-detail">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton defaultHref="/index-route-reuse/tab1" />
|
||||
</IonButtons>
|
||||
<IonTitle>Tab 1 Detail</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div data-testid="irr-tab1-detail-content">Tab 1 Detail Content</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab1Outlet: React.FC = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonRouterOutlet>
|
||||
<Route index element={<Tab1Content />} />
|
||||
<Route path="detail" element={<Tab1Detail />} />
|
||||
</IonRouterOutlet>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
// Tab 2 nested outlet with its own index route
|
||||
const Tab2Content: React.FC = () => {
|
||||
return (
|
||||
<IonPage data-pageid="irr-tab2-home">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 2 Home</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div data-testid="irr-tab2-home-content">Tab 2 Index Route Content</div>
|
||||
<IonButton routerLink="/index-route-reuse/tab2/detail" id="irr-tab2-detail-btn">
|
||||
Go to Tab 2 Detail
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab2Detail: React.FC = () => {
|
||||
return (
|
||||
<IonPage data-pageid="irr-tab2-detail">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton defaultHref="/index-route-reuse/tab2" />
|
||||
</IonButtons>
|
||||
<IonTitle>Tab 2 Detail</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div data-testid="irr-tab2-detail-content">Tab 2 Detail Content</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab2Outlet: React.FC = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonRouterOutlet>
|
||||
<Route index element={<Tab2Content />} />
|
||||
<Route path="detail" element={<Tab2Detail />} />
|
||||
</IonRouterOutlet>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
// Tab 3 nested outlet with its own index route
|
||||
const Tab3Content: React.FC = () => {
|
||||
return (
|
||||
<IonPage data-pageid="irr-tab3-home">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 3 Home</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div data-testid="irr-tab3-home-content">Tab 3 Index Route Content</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab3Outlet: React.FC = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonRouterOutlet>
|
||||
<Route index element={<Tab3Content />} />
|
||||
</IonRouterOutlet>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
// Main tabs component
|
||||
const IndexRouteReuse: React.FC = () => {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Route path="tab1/*" element={<Tab1Outlet />} />
|
||||
<Route path="tab2/*" element={<Tab2Outlet />} />
|
||||
<Route path="tab3/*" element={<Tab3Outlet />} />
|
||||
<Route index element={<Navigate to="tab1" replace />} />
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/index-route-reuse/tab1">
|
||||
<IonIcon icon={triangle} />
|
||||
<IonLabel>Tab 1</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/index-route-reuse/tab2">
|
||||
<IonIcon icon={ellipse} />
|
||||
<IonLabel>Tab 2</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab3" href="/index-route-reuse/tab3">
|
||||
<IonIcon icon={square} />
|
||||
<IonLabel>Tab 3</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default IndexRouteReuse;
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user