Add E2E tests for BrowserRouter, plus implement querystring/hash support
This commit is contained in:
parent
8bc7c92683
commit
fc9cb1af65
|
|
@ -16,6 +16,8 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing
|
|||
/// </summary>
|
||||
public class BrowserRouter : IComponent, IDisposable
|
||||
{
|
||||
static readonly char[] _queryOrHashStartChar = new[] { '?', '#' };
|
||||
|
||||
RenderHandle _renderHandle;
|
||||
string _baseUriPrefix;
|
||||
string _locationAbsolute;
|
||||
|
|
@ -74,6 +76,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing
|
|||
throw new InvalidOperationException($"No value was specified for {nameof(PagesNamespace)}.");
|
||||
}
|
||||
|
||||
locationPath = StringUntilAny(locationPath, _queryOrHashStartChar);
|
||||
var componentTypeName = $"{PagesNamespace}{locationPath.Replace('/', '.')}";
|
||||
if (componentTypeName[componentTypeName.Length - 1] == '.')
|
||||
{
|
||||
|
|
@ -84,6 +87,14 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing
|
|||
?? throw new InvalidOperationException($"{nameof(BrowserRouter)} cannot find any component type with name {componentTypeName}.");
|
||||
}
|
||||
|
||||
private string StringUntilAny(string str, char[] chars)
|
||||
{
|
||||
var firstIndex = str.IndexOfAny(chars);
|
||||
return firstIndex < 0
|
||||
? str
|
||||
: str.Substring(0, firstIndex);
|
||||
}
|
||||
|
||||
private Type FindComponentTypeInAssemblyOrReferences(Assembly assembly, string typeName)
|
||||
=> assembly.GetType(typeName, throwOnError: false, ignoreCase: true)
|
||||
?? assembly.GetReferencedAssemblies()
|
||||
|
|
|
|||
|
|
@ -61,14 +61,22 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing
|
|||
/// <returns>A relative URI path.</returns>
|
||||
public static string ToBaseRelativePath(string baseUriPrefix, string absoluteUri)
|
||||
{
|
||||
// The absolute URI must be of the form "{baseUriPrefix}/something",
|
||||
// and from that we return "/something" (also stripping any querystring
|
||||
// and/or hash value)
|
||||
if (absoluteUri.StartsWith(baseUriPrefix, StringComparison.Ordinal)
|
||||
if (absoluteUri.Equals(baseUriPrefix, StringComparison.Ordinal))
|
||||
{
|
||||
// Special case: if you're exactly at the base URI, treat it as if you
|
||||
// were at "{baseUriPrefix}/" (i.e., with a following slash). It's a bit
|
||||
// ambiguous because we don't know whether the server would return the
|
||||
// same page whether or not the slash is present, but ASP.NET Core at
|
||||
// least does by default when using PathBase.
|
||||
return "/";
|
||||
}
|
||||
else if (absoluteUri.StartsWith(baseUriPrefix, StringComparison.Ordinal)
|
||||
&& absoluteUri.Length > baseUriPrefix.Length
|
||||
&& absoluteUri[baseUriPrefix.Length] == '/')
|
||||
{
|
||||
// TODO: Remove querystring and/or hash
|
||||
// The absolute URI must be of the form "{baseUriPrefix}/something",
|
||||
// and from that we return "/something" (also stripping any querystring
|
||||
// and/or hash value)
|
||||
return absoluteUri.Substring(baseUriPrefix.Length);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Test
|
|||
[InlineData("scheme://host", "scheme://host/path", "/path")]
|
||||
[InlineData("scheme://host/path", "scheme://host/path/", "/")]
|
||||
[InlineData("scheme://host/path", "scheme://host/path/more", "/more")]
|
||||
[InlineData("scheme://host/path", "scheme://host/path", "/")]
|
||||
public void ComputesCorrectValidBaseRelativePaths(string baseUriPrefix, string absoluteUri, string expectedResult)
|
||||
{
|
||||
var actualResult = UriHelper.ToBaseRelativePath(baseUriPrefix, absoluteUri);
|
||||
|
|
@ -35,7 +36,6 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Test
|
|||
[Theory]
|
||||
[InlineData("scheme://host", "otherscheme://host/")] // Mismatched prefix is error
|
||||
[InlineData("scheme://host", "scheme://otherhost/")] // Mismatched prefix is error
|
||||
[InlineData("scheme://host/path", "scheme://host/path")] // URI isn't within base URI space
|
||||
public void ThrowsForInvalidBaseRelativePaths(string baseUriPrefix, string absoluteUri)
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using BasicTestApp;
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
|
||||
{
|
||||
public class BasicTestAppTestBase : ServerTestBase<DevHostServerFixture<Program>>
|
||||
{
|
||||
public const string ServerPathBase = "/subdir";
|
||||
|
||||
public BasicTestAppTestBase(BrowserFixture browserFixture, DevHostServerFixture<Program> serverFixture)
|
||||
: base(browserFixture, serverFixture)
|
||||
{
|
||||
serverFixture.PathBase = ServerPathBase;
|
||||
}
|
||||
|
||||
protected IWebElement MountTestComponent<TComponent>() where TComponent : IComponent
|
||||
{
|
||||
var componentTypeName = typeof(TComponent).FullName;
|
||||
WaitUntilDotNetRunningInBrowser();
|
||||
((IJavaScriptExecutor)Browser).ExecuteScript(
|
||||
$"mountTestComponent('{componentTypeName}')");
|
||||
return Browser.FindElement(By.TagName("app"));
|
||||
}
|
||||
|
||||
protected void WaitUntilDotNetRunningInBrowser()
|
||||
{
|
||||
new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(driver =>
|
||||
{
|
||||
return ((IJavaScriptExecutor)driver)
|
||||
.ExecuteScript("return window.isTestReady;");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,23 +8,19 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using BasicTestApp;
|
||||
using BasicTestApp.HierarchicalImportsTest.Subdir;
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||
{
|
||||
public class ComponentRenderingTest
|
||||
: ServerTestBase<DevHostServerFixture<BasicTestApp.Program>>
|
||||
public class ComponentRenderingTest : BasicTestAppTestBase
|
||||
{
|
||||
public ComponentRenderingTest(BrowserFixture browserFixture, DevHostServerFixture<Program> serverFixture)
|
||||
: base(browserFixture, serverFixture)
|
||||
{
|
||||
serverFixture.PathBase = "/subdir";
|
||||
Navigate("/subdir", noReload: true);
|
||||
Navigate(ServerPathBase, noReload: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -203,23 +199,5 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
|||
elem => Assert.Equal(typeof(Complex).FullName, elem.Text),
|
||||
elem => Assert.Equal(typeof(AssemblyHashAlgorithm).FullName, elem.Text));
|
||||
}
|
||||
|
||||
private IWebElement MountTestComponent<TComponent>() where TComponent: IComponent
|
||||
{
|
||||
var componentTypeName = typeof(TComponent).FullName;
|
||||
WaitUntilDotNetRunningInBrowser();
|
||||
((IJavaScriptExecutor)Browser).ExecuteScript(
|
||||
$"mountTestComponent('{componentTypeName}')");
|
||||
return Browser.FindElement(By.TagName("app"));
|
||||
}
|
||||
|
||||
private void WaitUntilDotNetRunningInBrowser()
|
||||
{
|
||||
new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(driver =>
|
||||
{
|
||||
return ((IJavaScriptExecutor)driver)
|
||||
.ExecuteScript("return window.isTestReady;");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
using System;
|
||||
using BasicTestApp;
|
||||
using BasicTestApp.RouterTest;
|
||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||
{
|
||||
public class RoutingTest : BasicTestAppTestBase, IDisposable
|
||||
{
|
||||
private readonly ServerFixture _server;
|
||||
|
||||
public RoutingTest(BrowserFixture browserFixture, DevHostServerFixture<Program> serverFixture)
|
||||
: base(browserFixture, serverFixture)
|
||||
{
|
||||
_server = serverFixture;
|
||||
Navigate(ServerPathBase, noReload: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanArriveAtDefaultPage()
|
||||
{
|
||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanArriveAtNonDefaultPage()
|
||||
{
|
||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/Other");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanFollowLinkToOtherPage()
|
||||
{
|
||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Other")).Click();
|
||||
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanFollowLinkToDefaultPage()
|
||||
{
|
||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/Other");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Default")).Click();
|
||||
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanFollowLinkToOtherPageWithQueryString()
|
||||
{
|
||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Other with query")).Click();
|
||||
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanFollowLinkToDefaultPageWithQueryString()
|
||||
{
|
||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/Other");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Default with query")).Click();
|
||||
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanFollowLinkToOtherPageWithHash()
|
||||
{
|
||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Other with hash")).Click();
|
||||
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanFollowLinkToDefaultPageWithHash()
|
||||
{
|
||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/Other");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Default with hash")).Click();
|
||||
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Clear any existing state
|
||||
SetUrlViaPushState(ServerPathBase);
|
||||
MountTestComponent<TextOnlyComponent>();
|
||||
}
|
||||
|
||||
private void SetUrlViaPushState(string relativeUri)
|
||||
{
|
||||
var jsExecutor = (IJavaScriptExecutor)Browser;
|
||||
var absoluteUri = new Uri(_server.RootUri, relativeUri);
|
||||
jsExecutor.ExecuteScript($"history.pushState(null, '', '{absoluteUri.ToString()}')");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:63796/subdir",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
|
@ -18,6 +19,7 @@
|
|||
"BasicTestApp": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:63797/subdir",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
@using BasicTestApp.RouterTest
|
||||
<div id="test-info">This is the default page.</div>
|
||||
<c:Links />
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<ul>
|
||||
<li><a href="/subdir/RouterTest/">Default</a></li>
|
||||
<li><a href="/subdir/RouterTest/?abc=123">Default with query</a></li>
|
||||
<li><a href="/subdir/RouterTest/#blah">Default with hash</a></li>
|
||||
<li><a href="/subdir/RouterTest/Other">Other</a></li>
|
||||
<li><a href="/subdir/RouterTest/Other?abc=123">Other with query</a></li>
|
||||
<li><a href="/subdir/RouterTest/Other#blah">Other with hash</a></li>
|
||||
</ul>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@using BasicTestApp.RouterTest
|
||||
<div id="test-info">This is another page.</div>
|
||||
<c:Links />
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
@using Microsoft.AspNetCore.Blazor.Browser.Routing
|
||||
<c:BrowserRouter AppAssembly=@(typeof(BasicTestApp.Program).Assembly)
|
||||
PagesNamespace=@nameof(BasicTestApp)
|
||||
DefaultComponentName=@("Default") />
|
||||
Loading…
Reference in New Issue