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 })