Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FindByLabelText to find elements by the text of their labels #1252

Merged
merged 42 commits into from
Mar 17, 2024
Merged
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
5182c86
add label, aria-label, wrapped label
scottsauber Oct 30, 2023
d456d61
switch to strategy pattern
scottsauber Nov 1, 2023
2a1c102
feat: support for all element types that can have a label
scottsauber Nov 1, 2023
a3ea7d3
feat: support for all element types that can have a wrapped label
scottsauber Nov 1, 2023
b7fca5f
feat: support for all element types that can have an aria-label
scottsauber Nov 1, 2023
49b2c18
feat: support for all element types that can have an aria-labelledby
scottsauber Nov 1, 2023
ced93f0
style: remove comment
scottsauber Nov 1, 2023
c80345d
fix: use theorydata
scottsauber Nov 2, 2023
13b5d3f
fix: use method instead of list
scottsauber Nov 2, 2023
3ff3ef6
failing test to prove the re-rendered element issue
scottsauber Nov 13, 2023
78aaf7b
move to element factory to prevent re-renders causing issues
scottsauber Nov 13, 2023
61d53b9
fix: switch to array for strategies
scottsauber Nov 13, 2023
b0a1535
fix: remove todos
scottsauber Nov 13, 2023
81f8ecf
fix: move to new ielementwrapperfactory
scottsauber Nov 26, 2023
109cc6b
fix: use custom labelnotfoundexception
scottsauber Nov 26, 2023
1237a3e
feat: support for different casing sensitivity
scottsauber Nov 27, 2023
7ebab18
chore: add xml docs to indicate defaults of ByLabelTextOptions
scottsauber Nov 27, 2023
e063ad9
fix: make classes add public
scottsauber Mar 12, 2024
667e6f7
fix: remove project references
scottsauber Mar 12, 2024
8b8b1a9
fix: move to source generator to public
scottsauber Mar 12, 2024
a4f7a5e
chore: switch to use wrapper component for tests
scottsauber Mar 12, 2024
f8e98b0
chore: switch to use wrapper component for tests
scottsauber Mar 12, 2024
a8daf3c
chore: rename to labelquerycounter for re-rendering test
scottsauber Mar 12, 2024
ae78a6d
fix: remove warnings
scottsauber Mar 13, 2024
ad176c5
refactor: remove string duplication in tests
scottsauber Mar 13, 2024
3e24fb0
fix: remove nullability warning
scottsauber Mar 13, 2024
1ddc210
fix: add sealed to remove warnings
scottsauber Mar 13, 2024
ec458a3
feat: add support for whitespace
scottsauber Mar 14, 2024
68bfec6
add xml comments
scottsauber Mar 14, 2024
b12b0d9
fix: labelElement can be null if not found
egil Mar 15, 2024
1fd2984
chore: fix indention
egil Mar 15, 2024
f0f1b48
chore: remove duplicated word
egil Mar 15, 2024
6c646b9
fix: make label options immutable
egil Mar 15, 2024
056542d
test: verify generate test output
egil Mar 15, 2024
7dd0039
chore: remove whitespace in test case name
egil Mar 15, 2024
1f16c19
refactor: simplify null check
scottsauber Mar 17, 2024
3cacae2
fix: cover scenario where wrapped label has nested HTML
scottsauber Mar 17, 2024
be79633
fix: rename test
scottsauber Mar 17, 2024
94f6943
test: add additional test for nested html with for attributes
scottsauber Mar 17, 2024
9d19200
docs: cover FindByLabelText and update verify markup section to discu…
egil Mar 17, 2024
440d6ef
docs: verify-markup.md
egil Mar 17, 2024
69aaf6d
refactor: use configure options pattern instead of passing option object
egil Mar 17, 2024
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
111 changes: 58 additions & 53 deletions docs/site/docs/verification/verify-markup.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,63 @@ title: Verifying markup from a component

# Verifying markup from a component

When a component is rendered in a test, the result is a <xref:Bunit.IRenderedFragment> or a <xref:Bunit.IRenderedComponent`1>. Through these, it is possible to access the rendered markup (HTML) of the component and, in the case of <xref:Bunit.IRenderedComponent`1>, the instance of the component.
Generally, the strategy for verifying markup produced by components depends on whether you are creating reusable component library or a single-use Blazor app component.

> [!NOTE]
> An <xref:Bunit.IRenderedComponent`1> inherits from <xref:Bunit.IRenderedFragment>. This page will only cover features of the <xref:Bunit.IRenderedFragment> type. <xref:Bunit.IRenderedComponent`1> is covered on the <xref:verify-component-state> page.
With a **reusable component library**, the markup produced may be considered part of the externally observable behavior of the component, and that should thus be verified, since users of the component may depend on the markup having a specific structure. Consider using `MarkupMatches` and semantic comparison described below to get the best protection against regressions and good maintainability.

When **building components for a Blazor app**, the externally observable behavior of components are how they visibly look and behave from an end-users point of view, e.g. what the user sees and interact with in a browser. In this scenario, consider use `FindByLabelText` and related methods described below to inspect and assert against individual elements look and feel, for a good balance between protection against regressions and maintainability. Learn more about this testing approach at https://testing-library.com.

