Skip to content

Commit 2542c0d

Browse files
committed
switched renderer to hide its asynchrony
1 parent 0413ecf commit 2542c0d

13 files changed

Lines changed: 83 additions & 108 deletions

File tree

src/bunit.core.tests/TestRendererTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public async Task Test001()
2424
var res = new ConcurrentRenderEventSubscriber(sut.RenderEvents);
2525

2626
// act
27-
var cut = await sut.RenderComponent<Simple1>(Array.Empty<ComponentParameter>());
27+
var cut = sut.RenderComponent<Simple1>(Array.Empty<ComponentParameter>());
2828

2929
// assert
3030
res.RenderCount.ShouldBe(1);

src/bunit.core/Rendering/ITestRenderer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ public interface ITestRenderer
3838
/// <typeparam name="TComponent">Type of component to render.</typeparam>
3939
/// <param name="parameters">Parameters to pass to the component during first render.</param>
4040
/// <returns>The component and its assigned id.</returns>
41-
Task<(int ComponentId, TComponent Component)> RenderComponent<TComponent>(IEnumerable<ComponentParameter> parameters) where TComponent : IComponent;
41+
(int ComponentId, TComponent Component) RenderComponent<TComponent>(IEnumerable<ComponentParameter> parameters) where TComponent : IComponent;
4242

4343
/// <summary>
4444
/// Renders the provided <paramref name="renderFragment"/> inside a wrapper and returns
4545
/// the wrappers component id.
4646
/// </summary>
4747
/// <param name="renderFragment"><see cref="Microsoft.AspNetCore.Components.RenderFragment"/> to render.</param>
4848
/// <returns>The id of the wrapper component which the <paramref name="renderFragment"/> is rendered inside.</returns>
49-
Task<int> RenderFragment(RenderFragment renderFragment);
49+
int RenderFragment(RenderFragment renderFragment);
5050

5151
/// <summary>
5252
/// Notifies the renderer that an event has occurred.

src/bunit.core/Rendering/TestComponentRenderer.cs

