From 9ec73d856fc9a346be87ec181a3497bcce6286aa Mon Sep 17 00:00:00 2001 From: erKam <88717125+erkamyaman@users.noreply.github.com> Date: Sun, 11 Oct 2026 00:48:54 +0300 Subject: [PATCH] fix(router): report the skip when probing the current URL The probe action set the navigation id it listens for only after calling navigateByUrl. When the URL is the one the router is already on, Angular emits NavigationSkipped synchronously inside that call, so the probe ignored it and waited the full 10 seconds before answering that it did not finish. The id is now set, and the timeout started, before navigating, so the probe answers right away with the skip reason. --- .../src/__tests__/router-features.test.ts | 25 ++++++++++++++++++- packages/devtools/src/router-actions.ts | 16 ++++++------ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/packages/devtools/src/__tests__/router-features.test.ts b/packages/devtools/src/__tests__/router-features.test.ts index f2dbcb3a..e99fd02b 100644 --- a/packages/devtools/src/__tests__/router-features.test.ts +++ b/packages/devtools/src/__tests__/router-features.test.ts @@ -15,7 +15,7 @@ import { type Routes, } from '@angular/router'; import { RouterTestingHarness } from '@angular/router/testing'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { watchRouter, type NavigationRecord } from '../router.ts'; import { ConfigTracker, activeIds, walkConfig } from '../router-config.ts'; import { outletsOf } from '../router-links.ts'; @@ -286,6 +286,29 @@ describe('router features on a real Router', () => { }, ); + it.skipIf(!angularAtLeast('20.2.0'))( + 'reports the skip when probing the URL the router is already on', + async () => { + await router.navigateByUrl('/users/8'); + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }); + try { + const pending = runAction( + router as never, + navigations, + { action: 'probe', url: '/users/8' }, + () => {}, + ); + await vi.advanceTimersByTimeAsync(10_000); + const result = (await pending) as Record; + expect(result['error']).toBeUndefined(); + expect(result).toMatchObject({ matched: false, reason: expect.stringMatching(/ignored/) }); + expect(router.url).toBe('/users/8'); + } finally { + vi.useRealTimers(); + } + }, + ); + it.skipIf(!angularAtLeast('20.2.0'))('aborts the navigation in flight', async () => { const pending = router.navigateByUrl('/slow'); await new Promise((resolve) => setTimeout(resolve, 5)); diff --git a/packages/devtools/src/router-actions.ts b/packages/devtools/src/router-actions.ts index 3021247c..475f7457 100644 --- a/packages/devtools/src/router-actions.ts +++ b/packages/devtools/src/router-actions.ts @@ -732,19 +732,21 @@ function probe(router: AnyRecord, navigations: NavigationRecord[], url: string): ); } }); - setCaller('probe from DevTools', true); - const promise = read( - () => router['navigateByUrl'](url, { skipLocationChange: true }) as Promise, - null, - ); - targetId = navigationIdOf(router); - promise?.catch?.(() => {}); timer = setTimeout(() => { subscription.unsubscribe(); abortCurrent(); markProbe(targetId); resolve({ error: 'The probe did not finish within 10s.' }); }, WAIT_MS); + // Set before navigating: a skipped navigation (same URL) emits its event synchronously. + const current = navigationIdOf(router); + targetId = current < 0 ? current : current + 1; + setCaller('probe from DevTools', true); + const promise = read( + () => router['navigateByUrl'](url, { skipLocationChange: true }) as Promise, + null, + ); + promise?.catch?.(() => {}); }); }