Skip to content

Commit effc7cb

Browse files
committed
WebRootComponentManager tests
1 parent 21597ea commit effc7cb

File tree

2 files changed

+247
-2
lines changed

2 files changed

+247
-2
lines changed

src/Components/Server/test/Circuits/RemoteRendererTest.cs

Lines changed: 246 additions & 1 deletion
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.Diagnostics;
5+
using System.Globalization;
56
using System.Text.Json;
67
using Microsoft.AspNetCore.Components.Endpoints;
78
using Microsoft.AspNetCore.Components.Rendering;
@@ -24,6 +25,8 @@ public class RemoteRendererTest
2425
// failures.
2526
private static readonly TimeSpan Timeout = Debugger.IsAttached ? System.Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(10);
2627

28+
private const int MaxInteractiveServerRootComponentCount = 3;
29+
2730
private readonly IDataProtectionProvider _ephemeralDataProtectionProvider = new EphemeralDataProtectionProvider();
2831

2932
[Fact]
@@ -425,6 +428,210 @@ await renderer.Dispatcher.InvokeAsync(() => renderer.RenderComponentAsync<AutoPa
425428
exception.Message);
426429
}
427430

431+
[Fact]
432+
public async Task WebRootComponentManager_AddRootComponentAsync_Throws_IfMaxInteractiveServerComponentCountIsExceeded()
433+
{
434+
// Arrange
435+
var serviceProvider = CreateServiceProvider();
436+
var renderer = GetRemoteRenderer(serviceProvider);
437+
438+
// Act
439+
for (var i = 0; i < MaxInteractiveServerRootComponentCount; i++)
440+
{
441+
await AddWebRootComponentAsync(renderer, i);
442+
}
443+
444+
// Assert
445+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => AddWebRootComponentAsync(renderer, MaxInteractiveServerRootComponentCount));
446+
447+
Assert.Equal("Exceeded the maximum number of allowed server interactive root components.", ex.Message);
448+
}
449+
450+
[Fact]
451+
public async Task WebRootComponentManager_AddRootComponentAsync_Throws_IfDuplicateSsrComponentIdIsProvided()
452+
{
453+
// Arrange
454+
var serviceProvider = CreateServiceProvider();
455+
var renderer = GetRemoteRenderer(serviceProvider);
456+
457+
// Act
458+
await AddWebRootComponentAsync(renderer, 0);
459+
460+
// Assert
461+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => AddWebRootComponentAsync(renderer, 0));
462+
463+
Assert.Equal("A root component with SSR component ID 0 already exists.", ex.Message);
464+
}
465+
466+
[Fact]
467+
public async Task WebRootComponentManager_AddRootComponentAsync_Throws_IfKeyIsMalformed()
468+
{
469+
// Arrange
470+
var serviceProvider = CreateServiceProvider();
471+
var renderer = GetRemoteRenderer(serviceProvider);
472+
473+
// Act/assert
474+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
475+
{
476+
var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager();
477+
await webRootComponentManager.AddRootComponentAsync(
478+
0,
479+
typeof(TestComponent),
480+
"malformed-key",
481+
WebRootComponentParameters.Empty);
482+
});
483+
484+
Assert.Equal("The key 'malformed-key' had an invalid format.", ex.Message);
485+
}
486+
487+
[Fact]
488+
public async Task WebRootComponentManager_AddRootComponentAsync_CanAddAndRenderRootComponent()
489+
{
490+
// Arrange
491+
var serviceProvider = CreateServiceProvider();
492+
var renderer = GetRemoteRenderer(serviceProvider);
493+
494+
// Act
495+
await AddWebRootComponentAsync(renderer, 0);
496+
497+
// Assert
498+
Assert.Single(renderer._unacknowledgedRenderBatches);
499+
}
500+
501+
[Fact]
502+
public async Task WebRootComponentManager_UpdateRootComponentAsync_Throws_IfSsrComponentIdIsInvalid()
503+
{
504+
// Arrange
505+
var serviceProvider = CreateServiceProvider();
506+
var renderer = GetRemoteRenderer(serviceProvider);
507+
508+
// Act
509+
var key = await AddWebRootComponentAsync(renderer, 0);
510+
511+
// Assert
512+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
513+
{
514+
var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager();
515+
await webRootComponentManager.UpdateRootComponentAsync(1, key.ToString(), WebRootComponentParameters.Empty);
516+
});
517+
518+
Assert.Equal($"No root component exists with SSR component ID 1.", ex.Message);
519+
}
520+
521+
[Fact]
522+
public async Task WebRootComponentManager_UpdateRootComponentAsync_Throws_IfKeyDoesNotMatch()
523+
{
524+
// Arrange
525+
var serviceProvider = CreateServiceProvider();
526+
var renderer = GetRemoteRenderer(serviceProvider);
527+
528+
// Act
529+
await AddWebRootComponentAsync(renderer, 0);
530+
531+
// Assert
532+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
533+
{
534+
var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager();
535+
await webRootComponentManager.UpdateRootComponentAsync(0, "invalid-key", WebRootComponentParameters.Empty);
536+
});
537+
538+
Assert.Equal("Cannot update components with mismatching keys.", ex.Message);
539+
}
540+
541+
[Fact]
542+
public async Task WebRootComponentManager_UpdateRootComponentAsync_Works_IfComponentKeyWasSupplied()
543+
{
544+
// Arrange
545+
var serviceProvider = CreateServiceProvider();
546+
var renderer = GetRemoteRenderer(serviceProvider);
547+
548+
// Act
549+
var key = await AddWebRootComponentAsync(renderer, 0, "mykey");
550+
await renderer.Dispatcher.InvokeAsync(() =>
551+
{
552+
var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager();
553+
var parameters = new Dictionary<string, object> { ["Name"] = "value" };
554+
webRootComponentManager.UpdateRootComponentAsync(0, key.ToString(), CreateWebRootComponentParameters(parameters));
555+
});
556+
557+
// Assert
558+
Assert.Equal(2, renderer._unacknowledgedRenderBatches.Count); // Initial render, re-render
559+
}
560+
561+
[Fact]
562+
public async Task WebRootComponentManager_UpdateRootComponentAsync_DoesNothing_IfNoComponentKeyWasSuppliedAndParametersDidNotChange()
563+
{
564+
// Arrange
565+
var serviceProvider = CreateServiceProvider();
566+
var renderer = GetRemoteRenderer(serviceProvider);
567+
568+
// Act
569+
var key = await AddWebRootComponentAsync(renderer, 0);
570+
await renderer.Dispatcher.InvokeAsync(() =>
571+
{
572+
var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager();
573+
webRootComponentManager.UpdateRootComponentAsync(0, key.ToString(), WebRootComponentParameters.Empty);
574+
});
575+
576+
// Assert
577+
Assert.Single(renderer._unacknowledgedRenderBatches);
578+
}
579+
580+
[Fact]
581+
public async Task WebRootComponentManager_UpdateRootComponentAsync_ReinitializesComponent_IfNoComponentKeyWasSuppliedAndParameterChanged()
582+
{
583+
// Arrange
584+
var serviceProvider = CreateServiceProvider();
585+
var renderer = GetRemoteRenderer(serviceProvider);
586+
587+
// Act
588+
var key = await AddWebRootComponentAsync(renderer, 0);
589+
await renderer.Dispatcher.InvokeAsync(() =>
590+
{
591+
var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager();
592+
var parameters = new Dictionary<string, object> { ["Name"] = "value" };
593+
webRootComponentManager.UpdateRootComponentAsync(0, key.ToString(), CreateWebRootComponentParameters(parameters));
594+
});
595+
596+
// Assert
597+
Assert.Equal(3, renderer._unacknowledgedRenderBatches.Count); // Initial render, dispose, and re-initialize
598+
}
599+
600+
[Fact]
601+
public async Task WebRootComponentManager_RemoveRootComponent_Throws_IfSsrComponentIdIsInvalid()
602+
{
603+
// Arrange
604+
var serviceProvider = CreateServiceProvider();
605+
var renderer = GetRemoteRenderer(serviceProvider);
606+
607+
// Act
608+
var key = await AddWebRootComponentAsync(renderer, 0);
609+
610+
// Assert
611+
var ex = Assert.Throws<InvalidOperationException>(() => renderer.GetOrCreateWebRootComponentManager().RemoveRootComponent(1));
612+
613+
Assert.Equal($"No root component exists with SSR component ID 1.", ex.Message);
614+
}
615+
616+
[Fact]
617+
public async Task WebRootComponentManager_RemoveRootComponent_Works()
618+
{
619+
// Arrange
620+
var serviceProvider = CreateServiceProvider();
621+
var renderer = GetRemoteRenderer(serviceProvider);
622+
623+
// Act
624+
var key = await AddWebRootComponentAsync(renderer, 0);
625+
await renderer.Dispatcher.InvokeAsync(() =>
626+
{
627+
var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager();
628+
webRootComponentManager.RemoveRootComponent(0);
629+
});
630+
631+
// Assert
632+
Assert.Equal(2, renderer._unacknowledgedRenderBatches.Count); // Initial render, dispose
633+
}
634+
428635
private IServiceProvider CreateServiceProvider()
429636
{
430637
var serviceCollection = new ServiceCollection();
@@ -445,12 +652,50 @@ private TestRemoteRenderer GetRemoteRenderer(IServiceProvider serviceProvider, C
445652
return new TestRemoteRenderer(
446653
serviceProvider,
447654
NullLoggerFactory.Instance,
448-
new CircuitOptions(),
655+
new CircuitOptions
656+
{
657+
RootComponents =
658+
{
659+
MaxInteractiveServerRootComponentCount = MaxInteractiveServerRootComponentCount
660+
},
661+
},
449662
circuitClient ?? new CircuitClientProxy(),
450663
serverComponentDeserializer,
451664
NullLogger.Instance);
452665
}
453666

667+
private static Task<BoundaryMarkerKey> AddWebRootComponentAsync(RemoteRenderer renderer, int ssrComponentId, string componentKey = null)
668+
=> renderer.Dispatcher.InvokeAsync(async () =>
669+
{
670+
var webRootComponentManager = renderer.GetOrCreateWebRootComponentManager();
671+
var boundaryMarkerKey = new BoundaryMarkerKey(
672+
nameof(TestComponent).AsMemory(),
673+
ssrComponentId.ToString(CultureInfo.CurrentCulture).AsMemory(),
674+
componentKey?.AsMemory() ?? ReadOnlyMemory<char>.Empty);
675+
await webRootComponentManager.AddRootComponentAsync(
676+
ssrComponentId,
677+
typeof(TestComponent),
678+
boundaryMarkerKey.ToString(),
679+
WebRootComponentParameters.Empty);
680+
return boundaryMarkerKey;
681+
});
682+
683+
private static WebRootComponentParameters CreateWebRootComponentParameters(IDictionary<string, object> parameters)
684+
{
685+
var parameterView = ParameterView.FromDictionary(parameters);
686+
var (parameterDefinitions, parameterValues) = ComponentParameter.FromParameterView(parameterView);
687+
for (var i = 0; i < parameterValues.Count; i++)
688+
{
689+
// WebRootComponentParameters expects serialized parameter values to be JsonElements.
690+
var jsonElement = JsonSerializer.SerializeToElement(parameterValues[i]);
691+
parameterValues[i] = jsonElement;
692+
}
693+
return new WebRootComponentParameters(
694+
parameterView,
695+
parameterDefinitions.AsReadOnly(),
696+
parameterValues.AsReadOnly());
697+
}
698+
454699
private class TestRemoteRenderer : RemoteRenderer
455700
{
456701
public TestRemoteRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, CircuitOptions options, CircuitClientProxy client, IServerComponentDeserializer serverComponentDeserializer, ILogger logger)

src/Components/Shared/src/WebRootComponentManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public static async Task<WebRootComponent> CreateAndRenderAsync(
101101
{
102102
if (!BoundaryMarkerKey.TryParse(key.AsMemory(), out var boundaryMarkerKey))
103103
{
104-
throw new InvalidOperationException($"The boundary marker key '{boundaryMarkerKey}' had an invalid format.");
104+
throw new InvalidOperationException($"The key '{key}' had an invalid format.");
105105
}
106106

107107
var ssrComponentIdString = ssrComponentId.ToString(CultureInfo.InvariantCulture);

0 commit comments

Comments
 (0)