Lines changed: 20 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
using System.Linq;
44
using System.Runtime.ExceptionServices;
55
using System.Text;
6+
using System.Threading;
67
using System.Threading.Tasks;
78
using Bunit.RazorTesting;
89
using Microsoft.AspNetCore.Components;
10+
using Microsoft.AspNetCore.Components.Rendering;
911
using Microsoft.AspNetCore.Components.RenderTree;
1012
using Microsoft.Extensions.DependencyInjection;
1113
using Microsoft.Extensions.Logging;
@@ -19,6 +21,7 @@ namespace Bunit.Rendering
1921
public class TestComponentRenderer : Renderer
2022
{
2123
private static readonly ServiceProvider ServiceProvider = new ServiceCollection().BuildServiceProvider();
24+
private static readonly Task CanceledRenderTask = Task.FromCanceled(new CancellationToken(canceled: true));
2225
private Exception? _unhandledException;
2326

2427
/// <inheritdoc/>
@@ -40,67 +43,50 @@ public TestComponentRenderer(IServiceProvider serviceProvider, ILoggerFactory lo
4043
/// </summary>
4144
/// <param name="componentType">Razor-based test to render.</param>
4245
/// <returns>A list of <see cref="FragmentBase"/> test definitions found in the test file.</returns>
43-
public async Task<IReadOnlyList<RazorTestBase>> GetRazorTestsFromComponent(Type componentType)
46+
public IReadOnlyList<RazorTestBase> GetRazorTestsFromComponent(Type componentType)
4447
{
45-
var componentId = await Dispatcher.InvokeAsync(() => RenderComponent(componentType)).ConfigureAwait(false);
46-
AssertNoUnhandledExceptions();
48+
var componentId = RenderComponent(componentType);
4749
return GetRazorTests<RazorTestBase>(componentId);
4850
}
4951

50-
/// <summary>
51-
/// Renders the provided <paramref name="renderFragment"/>.
52-
/// </summary>
53-
/// <typeparam name="TComponent">The type of components to find in the render tree after rendering.</typeparam>
54-
/// <param name="renderFragment">The <see cref="RenderFragment"/> to render.</param>
55-
/// <returns>A list of <typeparamref name="TComponent"/> found in the <paramref name="renderFragment"/>'s render tree.</returns>
56-
public async Task<IReadOnlyList<TComponent>> RenderAndGetTestComponents<TComponent>(RenderFragment renderFragment)
52+
private int RenderComponent(Type componentType)
5753
{
58-
var componentId = await RenderFragmentInsideWrapper(renderFragment);
59-
return GetRazorTests<TComponent>(componentId);
60-
}
54+
int componentId = -1;
6155

62-
private async Task<int> RenderComponent(Type componentType)
63-
{
64-
var component = InstantiateComponent(componentType);
65-
AssertNoUnhandledExceptions();
66-
var componentId = AssignRootComponentId(component);
67-
AssertNoUnhandledExceptions();
56+
Dispatcher.InvokeAsync(() =>
57+
{
58+
var component = InstantiateComponent(componentType);
59+
componentId = AssignRootComponentId(component);
60+
return RenderRootComponentAsync(componentId);
61+
}).Wait();
6862

69-
await RenderRootComponentAsync(componentId).ConfigureAwait(false);
7063
AssertNoUnhandledExceptions();
7164

7265
return componentId;
7366
}
7467

75-
private async Task<int> RenderFragmentInsideWrapper(RenderFragment renderFragment)
68+
private IReadOnlyList<TComponent> GetRazorTests<TComponent>(int fromComponentId)
7669
{
77-
var wrapper = new WrapperComponent();
78-
79-
var wrapperId = AssignRootComponentId(wrapper);
80-
AssertNoUnhandledExceptions();
81-
82-
await Dispatcher.InvokeAsync(() => wrapper.Render(renderFragment)).ConfigureAwait(false);
83-
AssertNoUnhandledExceptions();
70+
var ownFrames = GetCurrentRenderTreeFrames(fromComponentId);
8471

85-
return wrapperId;
86-
}
72+
if (ownFrames.Count == 0)
73+
return Array.Empty<TComponent>();
8774

88-
private IReadOnlyList<TComponent> GetRazorTests<TComponent>(int fromComponentId)
89-
{
9075
var result = new List<TComponent>();
91-
var ownFrames = GetCurrentRenderTreeFrames(fromComponentId);
76+
9277
for (var i = 0; i < ownFrames.Count; i++)
9378
{
9479
ref var frame = ref ownFrames.Array[i];
9580
if (frame.FrameType == RenderTreeFrameType.Component)
9681
if (frame.Component is TComponent component)
9782
result.Add(component);
9883
}
84+
9985
return result;
10086
}
10187

10288
/// <inheritdoc/>
103-
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch) => Task.CompletedTask;
89+
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch) => CanceledRenderTask;
10490

10591
/// <inheritdoc/>
10692
protected override void HandleException(Exception exception) => _unhandledException = exception;
@@ -113,19 +99,5 @@ private void AssertNoUnhandledExceptions()
11399
ExceptionDispatchInfo.Capture(unhandled).Throw();
114100
}
115101
}
116-
117-
private class WrapperComponent : IComponent
118-
{
119-
private RenderHandle _renderHandle;
120-
121-
public void Attach(RenderHandle renderHandle) => _renderHandle = renderHandle;
122-
123-
public Task SetParametersAsync(ParameterView parameters) => throw new InvalidOperationException($"WrapperComponent shouldn't receive any parameters");
124-
125-
public void Render(RenderFragment renderFragment)
126-
{
127-
_renderHandle.Render(renderFragment);
128-
}
129-
}
130102
}
131103
}

src/bunit.core/Rendering/TestRenderer.cs

Lines changed: 14 additions & 38 deletions
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, ITestRenderer
17+
public partial class TestRenderer : Renderer, ITestRenderer
1818
{
1919
private const string LOGGER_CATEGORY = nameof(Bunit) + "." + nameof(TestRenderer);
2020
private static readonly Type CascadingValueType = typeof(CascadingValue<>);
@@ -39,16 +39,16 @@ public TestRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFacto
3939
}
4040

