Skip to content

Commit 5dbf349

Browse files
committed
Add E2E tests that validate the behavior is equivalent on SSR and interactive routing
1 parent f285c36 commit 5dbf349

File tree

15 files changed

+417
-1
lines changed

15 files changed

+417
-1
lines changed

src/Components/Components/src/Routing/RouteTableFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ internal static InboundRouteEntry CreateEntry([DynamicallyAccessedMembers(Compon
175175
}
176176
}
177177

178-
if (parsedTemplate != null || routeParameterNames != null)
178+
if (parsedTemplate == null)
179179
{
180180
throw new InvalidOperationException($"Unable to find the provided template '{template}'");
181181
}

src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public static IRazorComponentsBuilder AddRazorComponents(this IServiceCollection
4444
// Endpoints
4545
services.TryAddSingleton<RazorComponentEndpointDataSourceFactory>();
4646
services.TryAddSingleton<RazorComponentEndpointFactory>();
47+
services.TryAddScoped<IRazorComponentEndpointInvoker, RazorComponentEndpointInvoker>();
4748

4849
// Common services required for components server side rendering
4950
services.TryAddSingleton<ServerComponentSerializer>(services => new ServerComponentSerializer(services.GetRequiredService<IDataProtectionProvider>()));
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Components.TestServer.RazorComponents;
5+
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
6+
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
7+
using Microsoft.AspNetCore.E2ETesting;
8+
using OpenQA.Selenium;
9+
using TestServer;
10+
using Xunit.Abstractions;
11+
12+
namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests;
13+
14+
public class UnifiedRoutingTests : ServerTestBase<BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<Root>>>
15+
{
16+
public UnifiedRoutingTests(
17+
BrowserFixture browserFixture,
18+
BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<Root>> serverFixture,
19+
ITestOutputHelper output)
20+
: base(browserFixture, serverFixture, output)
21+
{
22+
}
23+
24+
public override Task InitializeAsync()
25+
=> InitializeAsync(BrowserFixture.StreamingContext);
26+
27+
[Fact]
28+
public void Routing_CanRenderPagesWithParameters_And_TransitionToInteractive()
29+
{
30+
ExecuteRoutingTestCore("routing/parameters/value", "value");
31+
}
32+
33+
[Fact]
34+
public void Routing_CanRenderPagesWithConstrainedParameters_And_TransitionToInteractive()
35+
{
36+
ExecuteRoutingTestCore("routing/constraints/5", "5");
37+
}
38+
39+
[Fact]
40+
public void Routing_CanRenderPagesWithComplexSegments_And_TransitionToInteractive()
41+
{
42+
ExecuteRoutingTestCore("routing/complex-segment(value)", "value");
43+
}
44+
45+
[Fact]
46+
public void Routing_CanRenderPagesWithParametersWithDefaultValue_And_TransitionToInteractive()
47+
{
48+
ExecuteRoutingTestCore("routing/defaults", "default");
49+
}
50+
51+
[Fact]
52+
public void Routing_CanRenderPagesWithOptionalParameters_And_TransitionToInteractive()
53+
{
54+
ExecuteRoutingTestCore("routing/optional", "null");
55+
}
56+
57+
[Fact]
58+
public void Routing_CanRenderPagesWithCatchAllParameters_And_TransitionToInteractive()
59+
{
60+
ExecuteRoutingTestCore("routing/catch-all/rest/of/the/path", "rest/of/the/path");
61+
}
62+
63+
[Fact]
64+
public void Routing_CanRenderPagesWithConstrainedCatchAllParameters_And_TransitionToInteractive()
65+
{
66+
ExecuteRoutingTestCore("routing/constrained-catch-all/a/b", "a/b");
67+
}
68+
69+
private void ExecuteRoutingTestCore(string url, string expectedValue)
70+
{
71+
GoTo($"{url}?suppress-autostart");
72+
73+
Browser.Equal(expectedValue, () => Browser.FindElement(By.Id("parameter-value")).Text);
74+
75+
Browser.Exists(By.Id("call-blazor-start"));
76+
77+
Browser.Click(By.Id("call-blazor-start"));
78+
79+
Browser.Exists(By.Id("interactive"));
80+
81+
Browser.Equal(expectedValue, () => Browser.FindElement(By.Id("parameter-value")).Text);
82+
}
83+
84+
private void GoTo(string relativePath)
85+
{
86+
Navigate($"{ServerPathBase}/{relativePath}");
87+
}
88+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Globalization;
5+
using Components.TestServer.RazorComponents;
6+
using Microsoft.AspNetCore.Components.Server.Circuits;
7+
using Microsoft.AspNetCore.Components.Web;
8+
using Microsoft.AspNetCore.DataProtection;
9+
10+
namespace TestServer;
11+
12+
public class BlazorWebServerStartup
13+
{
14+
public BlazorWebServerStartup(IConfiguration configuration)
15+
{
16+
Configuration = configuration;
17+
}
18+
19+
public IConfiguration Configuration { get; }
20+
21+
// This method gets called by the runtime. Use this method to add services to the container.
22+
public void ConfigureServices(IServiceCollection services)
23+
{
24+
services.AddRazorComponents()
25+
.AddServerComponents(options =>
26+
{
27+
options.RootComponents.MaxJSRootComponents = 5; // To make it easier to test
28+
options.RootComponents.RegisterForJavaScript<BasicTestApp.DynamicallyAddedRootComponent>("my-dynamic-root-component");
29+
options.RootComponents.RegisterForJavaScript<BasicTestApp.JavaScriptRootComponentParameterTypes>(
30+
"component-with-many-parameters",
31+
javaScriptInitializer: "myJsRootComponentInitializers.testInitializer");
32+
});
33+
services.AddSingleton<ResourceRequestLog>();
34+
services.AddTransient<BasicTestApp.FormsTest.ValidationComponentDI.SaladChef>();
35+
36+
var circuitContextAccessor = new TestCircuitContextAccessor();
37+
services.AddSingleton<CircuitHandler>(circuitContextAccessor);
38+
services.AddSingleton(circuitContextAccessor);
39+
40+
// Since tests run in parallel, we use an ephemeral key provider to avoid filesystem
41+
// contention issues.
42+
services.AddSingleton<IDataProtectionProvider, EphemeralDataProtectionProvider>();
43+
}
44+
45+
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
46+
public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env, ResourceRequestLog resourceRequestLog)
47+
{
48+
var enUs = new CultureInfo("en-US");
49+
CultureInfo.DefaultThreadCurrentCulture = enUs;
50+
CultureInfo.DefaultThreadCurrentUICulture = enUs;
51+
52+
if (env.IsDevelopment())
53+
{
54+
app.UseDeveloperExceptionPage();
55+
}
56+
57+
// Mount the server-side Blazor app on /subdir
58+
app.Map("/subdir", app =>
59+
{
60+
app.Use((context, next) =>
61+
{
62+
if (context.Request.Path.Value.EndsWith("/images/blazor_logo_1000x.png", StringComparison.Ordinal))
63+
{
64+
resourceRequestLog.AddRequest(context.Request);
65+
}
66+
67+
return next(context);
68+
});
69+
70+
app.UseStaticFiles();
71+
72+
app.UseRouting();
73+
app.UseEndpoints(endpoints =>
74+
{
75+
endpoints.MapRazorComponents<Root>()
76+
.AddServerRenderMode();
77+
});
78+
});
79+
}
80+
}

src/Components/test/testassets/Components.TestServer/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Reflection;
5+
using Components.TestServer.RazorComponents;
56
using Microsoft.AspNetCore.Hosting.Server;
67
using Microsoft.AspNetCore.Hosting.Server.Features;
78
using Microsoft.Extensions.Logging.Testing;
@@ -29,6 +30,7 @@ public static async Task Main(string[] args)
2930
["Save state"] = (BuildWebHost<SaveState>(CreateAdditionalArgs(args)), "/save-state"),
3031
["Globalization + Localization (Server-side)"] = (BuildWebHost<InternationalizationStartup>(CreateAdditionalArgs(args)), "/subdir"),
3132
["Server-side blazor"] = (BuildWebHost<ServerStartup>(CreateAdditionalArgs(args)), "/subdir"),
33+
["Blazor web with server-side blazor root component"] = (BuildWebHost<RazorComponentEndpointsStartup<Root>>(CreateAdditionalArgs(args)), "/subdir"),
3234
["Hosted client-side blazor"] = (BuildWebHost<ClientStartup>(CreateAdditionalArgs(args)), "/subdir"),
3335
["Hot Reload"] = (BuildWebHost<HotReloadStartup>(CreateAdditionalArgs(args)), "/subdir"),
3436
["Dev server client-side blazor"] = CreateDevServerHost(CreateAdditionalArgs(args))
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
@page "/routing/catch-all/{*parameter}"
2+
3+
<h3>Catch all</h3>
4+
5+
<p id="parameter-value">@Parameter</p>
6+
7+
@if (_interactive)
8+
{
9+
<p id="interactive">Rendered interactive.</p>
10+
}
11+
12+
@code {
13+
private bool _interactive;
14+
15+
[Parameter] public string Parameter { get; set; }
16+
17+
protected override void OnAfterRender(bool firstRender)
18+
{
19+
if (firstRender)
20+
{
21+
_interactive = true;
22+
StateHasChanged();
23+
}
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@page "/routing/complex-segment({parameter})"
2+
<h3>Complex segment parameters</h3>
3+
4+
<p id="parameter-value">@Parameter</p>
5+
6+
@if(_interactive)
7+
{
8+
<p id="interactive">Rendered interactive.</p>
9+
}
10+
11+
@code {
12+
private bool _interactive;
13+
14+
[Parameter] public string Parameter { get; set; }
15+
16+
protected override void OnAfterRender(bool firstRender)
17+
{
18+
if (firstRender)
19+
{
20+
_interactive = true;
21+
StateHasChanged();
22+
}
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@page "/routing/constrained-catch-all/{*parameter:length(2,4)}"
2+
<h3>Parameters</h3>
3+
4+
<p id="parameter-value">@Parameter</p>
5+
6+
@if (_interactive)
7+
{
8+
<p id="interactive">Rendered interactive.</p>
9+
}
10+
11+
@code {
12+
private bool _interactive;
13+
14+
[Parameter] public string Parameter { get; set; }
15+
16+
protected override void OnAfterRender(bool firstRender)
17+
{
18+
if (firstRender)
19+
{
20+
_interactive = true;
21+
StateHasChanged();
22+
}
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@page "/routing/constraints/{parameter:int}"
2+
<h3>Constrained Parameters</h3>
3+
4+
<p id="parameter-value">@Parameter</p>
5+
6+
@if (_interactive)
7+
{
8+
<p id="interactive">Rendered interactive.</p>
9+
}
10+
11+
@code {
12+
private bool _interactive;
13+
14+
[Parameter] public int Parameter { get; set; }
15+
16+
protected override void OnAfterRender(bool firstRender)
17+
{
18+
if (firstRender)
19+
{
20+
_interactive = true;
21+
StateHasChanged();
22+
}
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@page "/routing/defaults/{parameter=default}";
2+
<h3>Default Parameters</h3>
3+
4+
<p id="parameter-value">@Parameter</p>
5+
6+
@if (_interactive)
7+
{
8+
<p id="interactive">Rendered interactive.</p>
9+
}
10+
11+
@code {
12+
private bool _interactive;
13+
14+
[Parameter] public string Parameter { get; set; }
15+
16+
protected override void OnAfterRender(bool firstRender)
17+
{
18+
if (firstRender)
19+
{
20+
_interactive = true;
21+
StateHasChanged();
22+
}
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@page "/routing/optional/{parameter?}"
2+
<h3>Optional Parameters</h3>
3+
4+
<p id="parameter-value">@(Parameter ?? "null")</p>
5+
6+
@if (_interactive)
7+
{
8+
<p id="interactive">Rendered interactive.</p>
9+
}
10+
11+
@code {
12+
private bool _interactive;
13+
14+
[Parameter] public string Parameter { get; set; }
15+
16+
protected override void OnAfterRender(bool firstRender)
17+
{
18+
if (firstRender)
19+
{
20+
_interactive = true;
21+
StateHasChanged();
22+
}
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@page "/routing/parameters/{parameter}"
2+
<h3>Parameters</h3>
3+
4+
<p id="parameter-value">@Parameter</p>
5+
6+
@if (_interactive)
7+
{
8+
<p id="interactive">Rendered interactive.</p>
9+
}
10+
11+
@code {
12+
private bool _interactive;
13+
14+
[Parameter] public string Parameter { get; set; }
15+
16+
protected override void OnAfterRender(bool firstRender)
17+
{
18+
if (firstRender)
19+
{
20+
_interactive = true;
21+
StateHasChanged();
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)