Skip to content

Commit dcf45f5

Browse files
committed
Added TryAdd to ComponentParameterCollectionBuilder to enable adding parameters to an builder with an unknown type
1 parent 8ea8ee0 commit dcf45f5

File tree

2 files changed

+85
-2
lines changed

2 files changed

+85
-2
lines changed

src/bunit.core/ComponentParameterCollectionBuilder.cs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace Bunit
1414
/// <typeparam name="TComponent">The type of component under test to add the parameters</typeparam>
1515
public sealed class ComponentParameterCollectionBuilder<TComponent> where TComponent : IComponent
1616
{
17+
private static readonly Type TComponentType = typeof(TComponent);
1718
private const string ChildContent = nameof(ChildContent);
1819

1920
/// <summary>
@@ -303,6 +304,38 @@ public ComponentParameterCollectionBuilder<TComponent> AddUnmatched(string name,
303304
return AddParameter(name, value);
304305
}
305306

307+
/// <summary>
308+
/// Try to add a <paramref name="value"/> for a parameter with the <paramref name="name"/>, if
309+
/// <typeparamref name="TComponent"/> has a property with that name, AND that property has a <see cref="ParameterAttribute"/>
310+
/// or a <see cref="CascadingParameterAttribute"/>.
311+
/// </summary>
312+
/// <remarks>
313+
/// This is an untyped version of the <see cref="Add{TValue}(Expression{Func{TComponent, TValue}}, TValue)"/> method. Always
314+
/// prefer the strongly typed <c>Add</c> methods whenever possible.
315+
/// </remarks>
316+
/// <typeparam name="TValue">Value type.</typeparam>
317+
/// <param name="name">Name of the property for the parameter.</param>
318+
/// <param name="value">Value to assign to the parameter</param>
319+
/// <returns>True if parameter with the name exists and value was set, false otherwise.</returns>
320+
public bool TryAdd<TValue>(string name, [AllowNull] TValue value)
321+
{
322+
if (TComponentType.GetProperty(name, BindingFlags.Public | BindingFlags.Instance) is PropertyInfo ccProp)
323+
{
324+
if (ccProp.GetCustomAttribute<ParameterAttribute>(inherit: false) is not null)
325+
{
326+
AddParameter(name, value);
327+
return true;
328+
}
329+
330+
if(ccProp.GetCustomAttribute<CascadingParameterAttribute>(inherit: false) is CascadingParameterAttribute cpa)
331+
{
332+
AddCascadingValueParameter(cpa.Name, value);
333+
return true;
334+
}
335+
}
336+
return false;
337+
}
338+
306339
/// <summary>
307340
/// Builds the <see cref="ComponentParameterCollection"/>.
308341
/// </summary>
@@ -325,8 +358,8 @@ private static (string paramName, string? cascadingValueName, bool isCascading)
325358
}
326359

327360
private static bool HasChildContentParameter()
328-
=> typeof(TComponent).GetProperty(ChildContent, BindingFlags.Public | BindingFlags.Instance) is PropertyInfo ccProp
329-
&& ccProp.GetCustomAttribute<ParameterAttribute>(inherit: false) != null;
361+
=> TComponentType.GetProperty(ChildContent, BindingFlags.Public | BindingFlags.Instance) is PropertyInfo ccProp
362+
&& ccProp.GetCustomAttribute<ParameterAttribute>(inherit: false) is not null;
330363

331364
private ComponentParameterCollectionBuilder<TComponent> AddParameter<TValue>(string name, [AllowNull] TValue value)
332365
{

tests/bunit.core.tests/ComponentParameterCollectionBuilderTests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,56 @@ public void Test101()
494494
builder.Build().ShouldHaveSingleItem();
495495
}
496496

497+
[Fact(DisplayName = "TryAdd returns false when parameter does not exist on component")]
498+
public void Test200()
499+
{
500+
var sut = new ComponentParameterCollectionBuilder<NoParams>();
501+
502+
var result = sut.TryAdd("FOO", "VALUE");
503+
504+
result.ShouldBeFalse();
505+
sut.Build().Count.ShouldBe(0);
506+
}
507+
508+
[Fact(DisplayName = "TryAdd returns true when parameter exists on component")]
509+
public void Test201()
510+
{
511+
var name = nameof(Params.ValueTypeParam);
512+
var input = 42;
513+
514+
var result = Builder.TryAdd(name, input);
515+
516+
result.ShouldBeTrue();
517+
Builder.Build().ShouldHaveSingleItem()
518+
.ShouldBeParameter(name, input, isCascadingValue: false);
519+
}
520+
521+
[Fact(DisplayName = "TryAdd returns true when unnamed cascading parameter exists on component")]
522+
public void Test202()
523+
{
524+
var name = nameof(Params.CC);
525+
var input = 42;
526+
527+
var result = Builder.TryAdd(name, input);
528+
529+
result.ShouldBeTrue();
530+
Builder.Build().ShouldHaveSingleItem()
531+
.ShouldBeParameter(null, input, isCascadingValue: true);
532+
}
533+
534+
[Fact(DisplayName = "TryAdd returns true when named cascading parameter exists on component")]
535+
public void Test203()
536+
{
537+
var name = nameof(Params.NamedCC);
538+
var input = 42;
539+
540+
var result = Builder.TryAdd(name, input);
541+
542+
result.ShouldBeTrue();
543+
Builder.Build().ShouldHaveSingleItem()
544+
.ShouldBeParameter(name + "NAME", input, isCascadingValue: true);
545+
}
546+
497547
private class Params : ComponentBase
498548
{
499549
[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object>? Attributes { get; set; }

0 commit comments

Comments
 (0)