Skip to content

Commit 75fa1eb

Browse files
committed
Docs: completed verify markup page
1 parent b6b6426 commit 75fa1eb

File tree

3 files changed

+177
-27
lines changed

3 files changed

+177
-27
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<input type="text" placeholder="Add new item"
2+
@bind="newItemValue"
3+
@onkeyup="OnTextInput" />
4+
<ul>
5+
@foreach (var item in items)
6+
{
7+
<li @onclick="() => items.Remove(item)">@item</li>
8+
}
9+
</ul>
10+
@code
11+
{
12+
private string newItemValue = string.Empty;
13+
private List<string> items = new List<string>();
14+
15+
private void OnTextInput(KeyboardEventArgs args)
16+
{
17+
if(args.Key == "Enter")
18+
{
19+
items.Add(newItemValue);
20+
newItemValue = string.Empty;
21+
}
22+
}
23+
}

docs/samples/tests/xunit/VerifyMarkupExamples.cs

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,16 @@ public void MarkupMatchesOnNode()
4545
smallElm.MarkupMatches(@"<small class=""mark text-muted"">Secondary text</small>");
4646
}
4747

48-
[Fact]
49-
public void MarkupMatchesOnTextNode()
50-
{
51-
using var ctx = new TestContext();
48+
// [Fact]
49+
// public void MarkupMatchesOnTextNode()
50+
// {
51+
// using var ctx = new TestContext();
5252

53-
var cut = ctx.RenderComponent<Heading>();
53+
// var cut = ctx.RenderComponent<Heading>();
5454

55-
var smallElmText = cut.Find("small").TextContent;
56-
smallElmText.MarkupMatches("Secondary text");
57-
}
55+
// var smallElmText = cut.Find("small").TextContent;
56+
// smallElmText.MarkupMatches("Secondary text");
57+
// }
5858

5959
[Fact]
6060
public void FindAndFindAll()
@@ -70,5 +70,75 @@ public void FindAndFindAll()
7070
Assert.Equal(2, tableCells.Count);
7171
Assert.All(tableCells, td => td.HasAttribute("style"));
7272
}
73+
74+
[Fact]
75+
public void GetChangesSinceFirstRenderTest()
76+
{
77+
using var ctx = new TestContext();
78+
var cut = ctx.RenderComponent<Counter>();
79+
80+
// Act - increment the counter
81+
cut.Find("button").Click();
82+
83+
// Assert - find differences between first render and click
84+
var diffs = cut.GetChangesSinceFirstRender();
85+
86+
// Only expect there to be one change
87+
var diff = diffs.ShouldHaveSingleChange();
88+
// and that change should be a text
89+
// change to "Current count: 1"
90+
diff.ShouldBeTextChange("Current count: 1");
91+
}
92+
93+
[Fact]
94+
public void GetChangesSinceX()
95+
{
96+
// Arrange
97+
using var ctx = new TestContext();
98+
var cut = ctx.RenderComponent<CheckList>();
99+
var inputField = cut.Find("input");
100+
101+
// Add first item
102+
inputField.Change("First item");
103+
inputField.KeyUp(key: "Enter");
104+
105+
// Assert that first item was added correctly
106+
var diffs = cut.GetChangesSinceFirstRender();
107+
diffs.ShouldHaveSingleChange()
108+
.ShouldBeAddition("<li>First item</li>");
109+
110+
// Save snapshot of current DOM nodes
111+
cut.SaveSnapshot();
112+
113+
// Add a second item
114+
inputField.Change("Second item");
115+
inputField.KeyUp(key: "Enter");
116+
117+
// Assert that both first and second item was added
118+
// since the first render
119+
diffs = cut.GetChangesSinceFirstRender();
120+
diffs.ShouldHaveChanges(
121+
diff => diff.ShouldBeAddition("<li>First item</li>"),
122+
diff => diff.ShouldBeAddition("<li>Second item</li>")
123+
);
124+
125+
// Assert that only the second item was added
126+
// since the call to SaveSnapshot()
127+
diffs = cut.GetChangesSinceSnapshot();
128+
diffs.ShouldHaveSingleChange()
129+
.ShouldBeAddition("<li>Second item</li>");
130+
131+
// Save snapshot again of current DOM nodes
132+
cut.SaveSnapshot();
133+
134+
// Click last item to remove it from list
135+
cut.Find("li:last-child").Click();
136+
137+
// Assert that the second item was removed
138+
// since the call to SaveSnapshot()
139+
diffs = cut.GetChangesSinceSnapshot();
140+
diffs.ShouldHaveSingleChange()
141+
.ShouldBeRemoval("<li>Second item</li>");
142+
}
73143
}
74144
}

