Skip to content

Commit 5e46846

Browse files
committed
Added RenderFragment helper method to ComponentTestFixture. Better error handling in SetParametersAndRender method.
1 parent 83252a9 commit 5e46846

File tree

7 files changed

+199
-20
lines changed

7 files changed

+199
-20
lines changed

src/ClassDiagram.cd

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,21 @@
1414
</TypeIdentifier>
1515
</Interface>
1616
<Interface Name="Egil.RazorComponents.Testing.IRenderedFragment">
17-
<Position X="3.5" Y="7" Width="3.25" />
17+
<Position X="9.75" Y="1.25" Width="3.25" />
1818
<TypeIdentifier>
1919
<HashCode>CAgAAAAAAAAAAAAAABAQACAAAAAIAAAAAAAAAQQAAAA=</HashCode>
2020
<FileName>Rendering\IRenderedFragment.cs</FileName>
2121
</TypeIdentifier>
2222
</Interface>
2323
<Interface Name="Egil.RazorComponents.Testing.IRenderedComponent&lt;TComponent&gt;">
24-
<Position X="2.75" Y="10.25" Width="4.75" />
24+
<Position X="9" Y="4.5" Width="4.75" />
2525
<TypeIdentifier>
2626
<HashCode>AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQBAAAAAAAAA=</HashCode>
2727
<FileName>Rendering\IRenderedComponent.cs</FileName>
2828
</TypeIdentifier>
2929
</Interface>
3030
<Interface Name="Egil.RazorComponents.Testing.IRazorTestContext">
31-
<Position X="3" Y="4.5" Width="5.5" />
31+
<Position X="2.75" Y="4.5" Width="5.5" />
3232
<TypeIdentifier>
3333
<HashCode>QAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
3434
<FileName>IRazorTestContext.cs</FileName>

src/ComponentTestFixture.cs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -144,34 +144,60 @@ protected static ComponentParameter CascadingValue(object value)
144144
}
145145

146146
/// <summary>
147-
/// Creates a ChildContent <see cref="ComponentParameter"/> with the provided <paramref name="markup"/> as the <see cref="RenderFragment"/>.
147+
/// Creates a ChildContent <see cref="Microsoft.AspNetCore.Components.RenderFragment"/> with the provided
148+
/// <paramref name="markup"/> as rendered output.
148149
/// </summary>
149150
/// <param name="markup">Markup to pass to the child content parameter</param>
150151
/// <returns>The <see cref="ComponentParameter"/>.</returns>
151152
protected static ComponentParameter ChildContent(string markup)
152153
{
153-
return ComponentParameter.CreateParameter(nameof(ChildContent), markup.ToMarkupRenderFragment());
154+
return RenderFragment(nameof(ChildContent), markup);
154155
}
155156

156157
/// <summary>
157-
/// Creates a ChildContent <see cref="RenderFragment"/> which will render a <typeparamref name="TComponent"/> component
158+
/// Creates a ChildContent <see cref="Microsoft.AspNetCore.Components.RenderFragment"/> which will render a <typeparamref name="TComponent"/> component
158159
/// with the provided <paramref name="parameters"/> as input.
159160
/// </summary>
160-
/// <typeparam name="TComponent">The type of the component to render with the <see cref="RenderFragment"/></typeparam>
161+
/// <typeparam name="TComponent">The type of the component to render with the <see cref="Microsoft.AspNetCore.Components.RenderFragment"/></typeparam>
161162
/// <param name="parameters">Parameters to pass to the <typeparamref name="TComponent"/>.</param>
162163
/// <returns>The <see cref="ComponentParameter"/>.</returns>
163164
protected static ComponentParameter ChildContent<TComponent>(params ComponentParameter[] parameters) where TComponent : class, IComponent
164165
{
165-
return ComponentParameter.CreateParameter(nameof(ChildContent), parameters.ToComponentRenderFragment<TComponent>());
166+
return RenderFragment<TComponent>(nameof(ChildContent), parameters);
166167
}
167168

