Skip to content

Commit 122f9f5

Browse files
committed
Changes
1 parent 186eeef commit 122f9f5

36 files changed

Lines changed: 519 additions & 610 deletions

src/bunit.core/Extensions/ComponentParamenterExtensions.cs

Lines changed: 71 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,68 +10,85 @@ namespace Bunit
1010
/// Helpful extensions for working with <see cref="ComponentParameter"/> and collections of these.
1111
/// </summary>
1212
public static class ComponentParamenterExtensions
13-
{
14-
/// <summary>
15-
/// Creates a <see cref="RenderFragment"/> that will render a component of <typeparamref name="TComponent"/> type,
16-
/// with the provided <paramref name="parameters"/>. If one or more of the <paramref name="parameters"/> include
17-
/// a cascading values, the <typeparamref name="TComponent"/> will be wrapped in <see cref="Microsoft.AspNetCore.Components.CascadingValue{TValue}"/>
18-
/// components.
19-
/// </summary>
20-
/// <typeparam name="TComponent">Type of component to render in the render fragment</typeparam>
21-
/// <param name="parameters">Parameters to pass to the component</param>
22-
/// <returns>The <see cref="RenderFragment"/>.</returns>
23-
public static RenderFragment ToComponentRenderFragment<TComponent>(this IReadOnlyList<ComponentParameter> parameters) where TComponent : class, IComponent
24-
{
25-
var cascadingParams = new Queue<ComponentParameter>(parameters.Where(x => x.IsCascadingValue));
13+
{
14+
/// <summary>
15+
/// Converts a collection of <see cref="ComponentParameter"/> into a <see cref="ParameterValue"/>.
16+
/// </summary>
17+
/// <param name="parameters">Collection to convert.</param>
18+
/// <returns>The <see cref="ParameterValue"/>.</returns>
19+
public static ParameterView ToParameterView(this IEnumerable<ComponentParameter> parameters)
20+
{
21+
if (parameters.Any())
22+
{
23+
var p = parameters.Where(x => !x.IsCascadingValue).ToDictionary(x => x.Name, x => x.Value);
24+
return ParameterView.FromDictionary(p);
25+
}
26+
else
27+
return ParameterView.Empty;
28+
}
2629

27-
if (cascadingParams.Count > 0)
28-
return CreateCascadingValueRenderFragment(cascadingParams, parameters);
29-
else
30-
return CreateComponentRenderFragment(parameters);
30+
/// <summary>
31+
/// Creates a <see cref="RenderFragment"/> that will render a component of <typeparamref name="TComponent"/> type,
32+
/// with the provided <paramref name="parameters"/>. If one or more of the <paramref name="parameters"/> include
33+
/// a cascading values, the <typeparamref name="TComponent"/> will be wrapped in <see cref="Microsoft.AspNetCore.Components.CascadingValue{TValue}"/>
34+
/// components.
35+
/// </summary>
36+
/// <typeparam name="TComponent">Type of component to render in the render fragment</typeparam>
37+
/// <param name="parameters">Parameters to pass to the component</param>
38+
/// <returns>The <see cref="RenderFragment"/>.</returns>
39+
public static RenderFragment ToComponentRenderFragment<TComponent>(this IReadOnlyList<ComponentParameter> parameters) where TComponent : class, IComponent
40+
{
41+
var cascadingParams = new Queue<ComponentParameter>(parameters.Where(x => x.IsCascadingValue));
3142

32-
static RenderFragment CreateCascadingValueRenderFragment(Queue<ComponentParameter> cascadingParams, IReadOnlyList<ComponentParameter> parameters)
33-
{
34-
var cp = cascadingParams.Dequeue();
35-
var cascadingValueType = GetCascadingValueType(cp);
36-
return builder =>
37-
{
38-
builder.OpenComponent(0, cascadingValueType);
39-
if (cp.Name is { })
40-
builder.AddAttribute(1, nameof(CascadingValue<object>.Name), cp.Name);
43+
if (cascadingParams.Count > 0)
44+
return CreateCascadingValueRenderFragment(cascadingParams, parameters);
45+
else
46+
return CreateComponentRenderFragment(parameters);
4147

42-
builder.AddAttribute(2, nameof(CascadingValue<object>.Value), cp.Value);
43-
builder.AddAttribute(3, nameof(CascadingValue<object>.IsFixed), true);
48+
static RenderFragment CreateCascadingValueRenderFragment(Queue<ComponentParameter> cascadingParams, IReadOnlyList<ComponentParameter> parameters)
49+
{
50+
var cp = cascadingParams.Dequeue();
51+
var cascadingValueType = GetCascadingValueType(cp);
52+
return builder =>
53+
{
54+
builder.OpenComponent(0, cascadingValueType);
55+
if (cp.Name is { })
56+
builder.AddAttribute(1, nameof(CascadingValue<object>.Name), cp.Name);
4457

45-
if (cascadingParams.Count > 0)
46-
builder.AddAttribute(4, nameof(CascadingValue<object>.ChildContent), CreateCascadingValueRenderFragment(cascadingParams, parameters));
47-
else
48-
builder.AddAttribute(4, nameof(CascadingValue<object>.ChildContent), CreateComponentRenderFragment(parameters));
58+
builder.AddAttribute(2, nameof(CascadingValue<object>.Value), cp.Value);
59+
builder.AddAttribute(3, nameof(CascadingValue<object>.IsFixed), true);
4960

50-
builder.CloseComponent();
51-
};
52-
}
61+
if (cascadingParams.Count > 0)
62+
builder.AddAttribute(4, nameof(CascadingValue<object>.ChildContent), CreateCascadingValueRenderFragment(cascadingParams, parameters));
63+
else
64+
builder.AddAttribute(4, nameof(CascadingValue<object>.ChildContent), CreateComponentRenderFragment(parameters));
5365

54-
static RenderFragment CreateComponentRenderFragment(IReadOnlyList<ComponentParameter> parameters)
55-
{
56-
return builder =>
57-
{
58-
builder.OpenComponent(0, typeof(TComponent));
66+
builder.CloseComponent();
67+
};
68+
}
5969

60-
foreach (var parameterValue in parameters.Where(x => !x.IsCascadingValue))
61-
builder.AddAttribute(1, parameterValue.Name, parameterValue.Value);
70+
static RenderFragment CreateComponentRenderFragment(IReadOnlyList<ComponentParameter> parameters)
71+
{
72+
return builder =>
73+
{
74+
builder.OpenComponent(0, typeof(TComponent));
6275

63-
builder.CloseComponent();
64-
};
65-
}
66-
}
76+
foreach (var parameterValue in parameters.Where(x => !x.IsCascadingValue))
77+
builder.AddAttribute(1, parameterValue.Name, parameterValue.Value);
6778

68-
private static readonly Type CascadingValueType = typeof(CascadingValue<>);
79+
builder.CloseComponent();
80+
};
81+
}
82+
}
6983

70-
private static Type GetCascadingValueType(ComponentParameter parameter)
71-
{
72-
if (parameter.Value is null) throw new InvalidOperationException("Cannot get the type of a null object");
73-
var cascadingValueType = parameter.Value.GetType();
74-
return CascadingValueType.MakeGenericType(cascadingValueType);
75-
}
76-
}
84+
private static readonly Type CascadingValueType = typeof(CascadingValue<>);
85+
86+
private static Type GetCascadingValueType(ComponentParameter parameter)
87+
{
88+
if (parameter.Value is null)
89+
throw new InvalidOperationException("Cannot get the type of a null object");
90+
var cascadingValueType = parameter.Value.GetType();
91+
return CascadingValueType.MakeGenericType(cascadingValueType);
92+
}
93+
}
7794
}

