From fb20acd6d7dfe6717b35e9ad9e2b075fa7d398ec Mon Sep 17 00:00:00 2001 From: Jeroen Bos Date: Wed, 5 Aug 2020 15:57:59 +0200 Subject: [PATCH 01/10] Add InvokeAsync overloads --- src/bunit.core/IRenderedComponentBase.cs | 7 +++++++ src/bunit.web/Rendering/RenderedComponent.cs | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/bunit.core/IRenderedComponentBase.cs b/src/bunit.core/IRenderedComponentBase.cs index 8b0ff0490..92ae2fa75 100644 --- a/src/bunit.core/IRenderedComponentBase.cs +++ b/src/bunit.core/IRenderedComponentBase.cs @@ -24,6 +24,13 @@ public interface IRenderedComponentBase : IRenderedFragmentBase wher /// A that will be completed when the action has finished executing. Task InvokeAsync(Action callback); + /// + /// Invokes the given in the context of the associated . + /// + /// + /// A that will be completed when the action has finished executing. + Task InvokeAsync(Func callback); + /// /// Render the component under test again. /// diff --git a/src/bunit.web/Rendering/RenderedComponent.cs b/src/bunit.web/Rendering/RenderedComponent.cs index 9ff1e4ea5..0b597a8e6 100644 --- a/src/bunit.web/Rendering/RenderedComponent.cs +++ b/src/bunit.web/Rendering/RenderedComponent.cs @@ -20,6 +20,9 @@ public RenderedComponent(IServiceProvider services, int componentId, TComponent /// public Task InvokeAsync(Action callback) => Renderer.Dispatcher.InvokeAsync(callback); + /// + public Task InvokeAsync(Func callback) => Renderer.Dispatcher.InvokeAsync(callback); + /// public void Render() => SetParametersAndRender(ParameterView.Empty); From 14d3b80dc40a20e8a9d542d02b6b58fa32f3d3d8 Mon Sep 17 00:00:00 2001 From: Jeroen Bos Date: Wed, 5 Aug 2020 15:58:19 +0200 Subject: [PATCH 02/10] Add tests --- .../ComponentParameterFactoryTest.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/bunit.core.tests/ComponentParameterFactoryTest.cs b/src/bunit.core.tests/ComponentParameterFactoryTest.cs index c57dc1682..c1412a173 100644 --- a/src/bunit.core.tests/ComponentParameterFactoryTest.cs +++ b/src/bunit.core.tests/ComponentParameterFactoryTest.cs @@ -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; @@ -83,6 +84,46 @@ public void Test002() Should.Throw(() => instance.ItemTemplate!("")(null)).Message.ShouldBe("ItemTemplate"); } + [Fact(DisplayName = "Dispatcher awaits Task-returning callback")] + public async Task Test003() + { + // Arrange + var cut = RenderComponent(); + bool delegateFinished = false; + + async Task Callback() + { + await Task.Delay(10); + delegateFinished = true; + } + + // Act + await cut.InvokeAsync(Callback); + + // Assert + delegateFinished.ShouldBe(true); + } + + [Fact(DisplayName = "Dispatcher does not await void-returning callback")] + public async Task Test004() + { + // Arrange + var cut = RenderComponent(); + bool delegateFinished = false; + + async void Callback() + { + await Task.Delay(10); + delegateFinished = true; + } + + // Act + await cut.InvokeAsync(Callback); + + // Assert + delegateFinished.ShouldBe(false); + } + [Fact(DisplayName = "Template(name, markupFactory) helper correctly renders markup template")] public void Test100() { From b48b297cb914dbd3dabc8889aa7fadb9203c308c Mon Sep 17 00:00:00 2001 From: Jeroen Bos Date: Wed, 5 Aug 2020 15:48:14 +0200 Subject: [PATCH 03/10] Make methods on IRenderedComponentBase`1 awaitable --- .../ComponentParameterFactoryTest.cs | 2 +- src/bunit.core/IRenderedComponentBase.cs | 8 ++++---- .../Rendering/Internal/HtmlizerTests.cs | 2 +- .../Rendering/RenderedComponentTest.cs | 8 ++++---- .../Rendering/RenderedFragmentTest.cs | 2 +- src/bunit.web/Rendering/RenderedComponent.cs | 19 ++++++++----------- 6 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/bunit.core.tests/ComponentParameterFactoryTest.cs b/src/bunit.core.tests/ComponentParameterFactoryTest.cs index c1412a173..bd4391060 100644 --- a/src/bunit.core.tests/ComponentParameterFactoryTest.cs +++ b/src/bunit.core.tests/ComponentParameterFactoryTest.cs @@ -65,7 +65,7 @@ public void Test002() instance.ItemTemplate.ShouldBeNull(); // act - set components params and render - cut.SetParametersAndRender( + cut.SetParametersAndRenderAsync( ("some-unmatched-attribute", "unmatched value"), (nameof(AllTypesOfParams.RegularParam), "some value"), EventCallback(nameof(AllTypesOfParams.NonGenericCallback), () => throw new Exception("NonGenericCallback")), diff --git a/src/bunit.core/IRenderedComponentBase.cs b/src/bunit.core/IRenderedComponentBase.cs index 92ae2fa75..998f70e83 100644 --- a/src/bunit.core/IRenderedComponentBase.cs +++ b/src/bunit.core/IRenderedComponentBase.cs @@ -34,24 +34,24 @@ public interface IRenderedComponentBase : IRenderedFragmentBase wher /// /// Render the component under test again. /// - void Render(); + Task Render(); /// /// Render the component under test again with the provided . /// /// Parameters to pass to the component upon rendered - void SetParametersAndRender(ParameterView parameters); + Task SetParametersAndRenderAsync(ParameterView parameters); /// /// Render the component under test again with the provided . /// /// Parameters to pass to the component upon rendered - void SetParametersAndRender(params ComponentParameter[] parameters); + Task SetParametersAndRenderAsync(params ComponentParameter[] parameters); /// /// Render the component under test again with the provided parameters from the . /// /// An action that receives a . - void SetParametersAndRender(Action> parameterBuilder); + Task SetParametersAndRenderAsync(Action> parameterBuilder); } } diff --git a/src/bunit.web.tests/Rendering/Internal/HtmlizerTests.cs b/src/bunit.web.tests/Rendering/Internal/HtmlizerTests.cs index 7c8d995a7..8ebc8a1f0 100644 --- a/src/bunit.web.tests/Rendering/Internal/HtmlizerTests.cs +++ b/src/bunit.web.tests/Rendering/Internal/HtmlizerTests.cs @@ -47,7 +47,7 @@ public void Test003() var cut = RenderComponent(); cut.Find("button").HasAttribute("blazor:elementreference").ShouldBeTrue(); - cut.SetParametersAndRender(parameters => parameters.Add(p => p.OnClick, (MouseEventArgs e) => { })); + cut.SetParametersAndRenderAsync(parameters => parameters.Add(p => p.OnClick, (MouseEventArgs e) => { })); cut.Find("button").HasAttribute("blazor:elementreference").ShouldBeTrue(); } diff --git a/src/bunit.web.tests/Rendering/RenderedComponentTest.cs b/src/bunit.web.tests/Rendering/RenderedComponentTest.cs index e9533c313..5c142e314 100644 --- a/src/bunit.web.tests/Rendering/RenderedComponentTest.cs +++ b/src/bunit.web.tests/Rendering/RenderedComponentTest.cs @@ -25,7 +25,7 @@ public void Test004() { var cut = RenderComponent(parameters => parameters.AddChildContent("
")); - cut.SetParametersAndRender(parameters => parameters.AddChildContent("

