Skip to content

Commit 1e032b9

Browse files
committed
fix: support passing assigned render mode via parameter builder
1 parent 244680c commit 1e032b9

File tree

7 files changed

+101
-23
lines changed

7 files changed

+101
-23
lines changed

Directory.Build.props

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<ImplicitUsings>enable</ImplicitUsings>
1717
<NoWarn>CA1014,NU5104,NETSDK1138,SYSLIB0051</NoWarn>
1818
<CheckEolTargetFramework>false</CheckEolTargetFramework>
19+
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
1920

2021
<!-- Used by code coverage -->
2122
<DebugType>full</DebugType>

src/bunit.core/ComponentParameterCollection.cs

+11
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ public class ComponentParameterCollection : ICollection<ComponentParameter>, IRe
1919
/// <inheritdoc />
2020
public bool IsReadOnly { get; }
2121

22+
#if NET9_0_OR_GREATER
23+
/// <summary>
24+
/// Gets or sets the <see cref="IComponentRenderMode"/> that will be specified in
25+
/// the render tree for component the parameters are being passed to.
26+
/// </summary>
27+
public IComponentRenderMode? RenderMode { get; set; }
28+
#endif
29+
2230
/// <summary>
2331
/// Adds a <paramref name="item"/> to the collection.
2432
/// </summary>
@@ -104,6 +112,9 @@ void AddComponent(RenderTreeBuilder builder)
104112
{
105113
builder.OpenComponent<TComponent>(0);
106114
AddAttributes(builder);
115+
#if NET9_0_OR_GREATER
116+
builder.AddComponentRenderMode(RenderMode);
117+
#endif
107118
builder.CloseComponent();
108119
}
109120

src/bunit.core/ComponentParameterCollectionBuilder.cs

