diff --git a/packages/vue-router/src/router.ts b/packages/vue-router/src/router.ts
index 816b0b04667..cdcc51cca88 100644
--- a/packages/vue-router/src/router.ts
+++ b/packages/vue-router/src/router.ts
@@ -459,10 +459,15 @@ export const createIonRouter = (
routeInfo.lastPathname =
currentRouteInfo?.pathname || routeInfo.lastPathname;
routeInfo.pushedByRoute = pushedByRoute;
+ /**
+ * Prefer the direction/animation the caller specified on
+ * the navigate call; fall back to the leaving route's
+ * values only when none was provided.
+ */
routeInfo.routerDirection =
- currentRouteInfo?.routerDirection || routeInfo.routerDirection;
+ routeInfo.routerDirection || currentRouteInfo?.routerDirection;
routeInfo.routerAnimation =
- currentRouteInfo?.routerAnimation || routeInfo.routerAnimation;
+ routeInfo.routerAnimation || currentRouteInfo?.routerAnimation;
routeInfo.prevRouteLastPathname = currentRouteInfo?.lastPathname;
}
}
diff --git a/packages/vue/test/base/src/App.vue b/packages/vue/test/base/src/App.vue
index 6def7e55b0f..dd5918c81d8 100644
--- a/packages/vue/test/base/src/App.vue
+++ b/packages/vue/test/base/src/App.vue
@@ -7,7 +7,7 @@
diff --git a/packages/vue/test/base/tests/e2e/playwright/replace-direction.spec.ts b/packages/vue/test/base/tests/e2e/playwright/replace-direction.spec.ts
new file mode 100644
index 00000000000..e70a1427de4
--- /dev/null
+++ b/packages/vue/test/base/tests/e2e/playwright/replace-direction.spec.ts
@@ -0,0 +1,57 @@
+import type { Page } from '@playwright/test';
+import { test, expect } from './utils/test-base';
+import { ionPageVisible, ionBackClick, ionRouterNavigate, routerPush } from './utils/test-utils';
+
+/**
+ * useIonRouter.navigate(url, routerDirection, routerAction) must honor the
+ * caller's explicit routerDirection even when routerAction is "replace",
+ * regardless of the direction stored on the leaving route.
+ *
+ * @see https://github.com/ionic-team/ionic-framework/issues/24995
+ */
+test.describe('useIonRouter.navigate with routerAction="replace"', () => {
+ async function readCurrentDirection(page: Page): Promise {
+ await page.waitForFunction(() => Boolean((window as any).debugIonNavManager));
+ return page.evaluate(
+ () => (window as any).debugIonNavManager.getCurrentRouteInfo()?.routerDirection
+ );
+ }
+
+ // Leaving route's recorded direction is "none" on initial load.
+ test('forward+replace from initial route preserves forward direction', async ({ page }) => {
+ await page.goto('/');
+ await ionPageVisible(page, 'home');
+
+ await ionRouterNavigate(page, '/routing', 'forward', 'replace');
+ await ionPageVisible(page, 'routing');
+
+ expect(await readCurrentDirection(page)).toBe('forward');
+ });
+
+ // Leaving route's recorded direction is "back" after a back nav.
+ test('forward+replace after a back nav preserves forward direction', async ({ page }) => {
+ await page.goto('/');
+ await routerPush(page, '/routing');
+ await ionPageVisible(page, 'routing');
+
+ await ionBackClick(page, 'routing');
+ await ionPageVisible(page, 'home');
+ expect(await readCurrentDirection(page)).toBe('back');
+
+ await ionRouterNavigate(page, '/inputs', 'forward', 'replace');
+ await ionPageVisible(page, 'inputs');
+
+ expect(await readCurrentDirection(page)).toBe('forward');
+ });
+
+ test('default useIonRouter.replace() keeps direction "root"', async ({ page }) => {
+ await page.goto('/');
+ await ionPageVisible(page, 'home');
+
+ await page.waitForFunction(() => Boolean((window as any).debugIonRouter));
+ await page.evaluate(() => (window as any).debugIonRouter.replace('/routing'));
+ await ionPageVisible(page, 'routing');
+
+ expect(await readCurrentDirection(page)).toBe('root');
+ });
+});
diff --git a/packages/vue/test/base/tests/e2e/playwright/utils/test-utils.ts b/packages/vue/test/base/tests/e2e/playwright/utils/test-utils.ts
index 19d8695cc27..3256f9e3a32 100644
--- a/packages/vue/test/base/tests/e2e/playwright/utils/test-utils.ts
+++ b/packages/vue/test/base/tests/e2e/playwright/utils/test-utils.ts
@@ -59,12 +59,14 @@ export async function routerGo(page: Page, n: number): Promise {
export async function ionRouterNavigate(
page: Page,
path: string,
- direction: 'root' | 'forward' | 'back' | 'none' = 'forward'
+ direction: 'root' | 'forward' | 'back' | 'none' = 'forward',
+ action?: 'push' | 'replace'
): Promise {
await waitForDebugIonRouter(page);
await page.evaluate(
- ({ p, d }: { p: string; d: string }) => (window as any).debugIonRouter.navigate(p, d),
- { p: path, d: direction }
+ ({ p, d, a }: { p: string; d: string; a?: string }) =>
+ (window as any).debugIonRouter.navigate(p, d, a),
+ { p: path, d: direction, a: action }
);
}
diff --git a/packages/vue/test/base/tests/unit/router-outlet.spec.ts b/packages/vue/test/base/tests/unit/router-outlet.spec.ts
index f25490c2a21..9c3b544faab 100644
--- a/packages/vue/test/base/tests/unit/router-outlet.spec.ts
+++ b/packages/vue/test/base/tests/unit/router-outlet.spec.ts
@@ -79,7 +79,8 @@ describe('Routing', () => {
expect.anything(),
expect.anything(),
expect.objectContaining({
- direction: "none",
+ // "root" and "none" both resolve to duration=0 via hasRootDirection.
+ direction: "root",
duration: 0,
animationBuilder: undefined
})
@@ -141,7 +142,7 @@ describe('Routing', () => {
expect.anything(),
expect.anything(),
expect.objectContaining({
- direction: "none",
+ direction: "root",
duration: undefined,
animationBuilder: animation
})