")); + cut.SetParametersAndRenderAsync(parameters => parameters.AddChildContent("

")); cut.Find("p").ShouldNotBeNull(); } @@ -36,7 +36,7 @@ public void Test0041() { var cut = RenderComponent(parameters => parameters.AddChildContent("

")); - cut.SetParametersAndRender(parameters => parameters.AddChildContent("

")); + cut.SetParametersAndRenderAsync(parameters => parameters.AddChildContent("

")); cut.Find("p").ShouldNotBeNull(); } @@ -50,8 +50,8 @@ public void Test003() var cut = RenderComponent>(); // assert - Should.Throw(() => cut.SetParametersAndRender(ps => ps.Add(p => p.UnnamedCascadingValue, 42))); - Should.Throw(() => cut.SetParametersAndRender(ps => ps.Add(p => p.NamedCascadingValue, 1337))); + Should.Throw(() => cut.SetParametersAndRenderAsync(ps => ps.Add(p => p.UnnamedCascadingValue, 42))); + Should.Throw(() => cut.SetParametersAndRenderAsync(ps => ps.Add(p => p.NamedCascadingValue, 1337))); } } } diff --git a/src/bunit.web.tests/Rendering/RenderedFragmentTest.cs b/src/bunit.web.tests/Rendering/RenderedFragmentTest.cs index ac01f291f..577409c8a 100644 --- a/src/bunit.web.tests/Rendering/RenderedFragmentTest.cs +++ b/src/bunit.web.tests/Rendering/RenderedFragmentTest.cs @@ -83,7 +83,7 @@ public void Test009() cut.Instance.Counter.ShouldBe(1); - cut.SetParametersAndRender((nameof(ToggleClickHandler.HandleClicks), false)); + cut.SetParametersAndRenderAsync((nameof(ToggleClickHandler.HandleClicks), false)); cut.Find("#btn").Click(); diff --git a/src/bunit.web/Rendering/RenderedComponent.cs b/src/bunit.web/Rendering/RenderedComponent.cs index 0b597a8e6..928530f49 100644 --- a/src/bunit.web/Rendering/RenderedComponent.cs +++ b/src/bunit.web/Rendering/RenderedComponent.cs @@ -24,25 +24,22 @@ public RenderedComponent(IServiceProvider services, int componentId, TComponent public Task InvokeAsync(Func callback) => Renderer.Dispatcher.InvokeAsync(callback); /// - public void Render() => SetParametersAndRender(ParameterView.Empty); + public Task Render() => SetParametersAndRenderAsync(ParameterView.Empty); /// - public void SetParametersAndRender(ParameterView parameters) + public Task SetParametersAndRenderAsync(ParameterView parameters) { - InvokeAsync(() => - { - Instance.SetParametersAsync(parameters); - }); + return InvokeAsync(() => Instance.SetParametersAsync(parameters)); } /// - public void SetParametersAndRender(params ComponentParameter[] parameters) + public Task SetParametersAndRenderAsync(params ComponentParameter[] parameters) { - SetParametersAndRender(ToParameterView(parameters)); + return SetParametersAndRenderAsync(ToParameterView(parameters)); } /// - public void SetParametersAndRender(Action> parameterBuilder) + public Task SetParametersAndRenderAsync(Action> parameterBuilder) { if (parameterBuilder is null) throw new ArgumentNullException(nameof(parameterBuilder)); @@ -50,7 +47,7 @@ public void SetParametersAndRender(Action> var builder = new ComponentParameterBuilder(); parameterBuilder(builder); - SetParametersAndRender(ToParameterView(builder.Build())); + return SetParametersAndRenderAsync(ToParameterView(builder.Build())); } private static ParameterView ToParameterView(IReadOnlyList parameters) @@ -62,7 +59,7 @@ private static ParameterView ToParameterView(IReadOnlyList p foreach (var param in parameters) { if (param.IsCascadingValue) - throw new InvalidOperationException($"You cannot provide a new cascading value through the {nameof(SetParametersAndRender)} method."); + throw new InvalidOperationException($"You cannot provide a new cascading value through the {nameof(SetParametersAndRenderAsync)} method."); paramDict.Add(param.Name!, param.Value); } parameterView = ParameterView.FromDictionary(paramDict); From 4a481d0eed7856ba3ef3a57b8e06adc3854bc7b1 Mon Sep 17 00:00:00 2001 From: Jeroen Bos Date: Wed, 5 Aug 2020 15:49:58 +0200 Subject: [PATCH 04/10] Revert "Make methods on IRenderedComponentBase`1 awaitable" This reverts commit d5968062126bd820bf2523a75c508fd11b530c2f. --- .../ComponentParameterFactoryTest.cs | 2 +- src/bunit.core/IRenderedComponentBase.cs | 8 ++++---- .../Rendering/Internal/HtmlizerTests.cs | 2 +- .../Rendering/RenderedComponentTest.cs | 8 ++++---- .../Rendering/RenderedFragmentTest.cs | 2 +- src/bunit.web/Rendering/RenderedComponent.cs | 19 +++++++++++-------- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/bunit.core.tests/ComponentParameterFactoryTest.cs b/src/bunit.core.tests/ComponentParameterFactoryTest.cs index bd4391060..c1412a173 100644 --- a/src/bunit.core.tests/ComponentParameterFactoryTest.cs +++ b/src/bunit.core.tests/ComponentParameterFactoryTest.cs @@ -65,7 +65,7 @@ public void Test002() instance.ItemTemplate.ShouldBeNull(); // act - set components params and render - cut.SetParametersAndRenderAsync( + cut.SetParametersAndRender( ("some-unmatched-attribute", "unmatched value"), (nameof(AllTypesOfParams.RegularParam), "some value"), EventCallback(nameof(AllTypesOfParams.NonGenericCallback), () => throw new Exception("NonGenericCallback")), diff --git a/src/bunit.core/IRenderedComponentBase.cs b/src/bunit.core/IRenderedComponentBase.cs index 998f70e83..92ae2fa75 100644 --- a/src/bunit.core/IRenderedComponentBase.cs +++ b/src/bunit.core/IRenderedComponentBase.cs @@ -34,24 +34,24 @@ public interface IRenderedComponentBase : IRenderedFragmentBase wher ///