src/bunit.core/Extensions/WaitForExtensions/RenderWaitingHelperExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public static void WaitForAssertion(this ITestContext testContext, Action assert
6666
/// <param name="timeout">The maximum time to wait for the desired state.</param>
6767
/// <exception cref="ArgumentNullException">Thrown if <paramref name="renderedFragment"/> is null.</exception>
6868
/// <exception cref="WaitForStateFailedException">Thrown if the <paramref name="statePredicate"/> throw an exception during invocation, or if the timeout has been reached. See the inner exception for details.</exception>
69-
public static void WaitForState(this IRenderedFragmentCore renderedFragment, Func<bool> statePredicate, TimeSpan? timeout = null)
69+
public static void WaitForState(this IRenderedFragmentBase renderedFragment, Func<bool> statePredicate, TimeSpan? timeout = null)
7070
=> WaitForState(renderedFragment.RenderEvents, statePredicate, timeout);
7171

7272
/// <summary>
@@ -81,7 +81,7 @@ public static void WaitForState(this IRenderedFragmentCore renderedFragment, Fun
8181
/// <param name="timeout">The maximum time to attempt the verification.</param>
8282
/// <exception cref="ArgumentNullException">Thrown if <paramref name="renderedFragment"/> is null.</exception>
8383
/// <exception cref="WaitForAssertionFailedException">Thrown if the timeout has been reached. See the inner exception to see the captured assertion exception.</exception>
84-
public static void WaitForAssertion(this IRenderedFragmentCore renderedFragment, Action assertion, TimeSpan? timeout = null)
84+
public static void WaitForAssertion(this IRenderedFragmentBase renderedFragment, Action assertion, TimeSpan? timeout = null)
8585
=> WaitForAssertion(renderedFragment.RenderEvents, assertion, timeout);
8686

8787
private static void WaitForRender(IObservable<RenderEvent> renderEventObservable, Action? renderTrigger = null, TimeSpan? timeout = null)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace Bunit
77
/// Represents a rendered component-under-test.
88
/// </summary>
99
/// <typeparam name="TComponent">The type of the component under test</typeparam>
10-
public interface IRenderedComponentCore<out TComponent> : IRenderedFragmentCore where TComponent : IComponent
10+
public interface IRenderedComponentBase<out TComponent> : IRenderedFragmentBase where TComponent : IComponent
1111
{
1212
/// <summary>
1313
/// Gets the component under test
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Bunit
66
/// <summary>
77
/// Represents a rendered fragment.
88
/// </summary>
9-
public interface IRenderedFragmentCore
9+
public interface IRenderedFragmentBase
1010
{
1111
/// <summary>
1212
/// Gets the id of the rendered component or fragment.
@@ -15,7 +15,7 @@ public interface IRenderedFragmentCore
1515

1616
/// <summary>
1717
/// Gets an <see cref="IObservable{RenderEvent}"/> which will provide subscribers with <see cref="RenderEvent"/>s
18-
/// whenever the <see cref="IRenderedFragmentCore"/> is rendered.
18+
/// whenever the <see cref="IRenderedFragmentBase"/> is rendered.
1919
/// </summary>
2020
IObservable<RenderEvent> RenderEvents { get; }
2121

src/bunit.core/ITestContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public partial interface ITestContext : IDisposable
1212
/// <summary>
1313
/// Gets the renderer used by the test context.
1414
/// </summary>
15-
TestRenderer Renderer { get; }
15+
ITestRenderer Renderer { get; }
1616

1717
/// <summary>
1818
/// Gets the service collection and service provider that is used when a
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Bunit.Rendering.RenderEvents;
4+
using Microsoft.AspNetCore.Components;
5+
using Microsoft.AspNetCore.Components.RenderTree;
6+
7+
namespace Bunit.Rendering
8+
{
9+
public interface ITestRenderer
10+
{
11+
Dispatcher Dispatcher { get; }
12+
IObservable<RenderEvent> RenderEvents { get; }
13+
14+
Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs);
15+
(int ComponentId, TComponent Component) FindComponent<TComponent>(int parentComponentId);
16+
System.Collections.Generic.IReadOnlyList<(int ComponentId, TComponent Component)> FindComponents<TComponent>(int parentComponentId);
17+
ArrayRange<RenderTreeFrame> GetCurrentRenderTreeFrames(int componentId);
18+
void InvokeAsync(Action callback);
19+
Task<(int ComponentId, TComponent Component)> RenderComponent<TComponent>(params ComponentParameter[] parameters) where TComponent : IComponent;
20+
Task<int> RenderFragment(RenderFragment renderFragment);
21+
}
22+
}

src/bunit.core/Rendering/RenderEvents/ComponentChangeEventSubscriber.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public sealed class ComponentChangeEventSubscriber : ConcurrentRenderEventSubscr
1010
/// <summary>
1111
/// Creates an instance of the <see cref="ComponentChangeEventSubscriber"/>.
1212
/// </summary>
13-
public ComponentChangeEventSubscriber(IRenderedFragmentCore testTarget, Action<RenderEvent>? onChange = null, Action? onCompleted = null)
13+
public ComponentChangeEventSubscriber(IRenderedFragmentBase testTarget, Action<RenderEvent>? onChange = null, Action? onCompleted = null)
1414
: base((testTarget ?? throw new ArgumentNullException(nameof(testTarget))).RenderEvents, onChange, onCompleted)
1515
{
1616
_targetComponentId = testTarget.ComponentId;

src/bunit.core/Rendering/RenderEvents/ConcurrentRenderEventSubscriber.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Bunit.Rendering.RenderEvents
55
{
66
/// <summary>
77
/// Represents a subscriber to <see cref="RenderEvent"/>s, published by
8-
/// the <see cref="TestRenderer"/>.
8+
/// the <see cref="ITestRenderer"/>.
99
/// </summary>
1010
public class ConcurrentRenderEventSubscriber : IObserver<RenderEvent>
1111
{
@@ -22,13 +22,13 @@ public class ConcurrentRenderEventSubscriber : IObserver<RenderEvent>
2222
public int RenderCount => Volatile.Read(ref _renderCount);
2323

2424
/// <summary>
25-
/// Gets whether the <see cref="TestRenderer"/> is disposed an no more
25+
/// Gets whether the <see cref="ITestRenderer"/> is disposed an no more
2626
/// renders will happen.
2727
/// </summary>
2828
public bool IsCompleted => Volatile.Read(ref _isCompleted);
2929

3030
/// <summary>
31-
/// Gets the latests <see cref="RenderEvent"/> received by the <see cref="TestRenderer"/>.
31+
/// Gets the latests <see cref="RenderEvent"/> received by the <see cref="ITestRenderer"/>.
3232
/// </summary>
3333
public RenderEvent? LatestRenderEvent => Volatile.Read(ref _latestRenderEvent);
3434

src/bunit.core/Rendering/RenderEvents/RenderEvent.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
namespace Bunit.Rendering.RenderEvents
55
{
66
/// <summary>
7-
/// Represents a render event from a <see cref="TestRenderer"/>.
7+
/// Represents a render event from a <see cref="ITestRenderer"/>.
88
/// </summary>
99
public sealed class RenderEvent
1010
{
11-
private readonly TestRenderer _renderer;
11+
private readonly ITestRenderer _renderer;
1212
private readonly RenderBatch _renderBatch;
1313

1414
/// <summary>
@@ -19,7 +19,7 @@ public sealed class RenderEvent
1919
/// <summary>
2020
/// Creates an instance of the <see cref="RenderEvent"/> type.
2121
/// </summary>
22-
public RenderEvent(in RenderBatch renderBatch, TestRenderer renderer)
22+
public RenderEvent(in RenderBatch renderBatch, ITestRenderer renderer)
2323
{
2424
_renderBatch = renderBatch;
2525
_renderer = renderer;

src/bunit.core/Rendering/TestRenderer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace Bunit.Rendering
1414
/// <summary>
1515
/// Generalized Blazor renderer for testing purposes.
1616
/// </summary>
17-
public class TestRenderer : Renderer
17+
public class TestRenderer : Renderer, ITestRenderer
1818
{
1919
private const string LOGGER_CATEGORY = nameof(Bunit) + "." + nameof(TestRenderer);
2020
private static readonly Type CascadingValueType = typeof(CascadingValue<>);

0 commit comments

Comments
 (0)