Skip to content

Commit

Permalink
Finished component parameter collection, closed #142, updated tests t…
Browse files Browse the repository at this point in the history
…o component parameter factory
  • Loading branch information
egil committed Sep 6, 2020
1 parent 17b2e03 commit cc1b1cc
Show file tree
Hide file tree
Showing 4 changed files with 420 additions and 159 deletions.
136 changes: 75 additions & 61 deletions src/bunit.core/ComponentParameterCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
using System.Threading.Tasks;
using Bunit.Rendering;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;

namespace Bunit
{
/// <summary>
/// A collection for <see cref="ComponentParameter" />
/// </summary>
public class ComponentParameterCollection
public class ComponentParameterCollection : IEnumerable<ComponentParameter>
{
private static readonly MethodInfo CreateTemplateWrapperMethod = typeof(ComponentParameterCollection).GetMethod(nameof(CreateTemplateWrapper), BindingFlags.NonPublic | BindingFlags.Static);
private static readonly Type CascadingValueType = typeof(CascadingValue<>);
Expand All @@ -39,6 +40,18 @@ public void Add(ComponentParameter parameter)
_parameters.Add(parameter);
}

/// <summary>
/// Adds an enumerable of parameters to the collection.
/// </summary>
/// <param name="parameters">Parameters to add.</param>
public void Add(IEnumerable<ComponentParameter> parameters)
{
foreach (var cp in parameters)
{
Add(cp);
}
}

/// <summary>
/// Checks if the <paramref name="parameter"/> is in the collection.
/// </summary>
Expand All @@ -53,10 +66,8 @@ public void Add(ComponentParameter parameter)
/// </summary>
/// <typeparam name="TComponent">Type of component to render.</typeparam>
public RenderFragment ToComponentRenderFragment<TComponent>() where TComponent : IComponent
{
var cascadingValues = new Queue<ComponentParameter>(
_parameters?.Where(x => x.IsCascadingValue) ?? Array.Empty<ComponentParameter>()
);
{
var cascadingValues = GetCascadingValues();

if (cascadingValues.Count > 0)
return AddCascadingValue;
Expand All @@ -66,15 +77,16 @@ public RenderFragment ToComponentRenderFragment<TComponent>() where TComponent :
void AddCascadingValue(RenderTreeBuilder builder)
{
var cv = cascadingValues.Dequeue();
builder.OpenComponent(0, GetCascadingValueType(cv));

if(cv.Name is string)
builder.AddAttribute(1, nameof(CascadingValue<object>.Name), cv.Value);
builder.OpenComponent(0, cv.Type);

builder.AddAttribute(2, nameof(CascadingValue<object>.Value), cv.Value);
if (cv.Parameter.Name is string)
builder.AddAttribute(1, nameof(CascadingValue<object>.Name), cv.Parameter.Name);

builder.AddAttribute(2, nameof(CascadingValue<object>.Value), cv.Parameter.Value);
builder.AddAttribute(3, nameof(CascadingValue<object>.IsFixed), true);

if(cascadingValues.Count > 0)
if (cascadingValues.Count > 0)
builder.AddAttribute(4, nameof(CascadingValue<object>.ChildContent), (RenderFragment)(AddCascadingValue));
else
builder.AddAttribute(4, nameof(CascadingValue<object>.ChildContent), (RenderFragment)(AddComponent));
Expand Down Expand Up @@ -134,8 +146,61 @@ void AddAttributes(RenderTreeBuilder builder)
throw new ArgumentException($"The parameter with the name '{pgroup.Key}' was added more than once. This parameter can only be added one time.");
}
}

Queue<(ComponentParameter Parameter, Type Type)> GetCascadingValues()
{
var cascadingValues = _parameters?.Where(x => x.IsCascadingValue)
.Select(x => (Parameter: x, Type: GetCascadingValueType(x)))
.ToArray() ?? Array.Empty<(ComponentParameter Parameter, Type Type)>();

// Detect duplicated unnamed values
for (int i = 0; i < cascadingValues.Length; i++)
{

for (int j = i + 1; j < cascadingValues.Length; j++)
{
if (cascadingValues[i].Type == cascadingValues[j].Type)
{
var iName = cascadingValues[i].Parameter.Name;
if (iName is null)
{
var cascadingValueType = cascadingValues[i].Type.GetGenericArguments()[0];
throw new ArgumentException($"Two or more unnamed cascading values with the type '{cascadingValueType.Name}' was added. " +
$"Only add one unnamed cascading value of the same type.");
}

if (iName.Equals(cascadingValues[j].Parameter.Name, StringComparison.Ordinal))
{
throw new ArgumentException($"Two or more named cascading values with the name '{iName}' and the same type was added. " +
$"Only add one named cascading value with the same name and type.");
}
}
}
}

return new Queue<(ComponentParameter Parameter, Type Type)>(cascadingValues);
}
}

/// <inheritdoc/>
public IEnumerator<ComponentParameter> GetEnumerator()
{
if (_parameters is null)
{
yield break;
}
else
{
for (int i = 0; i < _parameters.Count; i++)
{
yield return _parameters[i];
}
}
}

/// <inheritdoc/>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();

private static object WrapTemplates(Type templateParamterType, ComponentParameter[] templateParameters)
{
// gets the generic argument to RenderFragment<>, e.g. string with RenderFragment<string>
Expand Down Expand Up @@ -170,56 +235,5 @@ private static Type GetCascadingValueType(ComponentParameter parameter)
var cascadingValueType = parameter.Value.GetType();
return CascadingValueType.MakeGenericType(cascadingValueType);
}

// var parametersList = parameters as IReadOnlyList<ComponentParameter> ?? parameters.ToArray();
// var cascadingParams = new Queue<ComponentParameter>(parametersList.Where(x => x.IsCascadingValue));

// if (cascadingParams.Count > 0)
// return CreateCascadingValueRenderFragment(cascadingParams, parametersList);
// else
// return CreateComponentRenderFragment(parametersList);

// static RenderFragment CreateCascadingValueRenderFragment(Queue<ComponentParameter> cascadingParams, IReadOnlyList<ComponentParameter> parameters)
// {
// var cp = cascadingParams.Dequeue();
// var cascadingValueType = GetCascadingValueType(cp);
// return builder =>
// {
// builder.OpenComponent(0, cascadingValueType);
// if (cp.Name is { })
// builder.AddAttribute(1, nameof(CascadingValue<object>.Name), cp.Name);

// builder.AddAttribute(2, nameof(CascadingValue<object>.Value), cp.Value);
// builder.AddAttribute(3, nameof(CascadingValue<object>.IsFixed), true);

// if (cascadingParams.Count > 0)
// builder.AddAttribute(4, nameof(CascadingValue<object>.ChildContent), CreateCascadingValueRenderFragment(cascadingParams, parameters));
// else
// builder.AddAttribute(4, nameof(CascadingValue<object>.ChildContent), CreateComponentRenderFragment(parameters));

// builder.CloseComponent();
// };
// }

// static RenderFragment CreateComponentRenderFragment(IReadOnlyList<ComponentParameter> parameters)
// {
// return builder =>
// {
// builder.OpenComponent(0, typeof(TComponent));

// for (var i = 0; i < parameters.Count; i++)
// {
// var para = parameters[i];
// if (!para.IsCascadingValue && para.Name is { })
// builder.AddAttribute(i + 1, para.Name, para.Value);
// }

// builder.CloseComponent();
// };
// }
//}

//
}

}
14 changes: 13 additions & 1 deletion src/bunit.core/ComponentParameterFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,17 @@ public static ComponentParameter ChildContent<TComponent>(params ComponentParame
return RenderFragment<TComponent>(nameof(ChildContent), parameters);
}

