diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 4f3431aa3..5d22fe6bd 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -43,7 +43,7 @@
-
+
diff --git a/src/bunit.core/Extensions/WaitForHelpers/RenderedFragmentWaitForHelperExtensions.cs b/src/bunit.core/Extensions/WaitForHelpers/RenderedFragmentWaitForHelperExtensions.cs
index 2bf12d6e5..2439cf6f5 100644
--- a/src/bunit.core/Extensions/WaitForHelpers/RenderedFragmentWaitForHelperExtensions.cs
+++ b/src/bunit.core/Extensions/WaitForHelpers/RenderedFragmentWaitForHelperExtensions.cs
@@ -34,13 +34,29 @@ public static void WaitForState(this IRenderedFragmentBase renderedFragment, Fun
{
ExceptionDispatchInfo.Capture(aggregateException.InnerExceptions[0]).Throw();
}
- else
- {
- ExceptionDispatchInfo.Capture(e).Throw();
- }
+
+ throw;
}
}
+ ///
+ /// Wait until the provided action returns true,
+ /// or the is reached (default is one second).
+ ///
+ /// The is evaluated initially, and then each time
+ /// the renders.
+ ///
+ /// The render fragment or component to attempt to verify state against.
+ /// The predicate to invoke after each render, which must returns true when the desired state has been reached.
+ /// The maximum time to wait for the desired state.
+ /// Thrown if the throw an exception during invocation, or if the timeout has been reached. See the inner exception for details.
+ internal static async Task WaitForStateAsync(this IRenderedFragmentBase renderedFragment, Func statePredicate, TimeSpan? timeout = null)
+ {
+ using var waiter = new WaitForStateHelper(renderedFragment, statePredicate, timeout);
+
+ await waiter.WaitTask;
+ }
+
///
/// Wait until the provided passes (i.e. does not throw an
/// exception), or the is reached (default is one second).
@@ -66,10 +82,26 @@ public static void WaitForAssertion(this IRenderedFragmentBase renderedFragment,
{
ExceptionDispatchInfo.Capture(aggregateException.InnerExceptions[0]).Throw();
}
- else
- {
- ExceptionDispatchInfo.Capture(e).Throw();
- }
+
+ throw;
}
}
+
+ ///
+ /// Wait until the provided passes (i.e. does not throw an
+ /// exception), or the is reached (default is one second).
+ ///
+ /// The is attempted initially, and then each time the renders.
+ ///
+ /// The rendered fragment to wait for renders from and assert against.
+ /// The verification or assertion to perform.
+ /// The maximum time to attempt the verification.
+ /// Thrown if the timeout has been reached. See the inner exception to see the captured assertion exception.
+ [AssertionMethod]
+ internal static async Task WaitForAssertionAsync(this IRenderedFragmentBase renderedFragment, Action assertion, TimeSpan? timeout = null)
+ {
+ using var waiter = new WaitForAssertionHelper(renderedFragment, assertion, timeout);
+
+ await waiter.WaitTask;
+ }
}
diff --git a/src/bunit.core/Extensions/WaitForHelpers/WaitForHelper.cs b/src/bunit.core/Extensions/WaitForHelpers/WaitForHelper.cs
index e32dd7d73..d49411bc3 100644
--- a/src/bunit.core/Extensions/WaitForHelpers/WaitForHelper.cs
+++ b/src/bunit.core/Extensions/WaitForHelpers/WaitForHelper.cs
@@ -51,7 +51,7 @@ protected WaitForHelper(
this.completeChecker = completeChecker ?? throw new ArgumentNullException(nameof(completeChecker));
logger = renderedFragment.Services.CreateLogger>();
- checkPassedCompletionSource = new TaskCompletionSource();
+ checkPassedCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
timer = new Timer(_ =>
{
logger.LogWaiterTimedOut(renderedFragment.ComponentId);
@@ -121,17 +121,12 @@ private Task CreateWaitTask(IRenderedFragmentBase renderedFragment)
// Two to failure conditions, that the renderer captures an unhandled
// exception from a component or itself, or that the timeout is reached,
- // are executed on the renderes scheduler, to ensure that OnAfterRender
+ // are executed on the renderers scheduler, to ensure that OnAfterRender
// and the continuations does not happen at the same time.
- var failureTask = renderer.Dispatcher.InvokeAsync(() =>
+ var failureTask = renderer.Dispatcher.InvokeAsync(async () =>
{
- return renderer
- .UnhandledException
- .ContinueWith(
- x => Task.FromException(x.Result),
- CancellationToken.None,
- TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously,
- TaskScheduler.FromCurrentSynchronizationContext());
+ var exception = await renderer.UnhandledException;
+ return Task.FromException(exception);
}).Unwrap();
return Task
diff --git a/src/bunit.core/InternalsVisibleTo.cs b/src/bunit.core/InternalsVisibleTo.cs
index 8fceb7efa..a07259efa 100644
--- a/src/bunit.core/InternalsVisibleTo.cs
+++ b/src/bunit.core/InternalsVisibleTo.cs
@@ -1 +1,2 @@
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Bunit.Core.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010001be6b1a2ca57b09b7040e2ab0993e515296ae22aef4031a4fe388a1336fe21f69c7e8610e9935de6ed18d94b5c98429f99ef62ce3d0af28a7088f856239368ea808ad4c448aa2a8075ed581f989f36ed0d0b8b1cfcaf1ff6a4506c8a99b7024b6eb56996d08e3c9c1cf5db59bff96fcc63ccad155ef7fc63aab6a69862437b6")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Bunit.Web.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010001be6b1a2ca57b09b7040e2ab0993e515296ae22aef4031a4fe388a1336fe21f69c7e8610e9935de6ed18d94b5c98429f99ef62ce3d0af28a7088f856239368ea808ad4c448aa2a8075ed581f989f36ed0d0b8b1cfcaf1ff6a4506c8a99b7024b6eb56996d08e3c9c1cf5db59bff96fcc63ccad155ef7fc63aab6a69862437b6")]
diff --git a/src/bunit.core/Rendering/TestRenderer.cs b/src/bunit.core/Rendering/TestRenderer.cs
index 2997b8e15..7ad4faae0 100644
--- a/src/bunit.core/Rendering/TestRenderer.cs
+++ b/src/bunit.core/Rendering/TestRenderer.cs
@@ -12,7 +12,7 @@ public class TestRenderer : Renderer, ITestRenderer
private readonly List rootComponents = new();
private readonly ILogger logger;
private readonly IRenderedComponentActivator activator;
- private TaskCompletionSource unhandledExceptionTsc = new();
+ private TaskCompletionSource unhandledExceptionTsc = new(TaskCreationOptions.RunContinuationsAsynchronously);
private Exception? capturedUnhandledException;
///
@@ -352,7 +352,7 @@ protected override void HandleException(Exception exception)
if (!unhandledExceptionTsc.TrySetResult(capturedUnhandledException))
{
- unhandledExceptionTsc = new TaskCompletionSource();
+ unhandledExceptionTsc = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
unhandledExceptionTsc.SetResult(capturedUnhandledException);
}
}
@@ -362,7 +362,7 @@ private void ResetUnhandledException()
capturedUnhandledException = null;
if (unhandledExceptionTsc.Task.IsCompleted)
- unhandledExceptionTsc = new TaskCompletionSource();
+ unhandledExceptionTsc = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
}
private void AssertNoUnhandledExceptions()
diff --git a/src/bunit.web/Extensions/WaitForHelpers/RenderedFragmentWaitForHelperExtensions.cs b/src/bunit.web/Extensions/WaitForHelpers/RenderedFragmentWaitForHelperExtensions.cs
index 3b6bcccea..eb1e3b524 100644
--- a/src/bunit.web/Extensions/WaitForHelpers/RenderedFragmentWaitForHelperExtensions.cs
+++ b/src/bunit.web/Extensions/WaitForHelpers/RenderedFragmentWaitForHelperExtensions.cs
@@ -80,6 +80,77 @@ public static IRefreshableElementCollection WaitForElements(this IRend
public static IRefreshableElementCollection WaitForElements(this IRenderedFragment renderedFragment, string cssSelector, int matchElementCount, TimeSpan timeout)
=> WaitForElementsCore(renderedFragment, cssSelector, matchElementCount: matchElementCount, timeout: timeout);
+ ///
+ /// Wait until an element matching the exists in the ,
+ /// or the timeout is reached (default is one second).
+ ///
+ /// The render fragment or component find the matching element in.
+ /// The CSS selector to use to search for the element.
+ /// Thrown if no elements is found matching the within the default timeout. See the inner exception for details.
+ /// The .
+ internal static Task WaitForElementAsync(this IRenderedFragment renderedFragment, string cssSelector)
+ => WaitForElementCoreAsync(renderedFragment, cssSelector, timeout: null);
+
+ ///
+ /// Wait until an element matching the exists in the ,
+ /// or the is reached.
+ ///
+ /// The render fragment or component find the matching element in.
+ /// The CSS selector to use to search for the element.
+ /// The maximum time to wait for the element to appear.
+ /// Thrown if no elements is found matching the within the default timeout. See the inner exception for details.
+ /// The .
+ internal static Task WaitForElementAsync(this IRenderedFragment renderedFragment, string cssSelector, TimeSpan timeout)
+ => WaitForElementCoreAsync(renderedFragment, cssSelector, timeout: timeout);
+
+ ///
+ /// Wait until exactly element(s) matching the exists in the ,
+ /// or the timeout is reached (default is one second).
+ ///
+ /// The render fragment or component find the matching element in.
+ /// The CSS selector to use to search for elements.
+ /// The exact number of elements to that the should match.
+ /// Thrown if no elements is found matching the within the default timeout.
+ /// The .
+ internal static Task> WaitForElementsAsync(this IRenderedFragment renderedFragment, string cssSelector, int matchElementCount)
+ => WaitForElementsCoreAsync(renderedFragment, cssSelector, matchElementCount: matchElementCount, timeout: null);
+
+ ///
+ /// Wait until at least one element matching the exists in the ,
+ /// or the is reached.
+ ///
+ /// The render fragment or component find the matching element in.
+ /// The CSS selector to use to search for elements.
+ /// The maximum time to wait for elements to appear.
+ /// Thrown if no elements is found matching the within the default timeout.
+ /// The .
+ internal static Task> WaitForElementsAsync(this IRenderedFragment renderedFragment, string cssSelector, TimeSpan timeout)
+ => WaitForElementsCoreAsync(renderedFragment, cssSelector, matchElementCount: null, timeout: timeout);
+
+ ///
+ /// Wait until exactly element(s) matching the exists in the ,
+ /// or the is reached.
+ ///
+ /// The render fragment or component find the matching element in.
+ /// The CSS selector to use to search for elements.
+ /// The exact number of elements to that the should match.
+ /// The maximum time to wait for elements to appear.
+ /// Thrown if no elements is found matching the within the default timeout.
+ /// The .
+ internal static Task> WaitForElementsAsync(this IRenderedFragment renderedFragment, string cssSelector, int matchElementCount, TimeSpan timeout)
+ => WaitForElementsCoreAsync(renderedFragment, cssSelector, matchElementCount: matchElementCount, timeout: timeout);
+
+ ///
+ /// Wait until at least one element matching the exists in the ,
+ /// or the timeout is reached (default is one second).
+ ///
+ /// The render fragment or component find the matching element in.
+ /// The CSS selector to use to search for elements.
+ /// Thrown if no elements is found matching the within the default timeout.
+ /// The .
+ internal static Task> WaitForElementsAsync(this IRenderedFragment renderedFragment, string cssSelector)
+ => WaitForElementsCoreAsync(renderedFragment, cssSelector, matchElementCount: null, timeout: null);
+
private static IElement WaitForElementCore(this IRenderedFragment renderedFragment, string cssSelector, TimeSpan? timeout)
{
using var waiter = new WaitForElementHelper(renderedFragment, cssSelector, timeout);
@@ -94,16 +165,18 @@ private static IElement WaitForElementCore(this IRenderedFragment renderedFragme
{
ExceptionDispatchInfo.Capture(aggregateException.InnerExceptions[0]).Throw();
}
- else
- {
- ExceptionDispatchInfo.Capture(e).Throw();
- }
- // Unreachable code. Only here because compiler does not know that ExceptionDispatchInfo throws an exception
throw;
}
}
+ private static async Task WaitForElementCoreAsync(this IRenderedFragment renderedFragment, string cssSelector, TimeSpan? timeout)
+ {
+ using var waiter = new WaitForElementHelper(renderedFragment, cssSelector, timeout);
+
+ return await waiter.WaitTask;
+ }
+
private static IRefreshableElementCollection WaitForElementsCore(
this IRenderedFragment renderedFragment,
string cssSelector,
@@ -122,13 +195,19 @@ private static IRefreshableElementCollection WaitForElementsCore(
{
ExceptionDispatchInfo.Capture(aggregateException.InnerExceptions[0]).Throw();
}
- else
- {
- ExceptionDispatchInfo.Capture(e).Throw();
- }
- // Unreachable code. Only here because compiler does not know that ExceptionDispatchInfo throws an exception
throw;
}
}
+
+ private static async Task> WaitForElementsCoreAsync(
+ this IRenderedFragment renderedFragment,
+ string cssSelector,
+ int? matchElementCount,
+ TimeSpan? timeout)
+ {
+ using var waiter = new WaitForElementsHelper(renderedFragment, cssSelector, matchElementCount, timeout);
+
+ return await waiter.WaitTask;
+ }
}
diff --git a/src/bunit.web/JSInterop/InvocationHandlers/JSRuntimeInvocationHandlerBase{TResult}.cs b/src/bunit.web/JSInterop/InvocationHandlers/JSRuntimeInvocationHandlerBase{TResult}.cs
index 851e17131..5d423698e 100644
--- a/src/bunit.web/JSInterop/InvocationHandlers/JSRuntimeInvocationHandlerBase{TResult}.cs
+++ b/src/bunit.web/JSInterop/InvocationHandlers/JSRuntimeInvocationHandlerBase{TResult}.cs
@@ -31,7 +31,7 @@ public abstract class JSRuntimeInvocationHandlerBase
protected JSRuntimeInvocationHandlerBase(InvocationMatcher matcher, bool isCatchAllHandler)
{
invocationMatcher = matcher ?? throw new ArgumentNullException(nameof(matcher));
- completionSource = new TaskCompletionSource();
+ completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
IsCatchAllHandler = isCatchAllHandler;
}
@@ -41,7 +41,7 @@ protected JSRuntimeInvocationHandlerBase(InvocationMatcher matcher, bool isCatch
protected void SetCanceledBase()
{
if (completionSource.Task.IsCompleted)
- completionSource = new TaskCompletionSource();
+ completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
completionSource.SetCanceled();
}
@@ -54,7 +54,7 @@ protected void SetExceptionBase(TException exception)
where TException : Exception
{
if (completionSource.Task.IsCompleted)
- completionSource = new TaskCompletionSource();
+ completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
completionSource.SetException(exception);
}
@@ -66,7 +66,7 @@ protected void SetExceptionBase(TException exception)
protected void SetResultBase(TResult result)
{
if (completionSource.Task.IsCompleted)
- completionSource = new TaskCompletionSource();
+ completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
completionSource.SetResult(result);
}
diff --git a/src/bunit.web/Rendering/Internal/Htmlizer.cs b/src/bunit.web/Rendering/Internal/Htmlizer.cs
index f5b32d003..4b787a7b4 100644
--- a/src/bunit.web/Rendering/Internal/Htmlizer.cs
+++ b/src/bunit.web/Rendering/Internal/Htmlizer.cs
@@ -21,8 +21,6 @@ internal static class Htmlizer
internal const string BlazorAttrPrefix = "blazor:";
internal const string ElementReferenceAttrName = BlazorAttrPrefix + "elementReference";
- private static readonly HtmlEncoder HtmlEncoder = HtmlEncoder.Default;
-
private static readonly HashSet SelfClosingElements = new(StringComparer.OrdinalIgnoreCase)
{
"area",
@@ -95,7 +93,7 @@ private static int RenderCore(
case RenderTreeFrameType.Attribute:
throw new InvalidOperationException($"Attributes should only be encountered within {nameof(RenderElement)}");
case RenderTreeFrameType.Text:
- context.Result.Append(HtmlEncoder.Encode(frame.TextContent));
+ context.Result.Append(frame.TextContent);
return position + 1;
case RenderTreeFrameType.Markup:
context.Result.Append(frame.MarkupContent);
@@ -272,7 +270,7 @@ private static int RenderAttributes(
result.Append(frame.AttributeName);
result.Append('=');
result.Append('"');
- result.Append(HtmlEncoder.Encode(value));
+ result.Append(value);
result.Append('"');
break;
default:
@@ -299,4 +297,4 @@ public ReadOnlySpan GetRenderTreeFrames(int componentId)
public string? ClosestSelectValueAsString { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/bunit.web/TestDoubles/Authorization/FakeAuthenticationStateProvider.cs b/src/bunit.web/TestDoubles/Authorization/FakeAuthenticationStateProvider.cs
index 34e6b1aed..a077f1828 100644
--- a/src/bunit.web/TestDoubles/Authorization/FakeAuthenticationStateProvider.cs
+++ b/src/bunit.web/TestDoubles/Authorization/FakeAuthenticationStateProvider.cs
@@ -75,7 +75,7 @@ public void TriggerUnauthenticationStateChanged()
private void SetUnauthenticatedState()
{
if (authState.Task.IsCompleted)
- authState = new TaskCompletionSource();
+ authState = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
authState.SetResult(CreateUnauthenticationState());
}
@@ -83,7 +83,7 @@ private void SetUnauthenticatedState()
private void SetAuthorizingState()
{
if (authState.Task.IsCompleted)
- authState = new TaskCompletionSource();
+ authState = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
}
private void SetAuthenticatedState(
@@ -93,7 +93,7 @@ private void SetAuthenticatedState(
string? authenticationType)
{
if (authState.Task.IsCompleted)
- authState = new TaskCompletionSource();
+ authState = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
authState.SetResult(CreateAuthenticationState(userName, roles, claims, authenticationType));
}
diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props
index 143b11772..740ea9e19 100644
--- a/tests/Directory.Build.props
+++ b/tests/Directory.Build.props
@@ -16,13 +16,13 @@
-
+
-
+
diff --git a/tests/bunit.core.tests/Rendering/TestRendererTest.cs b/tests/bunit.core.tests/Rendering/TestRendererTest.cs
index 33f03507f..bf26d6dc9 100644
--- a/tests/bunit.core.tests/Rendering/TestRendererTest.cs
+++ b/tests/bunit.core.tests/Rendering/TestRendererTest.cs
@@ -337,7 +337,18 @@ public void Test100()
}
[Fact(DisplayName = "Can render component that awaits yielding task in OnInitializedAsync")]
- public void Test101()
+ [Trait("Category", "async")]
+ public async Task Test101()
+ {
+ var cut = RenderComponent(parameters =>
+ parameters.Add(p => p.EitherOr, Task.Delay(1)));
+
+ await cut.WaitForAssertionAsync(() => cut.Find("h1").TextContent.ShouldBe("SECOND"));
+ }
+
+ [Fact(DisplayName = "Can render component that awaits yielding task in OnInitializedAsync")]
+ [Trait("Category", "sync")]
+ public void Test101_Sync()
{
var cut = RenderComponent(parameters =>
parameters.Add(p => p.EitherOr, Task.Delay(1)));
diff --git a/tests/bunit.testassets/BlazorE2E/ComponentWithEscapableCharacters.razor b/tests/bunit.testassets/BlazorE2E/ComponentWithEscapableCharacters.razor
new file mode 100644
index 000000000..989c57a92
--- /dev/null
+++ b/tests/bunit.testassets/BlazorE2E/ComponentWithEscapableCharacters.razor
@@ -0,0 +1,5 @@
+
@Escaped
+
+@code {
+ private string Escaped => "url('')";
+}
diff --git a/tests/bunit.testassets/SampleComponents/ThrowsOnParameterSet.cs b/tests/bunit.testassets/SampleComponents/ThrowsOnParameterSet.cs
new file mode 100644
index 000000000..11608f9b4
--- /dev/null
+++ b/tests/bunit.testassets/SampleComponents/ThrowsOnParameterSet.cs
@@ -0,0 +1,13 @@
+namespace Bunit.TestAssets.SampleComponents;
+
+public class ThrowsOnParameterSet : ComponentBase
+{
+ private readonly string value = string.Empty;
+
+ [Parameter]
+ public string Value
+ {
+ get => value;
+ set => throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} is invalid");
+ }
+}
\ No newline at end of file
diff --git a/tests/bunit.testassets/SampleComponents/ThrowsOnParameterSet.razor b/tests/bunit.testassets/SampleComponents/ThrowsOnParameterSet.razor
deleted file mode 100644
index e1db8ec48..000000000
--- a/tests/bunit.testassets/SampleComponents/ThrowsOnParameterSet.razor
+++ /dev/null
@@ -1,17 +0,0 @@
-@code
-{
- private string value = string.Empty;
-
- [Parameter]
- // Temporary solution as the analyzer seems to ignore the .editorconfig
-#pragma warning disable BL0007 // Component parameter should be auto
- public string Value
- {
- get => value;
- set
- {
- throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} is invalid");
- }
- }
-#pragma warning restore BL0007 // Component parameter should be auto property
-}
\ No newline at end of file
diff --git a/tests/bunit.testassets/bunit.testassets.csproj b/tests/bunit.testassets/bunit.testassets.csproj
index fc1850e92..88a856ec5 100644
--- a/tests/bunit.testassets/bunit.testassets.csproj
+++ b/tests/bunit.testassets/bunit.testassets.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/tests/bunit.web.tests/BlazorE2E/ComponentRenderingTest.cs b/tests/bunit.web.tests/BlazorE2E/ComponentRenderingTest.cs
index e9f32a45d..35b0dfce4 100644
--- a/tests/bunit.web.tests/BlazorE2E/ComponentRenderingTest.cs
+++ b/tests/bunit.web.tests/BlazorE2E/ComponentRenderingTest.cs
@@ -65,7 +65,25 @@ public void CanTriggerEvents()
}
[Fact]
- public void CanTriggerAsyncEventHandlers()
+ [Trait("Category", "async")]
+ public async Task CanTriggerAsyncEventHandlers()
+ {
+ // Initial state is stopped
+ var cut = RenderComponent();
+ var stateElement = cut.Find("#state");
+ Assert.Equal("Stopped", stateElement.TextContent);
+
+ // Clicking 'tick' changes the state, and starts a task
+ cut.Find("#tick").Click();
+ Assert.Equal("Started", stateElement.TextContent);
+
+ cut.Find("#tock").Click();
+ await cut.WaitForAssertionAsync(() => Assert.Equal("Stopped", stateElement.TextContent));
+ }
+
+ [Fact]
+ [Trait("Category", "sync")]
+ public void CanTriggerAsyncEventHandlers_Sync()
{
// Initial state is stopped
var cut = RenderComponent();
@@ -509,7 +527,29 @@ public void CanRenderMultipleChildContent()
}
[Fact]
- public void CanAcceptSimultaneousRenderRequests()
+ [Trait("Category", "async")]
+ public async Task CanAcceptSimultaneousRenderRequests()
+ {
+ var expectedOutput = string.Join(
+ string.Empty,
+ Enumerable.Range(0, 100).Select(_ => "๐"));
+
+ var cut = RenderComponent();
+
+ // It's supposed to pause the rendering for this long. The WaitAssert below
+ // allows it to take up extra time if needed.
+ // await Task.Delay(1000);
+
+ var outputElement = cut.Find("#concurrent-render-output");
+
+ await cut.WaitForAssertionAsync(
+ () => Assert.Equal(expectedOutput, outputElement.TextContent.Trim()),
+ timeout: TimeSpan.FromMilliseconds(2000));
+ }
+
+ [Fact]
+ [Trait("Category", "sync")]
+ public void CanAcceptSimultaneousRenderRequests_Sync()
{
var expectedOutput = string.Join(
string.Empty,
@@ -529,7 +569,20 @@ public void CanAcceptSimultaneousRenderRequests()
}
[Fact]
- public void CanDispatchRenderToSyncContext()
+ [Trait("Category", "async")]
+ public async Task CanDispatchRenderToSyncContext()
+ {
+ var cut = RenderComponent();
+ var result = cut.Find("#result");
+
+ cut.Find("#run-with-dispatch").Click();
+
+ await cut.WaitForAssertionAsync(() => Assert.Equal("Success (completed synchronously)", result.TextContent.Trim()));
+ }
+
+ [Fact]
+ [Trait("Category", "sync")]
+ public void CanDispatchRenderToSyncContext_Sync()
{
var cut = RenderComponent();
var result = cut.Find("#result");
@@ -540,7 +593,20 @@ public void CanDispatchRenderToSyncContext()
}
[Fact]
- public void CanDoubleDispatchRenderToSyncContext()
+ [Trait("Category", "async")]
+ public async Task CanDoubleDispatchRenderToSyncContext()
+ {
+ var cut = RenderComponent();
+ var result = cut.Find("#result");
+
+ cut.Find("#run-with-double-dispatch").Click();
+
+ await cut.WaitForAssertionAsync(() => Assert.Equal("Success (completed synchronously)", result.TextContent.Trim()));
+ }
+
+ [Fact]
+ [Trait("Category", "sync")]
+ public void CanDoubleDispatchRenderToSyncContext_Sync()
{
var cut = RenderComponent();
var result = cut.Find("#result");
@@ -589,24 +655,58 @@ public void CanPatchRenderTreeToMatchLatestDOMState()
}
[Fact]
- public void CanHandleRemovedParentObjects()
+ [Trait("Category", "async")]
+ public async Task CanHandleRemovedParentObjects()
{
var cut = RenderComponent();
cut.Find("button").Click();
- cut.WaitForState(() => !cut.FindAll("div").Any());
+ await cut.WaitForStateAsync(() => !cut.FindAll("div").Any());
cut.FindAll("div").Count.ShouldBe(0);
}
[Fact]
+ [Trait("Category", "sync")]
+ public void CanHandleRemovedParentObjects_Sync()
+ {
+ var cut = RenderComponent();
+
+ cut.Find("button").Click();
+
+ cut.WaitForStateAsync(() => !cut.FindAll("div").Any());
+ cut.FindAll("div").Count.ShouldBe(0);
+ }
+
+ [Fact]
+ [Trait("Category", "async")]
public async Task CanHandleRemovedParentObjectsAsync()
{
var cut = RenderComponent();
await cut.Find("button").ClickAsync(new MouseEventArgs());
+ await cut.WaitForStateAsync(() => !cut.FindAll("div").Any());
+ cut.FindAll("div").Count.ShouldBe(0);
+ }
+
+ [Fact]
+ public void EscapableCharactersDontGetEncoded()
+ {
+ var cut = RenderComponent();
+
+ cut.Markup.ShouldBe("
url('')
");
+ }
+
+ [Fact]
+ [Trait("Category", "sync")]
+ public async Task CanHandleRemovedParentObjectsAsync_Sync()
+ {
+ var cut = RenderComponent();
+
+ await cut.Find("button").ClickAsync(new MouseEventArgs());
+
cut.WaitForState(() => !cut.FindAll("div").Any());
cut.FindAll("div").Count.ShouldBe(0);
}
-}
\ No newline at end of file
+}
diff --git a/tests/bunit.web.tests/EventDispatchExtensions/GeneralEventDispatchExtensionsTest.cs b/tests/bunit.web.tests/EventDispatchExtensions/GeneralEventDispatchExtensionsTest.cs
index b4b9b30b8..bf95d88ff 100644
--- a/tests/bunit.web.tests/EventDispatchExtensions/GeneralEventDispatchExtensionsTest.cs
+++ b/tests/bunit.web.tests/EventDispatchExtensions/GeneralEventDispatchExtensionsTest.cs
@@ -274,13 +274,31 @@ public static IEnumerable