diff --git a/src/Components/Server/src/Circuits/CircuitFactory.cs b/src/Components/Server/src/Circuits/CircuitFactory.cs index 0c086f054c03..a9aefb9fbfc9 100644 --- a/src/Components/Server/src/Circuits/CircuitFactory.cs +++ b/src/Components/Server/src/Circuits/CircuitFactory.cs @@ -93,6 +93,7 @@ public async ValueTask CreateCircuitHostAsync( resourceCollection); circuitActivitySource.Init(new Infrastructure.Server.ComponentsActivityLinkStore(renderer)); + renderer.GetOrCreateWebRootComponentManager(); // In Blazor Server we have already restored the app state, so we can get the handlers from DI. // In Blazor Web the state is provided in the first call to UpdateRootComponents, so we need to diff --git a/src/Components/test/E2ETest/Tests/StatePersistanceJSRootTest.cs b/src/Components/test/E2ETest/Tests/StatePersistanceJSRootTest.cs new file mode 100644 index 000000000000..9c554fccab79 --- /dev/null +++ b/src/Components/test/E2ETest/Tests/StatePersistanceJSRootTest.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Components.TestServer.RazorComponents; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using TestServer; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETests.Tests; + +// These tests are for Blazor Web implementation +// For Blazor Server and Webassembly, check SaveStateTest.cs +public class StatePersistanceJSRootTest : ServerTestBase>> +{ + public StatePersistanceJSRootTest( + BrowserFixture browserFixture, + BasicTestAppServerSiteFixture> serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + serverFixture.AdditionalArguments.AddRange("--RegisterDynamicJSRootComponent", "true"); + } + + [Theory] + [InlineData("ServerNonPrerendered")] + [InlineData("WebAssemblyNonPrerendered")] + public void PersistentStateIsSupportedInDynamicJSRoots(string renderMode) + { + Navigate($"subdir/WasmMinimal/dynamic-js-root.html?renderMode={renderMode}"); + + Browser.Equal("Counter", () => Browser.Exists(By.TagName("h1")).Text); + Browser.Equal("Current count: 0", () => Browser.Exists(By.CssSelector("p[role='status']")).Text); + + Browser.Click(By.CssSelector("button.btn-primary")); + Browser.Equal("Current count: 1", () => Browser.Exists(By.CssSelector("p[role='status']")).Text); + } +} diff --git a/src/Components/test/testassets/Components.TestServer/Program.cs b/src/Components/test/testassets/Components.TestServer/Program.cs index 568f809454a7..58e39b5d11c8 100644 --- a/src/Components/test/testassets/Components.TestServer/Program.cs +++ b/src/Components/test/testassets/Components.TestServer/Program.cs @@ -23,7 +23,8 @@ public static async Task Main(string[] args) ["Server authentication"] = (BuildWebHost(CreateAdditionalArgs(args)), "/subdir"), ["CORS (WASM)"] = (BuildWebHost(CreateAdditionalArgs(args)), "/subdir"), ["Prerendering (Server-side)"] = (BuildWebHost(CreateAdditionalArgs(args)), "/prerendered"), - ["Razor Component Endpoints"] = (BuildWebHost>(CreateAdditionalArgs(args)), "/subdir"), + ["Razor Component Endpoints"] = (BuildWebHost>(CreateAdditionalArgs(args)), "/subdir"), + ["Razor Component Endpoints with JS Root Component"] = (BuildWebHost>(CreateAdditionalArgs([.. args, "--RegisterDynamicJSRootComponent", "true"])), "/subdir"), ["Deferred component content (Server-side)"] = (BuildWebHost(CreateAdditionalArgs(args)), "/deferred-component-content"), ["Locked navigation (Server-side)"] = (BuildWebHost(CreateAdditionalArgs(args)), "/locked-navigation"), ["Client-side with fallback"] = (BuildWebHost(CreateAdditionalArgs(args)), "/fallback"), diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs b/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs index 861e0fbdf288..986d4c391fd0 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs +++ b/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs @@ -7,6 +7,7 @@ using System.Web; using Components.TestServer.RazorComponents; using Components.TestServer.RazorComponents.Pages.Forms; +using Components.TestServer.RazorComponents.Pages.PersistentState; using Components.TestServer.Services; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Server.Circuits; @@ -50,6 +51,10 @@ public void ConfigureServices(IServiceCollection services) options.DisconnectedCircuitMaxRetained = 0; options.DetailedErrors = true; } + if (Configuration.GetValue("RegisterDynamicJSRootComponent")) + { + options.RootComponents.RegisterForJavaScript("dynamic-js-root-counter"); + } }) .AddAuthenticationStateSerialization(options => { diff --git a/src/Components/test/testassets/Components.WasmMinimal/Pages/CounterWithPersistentState.razor b/src/Components/test/testassets/Components.WasmMinimal/Pages/CounterWithPersistentState.razor new file mode 100644 index 000000000000..f44a4a80f1ac --- /dev/null +++ b/src/Components/test/testassets/Components.WasmMinimal/Pages/CounterWithPersistentState.razor @@ -0,0 +1,23 @@ +@page "/persistent-state/counter" + + + + +@code{ + [Parameter, SupplyParameterFromQuery(Name = "renderMode")] + public string? RenderModeStr { get; set; } + + private RenderModeId _renderMode; + + protected override void OnInitialized() + { + if (!string.IsNullOrEmpty(RenderModeStr)) + { + _renderMode = RenderModeHelper.ParseRenderMode(RenderModeStr); + } + else + { + throw new ArgumentException("RenderModeStr cannot be null or empty", nameof(RenderModeStr)); + } + } +} \ No newline at end of file diff --git a/src/Components/test/testassets/Components.WasmMinimal/wwwroot/dynamic-js-root.html b/src/Components/test/testassets/Components.WasmMinimal/wwwroot/dynamic-js-root.html new file mode 100644 index 000000000000..284a4d151a11 --- /dev/null +++ b/src/Components/test/testassets/Components.WasmMinimal/wwwroot/dynamic-js-root.html @@ -0,0 +1,16 @@ + + + + + +
+
+ + + + + diff --git a/src/Components/test/testassets/TestContentPackage/PersistentComponents/ComponentWithPersistentState.razor b/src/Components/test/testassets/TestContentPackage/PersistentComponents/ComponentWithPersistentState.razor new file mode 100644 index 000000000000..d26ae13fc390 --- /dev/null +++ b/src/Components/test/testassets/TestContentPackage/PersistentComponents/ComponentWithPersistentState.razor @@ -0,0 +1,16 @@ +Counter + +

Counter

+ +

Current count: @currentCount

+ + + +@code { + [PersistentState] public int currentCount { get; set; } + + private void IncrementCount() + { + currentCount++; + } +}