docs/site/docs/verification/verify-markup.md

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,32 @@ title: Verifying Markup from a Component
88
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.
99

1010
> [!NOTE]
11-
> An <xref:Bunit.IRenderedComponent`1> inherits from <xref:Bunit.IRenderedFragment>. This page will only show how a <xref:Bunit.IRenderedFragment> is used. <xref:Bunit.IRenderedComponent`1> is covered on the <xref:verify-component-state> page.
11+
> 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.
1212
13-
This page cover the following **verification approaches**:
13+
This page cover the following **verification approaches:**
1414

1515
- Basic verification of raw markup.
1616
- Semantic comparison of markup.
1717
- Inspecting the individual DOM nodes in the DOM tree.
18-
- Diffing of markup between renders.
18+
- Finding expected differences in markup between renders.
1919

2020
The following sections will cover each of these.
2121

2222
## Basic Verification of Raw Markup
2323

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

26+
> [!WARNING]
27+
> 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, e.g. via the string `Trim()` method, to make the tests more stable. Otherwise a change to the formatting in your components might break the tests when it does not need to.
28+
>
29+
> 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.
30+
2631
To get the markup as a string, do the following:
2732

2833
[!code-csharp[](../../../samples/tests/xunit/VerifyMarkupExamples.cs?start=16&end=21&highlight=5)]
2934

3035
You can perform standard string assertions against the markup string, e.g. like checking if it contains a value or is empty.
3136

32-
> [!WARNING]
33-
> Be aware that all indentions and whitespace in your components (`.razor` files) are included in the rendered markup, so it is often wise to normalize the markup string a little, e.g. via the string `Trim()` method, to make the tests more stable. Otherwise a change to the formatting in your components might break the tests when it does not need to.
34-
3537
## Semantic Comparison of Markup
3638

3739
Working with raw markup only works well with very simple output, and even then, you have to sanitize it to get stable tests. A much better approach is to use the semantic HTML comparer that comes with bUnit.
@@ -50,13 +52,13 @@ Exactly the same as this HTML:
5052

5153
```html
5254
<span>
53-
Foo Bar
55+
Foo Bar
5456
</span>
5557
```
5658

57-
That is why it makes sense to allow tests to pass, _even_ when the rendered HTML markup is not entire identical to the expected HTML, from a normal string comparer perspective.
59+
That is why it makes sense to allow tests to pass, _even_ when the rendered HTML markup is not entirely identical to the expected HTML, from a normal string comparer's perspective.
5860

59-
bUnit's semantic HTML comparer safely ignores things like insignificant whitespace and the order of attributes on elements, and many more things. **This leads to much more stable tests, as e.g. a reformatted component doesn't break it's tests because insignificant whitespace changes.**
61+
bUnit's semantic HTML comparer safely ignores things like insignificant whitespace and the order of attributes on elements, and many more things. **This leads to much more stable tests, as e.g. a reformatted component doesn't break it's tests because of insignificant whitespace changes.**
6062

6163
### The MarkupMatches() Method
6264

