Skip to content

Commit ea0fa87

Browse files
authored
Merge pull request #1788 from bUnit-dev/release/v2.2
Release of new minor version v2.2
2 parents befdf2e + 0c9547f commit ea0fa87

15 files changed

+286
-167
lines changed

CHANGELOG.md

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

77
## [Unreleased]
88

9+
### Added
10+
- Added `FindByAllByLabel` to `bunit.web.query` package. By [@linkdotnet](https://github.com/linkdotnet).
11+
12+
### Fixed
13+
- Updated `AngleSharp.Diffing` to fix a bug related to unknown HTML elements. Reported by [@md-at-slashwhy](https://github.com/md-at-slashwhy).
14+
915
## [2.1.1] - 2025-11-21
1016

1117
### Changed

Directory.Packages.props

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
<!-- Shared code analyzers used for all projects in the solution -->
77
<ItemGroup Condition="!$(MSBuildProjectName.EndsWith('samples'))">
88
<GlobalPackageReference Include="AsyncFixer" Version="1.6.0" PrivateAssets="All" IncludeAssets="Runtime;Build;Native;contentFiles;Analyzers"/>
9-
<GlobalPackageReference Include="SonarAnalyzer.CSharp" Version="10.15.0.120848" PrivateAssets="All" IncludeAssets="Runtime;Build;Native;contentFiles;Analyzers"/>
9+
<GlobalPackageReference Include="SonarAnalyzer.CSharp" Version="10.16.1.129956" PrivateAssets="All" IncludeAssets="Runtime;Build;Native;contentFiles;Analyzers"/>
1010
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
11-
<PackageVersion Include="Meziantou.Polyfill" Version="1.0.60" />
11+
<PackageVersion Include="Meziantou.Polyfill" Version="1.0.68" />
1212
</ItemGroup>
1313

1414
<ItemGroup Label="Shared">
@@ -17,8 +17,8 @@
1717

1818
<PackageVersion Include="Serilog" Version="4.3.0"/>
1919
<PackageVersion Include="Serilog.Expressions" Version="5.0.0"/>
20-
<PackageVersion Include="AngleSharp.Diffing" Version="1.1.0"/>
21-
<PackageVersion Include="AngleSharp" Version="1.3.0"/>
20+
<PackageVersion Include="AngleSharp.Diffing" Version="1.1.1"/>
21+
<PackageVersion Include="AngleSharp" Version="1.4.0"/>
2222
<PackageVersion Include="AngleSharp.Css" Version="1.0.0-beta.157"/>
2323
</ItemGroup>
2424

@@ -81,18 +81,18 @@
8181
<PackageVersion Include="NSubstitute" Version="5.3.0" />
8282
<PackageVersion Include="NUnit3TestAdapter" Version="5.0.0" />
8383
<PackageVersion Include="RichardSzalay.MockHttp" Version="7.0.0" />
84-
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.2" />
84+
<PackageVersion Include="Serilog.Extensions.Logging" Version="10.0.0" />
8585
<PackageVersion Include="Shouldly" Version="4.3.0"/>
8686
<PackageVersion Include="Verify.SourceGenerators" Version="2.5.0"/>
87-
<PackageVersion Include="Verify.XunitV3" Version="31.0.2"/>
87+
<PackageVersion Include="Verify.XunitV3" Version="31.8.0"/>
8888
<PackageVersion Include="Xunit.Combinatorial" Version="2.0.24"/>
8989
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
9090
<PackageVersion Include="coverlet.msbuild" Version="6.0.4" />
9191
<PackageVersion Include="nunit" Version="4.4.0" />
9292
<PackageVersion Include="xunit" Version="2.9.3"/>
93-
<PackageVersion Include="xunit.v3" Version="3.2.0"/>
94-
<PackageVersion Include="xunit.v3.extensibility.core" Version="3.2.0" />
95-
<PackageVersion Include="xunit.v3.assert" Version="3.2.0"/>
93+
<PackageVersion Include="xunit.v3" Version="3.2.1"/>
94+
<PackageVersion Include="xunit.v3.extensibility.core" Version="3.2.1" />
95+
<PackageVersion Include="xunit.v3.assert" Version="3.2.1"/>
9696
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5"/>
9797
</ItemGroup>
9898

bunit.sln

Lines changed: 0 additions & 123 deletions
This file was deleted.

bunit.slnx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<Solution>
2+
<Folder Name="/.items/">
3+
<File Path=".config/dotnet-tools.json" />
4+
<File Path=".editorconfig" />
5+
<File Path=".gitattributes" />
6+
<File Path=".gitignore" />
7+
<File Path="Directory.Build.props" />
8+
<File Path="Directory.Packages.props" />
9+
<File Path="global.json" />
10+
<File Path="version.json" />
11+
</Folder>
12+
<Folder Name="/.text/">
13+
<File Path="CHANGELOG.md" />
14+
<File Path="MIGRATION.md" />
15+
<File Path="README.md" />
16+
</Folder>
17+
<Folder Name="/.workflows/">
18+
<File Path=".github/workflows/ci.yml" />
19+
<File Path=".github/workflows/docs-deploy.yml" />
20+
<File Path=".github/workflows/prepare-release.yml" />
21+
<File Path=".github/workflows/release.yml" />
22+
</Folder>
23+
<Folder Name="/src/">
24+
<File Path="src/.editorconfig" />
25+
<File Path="src/Directory.Build.props" />
26+
<Project Path="src/bunit.generators.internal/bunit.generators.internal.csproj" />
27+
<Project Path="src/bunit.generators/bunit.generators.csproj" />
28+
<Project Path="src/bunit.template/bunit.template.csproj">
29+
<Build Project="false" />
30+
</Project>
31+
<Project Path="src/bunit.web.query/bunit.web.query.csproj" />
32+
<Project Path="src/bunit/bunit.csproj" />
33+
</Folder>
34+
<Folder Name="/tests/">
35+
<File Path="tests/.editorconfig" />
36+
<File Path="tests/Directory.Build.props" />
37+
<File Path="tests/run-tests.ps1" />
38+
<File Path="tests/xunit.runner.json" />
39+
<Project Path="tests/bunit.generators.tests/bunit.generators.tests.csproj" />
40+
<Project Path="tests/bunit.testassets/bunit.testassets.csproj" />
41+
<Project Path="tests/bunit.tests/bunit.tests.csproj" />
42+
<Project Path="tests/bunit.web.query.tests/bunit.web.query.tests.csproj" />
43+
</Folder>
44+
</Solution>

docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ The following tools are required to build and view the documentation locally:
2828

2929
### View the documentation
3030

31-
1. Build the `bunit.sln` solution in the root folder in release configuration `dotnet build -c Release`.
31+
1. Build the `bunit.slxn` solution in the root folder in release configuration `dotnet build -c Release`.
3232
2. From `docs/site` run `docfx metadata` to generate the documentation site's metadata.
3333
3. After that run `docfx build` to generate the documentation site.
3434
4. From `docs/` run `serve-docs.cmd`. This will start up a local web server (using `dotnet serve`), hosting the generated documentation site.

src/bunit.web.query/Labels/LabelQueryExtensions.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using AngleSharp.Dom;
22
using Bunit.Labels.Strategies;
3+
using Bunit.Web.AngleSharp;
34

45
namespace Bunit;
56

@@ -35,6 +36,25 @@ public static IElement FindByLabelText(this IRenderedComponent<IComponent> rende
3536
return FindByLabelTextInternal(renderedComponent, labelText, options) ?? throw new LabelNotFoundException(labelText);
3637
}
3738

39+
/// <summary>
40+
/// Returns all elements (i.e. input, select, textarea, etc. elements) associated with the given label text.
41+
/// </summary>
42+
/// <param name="renderedComponent">The rendered fragment to search.</param>
43+
/// <param name="labelText">The text of the label to search (i.e. the InnerText of the Label, such as "First Name" for a `<label>First Name</label>`)</param>
44+
/// <param name="configureOptions">Method used to override the default behavior of FindAllByLabelText.</param>
45+
/// <returns>A read-only collection of elements matching the label text. Returns an empty collection if no matches are found.</returns>
46+
public static IReadOnlyList<IElement> FindAllByLabelText(this IRenderedComponent<IComponent> renderedComponent, string labelText, Action<ByLabelTextOptions>? configureOptions = null)
47+
{
48+
var options = ByLabelTextOptions.Default;
49+
if (configureOptions is not null)
50+
{
51+
options = options with { };
52+
configureOptions.Invoke(options);
53+
}
54+
55+
return FindAllByLabelTextInternal(renderedComponent, labelText, options);
56+
}
57+
3858
internal static IElement? FindByLabelTextInternal(this IRenderedComponent<IComponent> renderedComponent, string labelText, ByLabelTextOptions options)
3959
{
4060
foreach (var strategy in LabelTextQueryStrategies)
@@ -47,4 +67,28 @@ public static IElement FindByLabelText(this IRenderedComponent<IComponent> rende
4767

4868
return null;
4969
}
70+
71+
internal static IReadOnlyList<IElement> FindAllByLabelTextInternal(this IRenderedComponent<IComponent> renderedComponent, string labelText, ByLabelTextOptions options)
72+
{
73+
var results = new List<IElement>();
74+
75+
foreach (var strategy in LabelTextQueryStrategies)
76+
{
77+
results.AddRange(strategy.FindElements(renderedComponent, labelText, options));
78+
}
79+
80+
var seen = new HashSet<IElement>();
81+
var distinctResults = new List<IElement>();
82+
83+
foreach (var element in results)
84+
{
85+
var underlyingElement = element.Unwrap();
86+
if (seen.Add(underlyingElement))
87+
{
88+
distinctResults.Add(element);
89+
}
90+
}
91+
92+
return distinctResults;
93+
}
5094
}

src/bunit.web.query/Labels/Strategies/ILabelTextQueryStrategy.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@ namespace Bunit.Labels.Strategies;
44

55
internal interface ILabelTextQueryStrategy
66
{
7-
IElement? FindElement(IRenderedComponent<IComponent> renderedComponent, string labelText, ByLabelTextOptions options);
7+
IElement? FindElement(IRenderedComponent<IComponent> renderedComponent, string labelText, ByLabelTextOptions options)
8+
=> FindElements(renderedComponent, labelText, options).FirstOrDefault();
9+
10+
IEnumerable<IElement> FindElements(IRenderedComponent<IComponent> renderedComponent, string labelText, ByLabelTextOptions options);
811
}

src/bunit.web.query/Labels/Strategies/LabelTextUsingAriaLabelStrategy.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Bunit.Labels.Strategies;
55

66
internal sealed class LabelTextUsingAriaLabelStrategy : ILabelTextQueryStrategy
77
{
8-
public IElement? FindElement(IRenderedComponent<IComponent> renderedComponent, string labelText, ByLabelTextOptions options)
8+
public IEnumerable<IElement> FindElements(IRenderedComponent<IComponent> renderedComponent, string labelText, ByLabelTextOptions options)
99
{
1010
var caseSensitivityQualifier = options.ComparisonType switch
1111
{
@@ -15,11 +15,11 @@ internal sealed class LabelTextUsingAriaLabelStrategy : ILabelTextQueryStrategy
1515
_ => ""
1616
};
1717

18-
var element = renderedComponent.Nodes.TryQuerySelector($"[aria-label='{labelText}'{caseSensitivityQualifier}]");
18+
var elements = renderedComponent.Nodes.TryQuerySelectorAll($"[aria-label='{labelText}'{caseSensitivityQualifier}]");
1919

20-
if (element is null)
21-
return null;
22-
23-
return element.WrapUsing(new ByLabelTextElementFactory(renderedComponent, labelText, options));
20+
foreach (var element in elements)
21+
{
22+
yield return element.WrapUsing(new ByLabelTextElementFactory(renderedComponent, labelText, options));
23+
}
2424
}
2525
}

src/bunit.web.query/Labels/Strategies/LabelTextUsingAriaLabelledByStrategy.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@ namespace Bunit.Labels.Strategies;
55

66
internal sealed class LabelTextUsingAriaLabelledByStrategy : ILabelTextQueryStrategy
77
{
8-
public IElement? FindElement(IRenderedComponent<IComponent> renderedComponent, string labelText, ByLabelTextOptions options)
8+
public IEnumerable<IElement> FindElements(IRenderedComponent<IComponent> renderedComponent, string labelText, ByLabelTextOptions options)
99
{
1010
var elementsWithAriaLabelledBy = renderedComponent.Nodes.TryQuerySelectorAll("[aria-labelledby]");
1111

1212
foreach (var element in elementsWithAriaLabelledBy)
1313
{
1414
var labelElement = renderedComponent.Nodes.TryQuerySelector($"#{element.GetAttribute("aria-labelledby")}");
1515
if (labelElement is not null && labelElement.GetInnerText().Equals(labelText, options.ComparisonType))
16-
return element.WrapUsing(new ByLabelTextElementFactory(renderedComponent, labelText, options));
16+
yield return element.WrapUsing(new ByLabelTextElementFactory(renderedComponent, labelText, options));
1717
}
18-
19-
return null;
2018
}
2119
}

src/bunit.web.query/Labels/Strategies/LabelTextUsingForAttributeStrategy.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@ namespace Bunit.Labels.Strategies;
55

66
internal sealed class LabelTextUsingForAttributeStrategy : ILabelTextQueryStrategy
77
{
8-
public IElement? FindElement(IRenderedComponent<IComponent> renderedComponent, string labelText, ByLabelTextOptions options)
8+
public IEnumerable<IElement> FindElements(IRenderedComponent<IComponent> renderedComponent, string labelText, ByLabelTextOptions options)
99
{
10-
var matchingLabel = renderedComponent.Nodes.TryQuerySelectorAll("label")
11-
.SingleOrDefault(l => l.TextContent.Trim().Equals(labelText, options.ComparisonType));
10+
var matchingLabels = renderedComponent.Nodes.TryQuerySelectorAll("label")
11+
.Where(l => l.TextContent.Trim().Equals(labelText, options.ComparisonType));
1212

13-
if (matchingLabel is null)
14-
return null;
13+
foreach (var matchingLabel in matchingLabels)
14+
{
15+
var forAttribute = matchingLabel.GetAttribute("for");
16+
if (string.IsNullOrEmpty(forAttribute))
17+
continue;
1518

16-
var matchingElement = renderedComponent.Nodes.TryQuerySelector($"#{matchingLabel.GetAttribute("for")}");
17-
18-
if (matchingElement is null)
19-
return null;
20-
21-
return matchingElement.WrapUsing(new ByLabelTextElementFactory(renderedComponent, labelText, options));
19+
var matchingElement = renderedComponent.Nodes.TryQuerySelector($"#{forAttribute}");
20+
if (matchingElement is not null)
21+
yield return matchingElement.WrapUsing(new ByLabelTextElementFactory(renderedComponent, labelText, options));
22+
}
2223
}
2324
}

0 commit comments

Comments
 (0)