/// <summary>
/// Creates a ChildContent parameter that will pass the provided <paramref name="renderFragment"/>
/// to the parameter in the component.
/// </summary>
/// <param name="renderFragment">The <see cref="Microsoft.AspNetCore.Components.RenderFragment"/> to pass to the ChildContent parameter.</param>
/// <returns>The <see cref="ComponentParameter"/>.</returns>
public static ComponentParameter ChildContent(RenderFragment renderFragment)
{
return Parameter(nameof(ChildContent), renderFragment);
}

/// <summary>
/// Creates a <see cref="Microsoft.AspNetCore.Components.RenderFragment"/> with the provided
/// <paramref name="markup"/> as rendered output and passes it to the parameter specified in <paramref name="name"/>.
Expand All @@ -178,7 +189,8 @@ public static ComponentParameter RenderFragment(string name, string markup)
/// <returns>The <see cref="ComponentParameter"/>.</returns>
public static ComponentParameter RenderFragment<TComponent>(string name, params ComponentParameter[] parameters) where TComponent : class, IComponent
{
return ComponentParameter.CreateParameter(name, parameters.ToComponentRenderFragment<TComponent>());
var cpc = new ComponentParameterCollection() { parameters };
return ComponentParameter.CreateParameter(name, cpc.ToComponentRenderFragment<TComponent>());
}