@@ -81,11 +83,15 @@ Here we use the `Find(string cssSelector)` method to find the `<small>` element,
8183
> [!TIP]
8284
> Working with `Find()`, `FindAll()`, `INode` and `INodeList` is covered later on this page.
8385
86+
<!-- TODO UNCOMMENT WHEN MarkupMatches(this string markup ... ) IS DONE
8487
Text content can also be verified with the `MarkupMatches()` method, e.g. the text inside the `<small>` element. It has the advantage over regular string comparison that it removes insignificant whitespace in the text automatically, even between words, where a normal string `Trim()` method isn't enough. For example:
8588
8689
[!code-csharp[](../../../samples/tests/xunit/VerifyMarkupExamples.cs?start=51&end=56&highlight=5)]
90+
-->
8791

88-
The semantic HTML comparer can be customized to make a test case even more stable and easier to maintain. It is e.g. possible to ignore an element or attribute during comparison, or provide an regular expression to the comparer when comparing a specific element or attribute, to make the comparer work with generated data. Learn more about the customizations options on the <xref:semantic-html-comparison> page.
92+
The semantic HTML comparer can be customized to make a test case even more stable and easier to maintain. It is e.g. possible to ignore an element or attribute during comparison, or provide an regular expression to the comparer when comparing a specific element or attribute, to make the comparer work with generated data.
93+
94+
Learn more about the customizations options on the <xref:semantic-html-comparison> page.
8995

9096
## Inspecting DOM Nodes
9197

