mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(react-router): forward index and caseSensitive props through IonRoute
This commit is contained in:
@@ -2,6 +2,6 @@ import type { IonRouteProps } from '@ionic/react';
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
|
||||
export const IonRouteInner = ({ path, element }: IonRouteProps) => {
|
||||
return <Route path={path} element={element} />;
|
||||
export const IonRouteInner = ({ path, index, caseSensitive, element }: IonRouteProps) => {
|
||||
return <Route path={path} index={index} caseSensitive={caseSensitive} element={element} />;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
|
||||
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
|
||||
import { IonRoute, RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
|
||||
@@ -258,7 +258,8 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
|
||||
const routesChildren =
|
||||
getRoutesChildren(this.ionRouterOutlet.props.children) ?? this.ionRouterOutlet.props.children;
|
||||
const routeChildren = React.Children.toArray(routesChildren).filter(
|
||||
(child): child is React.ReactElement => React.isValidElement(child) && child.type === Route
|
||||
(child): child is React.ReactElement =>
|
||||
React.isValidElement(child) && (child.type === Route || child.type === IonRoute)
|
||||
);
|
||||
|
||||
const hasRelativeRoutes = routeChildren.some((route) => {
|
||||
@@ -1306,7 +1307,8 @@ function findRouteByRouteInfo(node: React.ReactNode, routeInfo: RouteInfo, paren
|
||||
|
||||
// Collect all route children
|
||||
const routeChildren = React.Children.toArray(routesChildren).filter(
|
||||
(child): child is React.ReactElement => React.isValidElement(child) && child.type === Route
|
||||
(child): child is React.ReactElement =>
|
||||
React.isValidElement(child) && (child.type === Route || child.type === IonRoute)
|
||||
);
|
||||
|
||||
// Sort routes by specificity (most specific first)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { IonRoute } from '@ionic/react';
|
||||
import React from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
@@ -33,7 +34,8 @@ export const getRoutesChildren = (node: React.ReactNode): React.ReactNode | unde
|
||||
export const extractRouteChildren = (children: React.ReactNode): React.ReactElement[] => {
|
||||
const routesChildren = getRoutesChildren(children) ?? children;
|
||||
return React.Children.toArray(routesChildren).filter(
|
||||
(child): child is React.ReactElement => React.isValidElement(child) && child.type === Route
|
||||
(child): child is React.ReactElement =>
|
||||
React.isValidElement(child) && (child.type === Route || child.type === IonRoute)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ import NestedTabsRelativeLinks from './pages/nested-tabs-relative-links/NestedTa
|
||||
import RootSplatTabs from './pages/root-splat-tabs/RootSplatTabs';
|
||||
import ContentChangeNavigation from './pages/content-change-navigation/ContentChangeNavigation';
|
||||
import SearchParams from './pages/search-params/SearchParams';
|
||||
import IonRoutePropsTest from './pages/ion-route-props/IonRouteProps';
|
||||
|
||||
setupIonicReact();
|
||||
|
||||
@@ -83,6 +84,7 @@ const App: React.FC = () => {
|
||||
<Route path="/root-splat-tabs/*" element={<RootSplatTabs />} />
|
||||
<Route path="/content-change-navigation/*" element={<ContentChangeNavigation />} />
|
||||
<Route path="/search-params" element={<SearchParams />} />
|
||||
<Route path="/ion-route-props/*" element={<IonRoutePropsTest />} />
|
||||
</IonRouterOutlet>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
|
||||
@@ -86,6 +86,9 @@ const Main: React.FC = () => {
|
||||
<IonItem routerLink="/search-params">
|
||||
<IonLabel>Search Params</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem routerLink="/ion-route-props">
|
||||
<IonLabel>IonRoute Props</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonRouterOutlet,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonRoute,
|
||||
} from '@ionic/react';
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* Test page for IonRoute index and caseSensitive props.
|
||||
*
|
||||
* Verifies that:
|
||||
* 1. <IonRoute index> renders as the default when navigating to the parent path
|
||||
* 2. <IonRoute caseSensitive> correctly enforces case-sensitive path matching
|
||||
*/
|
||||
|
||||
const IndexHome: React.FC = () => (
|
||||
<IonPage data-pageid="index-home">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Index Home</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<p id="index-home-text">This is the index route</p>
|
||||
<IonButton id="go-to-details" routerLink="/ion-route-props/details">
|
||||
Go to Details
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
|
||||
const Details: React.FC = () => (
|
||||
<IonPage data-pageid="ion-route-details">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Details</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<p id="details-text">Details page</p>
|
||||
<IonButton id="go-back-home" routerLink="/ion-route-props" routerDirection="back">
|
||||
Back to Home
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
|
||||
const CaseSensitivePage: React.FC = () => (
|
||||
<IonPage data-pageid="case-sensitive-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Case Sensitive Match</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<p id="case-sensitive-text">Matched case-sensitive route</p>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
|
||||
const IonRoutePropsTest: React.FC = () => (
|
||||
<IonRouterOutlet>
|
||||
{/* Index route: should render when navigating to /ion-route-props */}
|
||||
<IonRoute index element={<IndexHome />} />
|
||||
<IonRoute path="details" element={<Details />} />
|
||||
<IonRoute path="CaseSensitive" caseSensitive element={<CaseSensitivePage />} />
|
||||
{/* Catch-all for unmatched routes */}
|
||||
<Route path="*" element={<IndexHome />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
|
||||
export default IonRoutePropsTest;
|
||||
@@ -0,0 +1,42 @@
|
||||
const port = 3000;
|
||||
|
||||
describe('IonRoute Props', () => {
|
||||
/**
|
||||
* Tests that IonRoute correctly forwards index and caseSensitive props.
|
||||
*
|
||||
* Previously, IonRouteInner only destructured path and element,
|
||||
* silently dropping index and caseSensitive props.
|
||||
*/
|
||||
|
||||
describe('index prop', () => {
|
||||
it('should render the index route when navigating to parent path', () => {
|
||||
cy.visit(`http://localhost:${port}/ion-route-props`);
|
||||
cy.ionPageVisible('index-home');
|
||||
cy.get('#index-home-text').should('be.visible').and('contain', 'This is the index route');
|
||||
});
|
||||
|
||||
it('should navigate from index route to details and back', () => {
|
||||
cy.visit(`http://localhost:${port}/ion-route-props`);
|
||||
cy.ionPageVisible('index-home');
|
||||
cy.ionNav('ion-button', 'Go to Details');
|
||||
cy.ionPageVisible('ion-route-details');
|
||||
cy.get('#details-text').should('be.visible');
|
||||
cy.ionNav('ion-button', 'Back to Home');
|
||||
cy.ionPageVisible('index-home');
|
||||
});
|
||||
});
|
||||
|
||||
describe('caseSensitive prop', () => {
|
||||
it('should match the exact case path', () => {
|
||||
cy.visit(`http://localhost:${port}/ion-route-props/CaseSensitive`);
|
||||
cy.ionPageVisible('case-sensitive-page');
|
||||
cy.get('#case-sensitive-text').should('be.visible');
|
||||
});
|
||||
|
||||
it('should not match a different case path when caseSensitive is true', () => {
|
||||
cy.visit(`http://localhost:${port}/ion-route-props/casesensitive`);
|
||||
// The case-sensitive page should NOT be visible since the case doesn't match
|
||||
cy.get('[data-pageid="case-sensitive-page"]').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,8 @@ import { NavContext } from '../contexts/NavContext';
|
||||
|
||||
export interface IonRouteProps {
|
||||
path?: string;
|
||||
index?: boolean;
|
||||
caseSensitive?: boolean;
|
||||
show?: boolean;
|
||||
element: React.ReactElement;
|
||||
disableIonPageManagement?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user