Skip to content

Commit 38e7d0c

Browse files
authored
[Blazor] Add test coverage for prerendering closed generic components (#64643)
1 parent 63b69a2 commit 38e7d0c

File tree

3 files changed

+210
-0
lines changed

3 files changed

+210
-0
lines changed

src/Components/Endpoints/test/EndpointHtmlRendererTest.cs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,133 @@ public async Task CanPrerender_ComponentWithNullParameters_ServerPrerenderedMode
718718
Assert.Null(epilogueMarker.Type);
719719
}
720720

721+
[Fact]
722+
public async Task CanRender_ClosedGenericComponent()
723+
{
724+
// Arrange
725+
var httpContext = GetHttpContext();
726+
var writer = new StringWriter();
727+
728+
// Act
729+
var parameters = ParameterView.FromDictionary(new Dictionary<string, object> { { "Value", 42 } });
730+
var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GenericComponent<int>), null, parameters);
731+
await renderer.Dispatcher.InvokeAsync(() => result.WriteTo(writer, HtmlEncoder.Default));
732+
var content = writer.ToString();
733+
734+
// Assert
735+
Assert.Equal("<p>Generic value: 42</p>", content);
736+
}
737+
738+
[Fact]
739+
public async Task CanRender_ClosedGenericComponent_ServerMode()
740+
{
741+
// Arrange
742+
var httpContext = GetHttpContext();
743+
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
744+
.ToTimeLimitedDataProtector();
745+
746+
// Act
747+
var parameters = ParameterView.FromDictionary(new Dictionary<string, object> { { "Value", "TestString" } });
748+
var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GenericComponent<string>), new InteractiveServerRenderMode(false), parameters);
749+
var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentToString(result));
750+
var match = Regex.Match(content, ComponentPattern);
751+
752+
// Assert
753+
Assert.True(match.Success);
754+
var marker = JsonSerializer.Deserialize<ComponentMarker>(match.Groups[1].Value, ServerComponentSerializationSettings.JsonSerializationOptions);
755+
Assert.Equal(0, marker.Sequence);
756+
Assert.Null(marker.PrerenderId);
757+
Assert.NotNull(marker.Descriptor);
758+
Assert.Equal("server", marker.Type);
759+
760+
var unprotectedServerComponent = protector.Unprotect(marker.Descriptor);
761+
var serverComponent = JsonSerializer.Deserialize<ServerComponent>(unprotectedServerComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
762+
Assert.Equal(0, serverComponent.Sequence);
763+
Assert.Equal(typeof(GenericComponent<string>).Assembly.GetName().Name, serverComponent.AssemblyName);
764+
Assert.Equal(typeof(GenericComponent<string>).FullName, serverComponent.TypeName);
765+
Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);
766+
767+
var parameterDefinition = Assert.Single(serverComponent.ParameterDefinitions);
768+
Assert.Equal("Value", parameterDefinition.Name);
769+
Assert.Equal("System.String", parameterDefinition.TypeName);
770+
Assert.Equal("System.Private.CoreLib", parameterDefinition.Assembly);
771+
772+
var value = Assert.Single(serverComponent.ParameterValues);
773+
var rawValue = Assert.IsType<JsonElement>(value);
774+
Assert.Equal("TestString", rawValue.GetString());
775+
}
776+
777+
[Fact]
778+
public async Task CanPrerender_ClosedGenericComponent_ServerMode()
779+
{
780+
// Arrange
781+
var httpContext = GetHttpContext();
782+
var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
783+
.ToTimeLimitedDataProtector();
784+
785+
// Act
786+
var parameters = ParameterView.FromDictionary(new Dictionary<string, object> { { "Value", 123 } });
787+
var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GenericComponent<int>), RenderMode.InteractiveServer, parameters);
788+
var content = await renderer.Dispatcher.InvokeAsync(() => HtmlContentToString(result));
789+
var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);
790+
791+
// Assert
792+
Assert.True(match.Success);
793+
var preamble = match.Groups["preamble"].Value;
794+
var preambleMarker = JsonSerializer.Deserialize<ComponentMarker>(preamble, ServerComponentSerializationSettings.JsonSerializationOptions);
795+
Assert.Equal(0, preambleMarker.Sequence);
796+
Assert.NotNull(preambleMarker.PrerenderId);
797+
Assert.NotNull(preambleMarker.Descriptor);
798+
Assert.Equal("server", preambleMarker.Type);
799+
800+
var unprotectedServerComponent = protector.Unprotect(preambleMarker.Descriptor);
801+
var serverComponent = JsonSerializer.Deserialize<ServerComponent>(unprotectedServerComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
802+
Assert.NotEqual(default, serverComponent);
803+
Assert.Equal(0, serverComponent.Sequence);
804+
Assert.Equal(typeof(GenericComponent<int>).Assembly.GetName().Name, serverComponent.AssemblyName);
805+
Assert.Equal(typeof(GenericComponent<int>).FullName, serverComponent.TypeName);
806+
Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);
807+
808+
var prerenderedContent = match.Groups["content"].Value;
809+
Assert.Equal("<p>Generic value: 123</p>", prerenderedContent);
810+
811+
var epilogue = match.Groups["epilogue"].Value;
812+
var epilogueMarker = JsonSerializer.Deserialize<ComponentMarker>(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
813+
Assert.Equal(preambleMarker.PrerenderId, epilogueMarker.PrerenderId);
814+
}
815+
816+
[Fact]
817+
public async Task CanPrerender_ClosedGenericComponent_ClientMode()
818+
{
819+
// Arrange
820+
var httpContext = GetHttpContext();
821+
var writer = new StringWriter();
822+
823+
// Act
824+
var parameters = ParameterView.FromDictionary(new Dictionary<string, object> { { "Value", 456 } });
825+
var result = await renderer.PrerenderComponentAsync(httpContext, typeof(GenericComponent<int>), RenderMode.InteractiveWebAssembly, parameters);
826+
await renderer.Dispatcher.InvokeAsync(() => result.WriteTo(writer, HtmlEncoder.Default));
827+
var content = writer.ToString();
828+
content = AssertAndStripWebAssemblyOptions(content);
829+
var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);
830+
831+
// Assert
832+
Assert.True(match.Success);
833+
var preamble = match.Groups["preamble"].Value;
834+
var preambleMarker = JsonSerializer.Deserialize<ComponentMarker>(preamble, ServerComponentSerializationSettings.JsonSerializationOptions);
835+
Assert.NotNull(preambleMarker.PrerenderId);
836+
Assert.Equal("webassembly", preambleMarker.Type);
837+
Assert.Equal(typeof(GenericComponent<int>).Assembly.GetName().Name, preambleMarker.Assembly);
838+
Assert.Equal(typeof(GenericComponent<int>).FullName, preambleMarker.TypeName);
839+
840+
var prerenderedContent = match.Groups["content"].Value;
841+
Assert.Equal("<p>Generic value: 456</p>", prerenderedContent);
842+
843+
var epilogue = match.Groups["epilogue"].Value;
844+
var epilogueMarker = JsonSerializer.Deserialize<ComponentMarker>(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
845+
Assert.Equal(preambleMarker.PrerenderId, epilogueMarker.PrerenderId);
846+
}
847+
721848
[Fact]
722849
public async Task ComponentWithInvalidRenderMode_Throws()
723850
{
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@typeparam TValue
2+
3+
<p>Generic value: @(Value?.ToString() ?? "(null)")</p>
4+
@code {
5+
[Parameter] public TValue Value { get; set; }
6+
}

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

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Globalization;
45
using System.Text.Json;
56
using Microsoft.AspNetCore.Components.Endpoints;
67
using Microsoft.AspNetCore.DataProtection;
@@ -75,6 +76,74 @@ public void CanParseSingleMarkerWithNullParameters()
7576
Assert.Null(parameters["Parameter"]);
7677
}
7778