@@ -100,11 +106,11 @@ The DOM API in AngleSharp follows the W3C DOM API specifications and gives you t
100106
Users of the famous JavaScript framework [jQuery](https://jquery.com/) will recognize the two methods [`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)).
101107

102108
- [`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 non is found.
103-
- [`FindAll(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.FindAll(Bunit.IRenderedFragment,System.String,System.Boolean)) takes a "CSS selector" as input a list of `IElement` elements.
109+
- [`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.
104110

105-
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 `<FanyTable>` component listed below.
111+
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.
106112

107-
[!code-razor[FanyTable.razor](../../../samples/components/FanyTable.razor)]
113+
[!code-razor[FancyTable.razor](../../../samples/components/FancyTable.razor)]
108114

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

@@ -114,12 +120,63 @@ Once you have one or more elements, you verify against them by e.g. inspecting t
114120

115121
[!code-csharp[](../../../samples/tests/xunit/VerifyMarkupExamples.cs?start=69&end=71)]
116122

117-
#### Refreshable FindAll() Queries
123+
#### Auto-refreshing Find() Queries
124+
125+
A 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.
126+
127+
However, that does not apply to elements that are found by traversing the DOM tree via e.g. the <xref:Bunit.IRenderedFragment.Nodes> property on <xref:Bunit.IRenderedFragment>, as those nodes do not know when their root component is re-rendered, and thus, when they should be updated.
128+
129+
Therefore, always prefer using the [`Find(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.Find(Bunit.IRenderedFragment,System.String)) method when searching for a single element, or always reissue the query whenever you need the element.
130+
131+
#### Auto-refreshable FindAll() Queries
132+
133+
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 an collection of `IElement`, that automatically refreshes itself when the component the elements came from is re-rendered.
134+
135+
## Finding Expected Differences
136+
137+
It can sometimes be easier to verify that an expected change has happened in the rendered markup (and only that), than it can be to specify how all the rendered markup should look after re-render.
138+
139+
bUnit comes with a number of ways for finding lists of `IDiff`, the representation of a difference between two HTML fragments. All of these are direct methods or extension methods on the <xref:Bunit.IRenderedFragment> type, or on the `INode` or `INodeList` types:
140+
141+
- <xref:Bunit.IRenderedFragment.GetChangesSinceFirstRender> method on <xref:Bunit.IRenderedFragment>. This method returns a list of differences since the initial first render of a component.
142+
- <xref:Bunit.IRenderedFragment.GetChangesSinceSnapshot> and <xref:Bunit.IRenderedFragment.SaveSnapshot> methods on <xref:Bunit.IRenderedFragment>. These two methods combined makes it possible to get a list of differences between the last time the <xref:Bunit.IRenderedFragment.SaveSnapshot> method was called and a call to the <xref:Bunit.IRenderedFragment.GetChangesSinceSnapshot> method is placed.
143+
- `CompareTo()` methods from <xref:Bunit.CompareToExtensions> for the <xref:Bunit.IRenderedFragment>, `INode`, and `INodeList` types. These methods returns a list of differences between the two input HTML fragments.
144+
145+
In addition to this, there are a number of experimental assertion helpers for `IDiff` and `IEnumerable<IDiff>`, that makes it easier and more concise to declare your assertions.
146+
147+
Let's look at a few examples. In the first we will use the `<Counter>` component listed below:
148+
149+
[!code-razor[Counter.razor](../../../samples/components/Counter.razor)]
150+
151+
Here is an example of using the <xref:Bunit.IRenderedFragment.GetChangesSinceFirstRender> method:
152+
153+
[!code-csharp[](../../../samples/tests/xunit/VerifyMarkupExamples.cs?start=77&end=90&highlight=8,11,14)]
154+
155+
This is what happens in the test:
156+
157+
- On line 8, <xref:Bunit.IRenderedFragment.GetChangesSinceFirstRender> is used to get a list of differences.
158+
- On line 11, the [`ShouldHaveSingleChange()`](xref:Bunit.DiffAssertExtensions.ShouldHaveSingleChange(System.Collections.Generic.IEnumerable{AngleSharp.Diffing.Core.IDiff})) method is used to verify that there is only one change found.
159+
- On line 14, the [`ShouldBeTextChange()`](xref:Bunit.ShouldBeTextChangeAssertExtensions.ShouldBeTextChange(AngleSharp.Diffing.Core.IDiff,System.String,System.String)) method is used to verify that the single `IDiff` is a text change.
160+
161+
Testing a more **complex life cycle of a component** can be more easily done using the <xref:Bunit.IRenderedFragment.GetChangesSinceSnapshot> and <xref:Bunit.IRenderedFragment.SaveSnapshot> methods, along with a host of other assert helpers.
162+
163+
This example tests the `<CheckList>` component listed below. The component allow you to add new items to the check list by typing into the input field and hitting the `enter` key, and items can be removed again from the list by clicking on them.
164+
165+
[!code-razor[CheckList.razor](../../../samples/components/CheckList.razor)]
166+
167+
To test the end-to-end life cycle of adding and removing items from the `<CheckList>` component, do the following:
168+
169+
[!code-csharp[](../../../samples/tests/xunit/VerifyMarkupExamples.cs?start=96&end=141)]
118170

119-
### Useful DOM Properties and Methods for Asserting
171+
This is what happens in the test:
120172

121-
## Diffing DOM Nodes
173+
1. First the component is rendered and the input field is found.
174+
2. The the first item is added through the input field.
175+
3. The <xref:Bunit.IRenderedFragment.GetChangesSinceFirstRender>, [`ShouldHaveSingleChange()`](xref:Bunit.DiffAssertExtensions.ShouldHaveSingleChange(System.Collections.Generic.IEnumerable{AngleSharp.Diffing.Core.IDiff})) and [`ShouldBeAddition()`](xref:Bunit.ShouldBeAdditionAssertExtensions.ShouldBeAddition(AngleSharp.Diffing.Core.IDiff,System.String,System.String)) methods are used to verify that the item was correctly added.
176+
4. The <xref:Bunit.IRenderedFragment.SaveSnapshot> is used to save a snapshot of current DOM nodes internally in the `cut`. This reduces the number of diffs found in the following steps, simplifying verification.
177+
5. A second item is added to the check list.
178+
6. Two verifications is performed at this point, one using the <xref:Bunit.IRenderedFragment.GetChangesSinceFirstRender> method which finds two changes, and one using the <xref:Bunit.IRenderedFragment.GetChangesSinceSnapshot> method, that finds a single change. The first is only done for illustrative purposes.
179+
7. A new snapshot is saved, replacing the previous one, with a another call to the <xref:Bunit.IRenderedFragment.SaveSnapshot> method.
180+
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.
122181

123-
- Since first render
124-
- since snapshot
125-
- Assertion helpers for List of IDiff
182+
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.

0 commit comments

Comments
 (0)