4141
/// <inheritdoc/>
42-
public async Task<(int ComponentId, TComponent Component)> RenderComponent<TComponent>(IEnumerable<ComponentParameter> parameters) where TComponent : IComponent
42+
public (int ComponentId, TComponent Component) RenderComponent<TComponent>(IEnumerable<ComponentParameter> parameters) where TComponent : IComponent
4343
{
4444
var componentType = typeof(TComponent);
4545
var renderFragment = parameters.ToComponentRenderFragment<TComponent>();
46-
var wrapperId = await RenderFragmentInsideWrapper(renderFragment).ConfigureAwait(false);
46+
var wrapperId = RenderFragmentInsideWrapper(renderFragment);
4747
return FindComponent<TComponent>(wrapperId);
4848
}
4949

5050
/// <inheritdoc/>
51-
public Task<int> RenderFragment(RenderFragment renderFragment)
51+
public int RenderFragment(RenderFragment renderFragment)
5252
{
5353
return RenderFragmentInsideWrapper(renderFragment);
5454
}
@@ -84,28 +84,20 @@ public Task<int> RenderFragment(RenderFragment renderFragment)
8484
}
8585

8686
/// <inheritdoc/>
87-
public new async Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs)
87+
public new Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs)
8888
{
8989
if (fieldInfo is null)
9090
throw new ArgumentNullException(nameof(fieldInfo));
91+
9192
_logger.LogDebug(new EventId(1, nameof(DispatchEventAsync)), $"Starting trigger of '{fieldInfo.FieldValue}'");
9293

93-
await Dispatcher.InvokeAsync(async () =>
94-
{
95-
try
96-
{
97-
await base.DispatchEventAsync(eventHandlerId, fieldInfo, eventArgs);
98-
}
99-
catch (Exception e)
100-
{
101-
_unhandledException = e;
102-
throw;
103-
}
104-
});
94+
var result = Dispatcher.InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, fieldInfo, eventArgs));
10595

10696
AssertNoUnhandledExceptions();
10797

10898
_logger.LogDebug(new EventId(1, nameof(DispatchEventAsync)), $"Finished trigger of '{fieldInfo.FieldValue}'");
99+
100+
return result;
109101
}
110102

111103
/// <inheritdoc/>
@@ -114,14 +106,14 @@ public Task InvokeAsync(Action callback)
114106
return Dispatcher.InvokeAsync(callback);
115107
}
116108

117-
private async Task<int> RenderFragmentInsideWrapper(RenderFragment renderFragment)
109+
private int RenderFragmentInsideWrapper(RenderFragment renderFragment)
118110
{
119-
var wrapper = new WrapperComponent();
111+
var wrapper = new WrapperComponent(renderFragment);
120112

121113
var wrapperId = AssignRootComponentId(wrapper);
122114
AssertNoUnhandledExceptions();
123115

124-
await Dispatcher.InvokeAsync(() => wrapper.Render(renderFragment)).ConfigureAwait(false);
116+
Dispatcher.InvokeAsync(wrapper.Render).Wait();
125117
AssertNoUnhandledExceptions();
126118

127119
return wrapperId;
@@ -151,8 +143,9 @@ private void AssertNoUnhandledExceptions()
151143
{
152144
if (_unhandledException is { } unhandled)
153145
{
154-
_logger.LogError(new EventId(3, nameof(AssertNoUnhandledExceptions)), $"An unhandled exception happened during rendering: {unhandled.Message}");
155146
_unhandledException = null;
147+
var evt = new EventId(3, nameof(AssertNoUnhandledExceptions));
148+
_logger.LogError(evt, unhandled, $"An unhandled exception happened during rendering: {unhandled.Message}{Environment.NewLine}{unhandled.StackTrace}");
156149
ExceptionDispatchInfo.Capture(unhandled).Throw();
157150
}
158151
}
@@ -198,22 +191,5 @@ private void AssertNoUnhandledExceptions()
198191
}
199192
return result;
200193
}
201-
202-
/// <summary>
203-
/// Wrapper class that provides access to a <see cref="RenderHandle"/>.
204-
/// </summary>
205-
private class WrapperComponent : IComponent
206-
{
207-
private RenderHandle _renderHandle;
208-
209-
public void Attach(RenderHandle renderHandle) => _renderHandle = renderHandle;
210-
211-
public Task SetParametersAsync(ParameterView parameters) => throw new InvalidOperationException($"WrapperComponent shouldn't receive any parameters");
212-
213-
public void Render(RenderFragment renderFragment)
214-
{
215-
_renderHandle.Render(renderFragment);
216-
}
217-
}
218194
}
219195
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Microsoft.AspNetCore.Components;
2+
using System;
3+
using System.Threading.Tasks;
4+
5+
namespace Bunit.Rendering
6+
{
7+
/// <summary>
8+
/// Wrapper class that provides access to a <see cref="RenderHandle"/>.
9+
/// </summary>
10+
internal class WrapperComponent : IComponent
11+
{
12+
private readonly RenderFragment _renderFragment;
13+
private RenderHandle _renderHandle;
14+
15+
public WrapperComponent(RenderFragment renderFragment) => _renderFragment = renderFragment;
16+
17+
public void Attach(RenderHandle renderHandle) => _renderHandle = renderHandle;
18+
19+
public Task SetParametersAsync(ParameterView parameters) => throw new InvalidOperationException($"WrapperComponent shouldn't receive any parameters");
20+
21+
public void Render()
22+
{
23+
_renderHandle.Render(_renderFragment);
24+
}
25+
}
26+
}