/// Render the component under test again. /// - Task Render(); + void Render(); /// /// Render the component under test again with the provided . /// /// Parameters to pass to the component upon rendered - Task SetParametersAndRenderAsync(ParameterView parameters); + void SetParametersAndRender(ParameterView parameters); /// /// Render the component under test again with the provided . /// /// Parameters to pass to the component upon rendered - Task SetParametersAndRenderAsync(params ComponentParameter[] parameters); + void SetParametersAndRender(params ComponentParameter[] parameters); /// /// Render the component under test again with the provided parameters from the . /// /// An action that receives a . - Task SetParametersAndRenderAsync(Action> parameterBuilder); + void SetParametersAndRender(Action> parameterBuilder); } } diff --git a/src/bunit.web.tests/Rendering/Internal/HtmlizerTests.cs b/src/bunit.web.tests/Rendering/Internal/HtmlizerTests.cs index 8ebc8a1f0..7c8d995a7 100644 --- a/src/bunit.web.tests/Rendering/Internal/HtmlizerTests.cs +++ b/src/bunit.web.tests/Rendering/Internal/HtmlizerTests.cs @@ -47,7 +47,7 @@ public void Test003() var cut = RenderComponent(); cut.Find("button").HasAttribute("blazor:elementreference").ShouldBeTrue(); - cut.SetParametersAndRenderAsync(parameters => parameters.Add(p => p.OnClick, (MouseEventArgs e) => { })); + cut.SetParametersAndRender(parameters => parameters.Add(p => p.OnClick, (MouseEventArgs e) => { })); cut.Find("button").HasAttribute("blazor:elementreference").ShouldBeTrue(); } diff --git a/src/bunit.web.tests/Rendering/RenderedComponentTest.cs b/src/bunit.web.tests/Rendering/RenderedComponentTest.cs index 5c142e314..e9533c313 100644 --- a/src/bunit.web.tests/Rendering/RenderedComponentTest.cs +++ b/src/bunit.web.tests/Rendering/RenderedComponentTest.cs @@ -25,7 +25,7 @@ public void Test004() { var cut = RenderComponent(parameters => parameters.AddChildContent("
")); - cut.SetParametersAndRenderAsync(parameters => parameters.AddChildContent("

