Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add InvokeAsync overload #177

Merged
merged 10 commits into from
Aug 5, 2020
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
### Added
List of new features.

- Added `InvokeAsync(Func<Task>)` to `IRenderedComponentBase`. By [@JeroenBos](https://github.com/JeroenBos) in [#151](https://github.com/egil/bUnit/pull/177).
egil marked this conversation as resolved.
Show resolved Hide resolved

egil marked this conversation as resolved.
Show resolved Hide resolved
### Changed
List of changes in existing functionality.

egil marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
1 change: 1 addition & 0 deletions src/bunit.core.tests/ComponentParameterFactoryTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bunit.Rendering;
using Bunit.TestAssets.SampleComponents;
using Bunit.TestDoubles.JSInterop;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Bunit.TestAssets.SampleComponents;
using Shouldly;
using Xunit;

namespace Bunit.Extensions
{
public class RenderedComponentInvokeAsyncExtensionsTest : TestContext
egil marked this conversation as resolved.
Show resolved Hide resolved
{
[Fact(DisplayName = "Dispatcher awaits Task-returning callback")]
public async Task Test003()
{
// Arrange
var cut = RenderComponent<Simple1>();
bool delegateFinished = false;

async Task Callback()
{
await Task.Delay(10);
delegateFinished = true;
}

// Act
await cut.InvokeAsync(Callback);

// Assert
delegateFinished.ShouldBeTrue();
}

[Fact(DisplayName = "Dispatcher does not await void-returning callback")]
public async Task Test004()
{
// Arrange
var cut = RenderComponent<Simple1>();
bool delegateFinished = false;

async void Callback()
{
await Task.Delay(10);
delegateFinished = true;
}

// Act
await cut.InvokeAsync(Callback);

// Assert
delegateFinished.ShouldBeFalse();
}
}
}
44 changes: 44 additions & 0 deletions src/bunit.core/Extensions/RenderedComponentInvokeAsyncExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Bunit.Rendering;
using Microsoft.AspNetCore.Components;

namespace Bunit
{
/// <summary>
/// InvokeAsync extensions methods on <see cref="IRenderedComponentBase{TComponent}"/>.
/// </summary>
public static class RenderedComponentInvokeAsyncExtensions
{
/// <summary>
/// Invokes the given <paramref name="callback"/> in the context of the associated <see cref="ITestRenderer"/>.
/// </summary>
/// <param name="renderedComponent">The rendered component whose dispatcher to invoke with.</param>
/// <param name="callback"></param>
/// <returns>A <see cref="Task"/> that will be completed when the action has finished executing.</returns>
egil marked this conversation as resolved.
Show resolved Hide resolved
public static Task InvokeAsync<TComponent>(this IRenderedComponentBase<TComponent> renderedComponent, Action callback)
where TComponent : IComponent
{
if (renderedComponent is null)
throw new ArgumentNullException(nameof(renderedComponent));

return renderedComponent.Renderer.Dispatcher.InvokeAsync(callback);
}

/// <summary>
/// Invokes the given <paramref name="callback"/> in the context of the associated <see cref="ITestRenderer"/>.
/// </summary>
/// <param name="renderedComponent">The rendered component whose dispatcher to invoke with.</param>
/// <param name="callback"></param>
/// <returns>A <see cref="Task"/> that will be completed when the action has finished executing.</returns>
public static Task InvokeAsync<TComponent>(this IRenderedComponentBase<TComponent> renderedComponent, Func<Task> callback)
where TComponent : IComponent
{
if (renderedComponent is null)
throw new ArgumentNullException(nameof(renderedComponent));

return renderedComponent.Renderer.Dispatcher.InvokeAsync(callback);
}
}
}
87 changes: 87 additions & 0 deletions src/bunit.core/Extensions/RenderedComponentRenderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Bunit.Rendering;
using Microsoft.AspNetCore.Components;

namespace Bunit
{
/// <summary>
/// Re-render extension methods, optionally with new parameters, for <see cref="IRenderedComponentBase{TComponent}"/>.
/// </summary>
public static class RenderedComponentRenderExtensions
{
/// <summary>
/// Render the component under test again.
/// </summary>
/// <param name="renderedComponent">The rendered component to re-render.</param>
public static void Render<TComponent>(this IRenderedComponentBase<TComponent> renderedComponent) where TComponent : IComponent
=> SetParametersAndRender(renderedComponent, ParameterView.Empty);

/// <summary>
/// Render the component under test again with the provided <paramref name="parameters"/>.
/// </summary>
/// <param name="renderedComponent">The rendered component to re-render with new parameters</param>
/// <param name="parameters">Parameters to pass to the component upon rendered</param>
public static void SetParametersAndRender<TComponent>(this IRenderedComponentBase<TComponent> renderedComponent, ParameterView parameters)
where TComponent : IComponent
{
if (renderedComponent is null)
throw new ArgumentNullException(nameof(renderedComponent));

renderedComponent.InvokeAsync(() =>
{
renderedComponent.Instance.SetParametersAsync(parameters);
});
}

/// <summary>
/// Render the component under test again with the provided <paramref name="parameters"/>.
/// </summary>
/// <param name="renderedComponent">The rendered component to re-render with new parameters</param>
/// <param name="parameters">Parameters to pass to the component upon rendered</param>
public static void SetParametersAndRender<TComponent>(this IRenderedComponentBase<TComponent> renderedComponent, params ComponentParameter[] parameters) where TComponent : IComponent
{
if (renderedComponent is null)
throw new ArgumentNullException(nameof(renderedComponent));
SetParametersAndRender(renderedComponent, ToParameterView(parameters));
}

/// <summary>
/// Render the component under test again with the provided parameters from the <paramref name="parameterBuilder"/>.
/// </summary>
/// <param name="renderedComponent">The rendered component to re-render with new parameters</param>
/// <param name="parameterBuilder">An action that receives a <see cref="ComponentParameterBuilder{TComponent}"/>.</param>
public static void SetParametersAndRender<TComponent>(this IRenderedComponentBase<TComponent> renderedComponent, Action<ComponentParameterBuilder<TComponent>> parameterBuilder)
where TComponent : IComponent
{
if (renderedComponent is null)
throw new ArgumentNullException(nameof(renderedComponent));
if (parameterBuilder is null)
throw new ArgumentNullException(nameof(parameterBuilder));

var builder = new ComponentParameterBuilder<TComponent>();
parameterBuilder(builder);

SetParametersAndRender(renderedComponent, ToParameterView(builder.Build()));
}


private static ParameterView ToParameterView(IReadOnlyList<ComponentParameter> parameters)
{
var parameterView = ParameterView.Empty;
if (parameters.Any())
{
var paramDict = new Dictionary<string, object?>();
foreach (var param in parameters)
{
if (param.IsCascadingValue)
throw new InvalidOperationException($"You cannot provide a new cascading value through the {nameof(SetParametersAndRender)} method.");
paramDict.Add(param.Name!, param.Value);
}
parameterView = ParameterView.FromDictionary(paramDict);
}
return parameterView;
}
}
}
30 changes: 0 additions & 30 deletions src/bunit.core/IRenderedComponentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,5 @@ public interface IRenderedComponentBase<TComponent> : IRenderedFragmentBase wher
/// Gets the component under test
/// </summary>
TComponent Instance { get; }

/// <summary>
/// Invokes the given <paramref name="callback"/> in the context of the associated <see cref="ITestRenderer"/>.
/// </summary>
/// <param name="callback"></param>
/// <returns>A <see cref="Task"/> that will be completed when the action has finished executing.</returns>
Task InvokeAsync(Action callback);

/// <summary>
/// Render the component under test again.
/// </summary>
void Render();

/// <summary>
/// Render the component under test again with the provided <paramref name="parameters"/>.
/// </summary>
/// <param name="parameters">Parameters to pass to the component upon rendered</param>
void SetParametersAndRender(ParameterView parameters);

/// <summary>
/// Render the component under test again with the provided <paramref name="parameters"/>.
/// </summary>
/// <param name="parameters">Parameters to pass to the component upon rendered</param>
void SetParametersAndRender(params ComponentParameter[] parameters);

/// <summary>
/// Render the component under test again with the provided parameters from the <paramref name="parameterBuilder"/>.
/// </summary>
/// <param name="parameterBuilder">An action that receives a <see cref="ComponentParameterBuilder{TComponent}"/>.</param>
void SetParametersAndRender(Action<ComponentParameterBuilder<TComponent>> parameterBuilder);
}
}
7 changes: 6 additions & 1 deletion src/bunit.core/IRenderedFragmentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public interface IRenderedFragmentBase
/// <summary>
/// Gets the <see cref="IServiceProvider"/> used when rendering the component.
/// </summary>
IServiceProvider Services { get; }
IServiceProvider Services { get; }

