diff --git a/CHANGELOG.md b/CHANGELOG.md index e19c80f28..05227b33b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,6 @@ All notable changes to **bUnit** will be documented in this file. The project ad ### Changed - Changed test renderer such that updates to rendered components markup happen in the same synchronization context as the test framework is using (if any), if any, to avoid memory race conditions. By [@egil](https://github.com/egil). -- Changed default "WaitFor" timeout to 10 seconds. By [@egil](https://github.com/egil). ## [1.18.4] - 2023-02-26 diff --git a/src/bunit.core/Rendering/TestRenderer.cs b/src/bunit.core/Rendering/TestRenderer.cs index f82d911fb..4df3e06f1 100644 --- a/src/bunit.core/Rendering/TestRenderer.cs +++ b/src/bunit.core/Rendering/TestRenderer.cs @@ -8,6 +8,7 @@ namespace Bunit.Rendering; /// public class TestRenderer : Renderer, ITestRenderer { + private readonly object renderTreeUpdateLock = new(); private readonly SynchronizationContext? usersSyncContext = SynchronizationContext.Current; private readonly Dictionary renderedComponents = new(); private readonly List rootComponents = new(); @@ -80,33 +81,39 @@ public Task DispatchEventAsync( if (fieldInfo is null) throw new ArgumentNullException(nameof(fieldInfo)); - var result = Dispatcher.InvokeAsync(() => + // Calling base.DispatchEventAsync updates the render tree + // if the event contains associated data. + lock (renderTreeUpdateLock) { - ResetUnhandledException(); - - try - { - return base.DispatchEventAsync(eventHandlerId, fieldInfo, eventArgs); - } - catch (ArgumentException ex) when (string.Equals(ex.Message, $"There is no event handler associated with this event. EventId: '{eventHandlerId}'. (Parameter 'eventHandlerId')", StringComparison.Ordinal)) + var result = Dispatcher.InvokeAsync(() => { - if (ignoreUnknownEventHandlers) + ResetUnhandledException(); + + try { - return Task.CompletedTask; + return base.DispatchEventAsync(eventHandlerId, fieldInfo, eventArgs); } + catch (ArgumentException ex) when (string.Equals(ex.Message, $"There is no event handler associated with this event. EventId: '{eventHandlerId}'. (Parameter 'eventHandlerId')", StringComparison.Ordinal)) + { + if (ignoreUnknownEventHandlers) + { + return Task.CompletedTask; + } + + var betterExceptionMsg = new UnknownEventHandlerIdException(eventHandlerId, fieldInfo, ex); + return Task.FromException(betterExceptionMsg); + } + }); - var betterExceptionMsg = new UnknownEventHandlerIdException(eventHandlerId, fieldInfo, ex); - return Task.FromException(betterExceptionMsg); + if (result.IsFaulted && result.Exception is not null) + { + HandleException(result.Exception); } - }); - if (result.IsFaulted && result.Exception is not null) - { - HandleException(result.Exception); - } + AssertNoUnhandledExceptions(); - AssertNoUnhandledExceptions(); - return result; + return result; + } } /// @@ -150,6 +157,19 @@ public void DisposeComponents() AssertNoUnhandledExceptions(); } + /// + protected override void ProcessPendingRender() + { + // Blocks updates to the renderers internal render tree + // while the render tree is being read elsewhere. + // base.ProcessPendingRender calls UpdateDisplayAsync, + // so there is no need to lock in that method. + lock (renderTreeUpdateLock) + { + base.ProcessPendingRender(); + } + } + /// protected override Task UpdateDisplayAsync(in RenderBatch renderBatch) { @@ -281,7 +301,12 @@ private IReadOnlyList> FindComponents>(); var framesCollection = new RenderTreeFrameDictionary(); - FindComponentsInRenderTree(parentComponent.ComponentId); + // Blocks the renderer from changing the render tree + // while this method searches through it. + lock (renderTreeUpdateLock) + { + FindComponentsInRenderTree(parentComponent.ComponentId); + } return result; diff --git a/tests/bunit.web.tests/Extensions/WaitForHelpers/RenderedFragmentWaitForElementsHelperExtensions.Async.Test.cs b/tests/bunit.web.tests/Extensions/WaitForHelpers/RenderedFragmentWaitForElementsHelperExtensions.Async.Test.cs index ef7b83465..d6a61aaee 100644 --- a/tests/bunit.web.tests/Extensions/WaitForHelpers/RenderedFragmentWaitForElementsHelperExtensions.Async.Test.cs +++ b/tests/bunit.web.tests/Extensions/WaitForHelpers/RenderedFragmentWaitForElementsHelperExtensions.Async.Test.cs @@ -5,7 +5,7 @@ namespace Bunit.Extensions.WaitForHelpers; public class RenderedFragmentWaitForElementsHelperExtensionsAsyncTest : TestContext { - private readonly static TimeSpan WaitForTestTimeout = TimeSpan.FromMilliseconds(5); + private readonly static TimeSpan WaitForTestTimeout = TimeSpan.FromMilliseconds(100); public RenderedFragmentWaitForElementsHelperExtensionsAsyncTest(ITestOutputHelper testOutput) {