Skip to content
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