Skip to content

Commit a0cea44

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

File tree

7 files changed

+395
-14
lines changed

7 files changed

+395
-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: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@ 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+
/// <exception cref="InvalidOperationException">Thrown if the element found does not match the type <typeparamref name="TElement"/>.</exception>
34+
public static TElement Find<TComponent, TElement>(this IRenderedComponent<TComponent> renderedComponent, string cssSelector)
35+
where TComponent : IComponent
36+
where TElement : class, IElement
2137
{
2238
ArgumentNullException.ThrowIfNull(renderedComponent);
2339

@@ -26,7 +42,11 @@ public static IElement Find<TComponent>(this IRenderedComponent<TComponent> rend
2642
if (result is null)
2743
throw new ElementNotFoundException(cssSelector);
2844

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

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

45-
return renderedComponent.Nodes.QuerySelectorAll(cssSelector).ToArray();
80+
return renderedComponent.Nodes.QuerySelectorAll(cssSelector).OfType<TElement>().ToArray();
4681
}
4782

4883
/// <summary>

0 commit comments

Comments
 (0)