This page covers the following **verification approaches:**

- Basic verification of raw markup
- Semantic comparison of markup
- Inspecting the individual DOM nodes in the DOM tree
- Semantic comparison of markup
- Finding expected differences in markup between renders
- Verification of raw markup

The following sections will cover each of these.

## Basic verification of raw markup
## Result of rendering components

To access the rendered markup of a component, just use the <xref:Bunit.IRenderedFragment.Markup> property on <xref:Bunit.IRenderedFragment>. This holds the *raw* HTML from the component as a `string`.
When a component is rendered in a test, the result is a <xref:Bunit.IRenderedFragment> or a <xref:Bunit.IRenderedComponent`1>. Through these, it is possible to access the rendered markup (HTML) of the component and, in the case of <xref:Bunit.IRenderedComponent`1>, the instance of the component.

> [!WARNING]
> Be aware that all indentions and whitespace in your components (`.razor` files) are included in the raw rendered markup, so it is often wise to normalize the markup string a little. For example, via the string `Trim()` method to make the tests more stable. Otherwise, a change to the formatting in your components might break the tests unnecessarily when it does not need to.
>
> To avoid these issues and others related to asserting against raw markup, use the semantic HTML comparer that comes with bUnit, described in the next section.
> [!NOTE]
> An <xref:Bunit.IRenderedComponent`1> inherits from <xref:Bunit.IRenderedFragment>. This page will only cover features of the <xref:Bunit.IRenderedFragment> type. <xref:Bunit.IRenderedComponent`1> is covered on the <xref:verify-component-state> page.

To get the markup as a string, do the following:
## Inspecting DOM nodes

