Skip to content

Commit 197a3d1

Browse files
authored
Replace NoHotReloadListenersAreOrdinarilyRegistered with unit tests (#37768)
Fixes #35449
1 parent 3d8215e commit 197a3d1

File tree

12 files changed

+87
-69
lines changed

12 files changed

+87
-69
lines changed

src/Components/Components/src/HotReload/HotReloadManager.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,22 @@
88

99
namespace Microsoft.AspNetCore.Components.HotReload
1010
{
11-
internal static class HotReloadManager
11+
internal sealed class HotReloadManager
1212
{
13-
public static event Action? OnDeltaApplied;
13+
public static readonly HotReloadManager Default = new();
14+
15+
public bool MetadataUpdateSupported { get; set; } = MetadataUpdater.IsSupported;
1416

1517
/// <summary>
1618
/// Gets a value that determines if OnDeltaApplied is subscribed to.
1719
/// </summary>
18-
public static bool IsSubscribedTo => OnDeltaApplied is not null;
20+
public bool IsSubscribedTo => OnDeltaApplied is not null;
21+
22+
public event Action? OnDeltaApplied;
1923

2024
/// <summary>
2125
/// MetadataUpdateHandler event. This is invoked by the hot reload host via reflection.
2226
/// </summary>
23-
public static void UpdateApplication(Type[]? _) => OnDeltaApplied?.Invoke();
27+
public static void UpdateApplication(Type[]? _) => Default.OnDeltaApplied?.Invoke();
2428
}
2529
}

src/Components/Components/src/HotReload/TestableMetadataUpdate.cs

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<linker>
22
<assembly fullname="Microsoft.AspNetCore.Components">
3-
<type fullname="Microsoft.AspNetCore.Components.HotReload.TestableMetadataUpdate" feature="System.Reflection.Metadata.MetadataUpdater.IsSupported" featurevalue="false">
4-
<method signature="System.Boolean get_IsSupported()" body="stub" value="false" />
3+
<type fullname="Microsoft.AspNetCore.Components.HotReload.HotReloadManager" feature="System.Reflection.Metadata.MetadataUpdater.IsSupported" featurevalue="false">
4+
<method signature="System.Boolean get_MetadataUpdateSupported()" body="stub" value="false" />
55
</type>
66
</assembly>
77
</linker>

src/Components/Components/src/RenderHandle.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public Dispatcher Dispatcher
4747
/// <summary>
4848
/// Gets a value that determines if the <see cref="Renderer"/> is triggering a render in response to a metadata update (hot-reload) change.
4949
/// </summary>
50-
public bool IsRenderingOnMetadataUpdate => TestableMetadataUpdate.IsSupported && (_renderer?.IsRenderingOnMetadataUpdate ?? false);
50+
public bool IsRenderingOnMetadataUpdate => HotReloadManager.Default.MetadataUpdateSupported && (_renderer?.IsRenderingOnMetadataUpdate ?? false);
5151

5252
internal bool IsRendererDisposed => _renderer?.Disposed
5353
?? throw new InvalidOperationException("No renderer has been initialized.");

src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ private static void UpdateRetainedChildComponent(
542542
var oldParameters = new ParameterView(ParameterViewLifetime.Unbound, oldTree, oldComponentIndex);
543543
var newParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder);
544544
var newParameters = new ParameterView(newParametersLifetime, newTree, newComponentIndex);
545-
if (!newParameters.DefinitelyEquals(oldParameters) || (TestableMetadataUpdate.IsSupported && diffContext.Renderer.IsRenderingOnMetadataUpdate))
545+
if (!newParameters.DefinitelyEquals(oldParameters) || (HotReloadManager.Default.MetadataUpdateSupported && diffContext.Renderer.IsRenderingOnMetadataUpdate))
546546
{
547547
componentState.SetDirectParameters(newParameters);
548548
}

src/Components/Components/src/RenderTree/Renderer.cs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,8 @@
33

44
#nullable disable warnings
55

6-
using System;
7-
using System.Collections.Generic;
86
using System.Diagnostics;
97
using System.Diagnostics.CodeAnalysis;
10-
using System.Threading;
11-
using System.Threading.Tasks;
128
using Microsoft.AspNetCore.Components.HotReload;
139
using Microsoft.AspNetCore.Components.Reflection;
1410
using Microsoft.AspNetCore.Components.Rendering;
@@ -45,6 +41,8 @@ public abstract partial class Renderer : IDisposable, IAsyncDisposable
4541
private Task? _disposeTask;
4642
private bool _rendererIsDisposed;
4743

44+
private bool _hotReloadInitialized;
45+
4846
/// <summary>
4947
/// Allows the caller to handle exceptions from the SynchronizationContext when one is available.
5048
/// </summary>
@@ -97,13 +95,11 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory,
9795
_serviceProvider = serviceProvider;
9896
_logger = loggerFactory.CreateLogger<Renderer>();
9997
_componentFactory = new ComponentFactory(componentActivator);
100-
101-
if (TestableMetadataUpdate.IsSupported)
102-
{
103-
HotReloadManager.OnDeltaApplied += RenderRootComponentsOnHotReload;
104-
}
10598
}
10699