")); + cut.SetParametersAndRender(parameters => parameters.AddChildContent("

")); cut.Find("p").ShouldNotBeNull(); } @@ -36,7 +36,7 @@ public void Test0041() { var cut = RenderComponent(parameters => parameters.AddChildContent("

")); - cut.SetParametersAndRenderAsync(parameters => parameters.AddChildContent("

")); + cut.SetParametersAndRender(parameters => parameters.AddChildContent("

")); cut.Find("p").ShouldNotBeNull(); } @@ -50,8 +50,8 @@ public void Test003() var cut = RenderComponent>(); // assert - Should.Throw(() => cut.SetParametersAndRenderAsync(ps => ps.Add(p => p.UnnamedCascadingValue, 42))); - Should.Throw(() => cut.SetParametersAndRenderAsync(ps => ps.Add(p => p.NamedCascadingValue, 1337))); + Should.Throw(() => cut.SetParametersAndRender(ps => ps.Add(p => p.UnnamedCascadingValue, 42))); + Should.Throw(() => cut.SetParametersAndRender(ps => ps.Add(p => p.NamedCascadingValue, 1337))); } } } diff --git a/src/bunit.web.tests/Rendering/RenderedFragmentTest.cs b/src/bunit.web.tests/Rendering/RenderedFragmentTest.cs index 577409c8a..ac01f291f 100644 --- a/src/bunit.web.tests/Rendering/RenderedFragmentTest.cs +++ b/src/bunit.web.tests/Rendering/RenderedFragmentTest.cs @@ -83,7 +83,7 @@ public void Test009() cut.Instance.Counter.ShouldBe(1); - cut.SetParametersAndRenderAsync((nameof(ToggleClickHandler.HandleClicks), false)); + cut.SetParametersAndRender((nameof(ToggleClickHandler.HandleClicks), false)); cut.Find("#btn").Click(); diff --git a/src/bunit.web/Rendering/RenderedComponent.cs b/src/bunit.web/Rendering/RenderedComponent.cs index 928530f49..0b597a8e6 100644 --- a/src/bunit.web/Rendering/RenderedComponent.cs +++ b/src/bunit.web/Rendering/RenderedComponent.cs @@ -24,22 +24,25 @@ public RenderedComponent(IServiceProvider services, int componentId, TComponent public Task InvokeAsync(Func callback) => Renderer.Dispatcher.InvokeAsync(callback); /// - public Task Render() => SetParametersAndRenderAsync(ParameterView.Empty); + public void Render() => SetParametersAndRender(ParameterView.Empty); /// - public Task SetParametersAndRenderAsync(ParameterView parameters) + public void SetParametersAndRender(ParameterView parameters) { - return InvokeAsync(() => Instance.SetParametersAsync(parameters)); + InvokeAsync(() => + { + Instance.SetParametersAsync(parameters); + }); } /// - public Task SetParametersAndRenderAsync(params ComponentParameter[] parameters) + public void SetParametersAndRender(params ComponentParameter[] parameters) { - return SetParametersAndRenderAsync(ToParameterView(parameters)); + SetParametersAndRender(ToParameterView(parameters)); } /// - public Task SetParametersAndRenderAsync(Action> parameterBuilder) + public void SetParametersAndRender(Action> parameterBuilder) { if (parameterBuilder is null) throw new ArgumentNullException(nameof(parameterBuilder)); @@ -47,7 +50,7 @@ public Task SetParametersAndRenderAsync(Action(); parameterBuilder(builder); - return SetParametersAndRenderAsync(ToParameterView(builder.Build())); + SetParametersAndRender(ToParameterView(builder.Build())); } private static ParameterView ToParameterView(IReadOnlyList parameters) @@ -59,7 +62,7 @@ private static ParameterView ToParameterView(IReadOnlyList p foreach (var param in parameters) { if (param.IsCascadingValue) - throw new InvalidOperationException($"You cannot provide a new cascading value through the {nameof(SetParametersAndRenderAsync)} method."); + 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); From ed65320c4412fabac51c507b39451df8ce6e3293 Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Wed, 5 Aug 2020 15:31:19 +0000 Subject: [PATCH 05/10] Refactored InvokeAsync and Render* methods out of IRenderedComponentBase, changed IRenderedFragment to enable converting method to extension methods --- CHANGELOG.md | 2 + .../ComponentParameterFactoryTest.cs | 40 --------- ...deredComponentInvokeAsyncExtensionsTest.cs | 54 ++++++++++++ .../RenderedComponentInvokeAsyncExtension.cs | 44 ++++++++++ .../RenderedComponentRenderExtensions.cs | 87 +++++++++++++++++++ src/bunit.core/IRenderedComponentBase.cs | 37 -------- src/bunit.core/IRenderedFragmentBase.cs | 7 +- src/bunit.web/Rendering/RenderedComponent.cs | 55 +----------- src/bunit.web/Rendering/RenderedFragment.cs | 8 +- 9 files changed, 197 insertions(+), 137 deletions(-) create mode 100644 src/bunit.core.tests/Extensions/RenderedComponentInvokeAsyncExtensionsTest.cs create mode 100644 src/bunit.core/Extensions/RenderedComponentInvokeAsyncExtension.cs create mode 100644 src/bunit.core/Extensions/RenderedComponentRenderExtensions.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index e81bae127..d053b1774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)` to `IRenderedComponentBase`. By [@JeroenBos](https://github.com/JeroenBos) in [#151](https://github.com/egil/bUnit/pull/177). + ### Changed List of changes in existing functionality. diff --git a/src/bunit.core.tests/ComponentParameterFactoryTest.cs b/src/bunit.core.tests/ComponentParameterFactoryTest.cs index c1412a173..9a8e424b0 100644 --- a/src/bunit.core.tests/ComponentParameterFactoryTest.cs +++ b/src/bunit.core.tests/ComponentParameterFactoryTest.cs @@ -84,46 +84,6 @@ public void Test002() Should.Throw(() => instance.ItemTemplate!("")(null)).Message.ShouldBe("ItemTemplate"); } - [Fact(DisplayName = "Dispatcher awaits Task-returning callback")] - public async Task Test003() - { - // Arrange - var cut = RenderComponent(); - bool delegateFinished = false; - - async Task Callback() - { - await Task.Delay(10); - delegateFinished = true; - } - - // Act - await cut.InvokeAsync(Callback); - - // Assert - delegateFinished.ShouldBe(true); - } - - [Fact(DisplayName = "Dispatcher does not await void-returning callback")] - public async Task Test004() - { - // Arrange - var cut = RenderComponent(); - bool delegateFinished = false; - - async void Callback() - { - await Task.Delay(10); - delegateFinished = true; - } - - // Act - await cut.InvokeAsync(Callback); - - // Assert - delegateFinished.ShouldBe(false); - } - [Fact(DisplayName = "Template(name, markupFactory) helper correctly renders markup template")] public void Test100() { diff --git a/src/bunit.core.tests/Extensions/RenderedComponentInvokeAsyncExtensionsTest.cs b/src/bunit.core.tests/Extensions/RenderedComponentInvokeAsyncExtensionsTest.cs new file mode 100644 index 000000000..323a8e2fb --- /dev/null +++ b/src/bunit.core.tests/Extensions/RenderedComponentInvokeAsyncExtensionsTest.cs @@ -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 + { + [Fact(DisplayName = "Dispatcher awaits Task-returning callback")] + public async Task Test003() + { + // Arrange + var cut = RenderComponent(); + 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(); + bool delegateFinished = false; + + async void Callback() + { + await Task.Delay(10); + delegateFinished = true; + } + + // Act + await cut.InvokeAsync(Callback); + + // Assert + delegateFinished.ShouldBeFalse(); + } + } +} diff --git a/src/bunit.core/Extensions/RenderedComponentInvokeAsyncExtension.cs b/src/bunit.core/Extensions/RenderedComponentInvokeAsyncExtension.cs new file mode 100644 index 000000000..f98019f01 --- /dev/null +++ b/src/bunit.core/Extensions/RenderedComponentInvokeAsyncExtension.cs @@ -0,0 +1,44 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using Bunit.Rendering; +using Microsoft.AspNetCore.Components; + +namespace Bunit +{ + ///