+21-8
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public ComponentParameterCollectionBuilder<TComponent> Add<TChildComponent>(Expr
7878
/// <param name="parameterSelector">A lambda function that selects the parameter.</param>
7979
/// <param name="markup">The markup string to pass to the <see cref="RenderFragment"/>.</param>
8080
/// <returns>This <see cref="ComponentParameterCollectionBuilder{TComponent}"/>.</returns>
81-
public ComponentParameterCollectionBuilder<TComponent> Add(Expression<Func<TComponent, RenderFragment?>> parameterSelector, [StringSyntax("Html")]string markup)
81+
public ComponentParameterCollectionBuilder<TComponent> Add(Expression<Func<TComponent, RenderFragment?>> parameterSelector, [StringSyntax("Html")] string markup)
8282
=> Add(parameterSelector, markup.ToMarkupRenderFragment());
8383

8484
/// <summary>
@@ -266,7 +266,7 @@ public ComponentParameterCollectionBuilder<TComponent> AddChildContent(RenderFra
266266
/// </summary>
267267
/// <param name="markup">The markup string to pass the ChildContent parameter wrapped in a <see cref="RenderFragment"/>.</param>
268268
/// <returns>This <see cref="ComponentParameterCollectionBuilder{TComponent}"/>.</returns>
269-
public ComponentParameterCollectionBuilder<TComponent> AddChildContent([StringSyntax("Html")]string markup)
269+
public ComponentParameterCollectionBuilder<TComponent> AddChildContent([StringSyntax("Html")] string markup)
270270
=> AddChildContent(markup.ToMarkupRenderFragment());
271271

272272
/// <summary>
@@ -344,11 +344,11 @@ public ComponentParameterCollectionBuilder<TComponent> Bind<TValue>(
344344
Action<TValue> changedAction,
345345
Expression<Func<TValue>>? valueExpression = null)
346346
{
347-
#if !NET8_0_OR_GREATER
347+
#if !NET8_0_OR_GREATER
348348
var (parameterName, _, isCascading) = GetParameterInfo(parameterSelector);
349-
#else
349+
#else
350350
var (parameterName, _, isCascading) = GetParameterInfo(parameterSelector, initialValue);
351-
#endif
351+
#endif
352352

353353
if (isCascading)
354354
throw new ArgumentException("Using Bind with a cascading parameter is not allowed.", parameterName);
@@ -397,6 +397,19 @@ static string TrimEnd(string source, string value)
397397
: source;
398398
}
399399

400+
#if NET9_0_OR_GREATER
401+
/// <summary>
402+
/// Sets (or unsets) the <see cref="IComponentRenderMode"/> for the component and child components.
403+
/// </summary>
404+
/// <param name="renderMode">The render mode to assign to the component, e.g. <c>RenderMode.InteractiveServer</c>, or <see langword="null"/>, to not assign a specific render mode.</param>
405+
/// <returns>This <see cref="ComponentParameterCollectionBuilder{TComponent}"/>.</returns>
406+
public ComponentParameterCollectionBuilder<TComponent> SetAssignedRenderMode(IComponentRenderMode? renderMode)
407+
{
408+
parameters.RenderMode = renderMode;
409+
return this;
410+
}
411+
#endif
412+
400413
/// <summary>
401414
/// Try to add a <paramref name="value"/> for a parameter with the <paramref name="name"/>, if
402415
/// <typeparamref name="TComponent"/> has a property with that name, AND that property has a <see cref="ParameterAttribute"/>
@@ -454,14 +467,14 @@ Expression<Func<TComponent, TValue>> parameterSelector
454467
: propInfoCandidate;
455468

456469
var paramAttr = propertyInfo?.GetCustomAttribute<ParameterAttribute>(inherit: true);
457-
#if !NET8_0_OR_GREATER
470+
#if !NET8_0_OR_GREATER
458471
var cascadingParamAttr = propertyInfo?.GetCustomAttribute<CascadingParameterAttribute>(inherit: true);
459472

460473
if (propertyInfo is null || (paramAttr is null && cascadingParamAttr is null))
461474
throw new ArgumentException($"The parameter selector '{parameterSelector}' does not resolve to a public property on the component '{typeof(TComponent)}' with a [Parameter] or [CascadingParameter] attribute.", nameof(parameterSelector));
462475

463476
return (propertyInfo.Name, CascadingValueName: cascadingParamAttr?.Name, IsCascading: cascadingParamAttr is not null);
464-
#else
477+
#else
465478
var cascadingParamAttrBase = propertyInfo?.GetCustomAttribute<CascadingParameterAttributeBase>(inherit: true);
466479

467480
if (propertyInfo is null || (paramAttr is null && cascadingParamAttrBase is null))
@@ -494,7 +507,7 @@ static ArgumentException CreateErrorMessageForSupplyFromQuery(
494507
NavigationManager.NavigateTo(uri);
495508
""");
496509
}
497-
#endif
510+
#endif
498511
}
499512

500513
private static bool HasChildContentParameter()

tests/bunit.core.tests/Rendering/RenderModeTests.razor

+37-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
@code{
2+
#if NET9_0_OR_GREATER
3+
}
14
@using Bunit.TestAssets.RenderModes;
25
@inherits TestContext
36
@code {
4-
#if NET9_0_OR_GREATER
57
[Fact(DisplayName = "TestRenderer provides RendererInfo")]
68
public void Test001()
79
{
@@ -104,7 +106,7 @@
104106
public void Test009()
105107
{
106108
// See: https://learn.microsoft.com/en-us/aspnet/core/blazor/components/sections?view=aspnetcore-8.0#section-interaction-with-other-blazor-features
107-
var cut = Render(@<SectionOutletComponent />);
109+
var cut = Render(@<SectionOutletComponent><ComponentThatPrintsAssignedRenderMode/></SectionOutletComponent>);
108110

109111
cut.MarkupMatches(@<p>Assigned Render Mode: InteractiveWebAssemblyRenderMode</p>);
110112
}
@@ -125,6 +127,36 @@
125127
</text>);
126128
}
127129

130+
[Fact(DisplayName = "SetAssignedRenderMode on root component")]
131+
public void Test011()
132+
{
133+
var cut = RenderComponent<ComponentThatPrintsAssignedRenderMode>(ps => ps.SetAssignedRenderMode(RenderMode.InteractiveServer));
134+
cut.MarkupMatches(@<p>Assigned Render Mode: InteractiveServerRenderMode</p>);
135+
}
136+
137+
[Fact(DisplayName = "SetAssignedRenderMode on parent component cascades to children")]
138+
public void Test012()
139+
{
140+
var cut = RenderComponent<ComponentWithChildContent>(ps => ps
141+
.SetAssignedRenderMode(RenderMode.InteractiveWebAssembly)
142+
.AddChildContent<ComponentThatPrintsAssignedRenderMode>());
143+
144+
cut.MarkupMatches(@<p>Assigned Render Mode: InteractiveWebAssemblyRenderMode</p>);
145+
}
146+
147+
[Fact(DisplayName = "SetAssignedRenderMode child components")]
148+
public void Test013()
149+
{
150+
var cut = RenderComponent<ComponentWithChildContent>(ps => ps
151+
.AddChildContent<ComponentThatPrintsAssignedRenderMode>(pps => pps.SetAssignedRenderMode(RenderMode.InteractiveServer))
152+
.AddChildContent<ComponentThatPrintsAssignedRenderMode>(pps => pps.SetAssignedRenderMode(RenderMode.InteractiveWebAssembly)));
153+
154+
cut.MarkupMatches(
155+
@<text>
156+
<p>Assigned Render Mode: InteractiveServerRenderMode</p>
157+
<p>Assigned Render Mode: InteractiveWebAssemblyRenderMode</p>
158+
</text>);
159+
}
128160

129161
[Fact(DisplayName = "Different assigned RenderMode between child and parent throws")]
130162
public void Test020()
@@ -138,5 +170,7 @@
138170

139171
act.ShouldThrow<RenderModeMisMatchException>(); // todo: figure out good exception to use
140172
}
141-
#endif
173+
}
174+
@code{
175+
#endif
142176
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Microsoft.AspNetCore.Components.Web;
2+
3+
#if NET8_0_OR_GREATER
4+
using Microsoft.AspNetCore.Components.Sections;
5+
#endif
6+
7+
namespace Bunit.TestAssets.RenderModes;
8+
9+
public class SectionOutletComponent : ComponentBase
10+
{
11+
#if NET8_0_OR_GREATER
12+
private static readonly Guid SectionId = Guid.NewGuid();
13+
#endif
14+
15+
[Parameter] public RenderFragment ChildContent { get; set; }
16+
17+
protected override void BuildRenderTree(RenderTreeBuilder builder)
18+
{
19+
#if NET8_0_OR_GREATER
20+
builder.OpenComponent<SectionOutlet>(0);
21+
builder.AddComponentParameter(1, nameof(SectionOutlet.SectionId), SectionId);
22+
builder.AddComponentRenderMode(RenderMode.InteractiveWebAssembly);
23+
builder.CloseComponent();
24+
builder.OpenComponent<SectionContent>(10);
25+
builder.AddComponentParameter(11, nameof(SectionContent.SectionId), SectionId);
26+
builder.AddAttribute(12, nameof(SectionContent.ChildContent), ChildContent);
27+
builder.CloseComponent();
28+
#endif
29+
}
30+
}

tests/bunit.testassets/RenderModes/SectionOutletComponent.razor

-12
This file was deleted.

tests/bunit.testassets/bunit.testassets.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<PackageReference Include="xunit.abstractions" />
2121
<PackageReference Include="xunit.assert" />
2222
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" />
23+
<PackageReference Include="Microsoft.AspNetCore.Components" />
2324
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" />
2425
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" />
2526
<PackageReference Include="System.Text.Json" />

0 commit comments

Comments
 (0)