79+
[Fact]
80+
public void CanParseSingleMarkerForClosedGenericComponent()
81+
{
82+
// Arrange
83+
var markers = SerializeMarkers(CreateMarkers(typeof(GenericTestComponent<int>)));
84+
var serverComponentDeserializer = CreateServerComponentDeserializer();
85+
86+
// Act & assert
87+
Assert.True(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors));
88+
var deserializedDescriptor = Assert.Single(descriptors);
89+
Assert.Equal(typeof(GenericTestComponent<int>).FullName, deserializedDescriptor.ComponentType.FullName);
90+
Assert.Equal(0, deserializedDescriptor.Sequence);
91+
}
92+
93+
[Fact]
94+
public void CanParseSingleMarkerForClosedGenericComponentWithStringTypeParameter()
95+
{
96+
// Arrange
97+
var markers = SerializeMarkers(CreateMarkers(typeof(GenericTestComponent<string>)));
98+
var serverComponentDeserializer = CreateServerComponentDeserializer();
99+
100+
// Act & assert
101+
Assert.True(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors));
102+
var deserializedDescriptor = Assert.Single(descriptors);
103+
Assert.Equal(typeof(GenericTestComponent<string>).FullName, deserializedDescriptor.ComponentType.FullName);
104+
Assert.Equal(0, deserializedDescriptor.Sequence);
105+
}
106+
107+
[Fact]
108+
public void CanParseSingleMarkerForClosedGenericComponentWithParameters()
109+
{
110+
// Arrange
111+
var markers = SerializeMarkers(CreateMarkers(
112+
(typeof(GenericTestComponent<int>), new Dictionary<string, object> { ["Value"] = 42 })));
113+
var serverComponentDeserializer = CreateServerComponentDeserializer();
114+
115+
// Act & assert
116+
Assert.True(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors));
117+
var deserializedDescriptor = Assert.Single(descriptors);
118+
Assert.Equal(typeof(GenericTestComponent<int>).FullName, deserializedDescriptor.ComponentType.FullName);
119+
Assert.Equal(0, deserializedDescriptor.Sequence);
120+
121+
var parameters = deserializedDescriptor.Parameters.ToDictionary();
122+
Assert.Single(parameters);
123+
Assert.Contains("Value", parameters.Keys);
124+
Assert.Equal(42, Convert.ToInt32(parameters["Value"]!, CultureInfo.InvariantCulture));
125+
}
126+
127+
[Fact]
128+
public void CanParseMultipleMarkersForClosedGenericComponents()
129+
{
130+
// Arrange
131+
var markers = SerializeMarkers(CreateMarkers(typeof(GenericTestComponent<int>), typeof(GenericTestComponent<string>)));
132+
var serverComponentDeserializer = CreateServerComponentDeserializer();
133+
134+
// Act & assert
135+
Assert.True(serverComponentDeserializer.TryDeserializeComponentDescriptorCollection(markers, out var descriptors));
136+
Assert.Equal(2, descriptors.Count);
137+
138+
var firstDescriptor = descriptors[0];
139+
Assert.Equal(typeof(GenericTestComponent<int>).FullName, firstDescriptor.ComponentType.FullName);
140+
Assert.Equal(0, firstDescriptor.Sequence);
141+
142+
var secondDescriptor = descriptors[1];
143+
Assert.Equal(typeof(GenericTestComponent<string>).FullName, secondDescriptor.ComponentType.FullName);
144+
Assert.Equal(1, secondDescriptor.Sequence);
145+
}
146+
78147
[Fact]
79148
public void CanParseMultipleMarkers()
80149
{
@@ -517,4 +586,12 @@ private class DynamicallyAddedComponent : IComponent
517586
public void Attach(RenderHandle renderHandle) => throw new NotImplementedException();
518587
public Task SetParametersAsync(ParameterView parameters) => throw new NotImplementedException();
519588
}
589+
590+
private class GenericTestComponent<T> : IComponent
591+
{
592+
[Parameter] public T Value { get; set; }
593+
594+
public void Attach(RenderHandle renderHandle) => throw new NotImplementedException();
595+
public Task SetParametersAsync(ParameterView parameters) => throw new NotImplementedException();
596+
}
520597
}

0 commit comments

Comments
 (0)