/// <summary>
/// Gets the <see cref="ITestRenderer"/> renderer that rendered the component.
/// </summary>
ITestRenderer Renderer { get; }
}
}
52 changes: 1 addition & 51 deletions src/bunit.web/Rendering/RenderedComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,56 +15,6 @@ internal class RenderedComponent<TComponent> : RenderedFragment, IRenderedCompon
public RenderedComponent(IServiceProvider services, int componentId, TComponent component) : base(services, componentId)
{
Instance = component;
}

/// <inheritdoc/>
public Task InvokeAsync(Action callback) => Renderer.Dispatcher.InvokeAsync(callback);

/// <inheritdoc/>
public void Render() => SetParametersAndRender(ParameterView.Empty);

/// <inheritdoc/>
public void SetParametersAndRender(ParameterView parameters)
{
InvokeAsync(() =>
{
Instance.SetParametersAsync(parameters);
});
}

/// <inheritdoc/>
public void SetParametersAndRender(params ComponentParameter[] parameters)
{
SetParametersAndRender(ToParameterView(parameters));
}

/// <inheritdoc/>
public void SetParametersAndRender(Action<ComponentParameterBuilder<TComponent>> parameterBuilder)
{
if (parameterBuilder is null)
throw new ArgumentNullException(nameof(parameterBuilder));

var builder = new ComponentParameterBuilder<TComponent>();
parameterBuilder(builder);

SetParametersAndRender(ToParameterView(builder.Build()));
}

private static ParameterView ToParameterView(IReadOnlyList<ComponentParameter> parameters)
{
var parameterView = ParameterView.Empty;
if (parameters.Any())
{
var paramDict = new Dictionary<string, object?>();
foreach (var param in parameters)
{
if (param.IsCascadingValue)
throw new InvalidOperationException($"You cannot provide a new cascading value through the {nameof(SetParametersAndRender)} method.");
paramDict.Add(param.Name!, param.Value);
}
parameterView = ParameterView.FromDictionary(paramDict);
}
return parameterView;
}
}
}
}
8 changes: 3 additions & 5 deletions src/bunit.web/Rendering/RenderedFragment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,14 @@ public class RenderedFragment : IRenderedFragment, IRenderEventHandler

private HtmlParser HtmlParser { get; }

/// <summary>
/// Gets the renderer used to render the <see cref="IRenderedFragmentBase"/>.
/// </summary>
protected ITestRenderer Renderer { get; }

/// <summary>
/// Gets the first rendered markup.
/// </summary>
protected string FirstRenderMarkup { get; }

/// <inheritdoc/>
public ITestRenderer Renderer { get; }

/// <inheritdoc/>
public IServiceProvider Services { get; }

Expand Down