src/bunit.web.tests/ComponentTestFixtureTest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ public void Test001()
3636
Should.Throw<Exception>(async () => await instance.NonGenericCallback.InvokeAsync(null)).Message.ShouldBe("NonGenericCallback");
3737
Should.Throw<Exception>(async () => await instance.GenericCallback.InvokeAsync(EventArgs.Empty)).Message.ShouldBe("GenericCallback");
3838

39-
new RenderedFragment(Services, Renderer.RenderFragment(instance.ChildContent!).GetAwaiter().GetResult()).Markup.ShouldBe(nameof(ChildContent));
40-
new RenderedFragment(Services, Renderer.RenderFragment(instance.OtherContent!).GetAwaiter().GetResult()).Markup.ShouldBe(nameof(AllTypesOfParams<string>.OtherContent));
39+
new RenderedFragment(Services, Renderer.RenderFragment(instance.ChildContent!)).Markup.ShouldBe(nameof(ChildContent));
40+
new RenderedFragment(Services, Renderer.RenderFragment(instance.OtherContent!)).Markup.ShouldBe(nameof(AllTypesOfParams<string>.OtherContent));
4141
Should.Throw<Exception>(() => instance.ItemTemplate!("")(null)).Message.ShouldBe("ItemTemplate");
4242
}
4343

@@ -75,8 +75,8 @@ public void Test002()
7575
instance.RegularParam.ShouldBe("some value");
7676
Should.Throw<Exception>(async () => await instance.NonGenericCallback.InvokeAsync(null)).Message.ShouldBe("NonGenericCallback");
7777
Should.Throw<Exception>(async () => await instance.GenericCallback.InvokeAsync(EventArgs.Empty)).Message.ShouldBe("GenericCallback");
78-
new RenderedFragment(Services, Renderer.RenderFragment(instance.ChildContent!).GetAwaiter().GetResult()).Markup.ShouldBe(nameof(ChildContent));
79-
new RenderedFragment(Services, Renderer.RenderFragment(instance.OtherContent!).GetAwaiter().GetResult()).Markup.ShouldBe(nameof(AllTypesOfParams<string>.OtherContent));
78+
new RenderedFragment(Services, Renderer.RenderFragment(instance.ChildContent!)).Markup.ShouldBe(nameof(ChildContent));
79+
new RenderedFragment(Services, Renderer.RenderFragment(instance.OtherContent!)).Markup.ShouldBe(nameof(AllTypesOfParams<string>.OtherContent));
8080
Should.Throw<Exception>(() => instance.ItemTemplate!("")(null)).Message.ShouldBe("ItemTemplate");
8181
}
8282

