diff --git a/src/Components/Web.JS/src/Services/NavigationManager.ts b/src/Components/Web.JS/src/Services/NavigationManager.ts index 47104cea3646..b3352b399f55 100644 --- a/src/Components/Web.JS/src/Services/NavigationManager.ts +++ b/src/Components/Web.JS/src/Services/NavigationManager.ts @@ -4,7 +4,7 @@ import '@microsoft/dotnet-js-interop'; import { resetScrollAfterNextBatch } from '../Rendering/Renderer'; import { EventDelegator } from '../Rendering/Events/EventDelegator'; -import { attachEnhancedNavigationListener, getInteractiveRouterRendererId, handleClickForNavigationInterception, hasInteractiveRouter, hasProgrammaticEnhancedNavigationHandler, isSamePageWithHash, isWithinBaseUriSpace, performProgrammaticEnhancedNavigation, performScrollToElementOnTheSamePage, scrollToElement, setHasInteractiveRouter, toAbsoluteUri } from './NavigationUtils'; +import { attachEnhancedNavigationListener, getInteractiveRouterRendererId, handleClickForNavigationInterception, hasInteractiveRouter, hasProgrammaticEnhancedNavigationHandler, isForSamePath, isSamePageWithHash, isWithinBaseUriSpace, performProgrammaticEnhancedNavigation, performScrollToElementOnTheSamePage, scrollToElement, setHasInteractiveRouter, toAbsoluteUri } from './NavigationUtils'; import { WebRendererId } from '../Rendering/WebRendererId'; import { isRendererAttached } from '../Rendering/WebRendererInteropMethods'; @@ -169,7 +169,9 @@ async function performInternalNavigation(absoluteInternalHref: string, intercept // position, so reset it. // To avoid ugly flickering effects, we don't want to change the scroll position until // we render the new page. As a best approximation, wait until the next batch. - resetScrollAfterNextBatch(); + if (!isForSamePath(absoluteInternalHref, location.href)) { + resetScrollAfterNextBatch(); + } saveToBrowserHistory(absoluteInternalHref, replace, state); diff --git a/src/Components/test/E2ETest/Tests/RoutingTest.cs b/src/Components/test/E2ETest/Tests/RoutingTest.cs index 6c430d24395c..dab29e059598 100644 --- a/src/Components/test/E2ETest/Tests/RoutingTest.cs +++ b/src/Components/test/E2ETest/Tests/RoutingTest.cs @@ -510,6 +510,43 @@ public void CanNavigateProgrammaticallyWithStateReplaceHistoryEntry() Assert.Equal(typeof(TestRouter).FullName, testSelector.SelectedOption.GetDomProperty("value")); } + [Fact] + public void NavigationToSamePathDoesNotScrollToTheTop() + { + // This test checks if the navigation to same path or path with query appeneded, + // keeps the scroll in the position from before navigation + // but moves it when we navigate to a fragment + SetUrlViaPushState("/"); + + var app = Browser.MountTestComponent(); + var testSelector = Browser.WaitUntilTestSelectorReady(); + + app.FindElement(By.LinkText("Programmatic navigation cases")).Click(); + Browser.True(() => Browser.Url.EndsWith("/ProgrammaticNavigationCases", StringComparison.Ordinal)); + Browser.Contains("programmatic navigation", () => app.FindElement(By.Id("test-info")).Text); + + var jsExecutor = (IJavaScriptExecutor)Browser; + var maxScrollPosition = (long)jsExecutor.ExecuteScript("return document.documentElement.scrollHeight - window.innerHeight;"); + // scroll max up to find the position of fragment + BrowserScrollY = 0; + var fragmentScrollPosition = (long)jsExecutor.ExecuteScript("return document.getElementById('fragment').getBoundingClientRect().top + window.scrollY;"); + + // scroll maximally down + BrowserScrollY = maxScrollPosition; + + app.FindElement(By.Id("do-self-navigate")).Click(); + var scrollPosition = BrowserScrollY; + Assert.True(scrollPosition == maxScrollPosition, "Expected to stay scrolled down."); + + app.FindElement(By.Id("do-self-navigate-with-query")).Click(); + scrollPosition = BrowserScrollY; + Assert.True(scrollPosition == maxScrollPosition, "Expected to stay scrolled down."); + + app.FindElement(By.Id("do-self-navigate-to-fragment")).Click(); + scrollPosition = BrowserScrollY; + Assert.True(scrollPosition == fragmentScrollPosition, "Expected to scroll to the fragment."); + } + [Fact] public void CanNavigateProgrammaticallyValidateNoReplaceHistoryEntry() { diff --git a/src/Components/test/testassets/BasicTestApp/RouterTest/ProgrammaticNavigationCases.razor b/src/Components/test/testassets/BasicTestApp/RouterTest/ProgrammaticNavigationCases.razor index 3c86f3d33929..c97ba8371759 100644 --- a/src/Components/test/testassets/BasicTestApp/RouterTest/ProgrammaticNavigationCases.razor +++ b/src/Components/test/testassets/BasicTestApp/RouterTest/ProgrammaticNavigationCases.razor @@ -3,6 +3,22 @@
This page has test cases for programmatic navigation.
+ + +
+ + + +
+ @@ -34,3 +50,46 @@ + +
+ @foreach (var i in Enumerable.Range(0, 200)) + { +

before fragment: @i

+ } + +
+ Middle section +
+ + @foreach (var i in Enumerable.Range(0, 200)) + { +

after fragment: @i

+ } +
+ +@code +{ + private void SelfNavigate() + { + NavigationManager.NavigateTo(NavigationManager.Uri.ToString(), false); + } + + private void SelfNavigateWithQuery() + { + var newUri = new UriBuilder(NavigationManager.Uri) + { + Query = $"random={Random.Shared.Next()}" + }; + NavigationManager.NavigateTo(newUri.ToString(), false); + } + + private void SelfNavigateWithFragment() + { + var newUri = new UriBuilder(NavigationManager.Uri) + { + Fragment = "fragment" + }; + NavigationManager.NavigateTo(newUri.ToString(), false); + } + +} \ No newline at end of file