Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ on:
workflow_dispatch:
inputs:
versionIncrement:
description: 'The version increment. Allowed values are "major" and "minor".'
description: 'The version increment. Allowed values are "major", "minor" and "build".'
type: choice
required: true
options:
- major
- minor
- build
default: 'minor'

jobs:
prepare-release:
name: 🚚 Prepare new release
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && contains(fromJson('["major","minor"]'), github.event.inputs.versionIncrement)
if: github.ref == 'refs/heads/main' && contains(fromJson('["major","minor","build"]'), github.event.inputs.versionIncrement)
steps:
- name: 🛒 Checkout repository
uses: actions/checkout@v6
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ All notable changes to **bUnit** will be documented in this file. The project ad

## [Unreleased]

### Added

- 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).
- Added generic overloads `WaitForElement{TComponent, TElement}` and `WaitForElements{TComponent, TElement}` to wait for specific element types. By [@linkdotnet](https://github.com/linkdotnet).

### Fixed

- Adding convenient overloads for `InputAsync` and `ChangeAsync` to have feature parity with the sync version. Reported by [@ScarletKuro](https://github.com/ScarletKuro). Fixed by [@linkdotnet](https://github.com/linkdotnet).

## [2.2.2] - 2025-12-08

### Added
Expand Down
30 changes: 15 additions & 15 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,29 @@
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.1"/>
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3"/>
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="8.0.21"/>
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="8.0.22"/>

<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.21"/>
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.21"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.22"/>
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.22"/>
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="8.0.21"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.21"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.21"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="8.0.22"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.22"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.22"/>

<PackageVersion Include="System.Text.Json" Version="8.0.5"/>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.10"/>
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10"/>
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="9.0.10"/>
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.11"/>
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.11"/>
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="9.0.11"/>

<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.10"/>
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.10"/>
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.10"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="9.0.10"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.10"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.10"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.11"/>
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.11"/>
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.11"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="9.0.11"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.11"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.11"/>

<PackageVersion Include="System.Text.Json" Version="9.0.9"/>
</ItemGroup>
Expand Down
107 changes: 0 additions & 107 deletions MIGRATION.md

This file was deleted.

1 change: 0 additions & 1 deletion bunit.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
</Folder>
<Folder Name="/.text/">
<File Path="CHANGELOG.md" />
<File Path="MIGRATION.md" />
<File Path="README.md" />
</Folder>
<Folder Name="/.workflows/">
Expand Down
36 changes: 35 additions & 1 deletion docs/site/docs/migrations/1to2.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,38 @@ cut.Find("button").Click(detail: 2, ctrlKey: true);

The last one was a method with all parameters of `MouseEventArgs` as optional parameters. This method has been removed in favor of using the `MouseEventArgs` directly.

Also `ClickAsync` - to align with its synchronous counterpart - doesn't take `MouseEventArgs` as mandatory parameter anymore. If not set, a default instance will be created.
Also `ClickAsync` - to align with its synchronous counterpart - doesn't take `MouseEventArgs` as mandatory parameter anymore. If not set, a default instance will be created.

## `DisposeComponents` is now async and called `DisposeComponentsAsync`

The `DisposeComponents` method has been renamed to `DisposeComponentsAsync` and is now asynchronous. To migrate, simply rename the method and add `await`:

```diff
- DisposeComponents();
+ await DisposeComponentsAsync();
```

## The `ComponentParameterFactory` and `ComponentParameter` has been removed
The `ComponentParameterFactory` class has been removed (and therefore the usage of `ComponentParameter`).
Instead, use the `Render` method (and its overloads) to pass parameters to components.

## `IRefreshableElementCollection` was removed
The `IRefreshableElementCollection` interface has been removed. With that, the overload in `FindAll` doesn't accept a `bool refresh` parameter anymore. Instead, simply call `FindAll` again to get a refreshed collection.

```csharp
var items = cut.FindAll("li", refresh: true);
items.Count.ShouldBe(3);
cut.Find("button").Click(); // This changes the list items

items.Count.ShouldBe(4);
```

Should be changed to:

```csharp
var items = cut.FindAll("li");
items.Count.ShouldBe(3);
cut.Find("button").Click(); // This changes the list items
items = cut.FindAll("li"); // Call FindAll again to refresh
items.Count.ShouldBe(4);
```
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<PropertyGroup Label="Package Validation" Condition="$(MSBuildProjectName) != 'bunit.generators'">
<EnablePackageValidation>true</EnablePackageValidation>
<GenerateCompatibilitySuppressionFile>true</GenerateCompatibilitySuppressionFile>
<PackageValidationBaselineVersion>1.25.3</PackageValidationBaselineVersion>
<PackageValidationBaselineVersion>2.0.66</PackageValidationBaselineVersion>
</PropertyGroup>

<PropertyGroup Label="NuGet package information">
Expand Down
22 changes: 20 additions & 2 deletions src/bunit/EventDispatchExtensions/InputEventDispatchExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Bunit;
public static partial class EventHandlerDispatchExtensions
{
/// <summary>
/// Raises the <c>@onchange</c> event on <paramref name="element"/>, passing the provided
/// Raises the <c>@onchange</c> event on <paramref name="element"/>, passing the provided
/// properties to the event handler via a <see cref="ChangeEventArgs"/> object.
/// </summary>
/// <param name="element">The element to raise the event on.</param>
Expand All @@ -18,14 +18,32 @@ public static void Change<T>(this IElement element, T value)
=> _ = ChangeAsync(element, CreateFrom(value));

/// <summary>
/// Raises the <c>@oninput</c> event on <paramref name="element"/>, passing the provided
/// Raises the <c>@onchange</c> event on <paramref name="element"/>, passing the provided
/// properties to the event handler via a <see cref="ChangeEventArgs"/> object.
/// </summary>
/// <param name="element">The element to raise the event on.</param>
/// <param name="value">The new value.</param>
public static void ChangeAsync<T>(this IElement element, T value)
=> _ = ChangeAsync(element, CreateFrom(value));

/// <summary>
/// Raises the <c>@oninput</c> event on <paramref name="element"/>, passing the provided
/// properties to the event handler via a <see cref="ChangeEventArgs"/> object.
/// </summary>
/// <param name="element">The element to raise the event on.</param>
/// <param name="value">The new value.</param>
public static void Input<T>(this IElement element, T value)
=> _ = InputAsync(element, CreateFrom(value));

/// <summary>
/// Raises the <c>@oninput</c> event on <paramref name="element"/>, passing the provided
/// properties to the event handler via a <see cref="ChangeEventArgs"/> object.
/// </summary>
/// <param name="element">The element to raise the event on.</param>
/// <param name="value">The new value.</param>
public static void InputAsync<T>(this IElement element, T value)
=> _ = InputAsync(element, CreateFrom(value));

private static ChangeEventArgs CreateFrom<T>(T value) => new() { Value = FormatValue(value) };

private static object? FormatValue<T>(T value)
Expand Down
38 changes: 36 additions & 2 deletions src/bunit/Extensions/RenderedComponentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ public static class RenderedComponentExtensions
/// <param name="cssSelector">The group of selectors to use.</param>
public static IElement Find<TComponent>(this IRenderedComponent<TComponent> renderedComponent, string cssSelector)
where TComponent : IComponent
=> Find<TComponent, IElement>(renderedComponent, cssSelector);

/// <summary>
/// Returns the first element of type <typeparamref name="TElement"/> from the rendered fragment or component under test,
/// using the provided <paramref name="cssSelector"/>, in a depth-first pre-order traversal
/// of the rendered nodes.
/// </summary>
/// <typeparam name="TComponent">The type of the component under test.</typeparam>
/// <typeparam name="TElement">The type of element to find (e.g., IHtmlInputElement).</typeparam>
/// <param name="renderedComponent">The rendered fragment to search.</param>
/// <param name="cssSelector">The group of selectors to use.</param>
/// <exception cref="ElementNotFoundException">Thrown if no element matches the <paramref name="cssSelector"/>.</exception>
public static TElement Find<TComponent, TElement>(this IRenderedComponent<TComponent> renderedComponent, string cssSelector)
where TComponent : IComponent
where TElement : class, IElement
{
ArgumentNullException.ThrowIfNull(renderedComponent);

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

return result.WrapUsing(new CssSelectorElementFactory((IRenderedComponent<IComponent>)renderedComponent, cssSelector));
if (result is not TElement)
throw new ElementNotFoundException(
$"The element matching '{cssSelector}' is of type '{result.GetType().Name}', not '{typeof(TElement).Name}'.");

return (TElement)result.WrapUsing(new CssSelectorElementFactory((IRenderedComponent<IComponent>)renderedComponent, cssSelector));
}

/// <summary>
Expand All @@ -39,10 +58,25 @@ public static IElement Find<TComponent>(this IRenderedComponent<TComponent> rend
/// <returns>An <see cref="IReadOnlyList{IElement}"/>, that can be refreshed to execute the search again.</returns>
public static IReadOnlyList<IElement> FindAll<TComponent>(this IRenderedComponent<TComponent> renderedComponent, string cssSelector)
where TComponent : IComponent
=> FindAll<TComponent, IElement>(renderedComponent, cssSelector);

/// <summary>
/// Returns a collection of elements of type <typeparamref name="TElement"/> from the rendered fragment or component under test,
/// using the provided <paramref name="cssSelector"/>, in a depth-first pre-order traversal
/// of the rendered nodes. Only elements matching the type <typeparamref name="TElement"/> are returned.
/// </summary>
/// <typeparam name="TComponent">The type of the component under test.</typeparam>
/// <typeparam name="TElement">The type of elements to find (e.g., IHtmlInputElement).</typeparam>
/// <param name="renderedComponent">The rendered fragment to search.</param>
/// <param name="cssSelector">The group of selectors to use.</param>
/// <returns>An <see cref="IReadOnlyList{TElement}"/> containing only elements matching the specified type.</returns>
public static IReadOnlyList<TElement> FindAll<TComponent, TElement>(this IRenderedComponent<TComponent> renderedComponent, string cssSelector)
where TComponent : IComponent
where TElement : class, IElement
{
ArgumentNullException.ThrowIfNull(renderedComponent);

return renderedComponent.Nodes.QuerySelectorAll(cssSelector).ToArray();
return renderedComponent.Nodes.QuerySelectorAll(cssSelector).OfType<TElement>().ToArray();
}

/// <summary>
Expand Down
Loading
Loading