168169
/// <summary>
169-
/// Creates a component parameter which will pass the <paramref name="template"/> <see cref="RenderFragment{TValue}" />
170+
/// Creates a <see cref="Microsoft.AspNetCore.Components.RenderFragment"/> with the provided
171+
/// <paramref name="markup"/> as rendered output and passes it to the parameter specified in <paramref name="name"/>.
172+
/// </summary>
173+
/// <param name="name">Parameter name.</param>
174+
/// <param name="markup">Markup to pass to the render fragment parameter</param>
175+
/// <returns>The <see cref="ComponentParameter"/>.</returns>
176+
protected static ComponentParameter RenderFragment(string name, string markup)
177+
{
178+
return ComponentParameter.CreateParameter(name, markup.ToMarkupRenderFragment());
179+
}
180+
181+
/// <summary>
182+
/// Creates a <see cref="Microsoft.AspNetCore.Components.RenderFragment"/> which will render a <typeparamref name="TComponent"/> component
183+
/// with the provided <paramref name="parameters"/> as input, and passes it to the parameter specified in <paramref name="name"/>.
184+
/// </summary>
185+
/// <typeparam name="TComponent">The type of the component to render with the <see cref="Microsoft.AspNetCore.Components.RenderFragment"/></typeparam>
186+
/// <param name="name">Parameter name.</param>
187+
/// <param name="parameters">Parameters to pass to the <typeparamref name="TComponent"/>.</param>
188+
/// <returns>The <see cref="ComponentParameter"/>.</returns>
189+
protected static ComponentParameter RenderFragment<TComponent>(string name, params ComponentParameter[] parameters) where TComponent : class, IComponent
190+
{
191+
return ComponentParameter.CreateParameter(name, parameters.ToComponentRenderFragment<TComponent>());
192+
}
193+
194+
/// <summary>
195+
/// Creates a component parameter which will pass the <paramref name="template"/> <see cref="Microsoft.AspNetCore.Components.RenderFragment{TValue}" />
170196
/// to the parameter with the name <paramref name="name"/>.
171197
/// </summary>
172-
/// <typeparam name="TValue"></typeparam>
173-
/// <param name="name">Parameter name</param>
174-
/// <param name="template"><see cref="RenderFragment{TValue}" /> to pass to the parameter</param>
198+
/// <typeparam name="TValue">The value used to build the content.</typeparam>
199+
/// <param name="name">Parameter name.</param>
200+
/// <param name="template"><see cref="Microsoft.AspNetCore.Components.RenderFragment{TValue}" /> to pass to the parameter.</param>
175201
/// <returns>The <see cref="ComponentParameter"/>.</returns>
176202
protected static ComponentParameter Template<TValue>(string name, RenderFragment<TValue> template)
177203
{

src/Rendering/ComponentParameter.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@ namespace Egil.RazorComponents.Testing
3131

3232
private ComponentParameter(string? name, object? value, bool isCascadingValue)
3333
{
34+
if (isCascadingValue && value is null)
35+
throw new ArgumentNullException(nameof(value), "Cascading values cannot be set to null");
36+
37+
if(!isCascadingValue && name is null)
38+
throw new ArgumentNullException(nameof(name), "A parameters name cannot be set to null");
39+
3440
Name = name;
3541
Value = value;
3642
IsCascadingValue = isCascadingValue;
37-
38-
if(IsCascadingValue && value is null)
39-
throw new ArgumentNullException(nameof(value), "Cascading values cannot be set to null");
4043
}
4144

4245
/// <summary>

src/Rendering/RenderedComponent.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,25 @@ public class RenderedComponent<TComponent> : RenderedFragment, IRenderedComponen
1313
/// <inheritdoc/>
1414
public TComponent Instance { get; }
1515

16-
internal RenderedComponent(TestContext testContext, IReadOnlyList<ComponentParameter> parameters)
16+
/// <summary>
17+
/// Instantiates a <see cref="RenderedComponent{TComponent}"/> which will render a component of type <typeparamref name="TComponent"/>
18+
/// with the provided <paramref name="parameters"/>.
19+
/// </summary>
20+
public RenderedComponent(ITestContext testContext, IReadOnlyList<ComponentParameter> parameters)
1721
: this(testContext, parameters.ToComponentRenderFragment<TComponent>()) { }
1822

19-
internal RenderedComponent(TestContext testContext, ParameterView parameters)
23+
/// <summary>
24+
/// Instantiates a <see cref="RenderedComponent{TComponent}"/> which will render a component of type <typeparamref name="TComponent"/>
25+
/// with the provided <paramref name="parameters"/>.
26+
/// </summary>
27+
public RenderedComponent(ITestContext testContext, ParameterView parameters)
2028
: this(testContext, parameters.ToComponentRenderFragment<TComponent>()) { }
2129

22-
internal RenderedComponent(TestContext testContext, RenderFragment renderFragment)
30+
/// <summary>
31+
/// Instantiates a <see cref="RenderedComponent{TComponent}"/> which will render the <paramref name="renderFragment"/> passed to it
32+
/// and attempt to find a component of type <typeparamref name="TComponent"/> in the render result.
33+
/// </summary>
34+
public RenderedComponent(ITestContext testContext, RenderFragment renderFragment)
2335
: base(testContext, renderFragment)
2436
{
2537
(ComponentId, Instance) = Container.GetComponent<TComponent>();
@@ -34,7 +46,14 @@ public void SetParametersAndRender(params ComponentParameter[] parameters)
3446
var parameterView = ParameterView.Empty;
3547
if (parameters.Length > 0)
3648
{
37-
var paramDict = parameters.ToDictionary(x => x.Name, x => x.Value);
49+
var paramDict = new Dictionary<string, object?>(parameters.Length);
50+
foreach (var param in parameters)
51+
{
52+
if(param.IsCascadingValue)
53+
throw new InvalidOperationException($"You cannot provide a new cascading value through the {nameof(SetParametersAndRender)} method.");
54+
55+
paramDict.Add(param.Name!, param.Value);
56+
}
3857
parameterView = ParameterView.FromDictionary(paramDict);
3958
}
4059
SetParametersAndRender(parameterView);

src/Rendering/RenderedFragment.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,13 @@ public class RenderedFragment : IRenderedFragment
3131
/// <inheritdoc/>
3232
public ITestContext TestContext { get; }
3333

34-
internal RenderedFragment(TestContext testContext, RenderFragment renderFragment)
34+
/// <summary>
35+
/// Instantiates a <see cref="RenderedFragment"/> which will render the <paramref name="renderFragment"/> passed to it.
36+
/// </summary>
37+
public RenderedFragment(ITestContext testContext, RenderFragment renderFragment)
3538
{
39+
if (testContext is null) throw new ArgumentNullException(nameof(testContext));
40+
3641
TestContext = testContext;
3742
_renderFragment = renderFragment;
3843
Container = new ContainerComponent(testContext.Renderer);

tests/AllTypesOfParamsTest.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System;
2+
using Egil.RazorComponents.Testing.Asserting;
3+
using Egil.RazorComponents.Testing.EventDispatchExtensions;
4+
using Microsoft.AspNetCore.Components;
5+
using Xunit;
6+
using Shouldly;
7+
using System.Threading.Tasks;
8+
using Egil.RazorComponents.Testing.Extensions;
9+
using Egil.RazorComponents.Testing.SampleComponents;
10+
11+
namespace Egil.RazorComponents.Testing
12+
{
13+
public class AllTypesOfParamsTest : ComponentTestFixture
14+
{
15+
[Fact(DisplayName = "All types of parameters are correctly assigned to component on render")]
16+
public void Test001()
17+
{
18+
Services.AddMockJsRuntime();
19+
20+
var cut = RenderComponent<AllTypesOfParams<string>>(
21+
("some-unmatched-attribute", "unmatched value"),
22+
(nameof(AllTypesOfParams<string>.RegularParam), "some value"),
23+
CascadingValue(42),
24+
CascadingValue(nameof(AllTypesOfParams<string>.NamedCascadingValue), 1337),
25+
EventCallback(nameof(AllTypesOfParams<string>.NonGenericCallback), () => throw new Exception("NonGenericCallback")),
26+
EventCallback(nameof(AllTypesOfParams<string>.GenericCallback), (EventArgs args) => throw new Exception("GenericCallback")),
27+
ChildContent(nameof(ChildContent)),
28+
RenderFragment(nameof(AllTypesOfParams<string>.OtherContent), nameof(AllTypesOfParams<string>.OtherContent)),
29+
Template<string>(nameof(AllTypesOfParams<string>.ItemTemplate), (item) => (builder) => throw new Exception("ItemTemplate"))
30+
);
31+
32+
// assert that all parameters have been set correctly
33+
var instance = cut.Instance;
34+
instance.Attributes["some-unmatched-attribute"].ShouldBe("unmatched value");
35+
instance.RegularParam.ShouldBe("some value");
36+
instance.UnnamedCascadingValue.ShouldBe(42);
37+
instance.NamedCascadingValue.ShouldBe(1337);
38+
Should.Throw<Exception>(async () => await instance.NonGenericCallback.InvokeAsync(null)).Message.ShouldBe("NonGenericCallback");
39+
Should.Throw<Exception>(async () => await instance.GenericCallback.InvokeAsync(EventArgs.Empty)).Message.ShouldBe("GenericCallback");
40+
new RenderedFragment(this, instance.ChildContent).GetMarkup().ShouldBe(nameof(ChildContent));
41+
new RenderedFragment(this, instance.OtherContent).GetMarkup().ShouldBe(nameof(AllTypesOfParams<string>.OtherContent));
42+
Should.Throw<Exception>(() => instance.ItemTemplate!("")(null)).Message.ShouldBe("ItemTemplate");
43+
}
44+
45+
[Fact(DisplayName = "All types of parameters are correctly assigned to component on re-render")]
46+
public void Test002()
47+
{
48+
// arrange
49+
Services.AddMockJsRuntime();
50+
var cut = RenderComponent<AllTypesOfParams<string>>();
51+
52+
// assert that no parameters have been set initially
53+
var instance = cut.Instance;
54+
instance.Attributes.ShouldBeNull();
55+
instance.RegularParam.ShouldBeNull();
56+
instance.UnnamedCascadingValue.ShouldBeNull();
57+
instance.NamedCascadingValue.ShouldBeNull();
58+
instance.NonGenericCallback.HasDelegate.ShouldBeFalse();
59+
instance.GenericCallback.HasDelegate.ShouldBeFalse();
60+
instance.ChildContent.ShouldBeNull();
61+
instance.OtherContent.ShouldBeNull();
62+
instance.ItemTemplate.ShouldBeNull();
63+
64+
// act - set components params and render
65+
cut.SetParametersAndRender(
66+
("some-unmatched-attribute", "unmatched value"),
67+
(nameof(AllTypesOfParams<string>.RegularParam), "some value"),
68+
EventCallback(nameof(AllTypesOfParams<string>.NonGenericCallback), () => throw new Exception("NonGenericCallback")),
69+
EventCallback(nameof(AllTypesOfParams<string>.GenericCallback), (EventArgs args) => throw new Exception("GenericCallback")),
70+
ChildContent<Wrapper>(ChildContent(nameof(ChildContent))),
71+
RenderFragment<Wrapper>(nameof(AllTypesOfParams<string>.OtherContent), ChildContent(nameof(AllTypesOfParams<string>.OtherContent))),
72+
Template<string>(nameof(AllTypesOfParams<string>.ItemTemplate), (item) => (builder) => throw new Exception("ItemTemplate"))
73+
);
74+
75+
instance.Attributes["some-unmatched-attribute"].ShouldBe("unmatched value");
76+
instance.RegularParam.ShouldBe("some value");
77+
Should.Throw<Exception>(async () => await instance.NonGenericCallback.InvokeAsync(null)).Message.ShouldBe("NonGenericCallback");
78+
Should.Throw<Exception>(async () => await instance.GenericCallback.InvokeAsync(EventArgs.Empty)).Message.ShouldBe("GenericCallback");
79+
new RenderedFragment(this, instance.ChildContent).GetMarkup().ShouldBe(nameof(ChildContent));
80+
new RenderedFragment(this, instance.OtherContent).GetMarkup().ShouldBe(nameof(AllTypesOfParams<string>.OtherContent));
81+
Should.Throw<Exception>(() => instance.ItemTemplate!("")(null)).Message.ShouldBe("ItemTemplate");
82+
}
83+
84+
[Fact(DisplayName = "Trying to set CascadingValue during SetParametersAndRender throws")]
85+
public void Test003()
86+
{
87+
// arrange
88+
Services.AddMockJsRuntime();
89+
var cut = RenderComponent<AllTypesOfParams<string>>();
90+
91+
// assert
92+
Should.Throw<InvalidOperationException>(() => cut.SetParametersAndRender(CascadingValue(42)));
93+
Should.Throw<InvalidOperationException>(() => cut.SetParametersAndRender(CascadingValue(nameof(AllTypesOfParams<string>.NamedCascadingValue), 1337)));
94+
}
95+
}
96+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@typeparam TItem
2+
@inject IJSRuntime jsRuntime
3+
@code {
4+
[Parameter(CaptureUnmatchedValues = true)]
5+
public IReadOnlyDictionary<string, object> Attributes { get; set; }
6+
7+
[Parameter]
8+
public string? RegularParam { get; set; }
9+
10+
[CascadingParameter]
11+
public int? UnnamedCascadingValue { get; set; }
12+
13+
[CascadingParameter(Name = nameof(NamedCascadingValue))]
14+
public int? NamedCascadingValue { get; set; }
15+
16+
[Parameter]
17+
public EventCallback NonGenericCallback { get; set; }
18+
19+
[Parameter]
20+
public EventCallback<EventArgs> GenericCallback { get; set; }
21+
22+
[Parameter]
23+
public RenderFragment? ChildContent { get; set; }
24+
25+
[Parameter]
26+
public RenderFragment? OtherContent { get; set; }
27+
28+
[Parameter]
29+
public RenderFragment<TItem>? ItemTemplate { get; set; }
30+
}

0 commit comments

Comments
 (0)