Skip to content

Commit 7dedd33

Browse files
committed
Tests of JsRuntimeAsserts
1 parent e99b1d7 commit 7dedd33

6 files changed

Lines changed: 204 additions & 13 deletions

File tree

src/Assembly.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Egil.RazorComponents.Testing.Library.Tests")]

src/Asserting/JsInvokeCountExpectedException.cs renamed to src/Mocking/JSInterop/JsInvokeCountExpectedException.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,40 @@
11
using System;
22
using System.Diagnostics.CodeAnalysis;
3+
using Egil.RazorComponents.Testing;
34
using Xunit.Sdk;
45

56
namespace Xunit.Sdk
67
{
8+
/// <summary>
9+
/// Represents a number of unexpected invocation to a <see cref="MockJsRuntimeInvokeHandler"/>.
10+
/// </summary>
711
[SuppressMessage("Design", "CA1032:Implement standard exception constructors", Justification = "<Pending>")]
812
public class JsInvokeCountExpectedException : AssertActualExpectedException
913
{
14+
/// <summary>
15+
/// Gets the expected invocation count.
16+
/// </summary>
17+
public int ExpectedInvocationCount { get; }
18+
19+
/// <summary>
20+
/// Gets the actual invocation count.
21+
/// </summary>
22+
public int ActualInvocationCount { get; }
23+
24+
/// <summary>
25+
/// Gets the identifier.
26+
/// </summary>
27+
public string Identifier { get; }
28+
29+
/// <summary>
30+
/// Creates an instance of the <see cref="JsInvokeCountExpectedException"/>.
31+
/// </summary>
1032
public JsInvokeCountExpectedException(string identifier, int expectedCount, int actualCount, string assertMethod, string? userMessage = null)
1133
: base(expectedCount, actualCount, CreateMessage(assertMethod, identifier, userMessage), "Expected number of calls", "Actual number of calls")
1234
{
35+
ExpectedInvocationCount = expectedCount;
36+
ActualInvocationCount = actualCount;
37+
Identifier = identifier;
1338
}
1439

1540
private static string CreateMessage(string assertMethod, string identifier, string? userMessage = null)

src/Asserting/JsRuntimeAssertExtensions.cs renamed to src/Mocking/JSInterop/JsRuntimeAssertExtensions.cs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@
66
using Xunit;
77
using Xunit.Sdk;
88

9-
namespace Egil.RazorComponents.Testing.Asserting
9+
namespace Egil.RazorComponents.Testing
1010
{
1111
/// <summary>
1212
/// Assert extensions for JsRuntimeMock
1313
/// </summary>
1414
public static class JsRuntimeAssertExtensions
1515
{
16+
/// <summary>
17+
/// Verifies that the <paramref name="identifier"/> was never invoked on the <paramref name="handler"/>.
18+
/// </summary>
19+
/// <param name="handler">Handler to verify against.</param>
20+
/// <param name="identifier">Identifier of invocation that should not have happened.</param>
21+
/// <param name="userMessage">A custom user message to display if the assertion fails.</param>
1622
public static void VerifyNotInvoke(this MockJsRuntimeInvokeHandler handler, string identifier, string? userMessage = null)
1723
{
1824
if (handler is null) throw new ArgumentNullException(nameof(handler));
@@ -22,9 +28,24 @@ public static void VerifyNotInvoke(this MockJsRuntimeInvokeHandler handler, stri
2228
}
2329
}
2430

25-
public static JsRuntimeInvocation VerifyInvoke(this MockJsRuntimeInvokeHandler handler, string identifier) => VerifyInvoke(handler, identifier, 1)[0];
31+
/// <summary>
32+
/// Verifies that the <paramref name="identifier"/> has been invoked one time.
33+
/// </summary>
34+
/// <param name="handler">Handler to verify against.</param>
35+
/// <param name="identifier">Identifier of invocation that should have been invoked.</param>
36+
/// <param name="userMessage">A custom user message to display if the assertion fails.</param>
37+
/// <returns>The <see cref="JsRuntimeInvocation"/>.</returns>
38+
public static JsRuntimeInvocation VerifyInvoke(this MockJsRuntimeInvokeHandler handler, string identifier, string? userMessage = null)
39+
=> VerifyInvoke(handler, identifier, 1, userMessage)[0];
2640

27-
public static IReadOnlyList<JsRuntimeInvocation> VerifyInvoke(this MockJsRuntimeInvokeHandler handler, string identifier, int calledTimes = 1, string? userMessage = null)
41+
/// <summary>
42+
/// Verifies that the <paramref name="identifier"/> has been invoked <paramref name="calledTimes"/> times.
43+
/// </summary>
44+
/// <param name="handler">Handler to verify against.</param>
45+
/// <param name="identifier">Identifier of invocation that should have been invoked.</param>
46+
/// <param name="userMessage">A custom user message to display if the assertion fails.</param>
47+
/// <returns>The <see cref="JsRuntimeInvocation"/>.</returns>
48+
public static IReadOnlyList<JsRuntimeInvocation> VerifyInvoke(this MockJsRuntimeInvokeHandler handler, string identifier, int calledTimes, string? userMessage = null)
2849
{
2950
if (handler is null) throw new ArgumentNullException(nameof(handler));
3051
if (calledTimes < 1)
@@ -40,17 +61,20 @@ public static IReadOnlyList<JsRuntimeInvocation> VerifyInvoke(this MockJsRuntime
4061
return invocations;
4162
}
4263

64+
/// <summary>
65+
/// Verifies that an argument <paramref name="actualArgument"/>
66+
/// passed to an JsRuntime invocation is an <see cref="ElementReference"/>
67+
/// to the <paramref name="expectedTargetElement"/>.
68+
/// </summary>
69+
/// <param name="actualArgument">object to verify.</param>
70+
/// <param name="expectedTargetElement">expected targeted element.</param>
4371
public static void ShouldBeElementReferenceTo(this object actualArgument, IElement expectedTargetElement)
4472
{
4573
if (actualArgument is null) throw new ArgumentNullException(nameof(actualArgument));
4674
if (expectedTargetElement is null) throw new ArgumentNullException(nameof(expectedTargetElement));
4775

48-
if (!(actualArgument is ElementReference elmRef))
49-
{
50-
throw new IsTypeException(typeof(ElementReference).FullName, actualArgument.GetType().FullName);
51-
}
52-
53-
var elmRefAttrName = Htmlizer.ToBlazorAttribute("elementreference");
76+
var elmRef = Assert.IsType<ElementReference>(actualArgument);
77+
var elmRefAttrName = Htmlizer.ELEMENT_REFERENCE_ATTR_NAME;
5478
var expectedId = expectedTargetElement.GetAttribute(elmRefAttrName);
5579
if (string.IsNullOrEmpty(expectedId) || !elmRef.Id.Equals(expectedId, StringComparison.Ordinal))
5680
{

src/Mocking/MockJsRuntimeExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@
22

33
namespace Egil.RazorComponents.Testing
44
{
5+
/// <summary>
6+
/// Helper methods for registering the MockJsRuntime with a <see cref="TestServiceProvider"/>.
7+
/// </summary>
58
public static class MockJsRuntimeExtensions
69
{
10+
/// <summary>
11+
/// Adds the <see cref="MockJsRuntimeInvokeHandler"/> to the <see cref="TestServiceProvider"/>.
12+
/// </summary>
13+
/// <returns>The added <see cref="MockJsRuntimeInvokeHandler"/>.</returns>
714
public static MockJsRuntimeInvokeHandler AddMockJsRuntime(this TestServiceProvider serviceProvider, JsRuntimeMockMode mode = JsRuntimeMockMode.Loose)
815
{
916
if (serviceProvider is null) throw new ArgumentNullException(nameof(serviceProvider));

src/Rendering/Htmlizer.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,17 @@ namespace Egil.RazorComponents.Testing
1010
[SuppressMessage("Usage", "BL0006:Do not use RenderTree types", Justification = "<Pending>")]
1111
internal class Htmlizer
1212
{
13-
private const string BLAZOR_ATTR_PREFIX = "blazor:";
1413
private static readonly HtmlEncoder HtmlEncoder = HtmlEncoder.Default;
1514

1615
private static readonly HashSet<string> SelfClosingElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
1716
{
1817
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"
1918
};
2019

21-
public static bool IsBlazorAttribute(string attributeName)
20+
public const string BLAZOR_ATTR_PREFIX = "blazor:";
21+
public const string ELEMENT_REFERENCE_ATTR_NAME = BLAZOR_ATTR_PREFIX + "elementreference";
22+
23+
public static bool IsBlazorAttribute(string attributeName)
2224
=> attributeName.StartsWith(BLAZOR_ATTR_PREFIX, StringComparison.Ordinal);
2325

2426
public static string ToBlazorAttribute(string attributeName)
@@ -200,9 +202,9 @@ private static int RenderAttributes(
200202
return candidateIndex;
201203
}
202204

203-
if(frame.FrameType == RenderTreeFrameType.ElementReferenceCapture)
205+
if (frame.FrameType == RenderTreeFrameType.ElementReferenceCapture)
204206
{
205-
result.Add($" {BLAZOR_ATTR_PREFIX}elementreference=\"{frame.AttributeName}\"");
207+
result.Add($" {ELEMENT_REFERENCE_ATTR_NAME}=\"{frame.AttributeName}\"");
206208
return candidateIndex;
207209
}
208210
// End of addition
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using AngleSharp.Dom;
7+
using Egil.RazorComponents.Testing.Diffing;
8+
using Microsoft.AspNetCore.Components;
9+
using Microsoft.JSInterop;
10+
using Moq;
11+
using Shouldly;
12+
using Xunit;
13+
using Xunit.Sdk;
14+
15+
namespace Egil.RazorComponents.Testing.Mocking.JSInterop
16+
{
17+
public class JsRuntimeAssertExtensionsTest
18+
{
19+
[Fact(DisplayName = "VerifyNotInvoke throws if handler is null")]
20+
public void Test001()
21+
{
22+
MockJsRuntimeInvokeHandler? handler = null;
23+
Should.Throw<ArgumentNullException>(() => JsRuntimeAssertExtensions.VerifyNotInvoke(handler!, ""));
24+
}
25+
26+
[Fact(DisplayName = "VerifyNotInvoke throws JsInvokeCountExpectedException if identifier " +
27+
"has been invoked one or more times")]
28+
public async Task Test002()
29+
{
30+
var identifier = "test";
31+
var handler = new MockJsRuntimeInvokeHandler();
32+
await handler.ToJsRuntime().InvokeVoidAsync(identifier);
33+
34+
Should.Throw<JsInvokeCountExpectedException>(() => handler.VerifyNotInvoke(identifier));
35+
}
36+
37+
[Fact(DisplayName = "VerifyNotInvoke throws JsInvokeCountExpectedException if identifier " +
38+
"has been invoked one or more times, with custom error message")]
39+
public async Task Test003()
40+
{
41+
var identifier = "test";
42+
var errMsg = "HELLO WORLD";
43+
var handler = new MockJsRuntimeInvokeHandler();
44+
await handler.ToJsRuntime().InvokeVoidAsync(identifier);
45+
46+
Should.Throw<JsInvokeCountExpectedException>(() => handler.VerifyNotInvoke(identifier, errMsg))
47+
.UserMessage.ShouldEndWith(errMsg);
48+
}
49+
50+
[Fact(DisplayName = "VerifyNotInvoke does not throw if identifier has not been invoked")]
51+
public void Test004()
52+
{
53+
var handler = new MockJsRuntimeInvokeHandler();
54+
55+
handler.VerifyNotInvoke("FOOBAR");
56+
}
57+
58+
[Fact(DisplayName = "VerifyInvoke throws if handler is null")]
59+
public void Test100()
60+
{
61+
MockJsRuntimeInvokeHandler? handler = null;
62+
Should.Throw<ArgumentNullException>(() => JsRuntimeAssertExtensions.VerifyInvoke(handler!, ""));
63+
Should.Throw<ArgumentNullException>(() => JsRuntimeAssertExtensions.VerifyInvoke(handler!, "", 42));
64+
}
65+
66+
[Fact(DisplayName = "VerifyInvoke throws invokeCount is less than 1")]
67+
public void Test101()
68+
{
69+
var handler = new MockJsRuntimeInvokeHandler();
70+
71+
Should.Throw<ArgumentException>(() => handler.VerifyInvoke("", 0));
72+
}
73+
74+
[Fact(DisplayName = "VerifyInvoke throws JsInvokeCountExpectedException when " +
75+
"invocation count doesn't match the expected")]
76+
public async Task Test103()
77+
{
78+
var identifier = "test";
79+
var handler = new MockJsRuntimeInvokeHandler();
80+
await handler.ToJsRuntime().InvokeVoidAsync(identifier);
81+
82+
var actual = Should.Throw<JsInvokeCountExpectedException>(() => handler.VerifyInvoke(identifier, 2));
83+
actual.ExpectedInvocationCount.ShouldBe(2);
84+
actual.ActualInvocationCount.ShouldBe(1);
85+
actual.Identifier.ShouldBe(identifier);
86+
}
87+
88+
[Fact(DisplayName = "VerifyInvoke returns the invocation(s) if the expected count matched")]
89+
public async Task Test104()
90+
{
91+
var identifier = "test";
92+
var handler = new MockJsRuntimeInvokeHandler();
93+
await handler.ToJsRuntime().InvokeVoidAsync(identifier);
94+
95+
var invocations = handler.VerifyInvoke(identifier, 1);
96+
invocations.ShouldBeSameAs(handler.Invocations[identifier]);
97+
98+
var invocation = handler.VerifyInvoke(identifier);
99+
invocation.ShouldBe(handler.Invocations[identifier][0]);
100+
}
101+
102+
[Fact(DisplayName = "ShouldBeElementReferenceTo throws if actualArgument or targeted element is null")]
103+
public void Test200()
104+
{
105+
Should.Throw<ArgumentNullException>(() => JsRuntimeAssertExtensions.ShouldBeElementReferenceTo(null!, null!))
106+
.ParamName.ShouldBe("actualArgument");
107+
Should.Throw<ArgumentNullException>(() => JsRuntimeAssertExtensions.ShouldBeElementReferenceTo(string.Empty, null!))
108+
.ParamName.ShouldBe("expectedTargetElement");
109+
}
110+
111+
[Fact(DisplayName = "ShouldBeElementReferenceTo throws if actualArgument is not a ElementReference")]
112+
public void Test201()
113+
{
114+
var obj = new object();
115+
Should.Throw<IsTypeException>(() => obj.ShouldBeElementReferenceTo(Mock.Of<IElement>()));
116+
}
117+
118+
[Fact(DisplayName = "ShouldBeElementReferenceTo throws if element reference does not point to the provided element")]
119+
public void Test202()
120+
{
121+
using var htmlParser = new TestHtmlParser();
122+
var elmRef = new ElementReference(Guid.NewGuid().ToString());
123+
var elm = htmlParser.Parse($"<p {Htmlizer.ELEMENT_REFERENCE_ATTR_NAME}=\"ASDF\" />").First() as IElement;
124+
125+
Should.Throw<AssertActualExpectedException>(() => elmRef.ShouldBeElementReferenceTo(elm));
126+
127+
var elmWithoutRefAttr = htmlParser.Parse($"<p />").First() as IElement;
128+
129+
Should.Throw<AssertActualExpectedException>(() => elmRef.ShouldBeElementReferenceTo(elmWithoutRefAttr));
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)