100+
internal HotReloadManager HotReloadManager { get; set; } = HotReloadManager.Default;
101+
102+
107103
private static IComponentActivator GetComponentActivatorOrDefault(IServiceProvider serviceProvider)
108104
{
109105
return serviceProvider.GetService<IComponentActivator>()
@@ -179,7 +175,18 @@ protected IComponent InstantiateComponent([DynamicallyAccessedMembers(Component)
179175
/// <returns>The component's assigned identifier.</returns>
180176
// Internal for unit testing
181177
protected internal int AssignRootComponentId(IComponent component)
182-
=> AttachAndInitComponent(component, -1).ComponentId;
178+
{
179+
if (!_hotReloadInitialized)
180+
{
181+
_hotReloadInitialized = true;
182+
if (HotReloadManager.MetadataUpdateSupported)
183+
{
184+
HotReloadManager.OnDeltaApplied += RenderRootComponentsOnHotReload;
185+
}
186+
}
187+
188+
return AttachAndInitComponent(component, -1).ComponentId;
189+
}
183190

184191
/// <summary>
185192
/// Gets the current render tree for a given component.
@@ -232,7 +239,7 @@ protected internal async Task RenderRootComponentAsync(int componentId, Paramete
232239
_pendingTasks ??= new();
233240

234241
var componentState = GetRequiredRootComponentState(componentId);
235-
if (TestableMetadataUpdate.IsSupported)
242+
if (HotReloadManager.MetadataUpdateSupported)
236243
{
237244
// When we're doing hot-reload, stash away the parameters used while rendering root components.
238245
// We'll use this to trigger re-renders on hot reload updates.
@@ -262,7 +269,7 @@ protected internal void RemoveRootComponent(int componentId)
262269
// Currently there's no known scenario where we need to support calling RemoveRootComponentAsync
263270
// during a batch, but if a scenario emerges we can add support.
264271
_batchBuilder.ComponentDisposalQueue.Enqueue(componentId);
265-
if (TestableMetadataUpdate.IsSupported)
272+
if (HotReloadManager.MetadataUpdateSupported)
266273
{
267274
_rootComponentsLatestParameters?.Remove(componentId);
268275
}
@@ -988,7 +995,7 @@ protected virtual void Dispose(bool disposing)
988995

989996
_rendererIsDisposed = true;
990997

991-
if (TestableMetadataUpdate.IsSupported)
998+
if (_hotReloadInitialized && HotReloadManager.MetadataUpdateSupported)
992999
{
9931000
HotReloadManager.OnDeltaApplied -= RenderRootComponentsOnHotReload;
9941001
}

src/Components/Components/src/Routing/Router.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ public void Attach(RenderHandle renderHandle)
9696
_locationAbsolute = NavigationManager.Uri;
9797
NavigationManager.LocationChanged += OnLocationChanged;
9898

99-
if (TestableMetadataUpdate.IsSupported)
99+
if (HotReloadManager.Default.MetadataUpdateSupported)
100100
{
101-
HotReloadManager.OnDeltaApplied += ClearRouteCaches;
101+
HotReloadManager.Default.OnDeltaApplied += ClearRouteCaches;
102102
}
103103
}
104104

@@ -140,9 +140,9 @@ public async Task SetParametersAsync(ParameterView parameters)
140140
public void Dispose()
141141
{
142142
NavigationManager.LocationChanged -= OnLocationChanged;
143-
if (TestableMetadataUpdate.IsSupported)
143+
if (HotReloadManager.Default.MetadataUpdateSupported)
144144
{
145-
HotReloadManager.OnDeltaApplied -= ClearRouteCaches;
145+
HotReloadManager.Default.OnDeltaApplied -= ClearRouteCaches;
146146
}
147147
}
148148

src/Components/Components/test/RendererTest.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Runtime.ExceptionServices;
1111
using System.Threading;
1212
using System.Threading.Tasks;
13+
using Microsoft.AspNetCore.Components.HotReload;
1314
using Microsoft.AspNetCore.Components.Rendering;
1415
using Microsoft.AspNetCore.Components.RenderTree;
1516
using Microsoft.AspNetCore.Components.Test.Helpers;
@@ -4733,6 +4734,53 @@ public async Task DisposeAsyncCallsComponentDisposeAsyncOnSyncContext()
47334734
Assert.True(wasOnSyncContext);
47344735
}
47354736

4737+
[Fact]
4738+
public async Task NoHotReloadListenersAreRegistered_WhenMetadataUpdatesAreNotSupported()
4739+
{
4740+
// Arrange
4741+
await using var renderer = new TestRenderer();
4742+
var hotReloadManager = new HotReloadManager { MetadataUpdateSupported = false };
4743+
renderer.HotReloadManager = hotReloadManager;
4744+
var component = new TestComponent(builder =>
4745+
{
4746+
builder.OpenElement(0, "h2");
4747+
builder.AddContent(1, "some text");
4748+
builder.CloseElement();
4749+
});
4750+
4751+
// Act
4752+
var componentId = renderer.AssignRootComponentId(component);
4753+
component.TriggerRender();
4754+
Assert.False(hotReloadManager.IsSubscribedTo);
4755+
4756+
await renderer.DisposeAsync();
4757+
}
4758+
4759+
[Fact]
4760+
public async Task DisposingRenderer_UnsubsribesFromHotReloadManager()
4761+
{
4762+
// Arrange
4763+
var renderer = new TestRenderer();
4764+
var hotReloadManager = new HotReloadManager { MetadataUpdateSupported = true };
4765+
renderer.HotReloadManager = hotReloadManager;
4766+
var component = new TestComponent(builder =>
4767+
{
4768+
builder.OpenElement(0, "h2");
4769+
builder.AddContent(1, "some text");
4770+
builder.CloseElement();
4771+
});
4772+
4773+
// Act
4774+
var componentId = renderer.AssignRootComponentId(component);
4775+
component.TriggerRender();
4776+
Assert.True(hotReloadManager.IsSubscribedTo);
4777+
4778+
await renderer.DisposeAsync();
4779+
4780+
// Assert
4781+
Assert.False(hotReloadManager.IsSubscribedTo);
4782+
}
4783+
47364784
private class TestComponentActivator<TResult> : IComponentActivator where TResult : IComponent, new()
47374785
{
47384786
public List<Type> RequestedComponentTypes { get; } = new List<Type>();

src/Components/Web/src/JSComponents/JSComponentInterop.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ public class JSComponentInterop
2828

2929
static JSComponentInterop()
3030
{
31-
if (MetadataUpdater.IsSupported)
31+
if (HotReloadManager.Default.MetadataUpdateSupported)
3232
{
33-
HotReloadManager.OnDeltaApplied += () => ParameterTypeCaches.Clear();
33+
HotReloadManager.Default.OnDeltaApplied += () => ParameterTypeCaches.Clear();
3434
}
3535
}
3636

src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -135,28 +135,6 @@ public void CanAccessAuthenticationStateDuringStaticPrerendering(string initialU
135135
Browser.Equal($"Hello, {interactiveUsername ?? "anonymous"}!", () => Browser.Exists(By.TagName("h1")).Text);
136136
}
137137

138-
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/35449")]
139-
public async Task NoHotReloadListenersAreOrdinarilyRegistered()
140-
{
141-
Navigate("/prerendered/prerendered-transition");
142-
143-
// Prerendered output shows "not connected"
144-
Browser.Equal("not connected", () => Browser.Exists(By.Id("connected-state")).Text);
145-
146-
// Once connected, output changes
147-
BeginInteractivity();
148-
Browser.Equal("connected", () => Browser.Exists(By.Id("connected-state")).Text);
149-
150-
// Once connected, output changes
151-
BeginInteractivity();
152-
Browser.Equal("connected", () => Browser.Exists(By.Id("connected-state")).Text);
153-
154-
// Now query the hot reload manager and verify nothing is still wired up by default.
155-
var httpClient = new HttpClient { BaseAddress = _serverFixture.RootUri };
156-
var hasEventHandlers = await httpClient.GetFromJsonAsync<bool>("/prerendered/ishotreloadsubscribedto");
157-
Assert.False(hasEventHandlers);
158-
}
159-
160138
private void BeginInteractivity()
161139
{
162140
Browser.Exists(By.Id("load-boot-script")).Click();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class HotReloadStartup
1414
{
1515
public HotReloadStartup()
1616
{
17-
TestableMetadataUpdate.TestIsSupported = true;
17+
HotReloadManager.Default.MetadataUpdateSupported = true;
1818
}
1919

2020
public void ConfigureServices(IServiceCollection services)

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
4747
app.UseRouting();
4848
app.UseEndpoints(endpoints =>
4949
{
50-
endpoints.MapGet("ishotreloadsubscribedto", () => HotReloadManager.IsSubscribedTo);
51-
5250
endpoints.MapRazorPages();
5351
endpoints.MapFallbackToPage("/PrerenderedHost");
5452
endpoints.MapBlazorHub();

0 commit comments

Comments
 (0)