[!code-csharp[](../../../samples/tests/xunit/VerifyMarkupExamples.cs?start=16&end=19&highlight=3)]
The rendered markup from a component is available as a DOM node through the <xref:Bunit.IRenderedFragment.Nodes> property on <xref:Bunit.IRenderedFragment>. The nodes and element types comes from [AngleSharp](https://anglesharp.github.io/) that follows the W3C DOM API specifications and gives you the same results as a state-of-the-art browser’s implementation of the DOM API in JavaScript. Besides the official DOM API, AngleSharp and bUnit add some useful extension methods on top. This makes working with DOM nodes convenient.

### Finding DOM elements

bUnit supports multiple different ways of searching and querying the rendered HTML elements:

- `FindByLabelText(string labelText)` that takes a text string used to label an input element and returns an `IElement` as output, or throws an exception if none are found (this is included in the experimental library [bunit.web.query](https://www.nuget.org/packages/bunit.web.query)). Use this method when possible compared to the generic `Find` and `FindAll` methods.
- [`Find(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.Find(Bunit.IRenderedFragment,System.String)) takes a "CSS selector" as input and returns an `IElement` as output, or throws an exception if none are found.
- [`FindAll(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.FindAll(Bunit.IRenderedFragment,System.String,System.Boolean)) takes a "CSS selector" as input and returns a list of `IElement` elements.

Let's see some examples of using the [`Find(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.Find(Bunit.IRenderedFragment,System.String)) and [`FindAll(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.FindAll(Bunit.IRenderedFragment,System.String,System.Boolean)) methods to query the `<FancyTable>` component listed below.

[!code-razor[FancyTable.razor](../../../samples/components/FancyTable.razor)]

To find the `<caption>` element and the first `<td>` elements in each row, do the following:

[!code-csharp[](../../../samples/tests/xunit/VerifyMarkupExamples.cs?start=54&end=57&highlight=3-4)]

Once you have one or more elements, you verify against them, such as by inspecting their properties through the DOM API. For example:

You can perform standard string assertions against the markup string, like checking whether it contains a value or is empty.
[!code-csharp[](../../../samples/tests/xunit/VerifyMarkupExamples.cs?start=59&end=61)]

#### Auto-refreshing Find() queries

An element found with the [`Find(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.Find(Bunit.IRenderedFragment,System.String)) method will be updated if the component it came from is re-rendered.

However, that does not apply to elements that are found by traversing the DOM tree via the <xref:Bunit.IRenderedFragment.Nodes> property on <xref:Bunit.IRenderedFragment>, for example, as those nodes do not know when their root component is re-rendered. Consequently, they don’t know when they should be updated.

As a result of this, it is always recommended to use the [`Find(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.Find(Bunit.IRenderedFragment,System.String)) method when searching for a single element. Alternatively, always reissue the query whenever you need the element.

#### Auto-refreshable FindAll() queries

The [`FindAll(string cssSelector, bool enableAutoRefresh = false)`](xref:Bunit.RenderedFragmentExtensions.FindAll(Bunit.IRenderedFragment,System.String,System.Boolean)) method has an optional parameter, `enableAutoRefresh`, which when set to `true` will return a collection of `IElement`. This automatically refreshes itself when the component the elements came from is re-rendered.

## Semantic comparison of markup

Expand Down Expand Up @@ -91,45 +120,6 @@ The semantic HTML comparer can be customized to make a test case even more stabl

Learn more about the customization options on the <xref:semantic-html-comparison> page.

## Inspecting DOM nodes

The rendered markup from a component is available as a DOM node through the <xref:Bunit.IRenderedFragment.Nodes> property on <xref:Bunit.IRenderedFragment>, as well as the `Find(string cssSelector)` and `FindAll(string cssSelector)` extension methods on <xref:Bunit.IRenderedFragment>.

The <xref:Bunit.IRenderedFragment.Nodes> property and the `FindAll()` method return an [AngleSharp](https://anglesharp.github.io/) `INodeList` type, and the `Find()` method returns an [AngleSharp](https://anglesharp.github.io/) `IElement` type.

The DOM API in AngleSharp follows the W3C DOM API specifications and gives you the same results as a state-of-the-art browser’s implementation of the DOM API in JavaScript. Besides the official DOM API, AngleSharp and bUnit add some useful extension methods on top. This makes working with DOM nodes convenient.

### Finding nodes with the Find() and FindAll() methods

Users of the famous JavaScript framework [jQuery](https://jquery.com/) will recognize these two methods:

- [`Find(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.Find(Bunit.IRenderedFragment,System.String)) takes a "CSS selector" as input and returns an `IElement` as output, or throws an exception if none are found.
- [`FindAll(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.FindAll(Bunit.IRenderedFragment,System.String,System.Boolean)) takes a "CSS selector" as input and returns a list of `IElement` elements.

Let's see some examples of using the [`Find(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.Find(Bunit.IRenderedFragment,System.String)) and [`FindAll(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.FindAll(Bunit.IRenderedFragment,System.String,System.Boolean)) methods to query the `<FancyTable>` component listed below.

[!code-razor[FancyTable.razor](../../../samples/components/FancyTable.razor)]

To find the `<caption>` element and the first `<td>` elements in each row, do the following:

[!code-csharp[](../../../samples/tests/xunit/VerifyMarkupExamples.cs?start=54&end=57&highlight=3-4)]

Once you have one or more elements, you verify against them, such as by inspecting their properties through the DOM API. For example:

[!code-csharp[](../../../samples/tests/xunit/VerifyMarkupExamples.cs?start=59&end=61)]

#### Auto-refreshing Find() queries

An element found with the [`Find(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.Find(Bunit.IRenderedFragment,System.String)) method will be updated if the component it came from is re-rendered.

However, that does not apply to elements that are found by traversing the DOM tree via the <xref:Bunit.IRenderedFragment.Nodes> property on <xref:Bunit.IRenderedFragment>, for example, as those nodes do not know when their root component is re-rendered. Consequently, they don’t know when they should be updated.

As a result of this, it is always recommended to use the [`Find(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.Find(Bunit.IRenderedFragment,System.String)) method when searching for a single element. Alternatively, always reissue the query whenever you need the element.

#### Auto-refreshable FindAll() queries

The [`FindAll(string cssSelector, bool enableAutoRefresh = false)`](xref:Bunit.RenderedFragmentExtensions.FindAll(Bunit.IRenderedFragment,System.String,System.Boolean)) method has an optional parameter, `enableAutoRefresh`, which when set to `true` will return a collection of `IElement`. This automatically refreshes itself when the component the elements came from is re-rendered.

## Finding expected differences

It can sometimes be easier to verify that an expected change, and only that change, has occurred in the rendered markup than it can be to specify how all the rendered markup should look after re-rendering.
Expand Down Expand Up @@ -178,3 +168,18 @@ This is what happens in the test:
8. Finally the last item in the list is found and clicked, and the <xref:Bunit.IRenderedFragment.GetChangesSinceSnapshot> method is used to find the changes, a single diff, which is verified as a removal of the second item.

As mentioned earlier, the `IDiff` assertion helpers are still experimental. Any feedback and suggestions for improvements should be directed to the [related issue](https://github.com/egil/bUnit/issues/84) on GitHub.

## Verification of raw markup

To access the rendered markup of a component, just use the <xref:Bunit.IRenderedFragment.Markup> property on <xref:Bunit.IRenderedFragment>. This holds the *raw* HTML from the component as a `string`.

> [!WARNING]
> Be aware that all indentions and whitespace in your components (`.razor` files) are included in the raw rendered markup, so it is often wise to normalize the markup string a little. For example, via the string `Trim()` method to make the tests more stable. Otherwise, a change to the formatting in your components might break the tests unnecessarily when it does not need to.
>
> To avoid these issues and others related to asserting against raw markup, use the semantic HTML comparer that comes with bUnit, described in the next section.

To get the markup as a string, do the following:

[!code-csharp[](../../../samples/tests/xunit/VerifyMarkupExamples.cs?start=16&end=19&highlight=3)]

You can perform standard string assertions against the markup string, like checking whether it contains a value or is empty.
egil marked this conversation as resolved.
Show resolved Hide resolved
Loading