src/bunit.web/RazorTesting/Fixture.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ private IReadOnlyList<FragmentBase> TestData
2525
{
2626
if (_testData is null)
2727
{
28-
var id = Renderer.RenderFragment(ChildContent).GetAwaiter().GetResult();
28+
var id = Renderer.RenderFragment(ChildContent);
2929
_testData = Renderer.FindComponents<FragmentBase>(id).Select(x => x.Component).ToArray();
3030
}
3131
return _testData;
@@ -127,14 +127,14 @@ private ComponentUnderTest SelectComponentUnderTest(string _)
127127

128128
private IRenderedComponent<TComponent> Factory<TComponent>(RenderFragment fragment) where TComponent : IComponent
129129
{
130-
var renderId = Renderer.RenderFragment(fragment).GetAwaiter().GetResult();
130+
var renderId = Renderer.RenderFragment(fragment);
131131
var (componentId, component) = Renderer.FindComponent<TComponent>(renderId);
132132
return new RenderedComponent<TComponent>(Services, componentId, component);
133133
}
134134

135135
private IRenderedFragment Factory(RenderFragment fragment)
136136
{
137-
var renderId = Renderer.RenderFragment(fragment).GetAwaiter().GetResult();
137+
var renderId = Renderer.RenderFragment(fragment);
138138
return new RenderedFragment(Services, renderId);
139139
}
140140

src/bunit.web/RazorTesting/SnapshotTest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ protected override async Task Run()
5252
if (SetupAsync is { })
5353
await TryRunAsync(SetupAsync, this).ConfigureAwait(false);
5454

55-
var testRenderId = await Renderer.RenderFragment(TestInput).ConfigureAwait(false);
56-
var expectedRenderId = await Renderer.RenderFragment(ExpectedOutput).ConfigureAwait(false);
57-
55+
var testRenderId = Renderer.RenderFragment(TestInput);
5856
var inputHtml = Htmlizer.GetHtml(Renderer, testRenderId);
57+
58+
var expectedRenderId = Renderer.RenderFragment(ExpectedOutput);
5959
var expectedHtml = Htmlizer.GetHtml(Renderer, expectedRenderId);
6060

6161
var parser = new TestHtmlParser();

src/bunit.web/Rendering/Internal/Htmlizer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ public static string GetHtml(ITestRenderer renderer, int componentId)
4141
var frames = renderer.GetCurrentRenderTreeFrames(componentId);
4242
var context = new HtmlRenderingContext(renderer);
4343
var newPosition = RenderFrames(context, frames, 0, frames.Count);
44-
Debug.Assert(newPosition == frames.Count);
44+
// Can be false in certain circumstances. Perhaps because component has been disposed?
45+
Debug.Assert(newPosition == frames.Count);
4546
return string.Join(string.Empty, context.Result);
4647
}
4748

src/bunit.web/TestContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public TestContext()
3434
/// <returns>The rendered <typeparamref name="TComponent"/></returns>
3535
public IRenderedComponent<TComponent> RenderComponent<TComponent>(params ComponentParameter[] parameters) where TComponent : IComponent
3636
{
37-
var renderResult = Renderer.RenderComponent<TComponent>(parameters).GetAwaiter().GetResult();
37+
var renderResult = Renderer.RenderComponent<TComponent>(parameters);
3838
return new RenderedComponent<TComponent>(Services, renderResult.ComponentId, renderResult.Component);
3939
}
4040

@@ -55,7 +55,7 @@ public virtual IRenderedComponent<TComponent> RenderComponent<TComponent>(Action
5555
var builder = new ComponentParameterBuilder<TComponent>();
5656
componentParameterBuilderAction(builder);
5757

58-
var renderResult = Renderer.RenderComponent<TComponent>(builder.Build()).GetAwaiter().GetResult();
58+
var renderResult = Renderer.RenderComponent<TComponent>(builder.Build());
5959
return new RenderedComponent<TComponent>(Services, renderResult.ComponentId, renderResult.Component);
6060
}
6161
}

0 commit comments

Comments
 (0)