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>
|
/// </summary>
|
||||||
public class BrowserRouter : IComponent, IDisposable
|
public class BrowserRouter : IComponent, IDisposable
|
||||||
{
|
{
|
||||||
|
static readonly char[] _queryOrHashStartChar = new[] { '?', '#' };
|
||||||
|
|
||||||
RenderHandle _renderHandle;
|
RenderHandle _renderHandle;
|
||||||
string _baseUriPrefix;
|
string _baseUriPrefix;
|
||||||
string _locationAbsolute;
|
string _locationAbsolute;
|
||||||
|
|
@ -74,6 +76,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing
|
||||||
throw new InvalidOperationException($"No value was specified for {nameof(PagesNamespace)}.");
|
throw new InvalidOperationException($"No value was specified for {nameof(PagesNamespace)}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
locationPath = StringUntilAny(locationPath, _queryOrHashStartChar);
|
||||||
var componentTypeName = $"{PagesNamespace}{locationPath.Replace('/', '.')}";
|
var componentTypeName = $"{PagesNamespace}{locationPath.Replace('/', '.')}";
|
||||||
if (componentTypeName[componentTypeName.Length - 1] == '.')
|
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}.");
|
?? 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)
|
private Type FindComponentTypeInAssemblyOrReferences(Assembly assembly, string typeName)
|
||||||
=> assembly.GetType(typeName, throwOnError: false, ignoreCase: true)
|
=> assembly.GetType(typeName, throwOnError: false, ignoreCase: true)
|
||||||
?? assembly.GetReferencedAssemblies()
|
?? assembly.GetReferencedAssemblies()
|
||||||
|
|
|
||||||
|
|
@ -61,14 +61,22 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing
|
||||||
/// <returns>A relative URI path.</returns>
|
/// <returns>A relative URI path.</returns>
|
||||||
public static string ToBaseRelativePath(string baseUriPrefix, string absoluteUri)
|
public static string ToBaseRelativePath(string baseUriPrefix, string absoluteUri)
|
||||||
{
|
{
|
||||||
// The absolute URI must be of the form "{baseUriPrefix}/something",
|
if (absoluteUri.Equals(baseUriPrefix, StringComparison.Ordinal))
|
||||||
// and from that we return "/something" (also stripping any querystring
|
{
|
||||||
// and/or hash value)
|
// Special case: if you're exactly at the base URI, treat it as if you
|
||||||
if (absoluteUri.StartsWith(baseUriPrefix, StringComparison.Ordinal)
|
// 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.Length > baseUriPrefix.Length
|
||||||
&& absoluteUri[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);
|
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", "scheme://host/path", "/path")]
|
||||||
[InlineData("scheme://host/path", "scheme://host/path/", "/")]
|
[InlineData("scheme://host/path", "scheme://host/path/", "/")]
|
||||||
[InlineData("scheme://host/path", "scheme://host/path/more", "/more")]
|
[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)
|
public void ComputesCorrectValidBaseRelativePaths(string baseUriPrefix, string absoluteUri, string expectedResult)
|
||||||
{
|
{
|
||||||
var actualResult = UriHelper.ToBaseRelativePath(baseUriPrefix, absoluteUri);
|
var actualResult = UriHelper.ToBaseRelativePath(baseUriPrefix, absoluteUri);
|
||||||
|
|
@ -35,7 +36,6 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Test
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("scheme://host", "otherscheme://host/")] // Mismatched prefix is error
|
[InlineData("scheme://host", "otherscheme://host/")] // Mismatched prefix is error
|
||||||
[InlineData("scheme://host", "scheme://otherhost/")] // 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)
|
public void ThrowsForInvalidBaseRelativePaths(string baseUriPrefix, string absoluteUri)
|
||||||
{
|
{
|
||||||
var ex = Assert.Throws<ArgumentException>(() =>
|
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 System.Numerics;
|
||||||
using BasicTestApp;
|
using BasicTestApp;
|
||||||
using BasicTestApp.HierarchicalImportsTest.Subdir;
|
using BasicTestApp.HierarchicalImportsTest.Subdir;
|
||||||
using Microsoft.AspNetCore.Blazor.Components;
|
|
||||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
|
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
|
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using OpenQA.Selenium.Support.UI;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
{
|
{
|
||||||
public class ComponentRenderingTest
|
public class ComponentRenderingTest : BasicTestAppTestBase
|
||||||
: ServerTestBase<DevHostServerFixture<BasicTestApp.Program>>
|
|
||||||
{
|
{
|
||||||
public ComponentRenderingTest(BrowserFixture browserFixture, DevHostServerFixture<Program> serverFixture)
|
public ComponentRenderingTest(BrowserFixture browserFixture, DevHostServerFixture<Program> serverFixture)
|
||||||
: base(browserFixture, serverFixture)
|
: base(browserFixture, serverFixture)
|
||||||
{
|
{
|
||||||
serverFixture.PathBase = "/subdir";
|
Navigate(ServerPathBase, noReload: true);
|
||||||
Navigate("/subdir", noReload: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -203,23 +199,5 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
elem => Assert.Equal(typeof(Complex).FullName, elem.Text),
|
elem => Assert.Equal(typeof(Complex).FullName, elem.Text),
|
||||||
elem => Assert.Equal(typeof(AssemblyHashAlgorithm).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": {
|
"IIS Express": {
|
||||||
"commandName": "IISExpress",
|
"commandName": "IISExpress",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "http://localhost:63796/subdir",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
|
|
@ -18,6 +19,7 @@
|
||||||
"BasicTestApp": {
|
"BasicTestApp": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "http://localhost:63797/subdir",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"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