Skip to content

Commit d5ebfbc

Browse files
committed
Added MarkupMatches(this string actual ...) extension methods. Make it easier to compare just the text content from a DON text node with a string, while still getting the benefit of the semantic HTML comparer.
1 parent 41540d5 commit d5ebfbc

File tree

4 files changed

+95
-73
lines changed

4 files changed

+95
-73
lines changed

CHANGELOG.md

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,50 +9,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
99
### Added
1010
List of new features.
1111

12-
- Authorization fakes added to make it much easier to test components that use authentication and authorization. By [@DarthPedro](https://github.com/DarthPedro) in [#151](https://github.com/egil/bUnit/pull/151).
12+
- Authorization fakes added to make it much easier to test components that use authentication and authorization. Learn more in the [Faking Blazor's Authentication and Authorization](https://bunit.egilhansen.com/docs/test-doubles/faking-auth) page. By [@DarthPedro](https://github.com/DarthPedro) in [#151](https://github.com/egil/bUnit/pull/151).
1313

14-
#### Authorization Fakes
15-
Added authentication/authorization fake services to make it easy to test Blazor components that use authorization either through code or the AuthorizeView component.
16-
You just need to call the AddTestAuthorization method on your TestContext.Services collection.
17-
18-
First define your component that uses AuthorizeView to render differently based on the user's authorization state:
19-
```
20-
@using Microsoft.AspNetCore.Authorization
21-
@using Microsoft.AspNetCore.Components.Authorization
22-
23-
<CascadingAuthenticationState>
24-
<AuthorizeView>
25-
<Authorized>
26-
Authorized!
27-
</Authorized>
28-
<NotAuthorized>
29-
Not authorized?
30-
</NotAuthorized>
31-
</AuthorizeView>
32-
</CascadingAuthenticationState>
33-
```
34-
Then define your test method to specify the authorization state with a user name, and run your test.
35-
```c#
36-
public void Test002()
37-
{
38-
// arrange
39-
using var ctx = new TestContext();
40-
var authContext = ctx.Services.AddTestAuthorization();
41-
authContext.SetAuthorized("TestUser", AuthorizationState.Authorized);
42-
43-
// act
44-
var cut = ctx.RenderComponent<SimpleAuthView>();
45-
46-
// assert
47-
cut.MarkupMatches("Authorized!");
48-
}
49-
```
14+
- Added `MarkupMatches(this string actual ...)` extension methods. Make it easier to compare just the text content from a DON text node with a string, while still getting the benefit of the semantic HTML comparer.
5015

5116
### Changed
5217
List of changes in existing functionality.
5318

5419
- `TestContextBase.Dispose` made virtual to allow inheritor's to override it. By [@SimonCropp](https://github.com/SimonCropp) in [#137](https://github.com/egil/bunit/pull/137).
55-
- Changed naming convention for JSMock feature. All classes and methods containing `Js` (meaning JavaScript) renamed to `JS`. By [yourilima](https://github.com/yourilima) in [#150](https://github.com/egil/bUnit/pull/150)
20+
- **[Breaking change]** Changed naming convention for JSMock feature and moved to new namespace, `Bunit.TestDoubles.JSInterop`. All classes and methods containing `Js` (meaning JavaScript) renamed to `JS` for consistency with Blazor's `IJSRuntime`. By [@yourilima](https://github.com/yourilima) in [#150](https://github.com/egil/bUnit/pull/150)
5621

5722
### Deprecated
5823
List of soon-to-be removed features.

docs/samples/tests/xunit/VerifyMarkupExamples.cs

Lines changed: 8 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()

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,9 @@ Here we use the `Find(string cssSelector)` method to find the `<small>` element,
8383
> [!TIP]
8484
> Working with `Find()`, `FindAll()`, `INode` and `INodeList` is covered later on this page.
8585
86-
<!-- TODO UNCOMMENT WHEN MarkupMatches(this string markup ... ) IS DONE
8786
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:
8887

8988
[!code-csharp[](../../../samples/tests/xunit/VerifyMarkupExamples.cs?start=51&end=56&highlight=5)]
90-
-->
9189

9290
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.
9391

src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,72 @@ namespace Bunit
1313
/// </summary>
1414
public static class MarkupMatchesAssertExtensions
1515
{
16+
/// <summary>
17+
/// Verifies that the rendered markup from the <paramref name="actual"/> markup fragment matches
18+
/// the <paramref name="expected"/> markup fragment, using the <see cref="HtmlComparer"/> type.
19+
/// </summary>
20+
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
21+
/// <param name="actual">The markup fragment to verify.</param>
22+
/// <param name="expected">The expected markup fragment.</param>
23+
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
24+
public static void MarkupMatches(this string actual, string expected, string? userMessage = null)
25+
{
26+
using var parser = new HtmlParser();
27+
var actualNodes = parser.Parse(actual);
28+
var expectedNodes = parser.Parse(expected);
29+
actualNodes.MarkupMatches(expectedNodes, userMessage);
30+
}
31+
32+
/// <summary>
33+
/// Verifies that the rendered markup from the <paramref name="actual"/> markup fragment matches
34+
/// the <paramref name="expected"/> <see cref="IRenderedFragmentBase"/>, using the <see cref="HtmlComparer"/> type.
35+
/// </summary>
36+
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
37+
/// <param name="actual">The markup fragment to verify.</param>
38+
/// <param name="expected">The expected <see cref="IRenderedFragmentBase"/>.</param>
39+
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
40+
public static void MarkupMatches(this string actual, IRenderedFragment expected, string? userMessage = null)
41+
{
42+
if (expected is null)
43+
throw new ArgumentNullException(nameof(expected));
44+
45+
var actualNodes = actual.ToNodeList(expected.Services.GetRequiredService<HtmlParser>());
46+
actualNodes.MarkupMatches(expected, userMessage);
47+
}
48+
49+
/// <summary>
50+
/// Verifies that the rendered markup from the <paramref name="actual"/> markup fragment matches
51+
/// the <paramref name="expected"/> <see cref="INodeList"/>, using the <see cref="HtmlComparer"/> type.
52+
/// </summary>
53+
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
54+
/// <param name="actual">The markup fragment to verify.</param>
55+
/// <param name="expected">The expected <see cref="INodeList"/>.</param>
56+
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
57+
public static void MarkupMatches(this string actual, INodeList expected, string? userMessage = null)
58+
{
59+
if (expected is null)
60+
throw new ArgumentNullException(nameof(expected));
61+
62+
var actualNodes = actual.ToNodeList(expected.GetHtmlParser());
63+
actualNodes.MarkupMatches(expected, userMessage);
64+
}
65+
66+
/// <summary>
67+
/// Verifies that the rendered markup from the <paramref name="actual"/> markup fragment matches
68+
/// the <paramref name="expected"/> <see cref="INode"/>, using the <see cref="HtmlComparer"/> type.
69+
/// </summary>
70+
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
71+
/// <param name="actual">The markup fragment to verify.</param>
72+
/// <param name="expected">The expected <see cref="INode"/>.</param>
73+
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
74+
public static void MarkupMatches(this string actual, INode expected, string? userMessage = null)
75+
{
76+
if (expected is null)
77+
throw new ArgumentNullException(nameof(expected));
78+
79+
var actualNodes = actual.ToNodeList(expected.GetHtmlParser());
80+
actualNodes.MarkupMatches(expected, userMessage);
81+
}
1682

1783
/// <summary>
1884
/// Verifies that the rendered markup from the <paramref name="actual"/> <see cref="IRenderedFragmentBase"/> matches
@@ -29,9 +95,7 @@ public static void MarkupMatches(this IRenderedFragment actual, string expected,
2995
if (expected is null)
3096
throw new ArgumentNullException(nameof(expected));
3197

32-
var htmlParser = actual.Services.GetRequiredService<HtmlParser>();
33-
var expectedNodes = htmlParser.Parse(expected);
34-
98+
var expectedNodes = expected.ToNodeList(actual.Services.GetRequiredService<HtmlParser>());
3599
actual.Nodes.MarkupMatches(expectedNodes, userMessage);
36100
}
37101

@@ -105,17 +169,8 @@ public static void MarkupMatches(this INode actual, string expected, string? use
105169
if (actual is null)
106170
throw new ArgumentNullException(nameof(actual));
107171

108-
INodeList expectedNodes;
109-
if (actual.GetHtmlParser() is { } parser)
110-
{
111-
expectedNodes = parser.Parse(expected);
112-
}
113-
else
114-
{
115-
using var newParser = new HtmlParser();
116-
expectedNodes = newParser.Parse(expected);
117-
}
118-
MarkupMatches(actual, expectedNodes, userMessage);
172+
var expectedNodes = expected.ToNodeList(actual.GetHtmlParser());
173+
actual.MarkupMatches(expectedNodes, userMessage);
119174
}
120175

121176
/// <summary>
@@ -132,17 +187,8 @@ public static void MarkupMatches(this INodeList actual, string expected, string?
132187
if (actual is null)
133188
throw new ArgumentNullException(nameof(actual));
134189

135-
INodeList expectedNodes;
136-
if (actual.Length > 0 && actual[0].GetHtmlParser() is { } parser)
137-
{
138-
expectedNodes = parser.Parse(expected);
139-
}
140-
else
141-
{
142-
using var newParser = new HtmlParser();
143-
expectedNodes = newParser.Parse(expected);
144-
}
145-
MarkupMatches(actual, expectedNodes, userMessage);
190+
var expectedNodes = expected.ToNodeList(actual.GetHtmlParser());
191+
actual.MarkupMatches(expectedNodes, userMessage);
146192
}
147193

148194
/// <summary>
@@ -195,5 +241,18 @@ public static void MarkupMatches(this INode actual, INodeList expected, string?
195241
if (diffs.Count != 0)
196242
throw new HtmlEqualException(diffs, expected, actual, userMessage);
197243
}
244+
245+
private static INodeList ToNodeList(this string markup, HtmlParser? htmlParser)
246+
{
247+
if (htmlParser is null)
248+
{
249+
using var newHtmlParser = new HtmlParser();
250+
return newHtmlParser.Parse(markup);
251+
}
252+
else
253+
{
254+
return htmlParser.Parse(markup);
255+
}
256+
}
198257
}
199258
}

0 commit comments

Comments
 (0)