Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NavigateTo does not scroll to the top in internal navigation to same url #60190

Merged
merged 2 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Components/Web.JS/src/Services/NavigationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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);

Expand Down
37 changes: 37 additions & 0 deletions src/Components/test/E2ETest/Tests/RoutingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestRouter>();
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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@

<div id="test-info">This page has test cases for programmatic navigation.</div>

<style>
.sticky-top {
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 1020;
background-color: white;
}
</style>

<div class="sticky-top">
<button id="do-self-navigate" @onclick="SelfNavigate">Navigate to Self</button>
<button id="do-self-navigate-with-query" @onclick="SelfNavigateWithQuery">Navigate to Self With Query</button>
<button id="do-self-navigate-to-fragment" @onclick="SelfNavigateWithFragment">Navigate to Self with Fragment</button>
</div>

<button id="do-other-navigation" @onclick="@(() => NavigationManager.NavigateTo("Other", new NavigationOptions()))">
Programmatic navigation (NavigationOptions overload)
</button>
Expand Down Expand Up @@ -34,3 +50,46 @@
<button id="do-other-navigation-state-replacehistoryentry" @onclick="@(() => NavigationManager.NavigateTo("Other", new NavigationOptions { HistoryEntryState = "state", ReplaceHistoryEntry = true }))">
Programmatic navigation (NavigationOptions overload) with replace and state
</button>

<div>
@foreach (var i in Enumerable.Range(0, 200))
{
<p>before fragment: @i</p>
}

<div id="fragment">
Middle section
</div>

@foreach (var i in Enumerable.Range(0, 200))
{
<p>after fragment: @i</p>
}
</div>

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

}
Loading