From 26240720f3c68b5356d012cfa0882b434ca31dc9 Mon Sep 17 00:00:00 2001 From: Steven Giesel Date: Fri, 19 Sep 2025 18:23:56 +0200 Subject: [PATCH] feat: Add asynchronous element retrieval methods for rendered components --- .../TriggerEventDispatchExtensions.cs | 2 +- .../IRefreshableElementCollection.cs | 24 ---- .../Internal/RefreshableElementCollection.cs | 59 --------- .../Extensions/RenderedComponentExtensions.cs | 26 ++-- .../RenderedComponentFindAsyncExtensions.cs | 103 +++++++++++++++ ...tWaitForHelperExtensions.WaitForElement.cs | 120 ++---------------- .../WaitForHelpers/WaitForElementHelper.cs | 13 +- .../WaitForHelpers/WaitForElementsHelper.cs | 12 +- .../BlazorE2E/ComponentRenderingTest.cs | 50 +++----- .../RefreshableQueryCollectionTest.cs | 63 --------- ...entWaitForElementsHelperExtensions.Test.cs | 100 --------------- ...nentWaitForElementsHelperExtensionsTest.cs | 46 +++---- tests/bunit.tests/bunit.tests.csproj | 4 + 13 files changed, 194 insertions(+), 428 deletions(-) delete mode 100644 src/bunit/Extensions/IRefreshableElementCollection.cs delete mode 100644 src/bunit/Extensions/Internal/RefreshableElementCollection.cs create mode 100644 src/bunit/Extensions/WaitForHelpers/RenderedComponentFindAsyncExtensions.cs delete mode 100644 tests/bunit.tests/Extensions/RefreshableQueryCollectionTest.cs delete mode 100644 tests/bunit.tests/Extensions/WaitForHelpers/RenderedComponentWaitForElementsHelperExtensions.Test.cs diff --git a/src/bunit/EventDispatchExtensions/TriggerEventDispatchExtensions.cs b/src/bunit/EventDispatchExtensions/TriggerEventDispatchExtensions.cs index d5dc40768..22ab2474c 100644 --- a/src/bunit/EventDispatchExtensions/TriggerEventDispatchExtensions.cs +++ b/src/bunit/EventDispatchExtensions/TriggerEventDispatchExtensions.cs @@ -67,7 +67,7 @@ public static Task TriggerEventAsync(this IElement element, string eventName, Ev // TriggerEventsAsync will traverse the DOM tree to find // all event handlers that needs to be triggered. This is done - // in the renderes synchronization context to avoid a race condition + // in the renderer synchronization context to avoid a race condition // between the DOM tree being updated and traversed. var result = renderer.Dispatcher.InvokeAsync( () => TriggerEventsAsync(renderer, element, eventName, eventArgs)); diff --git a/src/bunit/Extensions/IRefreshableElementCollection.cs b/src/bunit/Extensions/IRefreshableElementCollection.cs deleted file mode 100644 index b98c0e093..000000000 --- a/src/bunit/Extensions/IRefreshableElementCollection.cs +++ /dev/null @@ -1,24 +0,0 @@ -using AngleSharp.Dom; - -namespace Bunit; - -/// -/// Represents a collection, which queries and finds its -/// elements in an , based on a CSS selector. -/// The collection can be refreshed either manually or automatically. -/// -/// The type of in the collection. -public interface IRefreshableElementCollection : IReadOnlyList - where T : IElement -{ - /// - /// Gets or sets a value indicating whether the collection automatically refreshes when the - /// changes. - /// - bool EnableAutoRefresh { get; set; } - - /// - /// Trigger a refresh of the elements in the collection, by querying the rendered fragments DOM tree. - /// - void Refresh(); -} diff --git a/src/bunit/Extensions/Internal/RefreshableElementCollection.cs b/src/bunit/Extensions/Internal/RefreshableElementCollection.cs deleted file mode 100644 index f6041fe2a..000000000 --- a/src/bunit/Extensions/Internal/RefreshableElementCollection.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Collections; -using System.Diagnostics; -using AngleSharp.Dom; - -namespace Bunit; - -[DebuggerDisplay("Selector={cssSelector}, AutoRefresh={enableAutoRefresh}")] -internal sealed class RefreshableElementCollection : IRefreshableElementCollection -{ - private readonly IRenderedComponent renderedComponent; - private readonly string cssSelector; - private IHtmlCollection elements; - private bool enableAutoRefresh; - - public bool EnableAutoRefresh - { - get => enableAutoRefresh; - set - { - if (ShouldEnable(value)) - { - renderedComponent.OnMarkupUpdated += RefreshInternal; - } - - if (ShouldDisable(value)) - { - renderedComponent.OnMarkupUpdated -= RefreshInternal; - } - - enableAutoRefresh = value; - } - } - - private bool ShouldDisable(bool value) => !value && enableAutoRefresh; - - private bool ShouldEnable(bool value) => value && !enableAutoRefresh; - - internal RefreshableElementCollection(IRenderedComponent renderedComponent, string cssSelector) - { - this.renderedComponent = renderedComponent; - this.cssSelector = cssSelector; - elements = renderedComponent.Nodes.QuerySelectorAll(cssSelector); - } - - public void Refresh() => RefreshInternal(this, EventArgs.Empty); - - public IElement this[int index] => elements[index]; - - public int Count => elements.Length; - - public IEnumerator GetEnumerator() => elements.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private void RefreshInternal(object? sender, EventArgs args) - { - elements = renderedComponent.Nodes.QuerySelectorAll(cssSelector); - } -} diff --git a/src/bunit/Extensions/RenderedComponentExtensions.cs b/src/bunit/Extensions/RenderedComponentExtensions.cs index dfbefd584..585ff9d29 100644 --- a/src/bunit/Extensions/RenderedComponentExtensions.cs +++ b/src/bunit/Extensions/RenderedComponentExtensions.cs @@ -21,12 +21,7 @@ public static IElement Find(this IRenderedComponent rend { ArgumentNullException.ThrowIfNull(renderedComponent); - var result = renderedComponent.Nodes.QuerySelector(cssSelector); - - if (result is null) - throw new ElementNotFoundException(cssSelector); - - return result.WrapUsing(new CssSelectorElementFactory((IRenderedComponent)renderedComponent, cssSelector)); + return renderedComponent.FindAsync(cssSelector, timeout: TimeSpan.Zero).GetAwaiter().GetResult(); } /// @@ -36,13 +31,13 @@ public static IElement Find(this IRenderedComponent rend /// /// The rendered fragment to search. /// The group of selectors to use. - /// If true, the returned will automatically refresh its s whenever the changes. - /// An , that can be refreshed to execute the search again. - public static IRefreshableElementCollection FindAll(this IRenderedComponent renderedComponent, string cssSelector, bool enableAutoRefresh = false) + /// Obsolete: This parameter is no longer used and will be removed in a future version. + public static IReadOnlyList FindAll(this IRenderedComponent renderedComponent, string cssSelector, bool enableAutoRefresh = false) where TComponent : IComponent { ArgumentNullException.ThrowIfNull(renderedComponent); - return new RefreshableElementCollection((IRenderedComponent)renderedComponent, cssSelector) { EnableAutoRefresh = enableAutoRefresh }; + + return renderedComponent.FindAllAsync(cssSelector, TimeSpan.Zero).GetAwaiter().GetResult(); } /// @@ -87,4 +82,15 @@ public static IReadOnlyList> FindComponents< /// True if the contains the ; otherwise false. public static bool HasComponent(this IRenderedComponent renderedComponent) where TChildComponent : IComponent => FindComponents(renderedComponent).Count > 0; + + /// + /// Finds all elements from the rendered fragment or component under test, + /// + internal static IReadOnlyList FindAllInternal(this IRenderedComponent renderedComponent, string cssSelector) + where TComponent : IComponent + { + ArgumentNullException.ThrowIfNull(renderedComponent); + + return renderedComponent.FindAllAsync(cssSelector).GetAwaiter().GetResult(); + } } diff --git a/src/bunit/Extensions/WaitForHelpers/RenderedComponentFindAsyncExtensions.cs b/src/bunit/Extensions/WaitForHelpers/RenderedComponentFindAsyncExtensions.cs new file mode 100644 index 000000000..2bc332a56 --- /dev/null +++ b/src/bunit/Extensions/WaitForHelpers/RenderedComponentFindAsyncExtensions.cs @@ -0,0 +1,103 @@ +using AngleSharp.Dom; +using Bunit.Extensions.WaitForHelpers; + +namespace Bunit; + +/// +/// Helper methods that deal with asynchronous retrieval of elements from a rendered component. +/// +public static class RenderedComponentFindAsyncExtensions +{ + /// + /// 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 . + public static Task FindAsync(this IRenderedComponent renderedComponent, string cssSelector) + where TComponent : IComponent => FindCoreAsync(renderedComponent, 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 . + public static Task FindAsync(this IRenderedComponent renderedComponent, string cssSelector, TimeSpan timeout) + where TComponent : IComponent + => FindCoreAsync(renderedComponent, 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. + public static Task> FindAllAsync(this IRenderedComponent renderedComponent, string cssSelector, int matchElementCount) + where TComponent : IComponent + => FindAllCoreAsync(renderedComponent, 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. + public static Task> FindAllAsync(this IRenderedComponent renderedComponent, string cssSelector, TimeSpan timeout) + where TComponent : IComponent + => FindAllCoreAsync(renderedComponent, 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. + public static Task> FindAllAsync(this IRenderedComponent renderedComponent, string cssSelector, int matchElementCount, TimeSpan timeout) + where TComponent : IComponent + => FindAllCoreAsync(renderedComponent, 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. + public static Task> FindAllAsync(this IRenderedComponent renderedComponent, string cssSelector) + where TComponent : IComponent + => FindAllCoreAsync(renderedComponent, cssSelector, matchElementCount: null, timeout: null); + + private static async Task FindCoreAsync(this IRenderedComponent renderedComponent, string cssSelector, TimeSpan? timeout) + where TComponent : IComponent + { + using var waiter = new WaitForElementHelper(renderedComponent, cssSelector, timeout); + + return await waiter.WaitTask; + } + + + private static async Task> FindAllCoreAsync( + this IRenderedComponent renderedComponent, + string cssSelector, + int? matchElementCount, + TimeSpan? timeout) + where TComponent : IComponent + { + using var waiter = new WaitForElementsHelper(renderedComponent, cssSelector, matchElementCount, timeout); + + return (await waiter.WaitTask).ToArray(); + } +} diff --git a/src/bunit/Extensions/WaitForHelpers/RenderedComponentWaitForHelperExtensions.WaitForElement.cs b/src/bunit/Extensions/WaitForHelpers/RenderedComponentWaitForHelperExtensions.WaitForElement.cs index 912dece39..4a278dd0a 100644 --- a/src/bunit/Extensions/WaitForHelpers/RenderedComponentWaitForHelperExtensions.WaitForElement.cs +++ b/src/bunit/Extensions/WaitForHelpers/RenderedComponentWaitForHelperExtensions.WaitForElement.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Runtime.ExceptionServices; using AngleSharp.Dom; using Bunit.Extensions.WaitForHelpers; @@ -40,8 +41,7 @@ public static IElement WaitForElement(this IRenderedComponentThe 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 . - public static IRefreshableElementCollection WaitForElements(this IRenderedComponent renderedComponent, string cssSelector) + public static IReadOnlyList WaitForElements(this IRenderedComponent renderedComponent, string cssSelector) where TComponent : IComponent => WaitForElementsCore(renderedComponent, cssSelector, matchElementCount: null, timeout: null); /// @@ -52,8 +52,7 @@ public static IRefreshableElementCollection WaitForElementsThe 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 . - public static IRefreshableElementCollection WaitForElements(this IRenderedComponent renderedComponent, string cssSelector, int matchElementCount) + public static IReadOnlyList WaitForElements(this IRenderedComponent renderedComponent, string cssSelector, int matchElementCount) where TComponent : IComponent => WaitForElementsCore(renderedComponent, cssSelector, matchElementCount: matchElementCount, timeout: null); /// @@ -64,8 +63,7 @@ public static IRefreshableElementCollection WaitForElementsThe 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 . - public static IRefreshableElementCollection WaitForElements(this IRenderedComponent renderedComponent, string cssSelector, TimeSpan timeout) + public static IReadOnlyList WaitForElements(this IRenderedComponent renderedComponent, string cssSelector, TimeSpan timeout) where TComponent : IComponent => WaitForElementsCore(renderedComponent, cssSelector, matchElementCount: null, timeout: timeout); @@ -78,87 +76,10 @@ public static IRefreshableElementCollection WaitForElementsThe 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 . - public static IRefreshableElementCollection WaitForElements(this IRenderedComponent renderedComponent, string cssSelector, int matchElementCount, TimeSpan timeout) + public static IReadOnlyList WaitForElements(this IRenderedComponent renderedComponent, string cssSelector, int matchElementCount, TimeSpan timeout) where TComponent : IComponent => WaitForElementsCore(renderedComponent, 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 IRenderedComponent renderedComponent, string cssSelector) - where TComponent : IComponent => WaitForElementCoreAsync(renderedComponent, 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 IRenderedComponent renderedComponent, string cssSelector, TimeSpan timeout) - where TComponent : IComponent - => WaitForElementCoreAsync(renderedComponent, 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 IRenderedComponent renderedComponent, string cssSelector, int matchElementCount) - where TComponent : IComponent - => WaitForElementsCoreAsync(renderedComponent, 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 IRenderedComponent renderedComponent, string cssSelector, TimeSpan timeout) - where TComponent : IComponent - => WaitForElementsCoreAsync(renderedComponent, 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 IRenderedComponent renderedComponent, string cssSelector, int matchElementCount, TimeSpan timeout) - where TComponent : IComponent - => WaitForElementsCoreAsync(renderedComponent, 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 IRenderedComponent renderedComponent, string cssSelector) - where TComponent : IComponent - => WaitForElementsCoreAsync(renderedComponent, cssSelector, matchElementCount: null, timeout: null); - private static IElement WaitForElementCore(this IRenderedComponent renderedComponent, string cssSelector, TimeSpan? timeout) where TComponent : IComponent { @@ -172,20 +93,10 @@ private static IElement WaitForElementCore(this IRenderedComponent WaitForElementCoreAsync(this IRenderedComponent renderedComponent, string cssSelector, TimeSpan? timeout) - where TComponent : IComponent - { - using var waiter = new WaitForElementHelper(renderedComponent, cssSelector, timeout); - - return await waiter.WaitTask; - } - - private static IRefreshableElementCollection WaitForElementsCore( + private static IElement[] WaitForElementsCore( this IRenderedComponent renderedComponent, string cssSelector, int? matchElementCount, @@ -196,26 +107,13 @@ private static IRefreshableElementCollection WaitForElementsCore> WaitForElementsCoreAsync( - this IRenderedComponent renderedComponent, - string cssSelector, - int? matchElementCount, - TimeSpan? timeout) - where TComponent : IComponent - { - using var waiter = new WaitForElementsHelper(renderedComponent, cssSelector, matchElementCount, timeout); - - return await waiter.WaitTask; - } } diff --git a/src/bunit/Extensions/WaitForHelpers/WaitForElementHelper.cs b/src/bunit/Extensions/WaitForHelpers/WaitForElementHelper.cs index 316ead9d1..8830fc42b 100644 --- a/src/bunit/Extensions/WaitForHelpers/WaitForElementHelper.cs +++ b/src/bunit/Extensions/WaitForHelpers/WaitForElementHelper.cs @@ -1,4 +1,5 @@ using AngleSharp.Dom; +using Bunit.Web.AngleSharp; namespace Bunit.Extensions.WaitForHelpers; @@ -19,9 +20,19 @@ internal class WaitForElementHelper : WaitForHelper renderedComponent, string cssSelector, TimeSpan? timeout = null) : base(renderedComponent, () => { - var element = renderedComponent.Find(cssSelector); + var element = FindElement(renderedComponent, cssSelector); return (true, element); }, timeout) { } + + private static IElement FindElement(IRenderedComponent renderedComponent, string cssSelector) + { + var result = renderedComponent.Nodes.QuerySelector(cssSelector); + + if (result is null) + throw new ElementNotFoundException(cssSelector); + + return result.WrapUsing(new CssSelectorElementFactory((IRenderedComponent)renderedComponent, cssSelector)); + } } diff --git a/src/bunit/Extensions/WaitForHelpers/WaitForElementsHelper.cs b/src/bunit/Extensions/WaitForHelpers/WaitForElementsHelper.cs index 53f477193..29da6b442 100644 --- a/src/bunit/Extensions/WaitForHelpers/WaitForElementsHelper.cs +++ b/src/bunit/Extensions/WaitForHelpers/WaitForElementsHelper.cs @@ -1,13 +1,14 @@ using System.Globalization; using System.Text; using AngleSharp.Dom; +using Bunit.Web.AngleSharp; namespace Bunit.Extensions.WaitForHelpers; /// /// Represents an async wait helper, that will wait for a specified time for element(s) to become available in the DOM. /// -internal class WaitForElementsHelper : WaitForHelper, TComponent> +internal class WaitForElementsHelper : WaitForHelper, TComponent> where TComponent : IComponent { internal const string TimeoutBeforeFoundMessage = "The CSS selector did not result in any matching element(s) before the timeout period passed."; @@ -25,15 +26,18 @@ internal class WaitForElementsHelper : WaitForHelper renderedComponent, string cssSelector, int? matchElementCount, TimeSpan? timeout = null) : base(renderedComponent, () => { - var elements = renderedComponent.FindAll(cssSelector); + var elements = FindAllElements(renderedComponent, cssSelector); var checkPassed = matchElementCount is null - ? elements.Count > 0 - : elements.Count == matchElementCount; + ? elements.Length > 0 + : elements.Length == matchElementCount; return (checkPassed, elements); }, timeout) { this.matchElementCount = matchElementCount; } + + private static IHtmlCollection FindAllElements(IRenderedComponent renderedComponent, string cssSelector) + => renderedComponent.Nodes.QuerySelectorAll(cssSelector); } diff --git a/tests/bunit.tests/BlazorE2E/ComponentRenderingTest.cs b/tests/bunit.tests/BlazorE2E/ComponentRenderingTest.cs index f7ec92b68..16d2f04e2 100644 --- a/tests/bunit.tests/BlazorE2E/ComponentRenderingTest.cs +++ b/tests/bunit.tests/BlazorE2E/ComponentRenderingTest.cs @@ -101,27 +101,29 @@ public void CanTriggerAsyncEventHandlers_Sync() } [Fact] - public void CanTriggerKeyPressEvents() + public async Task CanTriggerKeyPressEvents() { // List is initially empty var cut = Render(); var inputElement = cut.Find("input"); - var liElements = cut.FindAll("li", enableAutoRefresh: true); + var liElements = await cut.FindAllAsync("li", 0); liElements.ShouldBeEmpty(); // Typing adds element inputElement.KeyPress("a"); + liElements = await cut.FindAllAsync("li"); liElements.ShouldAllBe(li => Assert.Equal("a", li.TextContent)); // Typing again adds another element inputElement.KeyPress("b"); + liElements = await cut.FindAllAsync("li"); liElements.ShouldAllBe( li => Assert.Equal("a", li.TextContent), li => Assert.Equal("b", li.TextContent)); } [Fact] - public void CanAddAndRemoveEventHandlersDynamically() + public async Task CanAddAndRemoveEventHandlersDynamically() { var cut = Render(); var countDisplayElement = cut.Find("p"); @@ -135,7 +137,7 @@ public void CanAddAndRemoveEventHandlersDynamically() // We can remove an event handler toggleClickHandlerCheckbox.Change(false); - Assert.Empty(cut.FindAll("#listening-message")); + Assert.Empty(await cut.FindAllAsync("#listening-message", 0)); incrementButton.Click(); Assert.Equal("Current count: 1", countDisplayElement.TextContent); @@ -229,13 +231,13 @@ public void ChildComponentsRerenderWhenPropertiesChanged() } [Fact] - public void CanAddAndRemoveChildComponentsDynamically() + public async Task CanAddAndRemoveChildComponentsDynamically() { // Initially there are zero child components var cut = Render(); var addButton = cut.Find(".addChild"); var removeButton = cut.Find(".removeChild"); - Assert.Empty(cut.FindAll("p")); + Assert.Empty(await cut.FindAllAsync("p", 0)); // Click to add/remove some child components addButton.Click(); @@ -279,13 +281,13 @@ public void ChildComponentsNotifiedWhenPropertiesChanged() } [Fact] - public void CanRenderFragmentsWhilePreservingSurroundingElements() + public async Task CanRenderFragmentsWhilePreservingSurroundingElements() { // Initially, the region isn't shown var cut = Render(); var originalButton = cut.Find("button"); - var fragmentElements = cut.FindAll("p[name=fragment-element]", enableAutoRefresh: true); + var fragmentElements = await cut.FindAllAsync("p[name=fragment-element]", 0); Assert.Empty(fragmentElements); // The JS-side DOM builder handles regions correctly, placing elements @@ -294,10 +296,12 @@ public void CanRenderFragmentsWhilePreservingSurroundingElements() // When we click the button, the region is shown originalButton.Click(); + fragmentElements = await cut.FindAllAsync("p[name=fragment-element]"); fragmentElements.Single().ShouldNotBeNull(); // The button itself was preserved, so we can click it again and see the effect originalButton.Click(); + fragmentElements = await cut.FindAllAsync("p[name=fragment-element]", 0); Assert.Empty(fragmentElements); } @@ -429,7 +433,7 @@ public void CanCaptureReferencesToDynamicallyAddedElements() } [Fact] - public void CanCaptureReferencesToDynamicallyAddedComponents() + public async Task CanCaptureReferencesToDynamicallyAddedComponents() { var cut = Render(); var incrementButton = cut.Find("#child-component button"); @@ -447,7 +451,7 @@ public void CanCaptureReferencesToDynamicallyAddedComponents() // Remove and re-add a new instance of the child, checking the text was reset toggleChildCheckbox.Change(false); - Assert.Empty(cut.FindAll("#child-component button")); + Assert.Empty(await cut.FindAllAsync("#child-component button", 0)); toggleChildCheckbox.Change(true); Assert.Equal("Current count: 0", currentCountText.TextContent); @@ -664,22 +668,10 @@ public async Task CanHandleRemovedParentObjects() cut.Find("button").Click(); - await cut.WaitForStateAsync(() => !cut.FindAll("div").Any()); - cut.FindAll("div").Count.ShouldBe(0); + var allElements = await cut.FindAllAsync("div", 0); + allElements.Count.ShouldBe(0); } - - [Fact] - [Trait("Category", "sync")] - public void CanHandleRemovedParentObjects_Sync() - { - var cut = Render(); - - cut.Find("button").Click(); - - cut.WaitForState(() => !cut.FindAll("div").Any()); - cut.FindAll("div").Count.ShouldBe(0); - } - + [Fact] [Trait("Category", "async")] public async Task CanHandleRemovedParentObjectsAsync() @@ -688,8 +680,8 @@ public async Task CanHandleRemovedParentObjectsAsync() await cut.Find("button").ClickAsync(new MouseEventArgs()); - await cut.WaitForStateAsync(() => !cut.FindAll("div").Any()); - cut.FindAll("div").Count.ShouldBe(0); + var elements = await cut.FindAllAsync("div", 0); + elements.Count.ShouldBe(0); } [Theory] @@ -712,8 +704,8 @@ public async Task CanHandleRemovedParentObjectsAsync_Sync() await cut.Find("button").ClickAsync(new MouseEventArgs()); - cut.WaitForState(() => !cut.FindAll("div").Any()); - cut.FindAll("div").Count.ShouldBe(0); + var allElements = await cut.FindAllAsync("div", 0); + allElements.Count.ShouldBe(0); } [Fact] diff --git a/tests/bunit.tests/Extensions/RefreshableQueryCollectionTest.cs b/tests/bunit.tests/Extensions/RefreshableQueryCollectionTest.cs deleted file mode 100644 index 91036fb81..000000000 --- a/tests/bunit.tests/Extensions/RefreshableQueryCollectionTest.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace Bunit; - -public class RefreshableQueryCollectionTest : BunitContext -{ - [Fact(DisplayName = "When the query returns no elements, the collection is empty")] - public void Test001() - { - var cut = Render(); - - var sut = new RefreshableElementCollection(cut, ".foo"); - - sut.ShouldBeEmpty(); - } - - [Fact(DisplayName = "When the query returns elements, the collection contains those elements")] - public void Test002() - { - var cut = Render(); - - var sut = new RefreshableElementCollection(cut, "h1"); - - sut.Count.ShouldBe(1); - sut[0].TagName.ShouldBe("H1"); - } - - [Fact(DisplayName = "When Refresh is called, the query is run again and new elements are made available")] - public void Test003() - { - var cut = Render(); - var sut = new RefreshableElementCollection(cut, "li"); - sut.Count.ShouldBe(0); - - cut.Find("button").Click(); - - sut.Refresh(); - sut.Count.ShouldBe(1); - } - - [Fact(DisplayName = "Enabling auto refresh automatically refreshes query when the rendered fragment renders and has changes")] - public void Test004() - { - var cut = Render(); - var sut = new RefreshableElementCollection(cut, "li") { EnableAutoRefresh = true }; - sut.Count.ShouldBe(0); - - cut.Find("button").Click(); - - sut.Count.ShouldBe(1); - } - - [Fact(DisplayName = "Disabling auto refresh turns off automatic refreshing queries on when rendered fragment changes")] - public void Test005() - { - var cut = Render(); - var sut = new RefreshableElementCollection(cut, "li") { EnableAutoRefresh = true }; - - sut.EnableAutoRefresh = false; - - cut.Find("button").Click(); - - sut.Count.ShouldBe(0); - } -} diff --git a/tests/bunit.tests/Extensions/WaitForHelpers/RenderedComponentWaitForElementsHelperExtensions.Test.cs b/tests/bunit.tests/Extensions/WaitForHelpers/RenderedComponentWaitForElementsHelperExtensions.Test.cs deleted file mode 100644 index 15167b052..000000000 --- a/tests/bunit.tests/Extensions/WaitForHelpers/RenderedComponentWaitForElementsHelperExtensions.Test.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Globalization; - -namespace Bunit.Extensions.WaitForHelpers; - -public class RenderedComponentWaitForElementsHelperExtensions : BunitContext -{ - private readonly static TimeSpan WaitForTestTimeout = TimeSpan.FromMilliseconds(5); - - public RenderedComponentWaitForElementsHelperExtensions(ITestOutputHelper testOutput) - { - BunitContext.DefaultWaitTimeout = TimeSpan.FromSeconds(30); - Services.AddXunitLogger(testOutput); - } - - [Fact(DisplayName = "WaitForElement waits until cssSelector returns at a element")] - [Trait("Category", "async")] - public async Task Test001() - { - var expectedMarkup = "

child content

"; - var cut = Render(ps => ps.AddChildContent(expectedMarkup)); - - var elm = await cut.WaitForElementAsync("main > p"); - - elm.MarkupMatches(expectedMarkup); - } - - [Fact(DisplayName = "WaitForElement throws exception after timeout when cssSelector does not result in matching element")] - [Trait("Category", "async")] - public async Task Test002() - { - var cut = Render(); - - var expected = await Should.ThrowAsync(async () => - await cut.WaitForElementAsync("#notHereElm", WaitForTestTimeout)); - - expected.Message.ShouldStartWith(WaitForElementHelper.TimeoutBeforeFoundMessage); - } - - [Fact(DisplayName = "WaitForElements waits until cssSelector returns at least one element")] - [Trait("Category", "async")] - public async Task Test021() - { - var expectedMarkup = "

child content

"; - var cut = Render(ps => ps.AddChildContent(expectedMarkup)); - - var elms = await cut.WaitForElementsAsync("main > p"); - - elms.MarkupMatches(expectedMarkup); - } - - [Fact(DisplayName = "WaitForElements throws exception after timeout when cssSelector does not result in matching elements")] - [Trait("Category", "async")] - public async Task Test022() - { - var cut = Render(); - - var expected = await Should.ThrowAsync(async () => - await cut.WaitForElementsAsync("#notHereElm", WaitForTestTimeout)); - - expected.Message.ShouldStartWith(WaitForElementsHelper.TimeoutBeforeFoundMessage); - expected.InnerException.ShouldBeNull(); - } - - [Fact(DisplayName = "WaitForElements with specific count N throws exception after timeout when cssSelector does not result in N matching elements")] - [Trait("Category", "async")] - public async Task Test023() - { - var cut = Render(); - - var expected = await Should.ThrowAsync(async () => - await cut.WaitForElementsAsync("#notHereElm", 2, WaitForTestTimeout)); - - expected.Message.ShouldStartWith(string.Format(CultureInfo.InvariantCulture, WaitForElementsHelper.TimeoutBeforeFoundWithCountMessage, 2)); - expected.InnerException.ShouldBeNull(); - } - - [Fact(DisplayName = "WaitForElements with specific count N waits until cssSelector returns at exact N elements")] - [Trait("Category", "async")] - public async Task Test024() - { - var expectedMarkup = "

child content

child content

child content

"; - var cut = Render(ps => ps.AddChildContent(expectedMarkup)); - - var elms = await cut.WaitForElementsAsync("main > p", matchElementCount: 3); - - elms.MarkupMatches(expectedMarkup); - } - - [Fact(DisplayName = "WaitForElements with specific count 0 waits until cssSelector returns at exact zero elements")] - [Trait("Category", "async")] - public async Task Test025() - { - var expectedMarkup = "

child content

"; - var cut = Render(ps => ps.AddChildContent(expectedMarkup)); - - var elms = await cut.WaitForElementsAsync("main > p", matchElementCount: 0); - - elms.ShouldBeEmpty(); - } -} diff --git a/tests/bunit.tests/Extensions/WaitForHelpers/RenderedComponentWaitForElementsHelperExtensionsTest.cs b/tests/bunit.tests/Extensions/WaitForHelpers/RenderedComponentWaitForElementsHelperExtensionsTest.cs index 052fb3559..312746426 100644 --- a/tests/bunit.tests/Extensions/WaitForHelpers/RenderedComponentWaitForElementsHelperExtensionsTest.cs +++ b/tests/bunit.tests/Extensions/WaitForHelpers/RenderedComponentWaitForElementsHelperExtensionsTest.cs @@ -2,98 +2,92 @@ namespace Bunit.Extensions.WaitForHelpers; -public class RenderedComponentWaitForElementsHelperExtensionsTest : BunitContext +public class RenderedComponentWaitForElementsHelperExtensions : BunitContext { private readonly static TimeSpan WaitForTestTimeout = TimeSpan.FromMilliseconds(5); - public RenderedComponentWaitForElementsHelperExtensionsTest(ITestOutputHelper testOutput) + public RenderedComponentWaitForElementsHelperExtensions(ITestOutputHelper testOutput) { BunitContext.DefaultWaitTimeout = TimeSpan.FromSeconds(30); Services.AddXunitLogger(testOutput); } [Fact(DisplayName = "WaitForElement waits until cssSelector returns at a element")] - [Trait("Category", "sync")] - public void Test001() + public async Task Test001() { var expectedMarkup = "

child content

"; var cut = Render(ps => ps.AddChildContent(expectedMarkup)); - var elm = cut.WaitForElement("main > p"); + var elm = await cut.FindAllAsync("main > p"); elm.MarkupMatches(expectedMarkup); } [Fact(DisplayName = "WaitForElement throws exception after timeout when cssSelector does not result in matching element")] - [Trait("Category", "sync")] - public void Test002() + public async Task Test002() { var cut = Render(); - var expected = Should.Throw(() => - cut.WaitForElement("#notHereElm", WaitForTestTimeout)); + var expected = await Should.ThrowAsync(async () => + await cut.FindAllAsync("#notHereElm", WaitForTestTimeout)); expected.Message.ShouldStartWith(WaitForElementHelper.TimeoutBeforeFoundMessage); } [Fact(DisplayName = "WaitForElements waits until cssSelector returns at least one element")] - [Trait("Category", "sync")] - public void Test021() + public async Task Test021() { var expectedMarkup = "

child content

"; var cut = Render(ps => ps.AddChildContent(expectedMarkup)); - var elms = cut.WaitForElements("main > p"); + var elms = await cut.FindAllAsync("main > p"); elms.MarkupMatches(expectedMarkup); } [Fact(DisplayName = "WaitForElements throws exception after timeout when cssSelector does not result in matching elements")] - [Trait("Category", "sync")] - public void Test022() + [Trait("Category", "async")] + public async Task Test022() { var cut = Render(); - var expected = Should.Throw(() => - cut.WaitForElements("#notHereElm", WaitForTestTimeout)); + var expected = await Should.ThrowAsync(async () => + await cut.FindAllAsync("#notHereElm", WaitForTestTimeout)); expected.Message.ShouldStartWith(WaitForElementsHelper.TimeoutBeforeFoundMessage); expected.InnerException.ShouldBeNull(); } [Fact(DisplayName = "WaitForElements with specific count N throws exception after timeout when cssSelector does not result in N matching elements")] - [Trait("Category", "sync")] - public void Test023() + public async Task Test023() { var cut = Render(); - var expected = Should.Throw(() => - cut.WaitForElements("#notHereElm", 2, WaitForTestTimeout)); + var expected = await Should.ThrowAsync(async () => + await cut.FindAllAsync("#notHereElm", 2, WaitForTestTimeout)); expected.Message.ShouldStartWith(string.Format(CultureInfo.InvariantCulture, WaitForElementsHelper.TimeoutBeforeFoundWithCountMessage, 2)); expected.InnerException.ShouldBeNull(); } [Fact(DisplayName = "WaitForElements with specific count N waits until cssSelector returns at exact N elements")] - [Trait("Category", "sync")] - public void Test024() + public async Task Test024() { var expectedMarkup = "

child content

child content

child content

"; var cut = Render(ps => ps.AddChildContent(expectedMarkup)); - var elms = cut.WaitForElements("main > p", matchElementCount: 3); + var elms = await cut.FindAllAsync("main > p", matchElementCount: 3); elms.MarkupMatches(expectedMarkup); } [Fact(DisplayName = "WaitForElements with specific count 0 waits until cssSelector returns at exact zero elements")] - [Trait("Category", "sync")] - public void Test025() + public async Task Test025() { var expectedMarkup = "

child content

"; var cut = Render(ps => ps.AddChildContent(expectedMarkup)); - var elms = cut.WaitForElements("main > p", matchElementCount: 0); + var elms = await cut.FindAllAsync("main > p", matchElementCount: 0); elms.ShouldBeEmpty(); } diff --git a/tests/bunit.tests/bunit.tests.csproj b/tests/bunit.tests/bunit.tests.csproj index 45f6e8727..e39baec4b 100644 --- a/tests/bunit.tests/bunit.tests.csproj +++ b/tests/bunit.tests/bunit.tests.csproj @@ -14,5 +14,9 @@ + + + + \ No newline at end of file