/// <summary>
Expand Down
79 changes: 75 additions & 4 deletions tests/bunit.core.tests/ComponentParameterCollectionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ namespace Bunit
{
public class ComponentParameterCollectionTest
{
private const string RegularParamName = nameof(AllTypesOfParams<string>.RegularParam);
private static readonly TestContext Context = new TestContext();

static ComponentParameterCollectionTest()
Expand Down Expand Up @@ -271,7 +270,73 @@ public void Test050()
var rf = sut.ToComponentRenderFragment<Params>();

var c = RenderWithRenderFragment(rf).Instance;
c.NullableStringCC.ShouldBe("FOO");
c.NullableCC.ShouldBe("FOO");
}

[Fact(DisplayName = "ToComponentRenderFragment wraps component in multiple unnamed cascading values in RenderFragment")]
public void Test051()
{
var sut = new ComponentParameterCollection();
sut.Add(ComponentParameter.CreateCascadingValue(null, "FOO"));
sut.Add(ComponentParameter.CreateCascadingValue(null, 42));

var rf = sut.ToComponentRenderFragment<Params>();

var c = RenderWithRenderFragment(rf).Instance;
c.NullableCC.ShouldBe("FOO");
c.CC.ShouldBe(42);
}

[Fact(DisplayName = "ToComponentRenderFragment throws when multiple unnamed cascading values with same type is added")]
public void Test052()
{
var sut = new ComponentParameterCollection();
sut.Add(ComponentParameter.CreateCascadingValue(null, "FOO"));
sut.Add(ComponentParameter.CreateCascadingValue(null, 42));
sut.Add(ComponentParameter.CreateCascadingValue(null, "BAR"));
sut.Add(ComponentParameter.CreateCascadingValue(null, Array.Empty<string>()));

Should.Throw<ArgumentException>(() => sut.ToComponentRenderFragment<Params>());
}

[Fact(DisplayName = "ToComponentRenderFragment wraps component in named cascading values in RenderFragment")]
public void Test053()
{
var sut = new ComponentParameterCollection();
sut.Add(ComponentParameter.CreateCascadingValue(nameof(Params.NullableNamedCC), "FOO"));

var rf = sut.ToComponentRenderFragment<Params>();

var c = RenderWithRenderFragment(rf).Instance;
c.NullableNamedCC.ShouldBe("FOO");
}

[Fact(DisplayName = "ToComponentRenderFragment wraps component in multiple named cascading values in RenderFragment")]
public void Test054()
{
var sut = new ComponentParameterCollection();
sut.Add(ComponentParameter.CreateCascadingValue(nameof(Params.NullableNamedCC), "FOO"));
sut.Add(ComponentParameter.CreateCascadingValue(nameof(Params.NamedCC), 42));
sut.Add(ComponentParameter.CreateCascadingValue(nameof(Params.AnotherNamedCC), 1337));

var rf = sut.ToComponentRenderFragment<Params>();

var c = RenderWithRenderFragment(rf).Instance;
c.NullableNamedCC.ShouldBe("FOO");
c.NamedCC.ShouldBe(42);
c.AnotherNamedCC.ShouldBe(1337);
}

[Fact(DisplayName = "ToComponentRenderFragment throws when multiple named cascading values with same name and type is added")]
public void Test055()
{
var sut = new ComponentParameterCollection();
sut.Add(ComponentParameter.CreateCascadingValue(nameof(Params.NullableNamedCC), "FOO"));
sut.Add(ComponentParameter.CreateCascadingValue(nameof(Params.NamedCC), 42));
sut.Add(ComponentParameter.CreateCascadingValue(nameof(Params.AnotherNamedCC), 1337));
sut.Add(ComponentParameter.CreateCascadingValue(nameof(Params.NamedCC), 42));

Should.Throw<ArgumentException>(() => sut.ToComponentRenderFragment<Params>());
}

private class Params : ComponentBase
Expand All @@ -290,8 +355,11 @@ private class Params : ComponentBase
[Parameter] public EventCallback<EventArgs>? NullableECWithArgs { get; set; }
[Parameter] public EventCallback<EventArgs> ECWithArgs { get; set; } = EventCallback<EventArgs>.Empty;
[Parameter] public RenderFragment<string>? Template { get; set; }
[CascadingParameter] public string? NullableStringCC { get; set; }
[CascadingParameter] public string? NullableCC { get; set; }
[CascadingParameter] public int CC { get; set; } = -1;
[CascadingParameter(Name = nameof(NullableNamedCC))] public string? NullableNamedCC { get; set; }
[CascadingParameter(Name = nameof(NamedCC))] public int NamedCC { get; set; } = -1;
[CascadingParameter(Name = nameof(AnotherNamedCC))] public int AnotherNamedCC { get; set; } = -1;

public void VerifyParamsHaveDefaultValues()
{
Expand All @@ -306,8 +374,11 @@ public void VerifyParamsHaveDefaultValues()
NullableECWithArgs.ShouldBeNull();
ECWithArgs.ShouldBe(EventCallback<EventArgs>.Empty);
Template.ShouldBeNull();
NullableStringCC.ShouldBeNull();
NullableCC.ShouldBeNull();
CC.ShouldBe(-1);
NullableNamedCC.ShouldBeNull();
NamedCC.ShouldBe(-1);
AnotherNamedCC.ShouldBe(-1);
}

protected override void BuildRenderTree(RenderTreeBuilder builder)
Expand Down
Loading

0 comments on commit cc1b1cc

Please sign in to comment.