Skip to content

Commit 83d0a35

Browse files
committed
feat: Add generic overloads for Find and WaitFor methods to support specific element types
1 parent ca409ec commit 83d0a35

File tree

7 files changed

+386
-14
lines changed

7 files changed

+386
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ All notable changes to **bUnit** will be documented in this file. The project ad
88

99
## Added
1010
- Added `FindByAllByLabel` to `bunit.web.query` package. By [@linkdotnet](https://github.com/linkdotnet).
11+
- Added generic overloads `Find<TComponent, TElement>` and `FindAll<TComponent, TElement>` to query for specific element types (e.g., `IHtmlInputElement`). By [@linkdotnet](https://github.com/linkdotnet).
12+
- Added generic overloads `WaitForElement<TComponent, TElement>` and `WaitForElements<TComponent, TElement>` to wait for specific element types. By [@linkdotnet](https://github.com/linkdotnet).
1113

1214
## [2.1.1] - 2025-11-21
1315

src/bunit/Extensions/RenderedComponentExtensions.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@ public static class RenderedComponentExtensions
1818
/// <param name="cssSelector">The group of selectors to use.</param>
1919
public static IElement Find<TComponent>(this IRenderedComponent<TComponent> renderedComponent, string cssSelector)
2020
where TComponent : IComponent
21+
=> Find<TComponent, IElement>(renderedComponent, cssSelector);
22+
23+
/// <summary>
24+
/// Returns the first element of type <typeparamref name="TElement"/> from the rendered fragment or component under test,
25+
/// using the provided <paramref name="cssSelector"/>, in a depth-first pre-order traversal
26+
/// of the rendered nodes.
27+
/// </summary>
28+
/// <typeparam name="TComponent">The type of the component under test.</typeparam>
29+
/// <typeparam name="TElement">The type of element to find (e.g., IHtmlInputElement).</typeparam>
30+
/// <param name="renderedComponent">The rendered fragment to search.</param>
31+
/// <param name="cssSelector">The group of selectors to use.</param>
32+
/// <exception cref="ElementNotFoundException">Thrown if no element matches the <paramref name="cssSelector"/>.</exception>
33+
public static TElement Find<TComponent, TElement>(this IRenderedComponent<TComponent> renderedComponent, string cssSelector)
34+
where TComponent : IComponent
35+
where TElement : class, IElement
2136
{
2237
ArgumentNullException.ThrowIfNull(renderedComponent);
2338

@@ -26,7 +41,11 @@ public static IElement Find<TComponent>(this IRenderedComponent<TComponent> rend
2641
if (result is null)
2742
throw new ElementNotFoundException(cssSelector);
2843

29-
return result.WrapUsing(new CssSelectorElementFactory((IRenderedComponent<IComponent>)renderedComponent, cssSelector));
44+
if (result is not TElement)
45+
throw new ElementNotFoundException(
46+
$"The element matching '{cssSelector}' is of type '{result.GetType().Name}', not '{typeof(TElement).Name}'.");
47+
48+
return (TElement)result.WrapUsing(new CssSelectorElementFactory((IRenderedComponent<IComponent>)renderedComponent, cssSelector));
3049
}
3150

3251
/// <summary>
@@ -39,10 +58,25 @@ public static IElement Find<TComponent>(this IRenderedComponent<TComponent> rend
3958
/// <returns>An <see cref="IReadOnlyList{IElement}"/>, that can be refreshed to execute the search again.</returns>
4059
public static IReadOnlyList<IElement> FindAll<TComponent>(this IRenderedComponent<TComponent> renderedComponent, string cssSelector)
4160
where TComponent : IComponent
61+
=> FindAll<TComponent, IElement>(renderedComponent, cssSelector);
62+
63+
/// <summary>
64+
/// Returns a collection of elements of type <typeparamref name="TElement"/> from the rendered fragment or component under test,
65+
/// using the provided <paramref name="cssSelector"/>, in a depth-first pre-order traversal
66+
/// of the rendered nodes. Only elements matching the type <typeparamref name="TElement"/> are returned.
67+
/// </summary>
68+
/// <typeparam name="TComponent">The type of the component under test.</typeparam>
69+
/// <typeparam name="TElement">The type of elements to find (e.g., IHtmlInputElement).</typeparam>
70+
/// <param name="renderedComponent">The rendered fragment to search.</param>
71+
/// <param name="cssSelector">The group of selectors to use.</param>
72+
/// <returns>An <see cref="IReadOnlyList{TElement}"/> containing only elements matching the specified type.</returns>
73+
public static IReadOnlyList<TElement> FindAll<TComponent, TElement>(this IRenderedComponent<TComponent> renderedComponent, string cssSelector)
74+
where TComponent : IComponent
75+
where TElement : class, IElement
4276
{
4377
ArgumentNullException.ThrowIfNull(renderedComponent);
4478

45-
return renderedComponent.Nodes.QuerySelectorAll(cssSelector).ToArray();
79+
return renderedComponent.Nodes.QuerySelectorAll(cssSelector).OfType<TElement>().ToArray();
4680
}
4781

4882
/// <summary>

0 commit comments

Comments
 (0)