mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(react-router): use stored parent paths for index route matching in findViewItemByPath
This commit is contained in:
@@ -207,6 +207,13 @@ const resolveIndexRouteMatch = (
|
||||
};
|
||||
|
||||
export class ReactRouterViewStack extends ViewStacks {
|
||||
/**
|
||||
* Stores the computed parent path for each outlet.
|
||||
* Used by findViewItemByPath to correctly evaluate index route matches
|
||||
* without requiring the outlet's React element or route children.
|
||||
*/
|
||||
private outletParentPaths = new Map<string, string>();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@@ -542,6 +549,11 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
// Non-fatal: if we fail to compute parentPath, fall back to previous behavior
|
||||
}
|
||||
|
||||
// Store the computed parentPath for use in findViewItemByPath
|
||||
if (parentPath !== undefined) {
|
||||
this.outletParentPaths.set(outletId, parentPath);
|
||||
}
|
||||
|
||||
// Sync child elements with stored viewItems (e.g. to reflect new props)
|
||||
React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => {
|
||||
// Ensure the child is a valid React element since we
|
||||
@@ -661,6 +673,8 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
let viewItem: ViewItem | undefined;
|
||||
let match: PathMatch<string> | null = null;
|
||||
let viewStack: ViewItem[];
|
||||
// Capture stored parent paths for use in nested matchView/matchDefaultRoute functions
|
||||
const storedParentPaths = this.outletParentPaths;
|
||||
|
||||
if (outletId) {
|
||||
viewStack = sortViewsBySpecificity(this.getViewItemsForOutlet(outletId));
|
||||
@@ -693,7 +707,8 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
const result = v.reactElement ? matchComponent(v.reactElement, pathname) : null;
|
||||
|
||||
if (!result) {
|
||||
const indexMatch = resolveIndexRouteMatch(v, pathname, undefined);
|
||||
const outletParentPath = storedParentPaths.get(v.outletId);
|
||||
const indexMatch = resolveIndexRouteMatch(v, pathname, outletParentPath);
|
||||
if (indexMatch) {
|
||||
match = indexMatch;
|
||||
viewItem = v;
|
||||
@@ -763,7 +778,8 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
const isIndexRoute = !!childProps.index;
|
||||
|
||||
if (isIndexRoute) {
|
||||
const indexMatch = resolveIndexRouteMatch(v, pathname, undefined);
|
||||
const outletParentPath = storedParentPaths.get(v.outletId);
|
||||
const indexMatch = resolveIndexRouteMatch(v, pathname, outletParentPath);
|
||||
if (indexMatch) {
|
||||
match = indexMatch;
|
||||
viewItem = v;
|
||||
@@ -836,6 +852,14 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
this.cleanupStaleViewItems(viewItem.outletId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Override clear to also clean up the stored parent path for the outlet.
|
||||
*/
|
||||
clear = (outletId: string) => {
|
||||
this.outletParentPaths.delete(outletId);
|
||||
return super.clear(outletId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Override remove
|
||||
*/
|
||||
|
||||
@@ -82,6 +82,9 @@ const UserDetails: React.FC = () => {
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonLabel data-testid="user-details-param">Details view user: {userId ?? 'missing'}</IonLabel>
|
||||
<IonButton routerLink="/nested-params" id="back-to-landing">
|
||||
Back to Landing
|
||||
</IonButton>
|
||||
<IonButton routerLink={`/nested-params/user/${userId}/settings`} id="go-to-settings">
|
||||
Go to Settings
|
||||
</IonButton>
|
||||
|
||||
@@ -49,6 +49,34 @@ describe('Nested Params', () => {
|
||||
cy.get('[data-testid="user-settings-param"]').should('contain', 'Settings view user: 123');
|
||||
});
|
||||
|
||||
it('/nested-params > Deep link to child then navigate to landing > Landing should show correctly', () => {
|
||||
// Deep-link directly to a nested child route
|
||||
cy.visit(`http://localhost:${port}/nested-params/user/42/details`);
|
||||
cy.get('[data-testid="user-details-param"]').should('contain', 'Details view user: 42');
|
||||
|
||||
// Navigate back to the landing (index route)
|
||||
cy.get('#back-to-landing').click();
|
||||
cy.ionPageVisible('nested-params-landing');
|
||||
|
||||
// The user layout page should be hidden (Ionic preserves it in DOM for back navigation)
|
||||
cy.get('[data-pageid^="nested-params-user-"]').should('have.class', 'ion-page-hidden');
|
||||
});
|
||||
|
||||
it('/nested-params > Deep link then navigate to landing and back > Round-trip should work', () => {
|
||||
// Deep-link directly to a nested child route
|
||||
cy.visit(`http://localhost:${port}/nested-params/user/42/details`);
|
||||
cy.get('[data-testid="user-details-param"]').should('contain', 'Details view user: 42');
|
||||
|
||||
// Navigate to the landing page
|
||||
cy.get('#back-to-landing').click();
|
||||
cy.ionPageVisible('nested-params-landing');
|
||||
|
||||
// Navigate back to user details
|
||||
cy.get('#go-to-user-42').click();
|
||||
cy.get('[data-testid="user-details-param"]').should('contain', 'Details view user: 42');
|
||||
cy.get('[data-testid="user-layout-param"]').should('contain', 'Layout sees user: 42');
|
||||
});
|
||||
|
||||
it('/nested-params > Navigate to user then back > No visual overlap during transition', () => {
|
||||
cy.visit(`http://localhost:${port}/nested-params`);
|
||||
cy.ionPageVisible('nested-params-landing');
|
||||
|
||||
Reference in New Issue
Block a user