+ /// InvokeAsync extensions methods on . + /// + public static class RenderedComponentInvokeAsyncExtensions + { + /// + /// Invokes the given in the context of the associated . + /// + /// The rendered component whose dispatcher to invoke with. + /// + /// A that will be completed when the action has finished executing. + public static Task InvokeAsync(this IRenderedComponentBase renderedComponent, Action callback) + where TComponent : IComponent + { + if (renderedComponent is null) + throw new ArgumentNullException(nameof(renderedComponent)); + + return renderedComponent.Renderer.Dispatcher.InvokeAsync(callback); + } + + /// + /// Invokes the given in the context of the associated . + /// + /// The rendered component whose dispatcher to invoke with. + /// + /// A that will be completed when the action has finished executing. + public static Task InvokeAsync(this IRenderedComponentBase renderedComponent, Func callback) + where TComponent : IComponent + { + if (renderedComponent is null) + throw new ArgumentNullException(nameof(renderedComponent)); + + return renderedComponent.Renderer.Dispatcher.InvokeAsync(callback); + } + } +} diff --git a/src/bunit.core/Extensions/RenderedComponentRenderExtensions.cs b/src/bunit.core/Extensions/RenderedComponentRenderExtensions.cs new file mode 100644 index 000000000..b2f158c44 --- /dev/null +++ b/src/bunit.core/Extensions/RenderedComponentRenderExtensions.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Bunit.Rendering; +using Microsoft.AspNetCore.Components; + +namespace Bunit +{ + /// + /// Re-render extension methods, optionally with new parameters, for . + /// + public static class RenderedComponentRenderExtensions + { + /// + /// Render the component under test again. + /// + /// The rendered component to re-render. + public static void Render(this IRenderedComponentBase renderedComponent) where TComponent : IComponent + => SetParametersAndRender(renderedComponent, ParameterView.Empty); + + /// + /// Render the component under test again with the provided . + /// + /// The rendered component to re-render with new parameters + /// Parameters to pass to the component upon rendered + public static void SetParametersAndRender(this IRenderedComponentBase renderedComponent, ParameterView parameters) + where TComponent : IComponent + { + if (renderedComponent is null) + throw new ArgumentNullException(nameof(renderedComponent)); + + renderedComponent.InvokeAsync(() => + { + renderedComponent.Instance.SetParametersAsync(parameters); + }); + } + + /// + /// Render the component under test again with the provided . + /// + /// The rendered component to re-render with new parameters + /// Parameters to pass to the component upon rendered + public static void SetParametersAndRender(this IRenderedComponentBase renderedComponent, params ComponentParameter[] parameters) where TComponent : IComponent + { + if (renderedComponent is null) + throw new ArgumentNullException(nameof(renderedComponent)); + SetParametersAndRender(renderedComponent, ToParameterView(parameters)); + } + + /// + /// Render the component under test again with the provided parameters from the . + /// + /// The rendered component to re-render with new parameters + /// An action that receives a . + public static void SetParametersAndRender(this IRenderedComponentBase renderedComponent, Action> 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(); + parameterBuilder(builder); + + SetParametersAndRender(renderedComponent, ToParameterView(builder.Build())); + } + + + private static ParameterView ToParameterView(IReadOnlyList parameters) + { + var parameterView = ParameterView.Empty; + if (parameters.Any()) + { + var paramDict = new Dictionary(); + 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; + } + } +} diff --git a/src/bunit.core/IRenderedComponentBase.cs b/src/bunit.core/IRenderedComponentBase.cs index 92ae2fa75..e6dc7f1bb 100644 --- a/src/bunit.core/IRenderedComponentBase.cs +++ b/src/bunit.core/IRenderedComponentBase.cs @@ -16,42 +16,5 @@ public interface IRenderedComponentBase : IRenderedFragmentBase wher /// Gets the component under test /// TComponent Instance { get; } - - /// - /// Invokes the given in the context of the associated . - /// - /// - /// A that will be completed when the action has finished executing. - Task InvokeAsync(Action callback); - - /// - /// Invokes the given in the context of the associated . - /// - /// - /// A that will be completed when the action has finished executing. - Task InvokeAsync(Func callback); - - /// - /// Render the component under test again. - /// - void Render(); - - /// - /// Render the component under test again with the provided . - /// - /// Parameters to pass to the component upon rendered - void SetParametersAndRender(ParameterView parameters); - - /// - /// Render the component under test again with the provided . - /// - /// Parameters to pass to the component upon rendered - void SetParametersAndRender(params ComponentParameter[] parameters); - - /// - /// Render the component under test again with the provided parameters from the . - /// - /// An action that receives a . - void SetParametersAndRender(Action> parameterBuilder); } } diff --git a/src/bunit.core/IRenderedFragmentBase.cs b/src/bunit.core/IRenderedFragmentBase.cs index 059dd4bfa..6029f4946 100644 --- a/src/bunit.core/IRenderedFragmentBase.cs +++ b/src/bunit.core/IRenderedFragmentBase.cs @@ -27,6 +27,11 @@ public interface IRenderedFragmentBase /// /// Gets the used when rendering the component. /// - IServiceProvider Services { get; } + IServiceProvider Services { get; } + + /// + /// Gets the renderer that rendered the component. + /// + ITestRenderer Renderer { get; } } } diff --git a/src/bunit.web/Rendering/RenderedComponent.cs b/src/bunit.web/Rendering/RenderedComponent.cs index 0b597a8e6..7837a8cbf 100644 --- a/src/bunit.web/Rendering/RenderedComponent.cs +++ b/src/bunit.web/Rendering/RenderedComponent.cs @@ -15,59 +15,6 @@ internal class RenderedComponent : RenderedFragment, IRenderedCompon public RenderedComponent(IServiceProvider services, int componentId, TComponent component) : base(services, componentId) { Instance = component; - } - - /// - public Task InvokeAsync(Action callback) => Renderer.Dispatcher.InvokeAsync(callback); - - /// - public Task InvokeAsync(Func callback) => Renderer.Dispatcher.InvokeAsync(callback); - - /// - public void Render() => SetParametersAndRender(ParameterView.Empty); - - /// - public void SetParametersAndRender(ParameterView parameters) - { - InvokeAsync(() => - { - Instance.SetParametersAsync(parameters); - }); - } - - /// - public void SetParametersAndRender(params ComponentParameter[] parameters) - { - SetParametersAndRender(ToParameterView(parameters)); - } - - /// - public void SetParametersAndRender(Action> parameterBuilder) - { - if (parameterBuilder is null) - throw new ArgumentNullException(nameof(parameterBuilder)); - - var builder = new ComponentParameterBuilder(); - parameterBuilder(builder); - - SetParametersAndRender(ToParameterView(builder.Build())); - } - - private static ParameterView ToParameterView(IReadOnlyList parameters) - { - var parameterView = ParameterView.Empty; - if (parameters.Any()) - { - var paramDict = new Dictionary(); - 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; - } + } } } diff --git a/src/bunit.web/Rendering/RenderedFragment.cs b/src/bunit.web/Rendering/RenderedFragment.cs index 558992014..cbddec5c4 100644 --- a/src/bunit.web/Rendering/RenderedFragment.cs +++ b/src/bunit.web/Rendering/RenderedFragment.cs @@ -29,16 +29,14 @@ public class RenderedFragment : IRenderedFragment, IRenderEventHandler private HtmlParser HtmlParser { get; } - /// - /// Gets the renderer used to render the . - /// - protected ITestRenderer Renderer { get; } - /// /// Gets the first rendered markup. /// protected string FirstRenderMarkup { get; } + /// + public ITestRenderer Renderer { get; } + /// public IServiceProvider Services { get; } From 62b9650a5d80994b80edf9449964cc1249b5a189 Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Wed, 5 Aug 2020 20:21:08 +0200 Subject: [PATCH 06/10] Update src/bunit.core/Extensions/RenderedComponentInvokeAsyncExtension.cs Co-authored-by: Jeroen Bos --- .../Extensions/RenderedComponentInvokeAsyncExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bunit.core/Extensions/RenderedComponentInvokeAsyncExtension.cs b/src/bunit.core/Extensions/RenderedComponentInvokeAsyncExtension.cs index f98019f01..cf444aa99 100644 --- a/src/bunit.core/Extensions/RenderedComponentInvokeAsyncExtension.cs +++ b/src/bunit.core/Extensions/RenderedComponentInvokeAsyncExtension.cs @@ -16,7 +16,7 @@ public static class RenderedComponentInvokeAsyncExtensions /// /// The rendered component whose dispatcher to invoke with. /// - /// A that will be completed when the action has finished executing. + /// A that will be completed when the action has finished executing or is suspended by an asynchronous operation. public static Task InvokeAsync(this IRenderedComponentBase renderedComponent, Action callback) where TComponent : IComponent { From d15adc9ac3ce3efa66a796bb151ab68a5ff08d46 Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Wed, 5 Aug 2020 18:48:26 +0000 Subject: [PATCH 07/10] update to docs --- docs/site/docs/interaction/trigger-renders.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/site/docs/interaction/trigger-renders.md b/docs/site/docs/interaction/trigger-renders.md index e19d7e54a..f2b2a9d11 100644 --- a/docs/site/docs/interaction/trigger-renders.md +++ b/docs/site/docs/interaction/trigger-renders.md @@ -5,7 +5,7 @@ title: Triggering a Render Life Cycle on a Component # Triggering a Render Life Cycle on a Component -When a component under test is rendered, an instance of the type is returned. Through that, it is possible to cause the component under test to render again directly through the method or one of the [`SetParametersAndRender(...)`](xref:Bunit.IRenderedComponentBase`1.SetParametersAndRender(Bunit.Rendering.ComponentParameter[])) methods or indirectly through the method. +When a component under test is rendered, an instance of the type is returned. Through that, it is possible to cause the component under test to render again directly through the [`Render()`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.IRenderedComponentBase{``0})) method or one of the [`SetParametersAndRender(...)`](xref:Bunit.RenderedComponentRenderExtensions.SetParametersAndRender``1(Bunit.IRenderedComponentBase{``0},System.Action{Bunit.ComponentParameterBuilder{``0}})) methods or indirectly through the [`InvokeAsync(...)`](xref:Bunit.RenderedComponentInvokeAsyncExtensions.InvokeAsync``1(Bunit.IRenderedComponentBase{``0},System.Action)) method. > [!WARNING] > The `Render()` and `SetParametersAndRender()` methods are not available in the type that is returned when calling the _non_-generic version of `GetComponentUnderTest()` in ``-based Razor tests. Call the generic version of `GetComponentUnderTest()` to get a . @@ -17,25 +17,25 @@ Let's look at how to use each of these methods to cause a re-render. ## Render -The tells the renderer to re-render the component, i.e. go through its life-cycle methods (except for `OnInitialized()` and `OnInitializedAsync()` methods). To use it, do the following: +The [`Render()`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.IRenderedComponentBase{``0})) tells the renderer to re-render the component, i.e. go through its life-cycle methods (except for `OnInitialized()` and `OnInitializedAsync()` methods). To use it, do the following: [!code-csharp[](../../../samples/tests/xunit/ReRenderTest.cs?start=17&end=24&highlight=6)] -The highlighted line shows the call to . +The highlighted line shows the call to [`Render()`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.IRenderedComponentBase{``0})). > [!TIP] > The number of renders a component has been through can be inspected and verified using the property. ## SetParametersAndRender -The [`SetParametersAndRender(...)`](xref:Bunit.IRenderedComponentBase`1.SetParametersAndRender(Bunit.Rendering.ComponentParameter[])) methods tells the renderer to re-render the component with new parameters, i.e. go through its life-cycle methods (except for `OnInitialized()` and `OnInitializedAsync()` methods), passing the new parameters to the `SetParametersAsync()` method, _but only the new parameters_. To use it, do the following: +The [`SetParametersAndRender(...)`](xref:Bunit.RenderedComponentRenderExtensions.SetParametersAndRender``1(Bunit.IRenderedComponentBase{``0},System.Action{Bunit.ComponentParameterBuilder{``0}})) methods tells the renderer to re-render the component with new parameters, i.e. go through its life-cycle methods (except for `OnInitialized()` and `OnInitializedAsync()` methods), passing the new parameters to the `SetParametersAsync()` method, _but only the new parameters_. To use it, do the following: [!code-csharp[](../../../samples/tests/xunit/ReRenderTest.cs?start=31&end=42&highlight=8-10)] -The highlighted line shows the call to , which is also available as if you prefer that method of passing parameters. +The highlighted line shows the call to [`SetParametersAndRender(parameter builder)`](xref:Bunit.RenderedComponentRenderExtensions.SetParametersAndRender``1(Bunit.IRenderedComponentBase{``0},System.Action{Bunit.ComponentParameterBuilder{``0}})), which is also available as a version that takes the zero or more component parameters, e.g. created through the component parameter factory helper methods, if you prefer that method of passing parameters. > [!NOTE] -> Passing parameters to components through the [`SetParametersAndRender(...)`](xref:Bunit.IRenderedComponentBase`1.SetParametersAndRender(Bunit.Rendering.ComponentParameter[])) methods is identical to doing it with the [`RenderComponent(...)`](xref:Bunit.IRenderedComponentBase`1.SetParametersAndRender(Bunit.Rendering.ComponentParameter[])) methods, described in detail on the page. +> Passing parameters to components through the [`SetParametersAndRender(...)`](xref:Bunit.RenderedComponentRenderExtensions.SetParametersAndRender``1(Bunit.IRenderedComponentBase{``0},System.Action{Bunit.ComponentParameterBuilder{``0}})) methods is identical to doing it with the `RenderComponent(...)` methods, described in detail on the page. ## InvokeAsync @@ -43,7 +43,7 @@ Invoking methods on a component under test, which causes a render, e.g. by calli > The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state. -If you receive this error, you need to invoke your method inside an `Action` delegate passed to the method. +If you receive this error, you need to invoke your method inside an `Action` delegate passed to the [`InvokeAsync(...)`](xref:Bunit.RenderedComponentInvokeAsyncExtensions.InvokeAsync``1(Bunit.IRenderedComponentBase{``0},System.Action)) method. Consider the `` component listed below: @@ -53,7 +53,7 @@ To invoke the `Calculate()` method on the component instance, do the following: [!code-csharp[](../../../samples/tests/xunit/ReRenderTest.cs?start=49&end=56&highlight=6)] -The highlighted line shows the call to , which is passed an `Action` delegate, that calls the `Calculate` method. +The highlighted line shows the call to [`InvokeAsync(...)`](xref:Bunit.RenderedComponentInvokeAsyncExtensions.InvokeAsync``1(Bunit.IRenderedComponentBase{``0},System.Action)), which is passed an `Action` delegate, that calls the `Calculate` method. > [!TIP] > The instance of a component under test is available through the property. \ No newline at end of file From c99427e1d2ca112626194cdeb5a22a6bc9381507 Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Wed, 5 Aug 2020 20:59:37 +0200 Subject: [PATCH 08/10] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d053b1774..2dd74d4bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ List of new features. ### Changed List of changes in existing functionality. +- Moved `InvokeAsync()`, `Render()` and `SetParametersAndRender()` methods out of `IRenderedComponentBase` into extension methods. By [@JeroenBos](https://github.com/JeroenBos) in [#151](https://github.com/egil/bUnit/pull/177). + ### Deprecated List of soon-to-be removed features. From 8d242201be4951faf7b003f1f6b6e4ff87c77061 Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Wed, 5 Aug 2020 20:59:48 +0200 Subject: [PATCH 09/10] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd74d4bd..9909dac91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) List of new features. - Added `InvokeAsync(Func)` to `IRenderedComponentBase`. By [@JeroenBos](https://github.com/JeroenBos) in [#151](https://github.com/egil/bUnit/pull/177). +- Added `ITestRenderer Renderer { get ; }` to `IRenderedFragment` to make it possible to simplify the `IRenderedComponentBase` interface. By [@JeroenBos](https://github.com/JeroenBos) in [#151](https://github.com/egil/bUnit/pull/177). ### Changed List of changes in existing functionality. From 05a0632ac42b9a381945b374f6ef5c555ce20991 Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Wed, 5 Aug 2020 20:59:57 +0200 Subject: [PATCH 10/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9909dac91..e5c427312 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added List of new features. -- Added `InvokeAsync(Func)` to `IRenderedComponentBase`. By [@JeroenBos](https://github.com/JeroenBos) in [#151](https://github.com/egil/bUnit/pull/177). +- Added `InvokeAsync(Func)` to `RenderedComponentInvokeAsyncExtensions`. By [@JeroenBos](https://github.com/JeroenBos) in [#151](https://github.com/egil/bUnit/pull/177). - Added `ITestRenderer Renderer { get ; }` to `IRenderedFragment` to make it possible to simplify the `IRenderedComponentBase` interface. By [@JeroenBos](https://github.com/JeroenBos) in [#151](https://github.com/egil/bUnit/pull/177). ### Changed