-
-
Notifications
You must be signed in to change notification settings - Fork 108
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add FindByLabelText to find elements by the text of their labels (
#1252) * add label, aria-label, wrapped label * switch to strategy pattern * feat: support for all element types that can have a label * feat: support for all element types that can have a wrapped label * feat: support for all element types that can have an aria-label * feat: support for all element types that can have an aria-labelledby * style: remove comment * fix: use theorydata * fix: use method instead of list * failing test to prove the re-rendered element issue * move to element factory to prevent re-renders causing issues * fix: switch to array for strategies * fix: remove todos * fix: move to new ielementwrapperfactory * fix: use custom labelnotfoundexception * feat: support for different casing sensitivity * chore: add xml docs to indicate defaults of ByLabelTextOptions * fix: make classes add public * fix: remove project references * fix: move to source generator to public * chore: switch to use wrapper component for tests * chore: switch to use wrapper component for tests * chore: rename to labelquerycounter for re-rendering test * fix: remove warnings * refactor: remove string duplication in tests * fix: remove nullability warning * fix: add sealed to remove warnings * feat: add support for whitespace * add xml comments * fix: labelElement can be null if not found * chore: fix indention * chore: remove duplicated word * fix: make label options immutable * test: verify generate test output * chore: remove whitespace in test case name * refactor: simplify null check * fix: cover scenario where wrapped label has nested HTML * fix: rename test * test: add additional test for nested html with for attributes * docs: cover FindByLabelText and update verify markup section to discuss different markup verify approaches * docs: verify-markup.md Co-authored-by: Steven Giesel <[email protected]> * refactor: use configure options pattern instead of passing option object --------- Co-authored-by: Egil Hansen <[email protected]> Co-authored-by: Steven Giesel <[email protected]>
- Loading branch information
1 parent
3ad9984
commit c40735b
Showing
23 changed files
with
734 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using AngleSharp.Dom; | ||
using Bunit.Web.AngleSharp; | ||
|
||
namespace Bunit; | ||
|
||
internal sealed class ByLabelTextElementFactory : IElementWrapperFactory | ||
{ | ||
private readonly IRenderedFragment testTarget; | ||
private readonly string labelText; | ||
private readonly ByLabelTextOptions options; | ||
|
||
public Action? OnElementReplaced { get; set; } | ||
|
||
public ByLabelTextElementFactory(IRenderedFragment testTarget, string labelText, ByLabelTextOptions options) | ||
{ | ||
this.testTarget = testTarget; | ||
this.labelText = labelText; | ||
this.options = options; | ||
testTarget.OnMarkupUpdated += FragmentsMarkupUpdated; | ||
} | ||
|
||
private void FragmentsMarkupUpdated(object? sender, EventArgs args) | ||
=> OnElementReplaced?.Invoke(); | ||
|
||
public TElement GetElement<TElement>() where TElement : class, IElement | ||
{ | ||
var element = testTarget.FindByLabelTextInternal(labelText, options) as TElement; | ||
|
||
return element ?? throw new ElementRemovedFromDomException(labelText); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
namespace Bunit; | ||
|
||
/// <summary> | ||
/// Allows overrides of behavior for FindByLabelText method | ||
/// </summary> | ||
public record class ByLabelTextOptions | ||
{ | ||
/// <summary> | ||
/// The default behavior used by FindByLabelText if no overrides are specified | ||
/// </summary> | ||
internal static readonly ByLabelTextOptions Default = new(); | ||
|
||
/// <summary> | ||
/// The StringComparison used for comparing the desired Label Text to the resulting HTML. Defaults to Ordinal (case sensitive). | ||
/// </summary> | ||
public StringComparison ComparisonType { get; set; } = StringComparison.Ordinal; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using AngleSharp.Dom; | ||
|
||
namespace Bunit; | ||
|
||
internal static class LabelElementExtensions | ||
{ | ||
internal static bool IsHtmlElementThatCanHaveALabel(this IElement element) => element.NodeName switch | ||
{ | ||
"INPUT" => true, | ||
"SELECT" => true, | ||
"TEXTAREA" => true, | ||
"BUTTON" => true, | ||
"METER" => true, | ||
"OUTPUT" => true, | ||
"PROGRESS" => true, | ||
_ => false | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
namespace Bunit; | ||
|
||
/// <summary> | ||
/// Represents a failure to find an element in the searched target | ||
/// using the Label's text. | ||
/// </summary> | ||
[Serializable] | ||
public class LabelNotFoundException : Exception | ||
{ | ||
/// <summary> | ||
/// Gets the Label Text used to search with. | ||
/// </summary> | ||
public string LabelText { get; } = ""; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="LabelNotFoundException"/> class. | ||
/// </summary> | ||
/// <param name="labelText"></param> | ||
public LabelNotFoundException(string labelText) | ||
: base($"Unable to find a label with the text of '{labelText}'.") | ||
{ | ||
LabelText = labelText; | ||
} | ||
|
||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="LabelNotFoundException"/> class. | ||
/// </summary> | ||
protected LabelNotFoundException(SerializationInfo info, StreamingContext context) | ||
: base(info, context) { } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
using AngleSharp.Dom; | ||
using Bunit.Labels.Strategies; | ||
|
||
namespace Bunit; | ||
|
||
/// <summary> | ||
/// Extension methods for querying IRenderedFragments by Label | ||
/// </summary> | ||
public static class LabelQueryExtensions | ||
{ | ||
private static readonly IReadOnlyList<ILabelTextQueryStrategy> LabelTextQueryStrategies = | ||
[ | ||
// This is intentionally in the order of most likely to minimize strategies tried to find the label | ||
new LabelTextUsingForAttributeStrategy(), | ||
new LabelTextUsingAriaLabelStrategy(), | ||
new LabelTextUsingWrappedElementStrategy(), | ||
new LabelTextUsingAriaLabelledByStrategy(), | ||
]; | ||
|
||
/// <summary> | ||
/// Returns the first element (i.e. an input, select, textarea, etc. element) associated with the given label text. | ||
/// </summary> | ||
/// <param name="renderedFragment">The rendered fragment to search.</param> | ||
/// <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> | ||
/// <param name="configureOptions">Method used to override the default behavior of FindByLabelText.</param> | ||
public static IElement FindByLabelText(this IRenderedFragment renderedFragment, string labelText, Action<ByLabelTextOptions>? configureOptions = null) | ||
{ | ||
var options = ByLabelTextOptions.Default; | ||
if (configureOptions is not null) | ||
{ | ||
options = options with { }; | ||
configureOptions.Invoke(options); | ||
} | ||
|
||
return FindByLabelTextInternal(renderedFragment, labelText, options) ?? throw new LabelNotFoundException(labelText); | ||
} | ||
|
||
internal static IElement? FindByLabelTextInternal(this IRenderedFragment renderedFragment, string labelText, ByLabelTextOptions options) | ||
{ | ||
foreach (var strategy in LabelTextQueryStrategies) | ||
{ | ||
var element = strategy.FindElement(renderedFragment, labelText, options); | ||
|
||
if (element is not null) | ||
return element; | ||
} | ||
|
||
return null; | ||
} | ||
} |
Oops, something went wrong.