diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 6864fc111..45998aa7a 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,7 +1,7 @@
name: CI
-on: [push, pull_request]
+on: push
env:
VERSION: 1337.0.0
@@ -28,8 +28,10 @@ jobs:
dotnet-version: '3.1.100'
- name: Building and verifying library
run: |
- dotnet build -c Release /nowarn:CS1591
+ dotnet build -c Release
dotnet test -c Release /nowarn:CS1591
+ dotnet build sample -c Release
+ dotnet test sample -c Release
- name: Creating library package
run: dotnet pack src/ -c Release -o ${GITHUB_WORKSPACE} -p:version=$VERSION /nowarn:CS1591
- name: Buidling template package
diff --git a/.gitignore b/.gitignore
index 7e2388353..be9a19011 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@ bld/
# Visual Studio 2015/2017 cache/options directory
.vs/
+.vscode/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
diff --git a/README.md b/README.md
index 429952ff6..a91aee89f 100644
--- a/README.md
+++ b/README.md
@@ -22,3 +22,10 @@ This library's goal is to make it easy to write _comprehensive, stable unit test
- [Mocking JsRuntime](https://github.com/egil/razor-components-testing-library/wiki/Mocking-JsRuntime)
- [References](https://github.com/egil/razor-components-testing-library/wiki/References)
- [Contribute](https://github.com/egil/razor-components-testing-library/wiki/Contribute)
+
+## Contributors
+
+Shout outs and a big thank you to the contributors to this library. Here they are, in alphabetically:
+
+- [Michael J Conrad (@Siphonophora)](https://github.com/Siphonophora)
+- [Rastislav Novotný (@duracellko)](https://github.com/duracellko)
\ No newline at end of file
diff --git a/Razor.Components.Testing.Library.sln b/Razor.Components.Testing.Library.sln
index fb516bc06..e08910db2 100644
--- a/Razor.Components.Testing.Library.sln
+++ b/Razor.Components.Testing.Library.sln
@@ -19,13 +19,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Egil.RazorComponents.Testin
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Egil.RazorComponents.Testing.Library.Tests", "tests\Egil.RazorComponents.Testing.Library.Tests.csproj", "{04E0142A-33CC-4E30-B903-F1370D94AD8C}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{26D90CB9-AF66-4F42-A16E-39D2CF69C8FB}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Egil.RazorComponents.Testing.Library.SampleApp", "sample\src\Egil.RazorComponents.Testing.Library.SampleApp.csproj", "{D1FE0F2A-D856-417E-A1FD-4ECE9C64D3AE}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Egil.RazorComponents.Testing.Library.SampleApp.Tests", "sample\tests\Egil.RazorComponents.Testing.Library.SampleApp.Tests.csproj", "{A7B05744-AA61-4F8E-8173-5DE812A4A745}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Razor.Components.Testing.Library.Template", "template\Razor.Components.Testing.Library.Template.csproj", "{FB46378D-BFB8-4C72-9CA3-0407D4665218}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Egil.Razor.Components.Testing.Library.Template", "template\Egil.Razor.Components.Testing.Library.Template.csproj", "{FB46378D-BFB8-4C72-9CA3-0407D4665218}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -41,14 +35,6 @@ Global
{04E0142A-33CC-4E30-B903-F1370D94AD8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04E0142A-33CC-4E30-B903-F1370D94AD8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04E0142A-33CC-4E30-B903-F1370D94AD8C}.Release|Any CPU.Build.0 = Release|Any CPU
- {D1FE0F2A-D856-417E-A1FD-4ECE9C64D3AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D1FE0F2A-D856-417E-A1FD-4ECE9C64D3AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D1FE0F2A-D856-417E-A1FD-4ECE9C64D3AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D1FE0F2A-D856-417E-A1FD-4ECE9C64D3AE}.Release|Any CPU.Build.0 = Release|Any CPU
- {A7B05744-AA61-4F8E-8173-5DE812A4A745}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A7B05744-AA61-4F8E-8173-5DE812A4A745}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A7B05744-AA61-4F8E-8173-5DE812A4A745}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A7B05744-AA61-4F8E-8173-5DE812A4A745}.Release|Any CPU.Build.0 = Release|Any CPU
{FB46378D-BFB8-4C72-9CA3-0407D4665218}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB46378D-BFB8-4C72-9CA3-0407D4665218}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB46378D-BFB8-4C72-9CA3-0407D4665218}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -60,8 +46,6 @@ Global
GlobalSection(NestedProjects) = preSolution
{AA96790B-67C9-4141-ACDB-037C8DC092EC} = {E006E9A4-F554-46DF-838F-812956521F64}
{04E0142A-33CC-4E30-B903-F1370D94AD8C} = {C929375E-BD70-4B78-88C1-BDD1623C3365}
- {D1FE0F2A-D856-417E-A1FD-4ECE9C64D3AE} = {26D90CB9-AF66-4F42-A16E-39D2CF69C8FB}
- {A7B05744-AA61-4F8E-8173-5DE812A4A745} = {26D90CB9-AF66-4F42-A16E-39D2CF69C8FB}
{FB46378D-BFB8-4C72-9CA3-0407D4665218} = {E006E9A4-F554-46DF-838F-812956521F64}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/sample/SampleApp.sln b/sample/SampleApp.sln
new file mode 100644
index 000000000..37fd9894d
--- /dev/null
+++ b/sample/SampleApp.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29613.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleApp", "src\SampleApp.csproj", "{0C4F7AE0-EA8A-4ECC-9003-1CEE4412BBA7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleApp.Tests", "tests\SampleApp.Tests.csproj", "{04F6D258-F69C-4BB5-87C5-3813C3CE33D8}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0C4F7AE0-EA8A-4ECC-9003-1CEE4412BBA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0C4F7AE0-EA8A-4ECC-9003-1CEE4412BBA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0C4F7AE0-EA8A-4ECC-9003-1CEE4412BBA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0C4F7AE0-EA8A-4ECC-9003-1CEE4412BBA7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {04F6D258-F69C-4BB5-87C5-3813C3CE33D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {04F6D258-F69C-4BB5-87C5-3813C3CE33D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {04F6D258-F69C-4BB5-87C5-3813C3CE33D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {04F6D258-F69C-4BB5-87C5-3813C3CE33D8}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {FBE5B0F6-5496-4BC5-BB38-CF16799DCF93}
+ EndGlobalSection
+EndGlobal
diff --git a/sample/src/Egil.RazorComponents.Testing.Library.SampleApp.csproj b/sample/src/SampleApp.csproj
similarity index 86%
rename from sample/src/Egil.RazorComponents.Testing.Library.SampleApp.csproj
rename to sample/src/SampleApp.csproj
index cce78d825..8a9ad2278 100644
--- a/sample/src/Egil.RazorComponents.Testing.Library.SampleApp.csproj
+++ b/sample/src/SampleApp.csproj
@@ -2,6 +2,7 @@
netcoreapp3.1
+ falseEgil.RazorComponents.Testing.SampleApp
diff --git a/sample/src/Startup.cs b/sample/src/Startup.cs
index 66e241d1a..cc6a48e25 100644
--- a/sample/src/Startup.cs
+++ b/sample/src/Startup.cs
@@ -9,10 +9,12 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using System.Diagnostics.CodeAnalysis;
using Egil.RazorComponents.Testing.SampleApp.Data;
namespace Egil.RazorComponents.Testing.SampleApp
{
+ [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "")]
public class Startup
{
public Startup(IConfiguration configuration)
@@ -22,6 +24,7 @@ public Startup(IConfiguration configuration)
public IConfiguration Configuration { get; }
+
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
diff --git a/sample/tests/Assembly.cs b/sample/tests/Assembly.cs
new file mode 100644
index 000000000..c2a9bc9c5
--- /dev/null
+++ b/sample/tests/Assembly.cs
@@ -0,0 +1 @@
+[assembly: System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
\ No newline at end of file
diff --git a/sample/tests/GlobalSuppressions.cs b/sample/tests/GlobalSuppressions.cs
new file mode 100644
index 000000000..4d1347fed
--- /dev/null
+++ b/sample/tests/GlobalSuppressions.cs
@@ -0,0 +1,6 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:Egil.RazorComponents.Testing.SampleApp.Tests.Components.AlertTest2.Test008~System.Threading.Tasks.Task")]
diff --git a/sample/tests/MockForecastService.cs b/sample/tests/MockForecastService.cs
index 71d96a0dc..771df16bb 100644
--- a/sample/tests/MockForecastService.cs
+++ b/sample/tests/MockForecastService.cs
@@ -4,10 +4,10 @@
namespace Egil.RazorComponents.Testing.SampleApp
{
-internal class MockForecastService : IWeatherForecastService
-{
- public TaskCompletionSource Task { get; } = new TaskCompletionSource();
+ internal class MockForecastService : IWeatherForecastService
+ {
+ public TaskCompletionSource Task { get; } = new TaskCompletionSource();
- public Task GetForecastAsync(DateTime startDate) => Task.Task;
-}
+ public Task GetForecastAsync(DateTime startDate) => Task.Task;
+ }
}
diff --git a/sample/tests/RazorTestComponents/Components/AlertRazorTest.razor b/sample/tests/RazorTestComponents/Components/AlertRazorTest.razor
index 2223bbdd6..9b83d74ff 100644
--- a/sample/tests/RazorTestComponents/Components/AlertRazorTest.razor
+++ b/sample/tests/RazorTestComponents/Components/AlertRazorTest.razor
@@ -1,7 +1,7 @@
-@inherits TestComponentBase
+@inherits TestComponentBase
@code {
- MockJsRuntimeInvokeHandler MockJsRuntime { get; set; }
+ MockJsRuntimeInvokeHandler MockJsRuntime { get; set; } = default!;
void Setup()
{
diff --git a/sample/tests/RazorTestComponents/Components/ThemedButtonTest.razor b/sample/tests/RazorTestComponents/Components/ThemedButtonTest.razor
index 4dc524c95..60cc8d91a 100644
--- a/sample/tests/RazorTestComponents/Components/ThemedButtonTest.razor
+++ b/sample/tests/RazorTestComponents/Components/ThemedButtonTest.razor
@@ -15,7 +15,6 @@
void Test()
{
var cut = GetComponentUnderTest();
- var x = cut.GetMarkup();
cut.Find("button").ClassList.ShouldContain("btn");
}
}
\ No newline at end of file
diff --git a/sample/tests/RazorTestComponents/Pages/FetchDataTest.razor b/sample/tests/RazorTestComponents/Pages/FetchDataTest.razor
index 84833cc21..2f2b021e1 100644
--- a/sample/tests/RazorTestComponents/Pages/FetchDataTest.razor
+++ b/sample/tests/RazorTestComponents/Pages/FetchDataTest.razor
@@ -23,7 +23,7 @@
void Setup()
{
- Services.AddService(forecastService);
+ Services.AddSingleton(forecastService);
}
void InitialLoadingHtmlRendersCorrectly()
diff --git a/sample/tests/Egil.RazorComponents.Testing.Library.SampleApp.Tests.csproj b/sample/tests/SampleApp.Tests.csproj
similarity index 90%
rename from sample/tests/Egil.RazorComponents.Testing.Library.SampleApp.Tests.csproj
rename to sample/tests/SampleApp.Tests.csproj
index d2e72634c..09facc247 100644
--- a/sample/tests/Egil.RazorComponents.Testing.Library.SampleApp.Tests.csproj
+++ b/sample/tests/SampleApp.Tests.csproj
@@ -20,7 +20,7 @@
-
+
diff --git a/sample/tests/Tests/Components/AlertTest.cs b/sample/tests/Tests/Components/AlertTest.cs
index 9067e065b..66fcba79e 100644
--- a/sample/tests/Tests/Components/AlertTest.cs
+++ b/sample/tests/Tests/Components/AlertTest.cs
@@ -1,7 +1,8 @@
-using System;
+using System;
using System.Threading.Tasks;
using Egil.RazorComponents.Testing.Asserting;
using Egil.RazorComponents.Testing.EventDispatchExtensions;
+using Egil.RazorComponents.Testing.Mocking.JSInterop;
using Egil.RazorComponents.Testing.SampleApp.Components;
using Egil.RazorComponents.Testing.SampleApp.Data;
using Microsoft.AspNetCore.Authentication;
@@ -208,7 +209,7 @@ public void Test007()
cut.MarkupMatches(string.Empty);
}
- [Fact(DisplayName = "Alert can be dismissed via Dismiss() mehod")]
+ [Fact(DisplayName = "Alert can be dismissed via Dismiss() method")]
public async Task Test008()
{
// Arrange
diff --git a/sample/tests/Tests/Components/FocussingInputTest.cs b/sample/tests/Tests/Components/FocussingInputTest.cs
index 98e7df81e..bc84d316b 100644
--- a/sample/tests/Tests/Components/FocussingInputTest.cs
+++ b/sample/tests/Tests/Components/FocussingInputTest.cs
@@ -4,7 +4,9 @@
using System.Text;
using System.Threading.Tasks;
using Egil.RazorComponents.Testing.Asserting;
+using Egil.RazorComponents.Testing.Mocking.JSInterop;
using Egil.RazorComponents.Testing.SampleApp.Components;
+using Egil.RazorComponents.Testing.Mocking.JSInterop;
using Xunit;
namespace Egil.RazorComponents.Testing.SampleApp.CodeOnlyTests.Components
@@ -24,6 +26,7 @@ public void Test001()
// Assert
// that there is a single call to document.body.focus.call
var invocation = jsRtMock.VerifyInvoke("document.body.focus.call");
+
// Assert that the invocation received a single argument
// and that it was a reference to the input element.
var expectedReferencedElement = cut.Find("input");
diff --git a/sample/tests/Tests/Components/TodoListTest.cs b/sample/tests/Tests/Components/TodoListTest.cs
index e7478c734..a0946c64b 100644
--- a/sample/tests/Tests/Components/TodoListTest.cs
+++ b/sample/tests/Tests/Components/TodoListTest.cs
@@ -1,7 +1,9 @@
using Shouldly;
using AngleSharp.Dom;
using Egil.RazorComponents.Testing.Asserting;
+using Egil.RazorComponents.Testing.Mocking.JSInterop;
using Egil.RazorComponents.Testing.EventDispatchExtensions;
+using Egil.RazorComponents.Testing.Mocking.JSInterop;
using Egil.RazorComponents.Testing.SampleApp.Components;
using Egil.RazorComponents.Testing.SampleApp.Data;
using Microsoft.AspNetCore.Components;
@@ -119,8 +121,8 @@ public void Test005()
cut.Find("input").Change(taskValue);
cut.Find("form").Submit();
- createdTask.ShouldNotBeNull();
- createdTask?.Text.ShouldBe(taskValue);
+ createdTask = createdTask.ShouldBeOfType();
+ createdTask.Text.ShouldBe(taskValue);
}
[Fact(DisplayName = "When add task form is submitted with no text OnAddingTodo is not called")]
diff --git a/sample/tests/Tests/Components/WikiSearchTest.cs b/sample/tests/Tests/Components/WikiSearchTest.cs
index 2d5bfd100..70bdb3292 100644
--- a/sample/tests/Tests/Components/WikiSearchTest.cs
+++ b/sample/tests/Tests/Components/WikiSearchTest.cs
@@ -5,6 +5,7 @@
using System.Threading.Tasks;
using Egil.RazorComponents.Testing.Asserting;
using Egil.RazorComponents.Testing.SampleApp.Components;
+using Egil.RazorComponents.Testing.Mocking.JSInterop;
using Shouldly;
using Xunit;
diff --git a/sample/tests/Tests/Pages/FetchDataTest.cs b/sample/tests/Tests/Pages/FetchDataTest.cs
index 99df3d578..1507beb99 100644
--- a/sample/tests/Tests/Pages/FetchDataTest.cs
+++ b/sample/tests/Tests/Pages/FetchDataTest.cs
@@ -8,6 +8,7 @@
using Xunit;
using Egil.RazorComponents.Testing.SampleApp.Pages;
using Shouldly;
+using Microsoft.Extensions.DependencyInjection;
namespace Egil.RazorComponents.Testing.SampleApp.CodeOnlyTests
{
@@ -17,7 +18,7 @@ public class FetchDataTest : ComponentTestFixture
public void Test001()
{
// Arrange - add the mock forecast service
- Services.AddService();
+ Services.AddSingleton();
// Act - render the FetchData component
var cut = RenderComponent();
@@ -35,7 +36,7 @@ public void Test002()
// Setup the mock forecast service
var forecasts = new[] { new WeatherForecast { Date = DateTime.Now, Summary = "Testy", TemperatureC = 42 } };
var mockForecastService = new MockForecastService();
- Services.AddService(mockForecastService);
+ Services.AddSingleton(mockForecastService);
// Arrange - render the FetchData component
var cut = RenderComponent();
diff --git a/sample/tests/Tests/Pages/TodosTest.cs b/sample/tests/Tests/Pages/TodosTest.cs
index 537fcc9b8..663b3afbd 100644
--- a/sample/tests/Tests/Pages/TodosTest.cs
+++ b/sample/tests/Tests/Pages/TodosTest.cs
@@ -10,6 +10,7 @@
using Egil.RazorComponents.Testing.SampleApp.Data;
using Egil.RazorComponents.Testing.EventDispatchExtensions;
using Egil.RazorComponents.Testing.SampleApp.Pages;
+using Microsoft.Extensions.DependencyInjection;
namespace Egil.RazorComponents.Testing.SampleApp.CodeOnlyTests.Pages
{
@@ -30,7 +31,7 @@ public void Test001()
var getTask = new TaskCompletionSource>();
var todoSrv = new Mock();
todoSrv.Setup(x => x.GetAll()).Returns(getTask.Task);
- Services.AddService(todoSrv.Object);
+ Services.AddSingleton(todoSrv.Object);
// act
var page = RenderComponent();
@@ -51,7 +52,7 @@ public void Test002()
var todos = new[] { new Todo { Id = 1, Text = "First" } };
var todoSrv = new Mock();
todoSrv.Setup(x => x.GetAll()).Returns(Task.FromResult>(todos));
- Services.AddService(todoSrv.Object);
+ Services.AddSingleton(todoSrv.Object);
// act
var page = RenderComponent();
@@ -66,7 +67,7 @@ public void Test003()
{
// arrange
var todoSrv = new Mock();
- Services.AddService(todoSrv.Object);
+ Services.AddSingleton(todoSrv.Object);
var page = RenderComponent();
// act
diff --git a/sample/tests/_Imports.razor b/sample/tests/_Imports.razor
index 4b2a8d793..f80e30b9b 100644
--- a/sample/tests/_Imports.razor
+++ b/sample/tests/_Imports.razor
@@ -1,7 +1,9 @@
-@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.Extensions.DependencyInjection
@using Egil.RazorComponents.Testing
@using Egil.RazorComponents.Testing.EventDispatchExtensions
+@using Egil.RazorComponents.Testing.Mocking.JSInterop
@using Egil.RazorComponents.Testing.Asserting
@using Egil.RazorComponents.Testing.SampleApp
diff --git a/src/Assembly.cs b/src/Assembly.cs
new file mode 100644
index 000000000..1dcfc7065
--- /dev/null
+++ b/src/Assembly.cs
@@ -0,0 +1 @@
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Egil.RazorComponents.Testing.Library.Tests")]
diff --git a/src/Asserting/GenericAssertExtensions.cs b/src/Asserting/CollectionAssertExtensions.cs
similarity index 96%
rename from src/Asserting/GenericAssertExtensions.cs
rename to src/Asserting/CollectionAssertExtensions.cs
index c6a0f007f..5d3609797 100644
--- a/src/Asserting/GenericAssertExtensions.cs
+++ b/src/Asserting/CollectionAssertExtensions.cs
@@ -8,9 +8,9 @@
namespace Egil.RazorComponents.Testing.Asserting
{
///
- /// Generic test assertions
+ /// Collection test assertions
///
- public static class GenericAssertExtensions
+ public static class CollectionAssertExtensions
{
///
/// Verifies that a collection contains exactly a given number of elements, which
diff --git a/src/Asserting/CompareToDiffingExtensions.cs b/src/Asserting/CompareToDiffingExtensions.cs
index 0c6919d1f..921c732e1 100644
--- a/src/Asserting/CompareToDiffingExtensions.cs
+++ b/src/Asserting/CompareToDiffingExtensions.cs
@@ -25,10 +25,9 @@ public static IReadOnlyList CompareTo(this IRenderedFragment actual, stri
if (actual is null) throw new ArgumentNullException(nameof(actual));
if (expected is null) throw new ArgumentNullException(nameof(expected));
- var actualNodes = actual.GetNodes();
- var expectedNodes = actual.TestContext.HtmlParser.Parse(expected);
+ var expectedNodes = actual.TestContext.CreateNodes(expected);
- return actualNodes.CompareTo(expectedNodes);
+ return actual.Nodes.CompareTo(expectedNodes);
}
///
@@ -43,7 +42,7 @@ public static IReadOnlyList CompareTo(this IRenderedFragment actual, IRen
if (actual is null) throw new ArgumentNullException(nameof(actual));
if (expected is null) throw new ArgumentNullException(nameof(expected));
- return actual.GetNodes().CompareTo(expected.GetNodes());
+ return actual.Nodes.CompareTo(expected.Nodes);
}
///
diff --git a/src/Asserting/DiffAssertExtensions.cs b/src/Asserting/DiffAssertExtensions.cs
index 461194674..1b1d2d5c3 100644
--- a/src/Asserting/DiffAssertExtensions.cs
+++ b/src/Asserting/DiffAssertExtensions.cs
@@ -37,7 +37,7 @@ public static IDiff ShouldHaveSingleChange(this IReadOnlyList diffs)
/// The total number of inspectors must exactly match the number of s in the collection
public static void ShouldHaveChanges(this IReadOnlyList diffs, params Action[] diffInspectors)
{
- Assert.Collection(diffs, diffInspectors);
+ CollectionAssertExtensions.ShouldAllBe(diffs, diffInspectors);
}
}
diff --git a/src/Asserting/HtmlEqualException.cs b/src/Asserting/HtmlEqualException.cs
index 4865b58c5..0d7c3c878 100644
--- a/src/Asserting/HtmlEqualException.cs
+++ b/src/Asserting/HtmlEqualException.cs
@@ -16,14 +16,6 @@ namespace Xunit.Sdk
[SuppressMessage("Design", "CA1032:Implement standard exception constructors", Justification = "")]
public class HtmlEqualException : AssertActualExpectedException
{
- ///
- /// Creates an instance of the type.
- ///
- public HtmlEqualException(IEnumerable diffs, IMarkupFormattable expected, IMarkupFormattable actual, string? userMessage, Exception innerException)
- : base(PrintHtml(expected), PrintHtml(actual), CreateUserMessage(diffs, userMessage), "Expected HTML", "Actual HTML", innerException)
- {
- }
-
///
/// Creates an instance of the type.
///
diff --git a/src/Asserting/MarkupMatchesAssertExtensions.cs b/src/Asserting/MarkupMatchesAssertExtensions.cs
index 87bf3a483..5f55c510a 100644
--- a/src/Asserting/MarkupMatchesAssertExtensions.cs
+++ b/src/Asserting/MarkupMatchesAssertExtensions.cs
@@ -25,10 +25,9 @@ public static void MarkupMatches(this IRenderedFragment actual, string expected,
if (actual is null) throw new ArgumentNullException(nameof(actual));
if (expected is null) throw new ArgumentNullException(nameof(expected));
- var actualNodes = actual.GetNodes();
- var expectedNodes = actual.TestContext.HtmlParser.Parse(expected);
+ var expectedNodes = actual.TestContext.CreateNodes(expected);
- actualNodes.MarkupMatches(expectedNodes, userMessage);
+ actual.Nodes.MarkupMatches(expectedNodes, userMessage);
}
///
@@ -44,7 +43,7 @@ public static void MarkupMatches(this IRenderedFragment actual, IRenderedFragmen
if (actual is null) throw new ArgumentNullException(nameof(actual));
if (expected is null) throw new ArgumentNullException(nameof(expected));
- actual.GetNodes().MarkupMatches(expected.GetNodes(), userMessage);
+ actual.Nodes.MarkupMatches(expected.Nodes, userMessage);
}
///
@@ -61,7 +60,7 @@ public static void MarkupMatches(this INodeList actual, IRenderedFragment expect
if (actual is null) throw new ArgumentNullException(nameof(actual));
if (expected is null) throw new ArgumentNullException(nameof(expected));
- actual.MarkupMatches(expected.GetNodes(), userMessage);
+ actual.MarkupMatches(expected.Nodes, userMessage);
}
///
@@ -78,7 +77,7 @@ public static void MarkupMatches(this INode actual, IRenderedFragment expected,
if (actual is null) throw new ArgumentNullException(nameof(actual));
if (expected is null) throw new ArgumentNullException(nameof(expected));
- actual.MarkupMatches(expected.GetNodes(), userMessage);
+ actual.MarkupMatches(expected.Nodes, userMessage);
}
///
diff --git a/src/Asserting/ShouldBeAdditionAssertExtensions.cs b/src/Asserting/ShouldBeAdditionAssertExtensions.cs
index fd9aed4ca..2f57c04e6 100644
--- a/src/Asserting/ShouldBeAdditionAssertExtensions.cs
+++ b/src/Asserting/ShouldBeAdditionAssertExtensions.cs
@@ -55,7 +55,7 @@ public static void ShouldBeAddition(this IDiff actualChange, string expectedChan
public static void ShouldBeAddition(this IDiff actualChange, IRenderedFragment expectedChange, string? userMessage = null)
{
if (expectedChange is null) throw new ArgumentNullException(nameof(expectedChange));
- ShouldBeAddition(actualChange, expectedChange.GetNodes(), userMessage);
+ ShouldBeAddition(actualChange, expectedChange.Nodes, userMessage);
}
///
diff --git a/src/Asserting/ShouldBeRemovalAssertExtensions.cs b/src/Asserting/ShouldBeRemovalAssertExtensions.cs
index f24a79179..d75498b46 100644
--- a/src/Asserting/ShouldBeRemovalAssertExtensions.cs
+++ b/src/Asserting/ShouldBeRemovalAssertExtensions.cs
@@ -54,7 +54,7 @@ public static void ShouldBeRemoval(this IDiff actualChange, string expectedChang
public static void ShouldBeRemoval(this IDiff actualChange, IRenderedFragment expectedChange, string? userMessage = null)
{
if (expectedChange is null) throw new ArgumentNullException(nameof(expectedChange));
- ShouldBeRemoval(actualChange, expectedChange.GetNodes(), userMessage);
+ ShouldBeRemoval(actualChange, expectedChange.Nodes, userMessage);
}
///
diff --git a/src/Asserting/ShouldBeTextChangeAssertExtensions.cs b/src/Asserting/ShouldBeTextChangeAssertExtensions.cs
index 93c79711d..e91cb17ad 100644
--- a/src/Asserting/ShouldBeTextChangeAssertExtensions.cs
+++ b/src/Asserting/ShouldBeTextChangeAssertExtensions.cs
@@ -10,13 +10,28 @@
namespace Egil.RazorComponents.Testing.Asserting
{
+ ///
+ /// Verification helpers for text
+ ///
public static class ShouldBeTextChangeAssertExtensions
{
+ ///
+ /// Verifies that a list of diffs contains only a single change, and that change is a change to a text node.
+ ///
+ /// The list of diffs to verify against.
+ /// The expected text change.
+ /// A custom error message to show if the verification fails.
public static void ShouldHaveSingleTextChange(this IReadOnlyList diffs, string expectedChange, string? userMessage = null)
{
DiffAssertExtensions.ShouldHaveSingleChange(diffs).ShouldBeTextChange(expectedChange, userMessage);
}
+ ///
+ /// Verifies that a diff is a change to a text node.
+ ///
+ /// The diff to verify.
+ /// The expected text change.
+ /// A custom error message to show if the verification fails.
public static void ShouldBeTextChange(this IDiff actualChange, string expectedChange, string? userMessage = null)
{
if (actualChange is null) throw new ArgumentNullException(nameof(actualChange));
@@ -29,12 +44,24 @@ public static void ShouldBeTextChange(this IDiff actualChange, string expectedCh
ShouldBeTextChange(actualChange, expected, userMessage);
}
+ ///
+ /// Verifies that a diff is a change to a text node.
+ ///
+ /// The diff to verify.
+ /// The rendered fragment containing the expected text change.
+ /// A custom error message to show if the verification fails.
public static void ShouldBeTextChange(this IDiff actualChange, IRenderedFragment expectedChange, string? userMessage = null)
{
if (expectedChange is null) throw new ArgumentNullException(nameof(expectedChange));
- ShouldBeTextChange(actualChange, expectedChange.GetNodes(), userMessage);
+ ShouldBeTextChange(actualChange, expectedChange.Nodes, userMessage);
}
+ ///
+ /// Verifies that a diff is a change to a text node.
+ ///
+ /// The diff to verify.
+ /// The node list containing the expected text change.
+ /// A custom error message to show if the verification fails.
public static void ShouldBeTextChange(this IDiff actualChange, INodeList expectedChange, string? userMessage = null)
{
if (actualChange is null) throw new ArgumentNullException(nameof(actualChange));
diff --git a/src/Components/ComponentUnderTest.cs b/src/Components/ComponentUnderTest.cs
index 4fccffb5a..d0d4a4375 100644
--- a/src/Components/ComponentUnderTest.cs
+++ b/src/Components/ComponentUnderTest.cs
@@ -4,9 +4,13 @@
namespace Egil.RazorComponents.Testing
{
-
+ ///
+ /// Represents a component that can be added inside a ,
+ /// where a component under test can be defined as the child content.
+ ///
public class ComponentUnderTest : FragmentBase
{
+ ///
public override Task SetParametersAsync(ParameterView parameters)
{
var result = base.SetParametersAsync(parameters);
diff --git a/src/Components/ContainerComponent.cs b/src/Components/ContainerComponent.cs
index 2fd7940bc..6a9092469 100644
--- a/src/Components/ContainerComponent.cs
+++ b/src/Components/ContainerComponent.cs
@@ -103,7 +103,7 @@ public void Render(RenderFragment renderFragment)
// than regular components with child content is not rendered
// and available via GetCurrentRenderTreeFrames for the componentId
// of the component that had the CascadingValue as a child.
- // Thus we call GetComponents recursivly with the CascadingValue's
+ // Thus we call GetComponents recursively with the CascadingValue's
// componentId to see if the TComponent is inside it.
result.AddRange(GetComponents(frame.ComponentId));
}
diff --git a/src/Components/Fixture.cs b/src/Components/Fixture.cs
index d196ec371..3a052007e 100644
--- a/src/Components/Fixture.cs
+++ b/src/Components/Fixture.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
namespace Egil.RazorComponents.Testing
@@ -12,8 +13,11 @@ namespace Egil.RazorComponents.Testing
public class Fixture : FragmentBase
{
private Action _setup = NoopTestMethod;
+ private Func _setupAsync = NoopTestMethodAsync;
private Action _test = NoopTestMethod;
+ private Func _testAsync = NoopTestMethodAsync;
private IReadOnlyCollection _tests = Array.Empty();
+ private IReadOnlyCollection> _testsAsync = Array.Empty>();
///
/// A description or name for the test that will be displayed if the test fails.
@@ -21,11 +25,17 @@ public class Fixture : FragmentBase
[Parameter] public string? Description { get; set; }
///
- /// Gets or sets the setup action to perform before the action
- /// and actions are invoked.
+ /// Gets or sets the setup action to perform before the action,
+ /// action and and actions are invoked.
///
[Parameter] public Action Setup { get => _setup; set => _setup = value ?? NoopTestMethod; }
+ ///
+ /// Gets or sets the asynchronous setup action to perform before the action,
+ /// action and and actions are invoked.
+ ///
+ [Parameter] public Func SetupAsync { get => _setupAsync; set => _setupAsync = value ?? NoopTestMethodAsync; }
+
///
/// Gets or sets the first test action to invoke, after the action has
/// executed (if provided).
@@ -35,6 +45,15 @@ public class Fixture : FragmentBase
///
[Parameter] public Action Test { get => _test; set => _test = value ?? NoopTestMethod; }
+ ///
+ /// Gets or sets the first test action to invoke, after the action has
+ /// executed (if provided).
+ ///
+ /// Use this to assert against the and 's
+ /// defined in the .
+ ///
+ [Parameter] public Func TestAsync { get => _testAsync; set => _testAsync = value ?? NoopTestMethodAsync; }
+
///
/// Gets or sets the test actions to invoke, one at the time, in the order they are placed
/// into the collection, after the action and the action has
@@ -45,6 +64,14 @@ public class Fixture : FragmentBase
///
[Parameter] public IReadOnlyCollection Tests { get => _tests; set => _tests = value ?? Array.Empty(); }
- private static void NoopTestMethod() { }
+ ///
+ /// Gets or sets the test actions to invoke, one at the time, in the order they are placed
+ /// into the collection, after the action and the action has
+ /// executed (if provided).
+ ///
+ /// Use this to assert against the and 's
+ /// defined in the .
+ ///
+ [Parameter] public IReadOnlyCollection> TestsAsync { get => _testsAsync; set => _testsAsync = value ?? Array.Empty>(); }
}
}
diff --git a/src/Components/Fragment.cs b/src/Components/Fragment.cs
index cad3e0ad4..ea0ab79bb 100644
--- a/src/Components/Fragment.cs
+++ b/src/Components/Fragment.cs
@@ -2,8 +2,16 @@
namespace Egil.RazorComponents.Testing
{
+ ///
+ /// Represents a component that can be added inside a , whose content
+ /// can be accessed in Razor-based test.
+ ///
public class Fragment : FragmentBase
{
+ ///
+ /// Gets or sets the id of the fragment. The can be used to retrieve
+ /// the fragment from a .
+ ///
[Parameter] public string Id { get; set; } = string.Empty;
}
}
diff --git a/src/Components/FragmentBase.cs b/src/Components/FragmentBase.cs
index c5b83a09a..a57b91c10 100644
--- a/src/Components/FragmentBase.cs
+++ b/src/Components/FragmentBase.cs
@@ -4,12 +4,23 @@
namespace Egil.RazorComponents.Testing
{
+ ///
+ /// Represents a fragment that can be used in or .
+ ///
public abstract class FragmentBase : IComponent
{
+ internal static void NoopTestMethod() { }
+ internal static Task NoopTestMethodAsync() => Task.CompletedTask;
+
+ ///
+ /// Gets or sets the child content of the fragment.
+ ///
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
+ ///
public void Attach(RenderHandle renderHandle) { }
+ ///
public virtual Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
diff --git a/src/Components/SnapshotTest.cs b/src/Components/SnapshotTest.cs
index f5cffdb5f..224139fff 100644
--- a/src/Components/SnapshotTest.cs
+++ b/src/Components/SnapshotTest.cs
@@ -16,6 +16,7 @@ namespace Egil.RazorComponents.Testing
public class SnapshotTest : FragmentBase
{
private Action _setup = NoopTestMethod;
+ private Func _setupAsync = NoopTestMethodAsync;
///
/// A description or name for the test that will be displayed if the test fails.
@@ -23,12 +24,16 @@ public class SnapshotTest : FragmentBase
[Parameter] public string? Description { get; set; }
///
- /// A method to be called component and component
- /// is rendered. Use to e.g. setup services that the test input needs to render.
+ /// Gets or sets the setup action to perform before the and
+ /// is rendered and compared.
///
[Parameter] public Action Setup { get => _setup; set => _setup = value ?? NoopTestMethod; }
- private static void NoopTestMethod() { }
+ ///
+ /// Gets or sets the setup action to perform before the and
+ /// is rendered and compared.
+ ///
+ [Parameter] public Func SetupAsync { get => _setupAsync; set => _setupAsync = value ?? NoopTestMethodAsync; }
}
///
diff --git a/src/Components/TestComponentBase.cs b/src/Components/TestComponentBase.cs
index 2969e0031..66d57cbd7 100644
--- a/src/Components/TestComponentBase.cs
+++ b/src/Components/TestComponentBase.cs
@@ -2,6 +2,8 @@
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
+using System.Threading.Tasks;
+using AngleSharp.Dom;
using Egil.RazorComponents.Testing.Asserting;
using Egil.RazorComponents.Testing.Diffing;
using Microsoft.AspNetCore.Components;
@@ -32,10 +34,6 @@ public override TestServiceProvider Services
public override TestRenderer Renderer
=> _testContextAdapter.HasActiveContext ? _testContextAdapter.Renderer : base.Renderer;
- ///
- public override TestHtmlParser HtmlParser
- => _testContextAdapter.HasActiveContext ? _testContextAdapter.HtmlParser : base.HtmlParser;
-
///
public TestComponentBase()
{
@@ -52,15 +50,15 @@ public TestComponentBase()
/// in the file and runs their associated tests.
///
[Fact(DisplayName = "Razor test runner")]
- public void RazorTest()
+ public async Task RazorTest()
{
var container = new ContainerComponent(_renderer.Value);
container.Render(BuildRenderTree);
- ExecuteFixtureTests(container);
- ExecuteSnapshotTests(container);
+ await ExecuteFixtureTests(container).ConfigureAwait(false);
+ await ExecuteSnapshotTests(container).ConfigureAwait(false);
}
-
+
///
public IRenderedFragment GetComponentUnderTest()
=> _testContextAdapter.GetComponentUnderTest();
@@ -77,6 +75,12 @@ public IRenderedFragment GetFragment(string? id = null)
public IRenderedComponent GetFragment(string? id = null) where TComponent : class, IComponent
=> _testContextAdapter.GetFragment(id);
+ ///
+ public override INodeList CreateNodes(string markup)
+ => _testContextAdapter.HasActiveContext
+ ? _testContextAdapter.CreateNodes(markup)
+ : base.CreateNodes(markup);
+
///
public override IRenderedComponent RenderComponent(params ComponentParameter[] parameters)
=> _testContextAdapter.HasActiveContext
@@ -92,7 +96,7 @@ public override void WaitForNextRender(Action renderTrigger, TimeSpan? timeout =
base.WaitForNextRender(renderTrigger, timeout);
}
- private void ExecuteFixtureTests(ContainerComponent container)
+ private async Task ExecuteFixtureTests(ContainerComponent container)
{
foreach (var (_, fixture) in container.GetComponents())
{
@@ -102,13 +106,20 @@ private void ExecuteFixtureTests(ContainerComponent container)
_testContextAdapter.ActivateRazorTestContext(testData);
InvokeFixtureAction(fixture, fixture.Setup);
+ await InvokeFixtureAction(fixture, fixture.SetupAsync).ConfigureAwait(false);
InvokeFixtureAction(fixture, fixture.Test);
+ await InvokeFixtureAction(fixture, fixture.TestAsync).ConfigureAwait(false);
foreach (var test in fixture.Tests)
{
InvokeFixtureAction(fixture, test);
}
+ foreach (var test in fixture.TestsAsync)
+ {
+ await InvokeFixtureAction(fixture, test).ConfigureAwait(false);
+ }
+
_testContextAdapter.DisposeActiveTestContext();
}
}
@@ -125,7 +136,19 @@ private static void InvokeFixtureAction(Fixture fixture, Action action)
}
}
- private void ExecuteSnapshotTests(ContainerComponent container)
+ private static async Task InvokeFixtureAction(Fixture fixture, Func action)
+ {
+ try
+ {
+ await action().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ throw new FixtureFailedException(fixture.Description ?? $"{action.Method.Name} failed:", ex);
+ }
+ }
+
+ private async Task ExecuteSnapshotTests(ContainerComponent container)
{
foreach (var (_, snapshot) in container.GetComponents())
{
@@ -134,6 +157,7 @@ private void ExecuteSnapshotTests(ContainerComponent container)
var context = _testContextAdapter.ActivateSnapshotTestContext(testData);
snapshot.Setup();
+ await snapshot.SetupAsync().ConfigureAwait(false);
var actual = context.RenderTestInput();
var expected = context.RenderExpectedOutput();
actual.MarkupMatches(expected, snapshot.Description);
diff --git a/src/Components/TestContextAdapter.cs b/src/Components/TestContextAdapter.cs
index 3a06f1142..c172f760f 100644
--- a/src/Components/TestContextAdapter.cs
+++ b/src/Components/TestContextAdapter.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using AngleSharp.Dom;
using Egil.RazorComponents.Testing.Diffing;
using Microsoft.AspNetCore.Components;
@@ -14,8 +15,6 @@ internal sealed class TestContextAdapter : IDisposable
public TestRenderer Renderer => _testContext?.Renderer ?? throw new InvalidOperationException("No active test context in the adapter");
- public TestHtmlParser HtmlParser => _testContext?.HtmlParser ?? throw new InvalidOperationException("No active test context in the adapter");
-
public bool HasActiveContext => !(_testContext is null);
public SnapshotTestContext ActivateSnapshotTestContext(IReadOnlyList testData)
@@ -41,6 +40,7 @@ public RazorTestContext ActivateRazorTestContext(IReadOnlyList tes
public void Dispose()
{
_testContext?.Dispose();
+ _razorTestContext?.Dispose();
_testContext = null;
_razorTestContext = null;
}
@@ -67,5 +67,8 @@ public void WaitForNextRender(Action renderTrigger, TimeSpan? timeout = null)
public IRenderedComponent RenderComponent(params ComponentParameter[] parameters) where TComponent : class, IComponent
=> _testContext?.RenderComponent(parameters) ?? throw new InvalidOperationException("No active test context in the adapter");
+
+ public INodeList CreateNodes(string markup)
+ => _testContext?.CreateNodes(markup) ?? throw new InvalidOperationException("No active test context in the adapter");
}
}
diff --git a/src/Diffing/BlazorDiffingHelpers.cs b/src/Diffing/BlazorDiffingHelpers.cs
index 750f1be78..24bafa366 100644
--- a/src/Diffing/BlazorDiffingHelpers.cs
+++ b/src/Diffing/BlazorDiffingHelpers.cs
@@ -3,8 +3,14 @@
namespace Egil.RazorComponents.Testing.Diffing
{
+ ///
+ /// Blazor Dffing Helpers
+ ///
public static class BlazorDiffingHelpers
{
+ ///
+ /// Represents a diffing filter that removes all special Blazor attributes added by the /.
+ ///
public static FilterDecision BlazorEventHandlerIdAttrFilter(in AttributeComparisonSource attrSource, FilterDecision currentDecision)
{
if (currentDecision == FilterDecision.Exclude) return currentDecision;
diff --git a/src/Diffing/DiffMarkupFormatter.cs b/src/Diffing/DiffMarkupFormatter.cs
index e2434f7f1..b1f5c303b 100644
--- a/src/Diffing/DiffMarkupFormatter.cs
+++ b/src/Diffing/DiffMarkupFormatter.cs
@@ -5,6 +5,9 @@
namespace Egil.RazorComponents.Testing.Diffing
{
+ ///
+ /// A markup formatter, that skips any special Blazor attributes added by the /.
+ ///
public class DiffMarkupFormatter : IMarkupFormatter
{
private readonly IMarkupFormatter _formatter = new PrettyMarkupFormatter()
@@ -13,14 +16,22 @@ public class DiffMarkupFormatter : IMarkupFormatter
Indentation = " "
};
+ ///
public string Attribute(IAttr attribute)
=> Htmlizer.IsBlazorAttribute(attribute?.Name ?? string.Empty)
? string.Empty
: _formatter.Attribute(attribute);
+ ///
public string CloseTag(IElement element, bool selfClosing) => _formatter.CloseTag(element, selfClosing);
+
+ ///
public string Comment(IComment comment) => _formatter.Comment(comment);
+
+ ///
public string Doctype(IDocumentType doctype) => _formatter.Doctype(doctype);
+
+ ///
public string OpenTag(IElement element, bool selfClosing)
{
if(element is null) throw new ArgumentNullException(nameof(element));
@@ -39,7 +50,10 @@ public string OpenTag(IElement element, bool selfClosing)
return result;
}
+ ///
public string Processing(IProcessingInstruction processing) => _formatter.Processing(processing);
+
+ ///
public string Text(ICharacterData text) => _formatter.Text(text);
}
}
diff --git a/src/ElementNotFoundException.cs b/src/ElementNotFoundException.cs
index 6be892897..1a3caf9fe 100644
--- a/src/ElementNotFoundException.cs
+++ b/src/ElementNotFoundException.cs
@@ -23,5 +23,17 @@ public ElementNotFoundException(string cssSelector) : base($"No elements were fo
{
CssSelector = cssSelector;
}
+
+ ///
+ public ElementNotFoundException()
+ {
+ CssSelector = string.Empty;
+ }
+
+ ///
+ public ElementNotFoundException(string message, Exception innerException) : base(message, innerException)
+ {
+ CssSelector = string.Empty;
+ }
}
}
diff --git a/src/EventDispatchExtensions/GeneralEventDispatchExtensions.cs b/src/EventDispatchExtensions/GeneralEventDispatchExtensions.cs
index 2e6cef37d..32e5ddd40 100644
--- a/src/EventDispatchExtensions/GeneralEventDispatchExtensions.cs
+++ b/src/EventDispatchExtensions/GeneralEventDispatchExtensions.cs
@@ -10,8 +10,6 @@
namespace Egil.RazorComponents.Testing.EventDispatchExtensions
{
- // TODO: add support for all event types listed here: https://github.com/aspnet/AspNetCore/blob/master/src/Components/Web/src/Web/EventHandlers.cs
-
///
/// General event dispatch helper extension methods.
///
diff --git a/src/EventDispatchExtensions/TouchEventDispatchExtensions.cs b/src/EventDispatchExtensions/TouchEventDispatchExtensions.cs
index cbafe58c9..65bbd1f9a 100644
--- a/src/EventDispatchExtensions/TouchEventDispatchExtensions.cs
+++ b/src/EventDispatchExtensions/TouchEventDispatchExtensions.cs
@@ -42,7 +42,8 @@ public static void TouchCancel(this IElement element, long detail = default, Tou
///
/// The element to raise the event on.
/// The event arguments to pass to the event handler.
- public static void TouchCancel(this IElement element, TouchEventArgs eventArgs) => _ = TouchCancelAsync(element, eventArgs);
+ public static void TouchCancel(this IElement element, TouchEventArgs eventArgs)
+ => _ = TouchCancelAsync(element, eventArgs);
///
/// Raises the @ontouchcancel event on , passing the provided
@@ -51,7 +52,8 @@ public static void TouchCancel(this IElement element, long detail = default, Tou
///
///
/// A task that completes when the event handler is done.
- public static Task TouchCancelAsync(this IElement element, TouchEventArgs eventArgs) => element.TriggerEventAsync("ontouchcancel", eventArgs);
+ public static Task TouchCancelAsync(this IElement element, TouchEventArgs eventArgs)
+ => element.TriggerEventAsync("ontouchcancel", eventArgs);
///
/// Raises the @ontouchend event on , passing the provided
@@ -82,7 +84,8 @@ public static void TouchEnd(this IElement element, long detail = default, TouchP
///
/// The element to raise the event on.
/// The event arguments to pass to the event handler.
- public static void TouchEnd(this IElement element, TouchEventArgs eventArgs) => _ = TouchEndAsync(element, eventArgs);
+ public static void TouchEnd(this IElement element, TouchEventArgs eventArgs)
+ => _ = TouchEndAsync(element, eventArgs);
///
/// Raises the @ontouchend event on , passing the provided
@@ -91,7 +94,8 @@ public static void TouchEnd(this IElement element, long detail = default, TouchP
///
///
/// A task that completes when the event handler is done.
- public static Task TouchEndAsync(this IElement element, TouchEventArgs eventArgs) => element.TriggerEventAsync("ontouchend", eventArgs);
+ public static Task TouchEndAsync(this IElement element, TouchEventArgs eventArgs)
+ => element.TriggerEventAsync("ontouchend", eventArgs);
///
/// Raises the @ontouchmove event on , passing the provided
@@ -122,7 +126,8 @@ public static void TouchMove(this IElement element, long detail = default, Touch
///
/// The element to raise the event on.
/// The event arguments to pass to the event handler.
- public static void TouchMove(this IElement element, TouchEventArgs eventArgs) => _ = TouchMoveAsync(element, eventArgs);
+ public static void TouchMove(this IElement element, TouchEventArgs eventArgs)
+ => _ = TouchMoveAsync(element, eventArgs);
///
/// Raises the @ontouchmove event on , passing the provided
@@ -131,7 +136,8 @@ public static void TouchMove(this IElement element, long detail = default, Touch
///
///
/// A task that completes when the event handler is done.
- public static Task TouchMoveAsync(this IElement element, TouchEventArgs eventArgs) => element.TriggerEventAsync("ontouchmove", eventArgs);
+ public static Task TouchMoveAsync(this IElement element, TouchEventArgs eventArgs)
+ => element.TriggerEventAsync("ontouchmove", eventArgs);
///
/// Raises the @ontouchstart event on , passing the provided
@@ -162,7 +168,8 @@ public static void TouchStart(this IElement element, long detail = default, Touc
///
/// The element to raise the event on.
/// The event arguments to pass to the event handler.
- public static void TouchStart(this IElement element, TouchEventArgs eventArgs) => _ = TouchStartAsync(element, eventArgs);
+ public static void TouchStart(this IElement element, TouchEventArgs eventArgs)
+ => _ = TouchStartAsync(element, eventArgs);
///
/// Raises the @ontouchstart event on , passing the provided
@@ -171,7 +178,8 @@ public static void TouchStart(this IElement element, long detail = default, Touc
///
///
/// A task that completes when the event handler is done.
- public static Task TouchStartAsync(this IElement element, TouchEventArgs eventArgs) => element.TriggerEventAsync("ontouchstart", eventArgs);
+ public static Task TouchStartAsync(this IElement element, TouchEventArgs eventArgs)
+ => element.TriggerEventAsync("ontouchstart", eventArgs);
///
/// Raises the @ontouchenter event on , passing the provided
@@ -202,7 +210,8 @@ public static void TouchEnter(this IElement element, long detail = default, Touc
///
/// The element to raise the event on.
/// The event arguments to pass to the event handler.
- public static void TouchEnter(this IElement element, TouchEventArgs eventArgs) => _ = TouchEnterAsync(element, eventArgs);
+ public static void TouchEnter(this IElement element, TouchEventArgs eventArgs)
+ => _ = TouchEnterAsync(element, eventArgs);
///
/// Raises the @ontouchenter event on , passing the provided
@@ -211,7 +220,8 @@ public static void TouchEnter(this IElement element, long detail = default, Touc
///
///
/// A task that completes when the event handler is done.
- public static Task TouchEnterAsync(this IElement element, TouchEventArgs eventArgs) => element.TriggerEventAsync("ontouchenter", eventArgs);
+ public static Task TouchEnterAsync(this IElement element, TouchEventArgs eventArgs)
+ => element.TriggerEventAsync("ontouchenter", eventArgs);
///
/// Raises the @ontouchleave event on , passing the provided
@@ -242,7 +252,8 @@ public static void TouchLeave(this IElement element, long detail = default, Touc
///
/// The element to raise the event on.
/// The event arguments to pass to the event handler.
- public static void TouchLeave(this IElement element, TouchEventArgs eventArgs) => _ = TouchLeaveAsync(element, eventArgs);
+ public static void TouchLeave(this IElement element, TouchEventArgs eventArgs)
+ => _ = TouchLeaveAsync(element, eventArgs);
///
/// Raises the @ontouchleave event on , passing the provided
@@ -251,6 +262,7 @@ public static void TouchLeave(this IElement element, long detail = default, Touc
///
///
/// A task that completes when the event handler is done.
- public static Task TouchLeaveAsync(this IElement element, TouchEventArgs eventArgs) => element.TriggerEventAsync("ontouchleave", eventArgs);
+ public static Task TouchLeaveAsync(this IElement element, TouchEventArgs eventArgs)
+ => element.TriggerEventAsync("ontouchleave", eventArgs);
}
}
diff --git a/src/Extensions/AngleSharpExtensions.cs b/src/Extensions/AngleSharpExtensions.cs
index 3eda41cf6..58560fc3d 100644
--- a/src/Extensions/AngleSharpExtensions.cs
+++ b/src/Extensions/AngleSharpExtensions.cs
@@ -194,7 +194,6 @@ public static IEnumerable AsEnumerable(this INode node)
yield return node;
}
-
///
/// Gets the stored in the s
/// owning context, if one is available.
diff --git a/src/ITestContext.cs b/src/ITestContext.cs
index cc76e7135..e1bcd76a6 100644
--- a/src/ITestContext.cs
+++ b/src/ITestContext.cs
@@ -1,4 +1,5 @@
using System;
+using AngleSharp.Dom;
using Egil.RazorComponents.Testing.Diffing;
using Microsoft.AspNetCore.Components;
@@ -19,11 +20,14 @@ public interface ITestContext : IDisposable
/// Gets the renderer used to render the components and fragments in this test context.
///
TestRenderer Renderer { get; }
-
+
///
- /// Gets the HTML parser used to parse HTML produced by components and fragments in this test context.
+ /// Parses a markup HTML string using the AngleSharps HTML5 parser
+ /// and returns a list of nodes.
///
- TestHtmlParser HtmlParser { get; }
+ /// The markup to parse.
+ /// The .
+ INodeList CreateNodes(string markup);
///
/// Instantiates and performs a first render of a component of type .
diff --git a/src/Asserting/JsInvokeCountExpectedException.cs b/src/Mocking/JSInterop/JsInvokeCountExpectedException.cs
similarity index 53%
rename from src/Asserting/JsInvokeCountExpectedException.cs
rename to src/Mocking/JSInterop/JsInvokeCountExpectedException.cs
index 636bf70ad..058902d5a 100644
--- a/src/Asserting/JsInvokeCountExpectedException.cs
+++ b/src/Mocking/JSInterop/JsInvokeCountExpectedException.cs
@@ -1,15 +1,40 @@
using System;
using System.Diagnostics.CodeAnalysis;
+using Egil.RazorComponents.Testing.Mocking.JSInterop;
using Xunit.Sdk;
namespace Xunit.Sdk
{
+ ///
+ /// Represents a number of unexpected invocation to a .
+ ///
[SuppressMessage("Design", "CA1032:Implement standard exception constructors", Justification = "")]
public class JsInvokeCountExpectedException : AssertActualExpectedException
{
+ ///
+ /// Gets the expected invocation count.
+ ///
+ public int ExpectedInvocationCount { get; }
+
+ ///
+ /// Gets the actual invocation count.
+ ///
+ public int ActualInvocationCount { get; }
+
+ ///
+ /// Gets the identifier.
+ ///
+ public string Identifier { get; }
+
+ ///
+ /// Creates an instance of the .
+ ///
public JsInvokeCountExpectedException(string identifier, int expectedCount, int actualCount, string assertMethod, string? userMessage = null)
: base(expectedCount, actualCount, CreateMessage(assertMethod, identifier, userMessage), "Expected number of calls", "Actual number of calls")
{
+ ExpectedInvocationCount = expectedCount;
+ ActualInvocationCount = actualCount;
+ Identifier = identifier;
}
private static string CreateMessage(string assertMethod, string identifier, string? userMessage = null)
diff --git a/src/Asserting/JsRuntimeAssertExtensions.cs b/src/Mocking/JSInterop/JsRuntimeAssertExtensions.cs
similarity index 53%
rename from src/Asserting/JsRuntimeAssertExtensions.cs
rename to src/Mocking/JSInterop/JsRuntimeAssertExtensions.cs
index 4fffda259..e78882cfa 100644
--- a/src/Asserting/JsRuntimeAssertExtensions.cs
+++ b/src/Mocking/JSInterop/JsRuntimeAssertExtensions.cs
@@ -6,13 +6,19 @@
using Xunit;
using Xunit.Sdk;
-namespace Egil.RazorComponents.Testing.Asserting
+namespace Egil.RazorComponents.Testing.Mocking.JSInterop
{
///
/// Assert extensions for JsRuntimeMock
///
public static class JsRuntimeAssertExtensions
{
+ ///
+ /// Verifies that the was never invoked on the .
+ ///
+ /// Handler to verify against.
+ /// Identifier of invocation that should not have happened.
+ /// A custom user message to display if the assertion fails.
public static void VerifyNotInvoke(this MockJsRuntimeInvokeHandler handler, string identifier, string? userMessage = null)
{
if (handler is null) throw new ArgumentNullException(nameof(handler));
@@ -22,9 +28,25 @@ public static void VerifyNotInvoke(this MockJsRuntimeInvokeHandler handler, stri
}
}
- public static JsRuntimeInvocation VerifyInvoke(this MockJsRuntimeInvokeHandler handler, string identifier) => VerifyInvoke(handler, identifier, 1)[0];
+ ///
+ /// Verifies that the has been invoked one time.
+ ///
+ /// Handler to verify against.
+ /// Identifier of invocation that should have been invoked.
+ /// A custom user message to display if the assertion fails.
+ /// The .
+ public static JsRuntimeInvocation VerifyInvoke(this MockJsRuntimeInvokeHandler handler, string identifier, string? userMessage = null)
+ => VerifyInvoke(handler, identifier, 1, userMessage)[0];
- public static IReadOnlyList VerifyInvoke(this MockJsRuntimeInvokeHandler handler, string identifier, int calledTimes = 1, string? userMessage = null)
+ ///
+ /// Verifies that the has been invoked times.
+ ///
+ /// Handler to verify against.
+ /// Identifier of invocation that should have been invoked.
+ /// The number of times the invocation is expected to have been called.
+ /// A custom user message to display if the assertion fails.
+ /// The .
+ public static IReadOnlyList VerifyInvoke(this MockJsRuntimeInvokeHandler handler, string identifier, int calledTimes, string? userMessage = null)
{
if (handler is null) throw new ArgumentNullException(nameof(handler));
if (calledTimes < 1)
@@ -40,17 +62,20 @@ public static IReadOnlyList VerifyInvoke(this MockJsRuntime
return invocations;
}
+ ///
+ /// Verifies that an argument
+ /// passed to an JsRuntime invocation is an
+ /// to the .
+ ///
+ /// object to verify.
+ /// expected targeted element.
public static void ShouldBeElementReferenceTo(this object actualArgument, IElement expectedTargetElement)
{
if (actualArgument is null) throw new ArgumentNullException(nameof(actualArgument));
if (expectedTargetElement is null) throw new ArgumentNullException(nameof(expectedTargetElement));
- if (!(actualArgument is ElementReference elmRef))
- {
- throw new IsTypeException(typeof(ElementReference).FullName, actualArgument.GetType().FullName);
- }
-
- var elmRefAttrName = Htmlizer.ToBlazorAttribute("elementreference");
+ var elmRef = Assert.IsType(actualArgument);
+ var elmRefAttrName = Htmlizer.ELEMENT_REFERENCE_ATTR_NAME;
var expectedId = expectedTargetElement.GetAttribute(elmRefAttrName);
if (string.IsNullOrEmpty(expectedId) || !elmRef.Id.Equals(expectedId, StringComparison.Ordinal))
{
diff --git a/src/Mocking/JSInterop/JsRuntimeInvocation.cs b/src/Mocking/JSInterop/JsRuntimeInvocation.cs
index a12d0095a..117953aa1 100644
--- a/src/Mocking/JSInterop/JsRuntimeInvocation.cs
+++ b/src/Mocking/JSInterop/JsRuntimeInvocation.cs
@@ -1,12 +1,14 @@
using System.Collections.Generic;
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Threading;
-namespace Egil.RazorComponents.Testing
+namespace Egil.RazorComponents.Testing.Mocking.JSInterop
{
///
/// Represents an invocation of JavaScript via the JsRuntime Mock
///
+ [SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "")]
public readonly struct JsRuntimeInvocation : IEquatable
{
///
@@ -24,29 +26,57 @@ namespace Egil.RazorComponents.Testing
///
public IReadOnlyList
- public sealed class TestServiceProvider : IServiceProvider, IDisposable
+ [SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix")]
+ public sealed class TestServiceProvider : IServiceProvider, IServiceCollection, IDisposable
{
- private readonly ServiceCollection _serviceCollection = new ServiceCollection();
+ private readonly IServiceCollection _serviceCollection;
private ServiceProvider? _serviceProvider;
+ ///
+ /// Gets a reusable default test service provider.
+ ///
+ public static readonly IServiceProvider Default = new TestServiceProvider(new ServiceCollection(), true);
+
///
/// Gets whether this has been initialized, and
/// no longer will accept calls to the AddService's methods.
///
public bool IsProviderInitialized => _serviceProvider is { };
- ///
- /// Adds a singleton service of the type specified in TService with an implementation
- /// type specified in TImplementation using the factory specified in implementationFactory
- /// to this .
- ///
- /// The type of the service to add.
- /// The type of the implementation to use.
- /// The factory that creates the service.
- /// A reference to this instance after the operation has completed.
- public TestServiceProvider AddService(Func implementationFactory)
- where TService : class
- where TImplementation : class, TService
- {
- CheckInitializedAndThrow();
- _serviceCollection.AddSingleton(implementationFactory);
- return this;
- }
+ ///
+ public int Count => _serviceCollection.Count;
- ///
- /// Adds a singleton service of the type specified in with a factory specified
- /// in to this .
- ///
- /// The type of the service to add.
- /// The factory that creates the service.
- /// A reference to this instance after the operation has completed.
- public TestServiceProvider AddService(Func implementationFactory) where TService : class
- {
- CheckInitializedAndThrow();
- _serviceCollection.AddSingleton(implementationFactory);
- return this;
- }
+ ///
+ public bool IsReadOnly => IsProviderInitialized || _serviceCollection.IsReadOnly;
- ///
- /// Adds a singleton service of the type specified in to this .
- ///
- /// The type of the service to add.
- /// A reference to this instance after the operation has completed.
- public TestServiceProvider AddService() where TService : class
+ ///
+ public ServiceDescriptor this[int index]
{
- CheckInitializedAndThrow();
- _serviceCollection.AddSingleton();
- return this;
+ get => _serviceCollection[index];
+ set
+ {
+ CheckInitializedAndThrow();
+ _serviceCollection[index] = value;
+ }
}
///
- /// Adds a singleton service of the type specified in to this .
+ /// Creates an instance of the and sets its service collection to the
+ /// provided , if any.
///
- /// The type of the service to register and the implementation to use.
- /// A reference to this instance after the operation has completed.
- public TestServiceProvider AddService(Type serviceType)
+ ///
+ public TestServiceProvider(IServiceCollection? initialServiceCollection = null) : this(initialServiceCollection ?? new ServiceCollection(), false)
{
- CheckInitializedAndThrow();
- _serviceCollection.AddSingleton(serviceType);
- return this;
}
- ///
- /// Adds a singleton service of the type specified in with an implementation
- /// type specified in to this .
- ///
- /// The type of the service to add.
- /// The type of the implementation to use.
- /// A reference to this instance after the operation has completed.
- public TestServiceProvider AddService()
- where TService : class
- where TImplementation : class, TService
+ private TestServiceProvider(IServiceCollection initialServiceCollection, bool initializeProvider)
{
- CheckInitializedAndThrow();
- _serviceCollection.AddSingleton();
- return this;
+ _serviceCollection = initialServiceCollection;
+ if (initializeProvider) _serviceProvider = _serviceCollection.BuildServiceProvider();
}
///
- /// Adds a singleton service of the type specified in with a factory
- /// specified in to this .
+ /// Get service of type T from the test provider.
///
- /// The type of the service to register.
- /// The factory that creates the service.
- ///
- public TestServiceProvider AddService(Type serviceType, Func implementationFactory)
+ /// The type of service object to get.
+ /// A service object of type T or null if there is no such service.
+ public TService GetService() => (TService)GetService(typeof(TService));
+
+ ///
+ public object GetService(Type serviceType)
{
- CheckInitializedAndThrow();
- _serviceCollection.AddSingleton(serviceType, implementationFactory);
- return this;
+ if (_serviceProvider is null)
+ _serviceProvider = _serviceCollection.BuildServiceProvider();
+
+ return _serviceProvider.GetService(serviceType);
}
- ///
- /// Adds a singleton service of the type specified in with an implementation
- /// of the type specified in to this .
- ///
- /// The type of the service to register.
- /// The implementation type of the service.
- /// A reference to this instance after the operation has completed.
- public TestServiceProvider AddService(Type serviceType, Type implementationType)
+ ///
+ public IEnumerator GetEnumerator() => _serviceCollection.GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ public void Dispose()
{
- CheckInitializedAndThrow();
- _serviceCollection.AddSingleton(serviceType, implementationType);
- return this;
+ _serviceProvider?.Dispose();
}
- ///
- /// Adds a singleton service of the type specified in with an instance specified in
- /// to this .
- ///
- /// The instance of the service.
- /// A reference to this instance after the operation has completed.
- public TestServiceProvider AddService(TService implementationInstance) where TService : class
+ ///
+ public int IndexOf(ServiceDescriptor item) => _serviceCollection.IndexOf(item);
+ ///
+ public void Insert(int index, ServiceDescriptor item)
{
CheckInitializedAndThrow();
- _serviceCollection.AddSingleton(implementationInstance);
- return this;
+ _serviceCollection.Insert(index, item);
}
-
- ///
- /// Adds a singleton service of the type specified in with an instance specified in
- /// to this .
- ///
- /// The type of the service to register.
- /// The instance of the service.
- /// A reference to this instance after the operation has completed.
- public TestServiceProvider AddService(Type serviceType, object implementationInstance)
+ ///
+ public void RemoveAt(int index)
{
CheckInitializedAndThrow();
- _serviceCollection.AddSingleton(serviceType, implementationInstance);
- return this;
+ _serviceCollection.RemoveAt(index);
}
- ///
- /// Get service of type T from the test provider.
- ///
- /// The type of service object to get.
- /// A service object of type T or null if there is no such service.
- public TService GetService()
+ ///
+ public void Add(ServiceDescriptor item)
{
- if (_serviceProvider is null)
- _serviceProvider = _serviceCollection.BuildServiceProvider();
- return _serviceProvider.GetService();
+ CheckInitializedAndThrow();
+ _serviceCollection.Add(item);
}
-
///
- public object GetService(Type serviceType)
+ public void Clear()
{
- if (_serviceProvider is null)
- _serviceProvider = _serviceCollection.BuildServiceProvider();
-
- return _serviceProvider.GetService(serviceType);
+ CheckInitializedAndThrow();
+ _serviceCollection.Clear();
}
///
- public void Dispose()
+ public bool Contains(ServiceDescriptor item) => _serviceCollection.Contains(item);
+ ///
+ public void CopyTo(ServiceDescriptor[] array, int arrayIndex) => _serviceCollection.CopyTo(array, arrayIndex);
+ ///
+ public bool Remove(ServiceDescriptor item)
{
- _serviceProvider?.Dispose();
+ CheckInitializedAndThrow();
+ return _serviceCollection.Remove(item);
}
private void CheckInitializedAndThrow()
{
if (IsProviderInitialized)
- throw new InvalidOperationException("New services cannot be added to provider after it has been initialized.");
+ throw new InvalidOperationException("Services cannot be added to provider after it has been initialized.");
}
}
-}
+}
\ No newline at end of file
diff --git a/template/Razor.Components.Testing.Library.Template.csproj b/template/Egil.Razor.Components.Testing.Library.Template.csproj
similarity index 91%
rename from template/Razor.Components.Testing.Library.Template.csproj
rename to template/Egil.Razor.Components.Testing.Library.Template.csproj
index 4c6922876..64e582ac4 100644
--- a/template/Razor.Components.Testing.Library.Template.csproj
+++ b/template/Egil.Razor.Components.Testing.Library.Template.csproj
@@ -26,6 +26,10 @@ This library's goal is to make it easy to write comprehensive, stable unit tests
+
+
+
+
\ No newline at end of file
diff --git a/template/template/Component1Test.cs b/template/template/Component1Test.cs
index 8f72cbeaf..98d7d5f69 100644
--- a/template/template/Component1Test.cs
+++ b/template/template/Component1Test.cs
@@ -3,6 +3,7 @@
using Egil.RazorComponents.Testing;
using Egil.RazorComponents.Testing.Diffing;
using Egil.RazorComponents.Testing.Asserting;
+using Egil.RazorComponents.Testing.Mocking.JSInterop;
using Egil.RazorComponents.Testing.EventDispatchExtensions;
namespace Company.RazorTests1
diff --git a/template/template/_Imports.razor b/template/template/_Imports.razor
index 29871f1da..d248d537f 100644
--- a/template/template/_Imports.razor
+++ b/template/template/_Imports.razor
@@ -1,6 +1,10 @@
@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.Extensions.DependencyInjection
+
@using Egil.RazorComponents.Testing
@using Egil.RazorComponents.Testing.Diffing
@using Egil.RazorComponents.Testing.Asserting
@using Egil.RazorComponents.Testing.EventDispatchExtensions
+@using Egil.RazorComponents.Testing.Mocking.JSInterop
+
@using Xunit
\ No newline at end of file
diff --git a/tests/Assembly.cs b/tests/Assembly.cs
new file mode 100644
index 000000000..c2a9bc9c5
--- /dev/null
+++ b/tests/Assembly.cs
@@ -0,0 +1 @@
+[assembly: System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
\ No newline at end of file
diff --git a/tests/Asserting/CompareToDiffingExtensionsTest.cs b/tests/Asserting/CompareToDiffingExtensionsTest.cs
new file mode 100644
index 000000000..6f2c60448
--- /dev/null
+++ b/tests/Asserting/CompareToDiffingExtensionsTest.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using AngleSharp.Dom;
+using Egil.RazorComponents.Testing.SampleComponents;
+using Egil.RazorComponents.Testing.TestUtililities;
+using Shouldly;
+using Xunit;
+
+namespace Egil.RazorComponents.Testing.Asserting
+{
+ public class CompareToDiffingExtensionsTest : ComponentTestFixture
+ {
+ ///
+ /// Returns an array of arrays containing:
+ /// (MethodInfo methodInfo, string argName, object[] methodArgs)
+ ///
+ ///
+ public static IEnumerable GetCompareToMethods()
+ {
+ var methods = typeof(CompareToExtensions)
+ .GetMethods()
+ .Where(x => x.Name.Equals(nameof(CompareToExtensions.CompareTo), StringComparison.Ordinal))
+ .ToList();
+
+ foreach (var method in methods)
+ {
+ var p1Info = method.GetParameters()[0];
+ var p2Info = method.GetParameters()[1];
+ object p1 = p1Info.ParameterType.ToMockInstance();
+ object p2 = p2Info.ParameterType.ToMockInstance();
+
+ yield return new object[] { method, p1Info.Name!, new object[] { null!, p2! } };
+ yield return new object[] { method, p2Info.Name!, new object[] { p1!, null! } };
+ }
+ }
+
+ [Theory(DisplayName = "CompareTo null values throws")]
+ [MemberData(nameof(GetCompareToMethods))]
+ public void Test001(MethodInfo methodInfo, string argName, object[] args)
+ {
+ Should.Throw(() => methodInfo.Invoke(null, args))
+ .InnerException
+ .ShouldBeOfType()
+ .ParamName.ShouldBe(argName);
+ }
+
+ [Fact(DisplayName = "CompareTo with rendered fragment and string")]
+ public void Test002()
+ {
+ var rf1 = RenderComponent((nameof(Simple1.Header), "FOO"));
+ var rf2 = RenderComponent((nameof(Simple1.Header), "BAR"));
+
+ rf1.CompareTo(rf2.Markup).Count.ShouldBe(1);
+ }
+
+ [Fact(DisplayName = "CompareTo with rendered fragment and rendered fragment")]
+ public void Test003()
+ {
+ var rf1 = RenderComponent((nameof(Simple1.Header), "FOO"));
+ var rf2 = RenderComponent((nameof(Simple1.Header), "BAR"));
+
+ rf1.CompareTo(rf2).Count.ShouldBe(1);
+ }
+
+ [Fact(DisplayName = "CompareTo with INode and INodeList")]
+ public void Test004()
+ {
+ var rf1 = RenderComponent((nameof(Simple1.Header), "FOO"));
+ var rf2 = RenderComponent((nameof(Simple1.Header), "BAR"));
+
+ var elm = rf1.Find("h1");
+ elm.CompareTo(rf2.Nodes).Count.ShouldBe(1);
+ }
+
+ [Fact(DisplayName = "CompareTo with INodeList and INode")]
+ public void Test005()
+ {
+ var rf1 = RenderComponent((nameof(Simple1.Header), "FOO"));
+ var rf2 = RenderComponent((nameof(Simple1.Header), "BAR"));
+
+ var elm = rf1.Find("h1");
+ rf2.Nodes.CompareTo(elm).Count.ShouldBe(1);
+ }
+ }
+}
diff --git a/tests/Asserting/DiffAssertExtensionsTest.cs b/tests/Asserting/DiffAssertExtensionsTest.cs
new file mode 100644
index 000000000..e974ac857
--- /dev/null
+++ b/tests/Asserting/DiffAssertExtensionsTest.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using AngleSharp.Diffing.Core;
+using Egil.RazorComponents.Testing.Diffing;
+using Moq;
+using Shouldly;
+using Xunit;
+using Xunit.Sdk;
+
+namespace Egil.RazorComponents.Testing.Asserting
+{
+ public class DiffAssertExtensionsTest
+ {
+ [Fact(DisplayName = "ShouldHaveSingleChange throws when input is null")]
+ public void Test001()
+ {
+ IReadOnlyList? diffs = null;
+ Exception? exception = null;
+
+ try
+ {
+ DiffAssertExtensions.ShouldHaveSingleChange(diffs!);
+ }
+ catch (Exception ex)
+ {
+ exception = ex;
+ };
+
+ exception.ShouldBeOfType();
+ }
+
+ [Theory(DisplayName = "ShouldHaveSingleChange throws when input length not exactly 1")]
+ [MemberData(nameof(GetDiffLists))]
+ public void Test002(IReadOnlyList diffs)
+ {
+ Exception? exception = null;
+
+ try
+ {
+ diffs.ShouldHaveSingleChange();
+ }
+ catch (Exception ex)
+ {
+ exception = ex;
+ };
+
+ exception.ShouldBeOfType();
+ }
+
+ [Fact(DisplayName = "ShouldHaveSingleChange returns the single diff in input when there is only one")]
+ public void Test003()
+ {
+ var input = new IDiff[] { Mock.Of() };
+
+ var output = input.ShouldHaveSingleChange();
+
+ output.ShouldBe(input[0]);
+ }
+
+ internal static IEnumerable GetDiffLists()
+ {
+ yield return new object[] { Array.Empty() };
+ yield return new object[]
+ {
+ new IDiff[]
+ {
+ Mock.Of(),
+ Mock.Of(),
+ }
+ };
+ }
+ }
+}
diff --git a/tests/Asserting/GenericCollectionAssertExtensionsTest.cs b/tests/Asserting/GenericCollectionAssertExtensionsTest.cs
new file mode 100644
index 000000000..bdf6857bc
--- /dev/null
+++ b/tests/Asserting/GenericCollectionAssertExtensionsTest.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Shouldly;
+using Xunit;
+using Xunit.Sdk;
+
+namespace Egil.RazorComponents.Testing.Asserting
+{
+ public class GenericCollectionAssertExtensionsTest
+ {
+ [Fact(DisplayName = "ShouldAllBe for Action throws CollectionException when " +
+ "the number of element inspectors does not match the " +
+ "number of items in the collection")]
+ public void Test001()
+ {
+ Exception? exception = null;
+
+ var collection = new string[] { "foo", "bar" };
+ try
+ {
+ collection.ShouldAllBe(x => { });
+ }
+ catch (Exception ex)
+ {
+ exception = ex;
+ };
+
+ var actual = exception.ShouldBeOfType();
+ actual.ActualCount.ShouldBe(collection.Length);
+ actual.ExpectedCount.ShouldBe(1);
+ }
+
+ [Fact(DisplayName = "ShouldAllBe for Action throws CollectionException if one of " +
+ "the element inspectors throws")]
+ public void Test002()
+ {
+ Exception? exception = null;
+
+ var collection = new string[] { "foo", "bar" };
+ try
+ {
+ collection.ShouldAllBe(x => { }, x => throw new Exception());
+ }
+ catch (Exception ex)
+ {
+ exception = ex;
+ };
+
+ var actual = exception.ShouldBeOfType();
+ actual.IndexFailurePoint.ShouldBe(1);
+ }
+
+ [Fact(DisplayName = "ShouldAllBe for Action throws CollectionException when " +
+ "the number of element inspectors does not match the " +
+ "number of items in the collection")]
+ public void Test003()
+ {
+ Exception? exception = null;
+
+ var collection = new string[] { "foo", "bar" };
+ try
+ {
+ collection.ShouldAllBe((x, i) => { });
+ }
+ catch (Exception ex)
+ {
+ exception = ex;
+ };
+
+ var actual = exception.ShouldBeOfType();
+ actual.ActualCount.ShouldBe(collection.Length);
+ actual.ExpectedCount.ShouldBe(1);
+ }
+
+ [Fact(DisplayName = "ShouldAllBe for Action throws CollectionException if one of " +
+ "the element inspectors throws")]
+ public void Test004()
+ {
+ Exception? exception = null;
+
+ var collection = new string[] { "foo", "bar" };
+ try
+ {
+ collection.ShouldAllBe((x, i) => { }, (x, i) => throw new Exception());
+ }
+ catch (Exception ex)
+ {
+ exception = ex;
+ };
+
+ var actual = exception.ShouldBeOfType();
+ actual.IndexFailurePoint.ShouldBe(1);
+ }
+
+ [Fact(DisplayName = "ShouldAllBe for Action passes elements to " +
+ "the element inspectors in the order of collection")]
+ public void Test005()
+ {
+ var collection = new string[] { "foo", "bar" };
+
+ collection.ShouldAllBe(
+ x => x.ShouldBe(collection[0]),
+ x => x.ShouldBe(collection[1])
+ );
+ }
+
+ [Fact(DisplayName = "ShouldAllBe for Action passes elements to " +
+ "the element inspectors in the order of collection, " +
+ "with the matching index")]
+ public void Test006()
+ {
+ var collection = new string[] { "foo", "bar" };
+
+ collection.ShouldAllBe(
+ (x, i) => { x.ShouldBe(collection[0]); i.ShouldBe(0); },
+ (x, i) => { x.ShouldBe(collection[1]); i.ShouldBe(1); }
+ );
+ }
+
+ }
+}
diff --git a/tests/AllTypesOfParamsTest.cs b/tests/ComponentTestFixtureTest.cs
similarity index 90%
rename from tests/AllTypesOfParamsTest.cs
rename to tests/ComponentTestFixtureTest.cs
index 6be21847d..fd5ec5e35 100644
--- a/tests/AllTypesOfParamsTest.cs
+++ b/tests/ComponentTestFixtureTest.cs
@@ -12,7 +12,7 @@
namespace Egil.RazorComponents.Testing
{
[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "")]
- public class AllTypesOfParamsTest : ComponentTestFixture
+ public class ComponentTestFixtureTest : ComponentTestFixture
{
[Fact(DisplayName = "All types of parameters are correctly assigned to component on render")]
public void Test001()
@@ -39,8 +39,8 @@ public void Test001()
instance.NamedCascadingValue.ShouldBe(1337);
Should.Throw(async () => await instance.NonGenericCallback.InvokeAsync(null)).Message.ShouldBe("NonGenericCallback");
Should.Throw(async () => await instance.GenericCallback.InvokeAsync(EventArgs.Empty)).Message.ShouldBe("GenericCallback");
- new RenderedFragment(this, instance.ChildContent!).GetMarkup().ShouldBe(nameof(ChildContent));
- new RenderedFragment(this, instance.OtherContent!).GetMarkup().ShouldBe(nameof(AllTypesOfParams.OtherContent));
+ new RenderedFragment(this, instance.ChildContent!).Markup.ShouldBe(nameof(ChildContent));
+ new RenderedFragment(this, instance.OtherContent!).Markup.ShouldBe(nameof(AllTypesOfParams.OtherContent));
Should.Throw(() => instance.ItemTemplate!("")(null)).Message.ShouldBe("ItemTemplate");
}
@@ -78,8 +78,8 @@ public void Test002()
instance.RegularParam.ShouldBe("some value");
Should.Throw(async () => await instance.NonGenericCallback.InvokeAsync(null)).Message.ShouldBe("NonGenericCallback");
Should.Throw(async () => await instance.GenericCallback.InvokeAsync(EventArgs.Empty)).Message.ShouldBe("GenericCallback");
- new RenderedFragment(this, instance.ChildContent!).GetMarkup().ShouldBe(nameof(ChildContent));
- new RenderedFragment(this, instance.OtherContent!).GetMarkup().ShouldBe(nameof(AllTypesOfParams.OtherContent));
+ new RenderedFragment(this, instance.ChildContent!).Markup.ShouldBe(nameof(ChildContent));
+ new RenderedFragment(this, instance.OtherContent!).Markup.ShouldBe(nameof(AllTypesOfParams.OtherContent));
Should.Throw(() => instance.ItemTemplate!("")(null)).Message.ShouldBe("ItemTemplate");
}
diff --git a/tests/BlazorElementReferencesIncludedInRenderedMarkup.razor b/tests/Components/TestComponentBaseTest/BlazorElementReferencesIncludedInRenderedMarkup.razor
similarity index 54%
rename from tests/BlazorElementReferencesIncludedInRenderedMarkup.razor
rename to tests/Components/TestComponentBaseTest/BlazorElementReferencesIncludedInRenderedMarkup.razor
index c3fd541d9..3133f8108 100644
--- a/tests/BlazorElementReferencesIncludedInRenderedMarkup.razor
+++ b/tests/Components/TestComponentBaseTest/BlazorElementReferencesIncludedInRenderedMarkup.razor
@@ -1,4 +1,4 @@
-@*@inherits TestComponentBase
+@inherits TestComponentBase
@@ -9,12 +9,12 @@
@code {
ElementReference refElm;
- void Test(IRazorTestContext context)
+ void Test()
{
- var cut = context.GetFragment();
-
- var html = cut.GetMarkup();
+ var cut = GetFragment();
+
+ var html = cut.Markup;
html.ShouldContain($"=\"{refElm.Id}\"");
}
-}*@
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/tests/CorrectImplicitRazorTestContextAvailable.razor b/tests/Components/TestComponentBaseTest/CorrectImplicitRazorTestContextAvailable.razor
similarity index 86%
rename from tests/CorrectImplicitRazorTestContextAvailable.razor
rename to tests/Components/TestComponentBaseTest/CorrectImplicitRazorTestContextAvailable.razor
index 03d04b0e4..f5fdad84c 100644
--- a/tests/CorrectImplicitRazorTestContextAvailable.razor
+++ b/tests/Components/TestComponentBaseTest/CorrectImplicitRazorTestContextAvailable.razor
@@ -14,11 +14,12 @@
void Setup1()
{
- Services.AddService(dep1Expected);
+ Services.AddSingleton(dep1Expected);
}
void Test1()
{
+ this.
GetComponentUnderTest().Find("p").TextContent.ShouldBe(dep1Expected.Name);
GetFragment().ShouldNotBeNull();
}
@@ -28,6 +29,13 @@
GetComponentUnderTest().Find("p").TextContent.ShouldBe(dep1Expected.Name);
GetFragment().ShouldNotBeNull();
}
+
+ Task TestAsync1()
+ {
+ GetComponentUnderTest().Find("p").TextContent.ShouldBe(dep1Expected.Name);
+ GetFragment().ShouldNotBeNull();
+ return Task.CompletedTask;
+ }
}
@@ -44,7 +52,7 @@
void Setup2()
{
- Services.AddService(dep2Expected);
+ Services.AddSingleton(dep2Expected);
}
void Test2()
@@ -78,7 +86,7 @@
}
}
-
+
@nameof(Dep3)
diff --git a/tests/Components/TestComponentBaseTest/FixtureMethodsShouldBeCalledInExpectedOrder.razor b/tests/Components/TestComponentBaseTest/FixtureMethodsShouldBeCalledInExpectedOrder.razor
new file mode 100644
index 000000000..c3601d795
--- /dev/null
+++ b/tests/Components/TestComponentBaseTest/FixtureMethodsShouldBeCalledInExpectedOrder.razor
@@ -0,0 +1,94 @@
+@inherits TestComponentBase
+
+[] { TestAsync2, TestAsync3 })>
+
+
+@code{
+ List callOrder = new List();
+ IRazorTestContext? seenContext;
+
+ void Setup()
+ {
+ seenContext = this;
+ callOrder.Add(nameof(Setup));
+ callOrder.Count.ShouldBe(1);
+ callOrder[0].ShouldBe(nameof(Setup));
+ }
+
+ void Test1()
+ {
+ callOrder.Add(nameof(Test1));
+ callOrder.Count.ShouldBe(2);
+ callOrder[0].ShouldBe(nameof(Setup));
+ callOrder[1].ShouldBe(nameof(Test1));
+ this.ShouldBe(seenContext);
+ }
+
+ Task TestAsync1()
+ {
+ callOrder.Add(nameof(TestAsync1));
+ callOrder.Count.ShouldBe(3);
+ callOrder[0].ShouldBe(nameof(Setup));
+ callOrder[1].ShouldBe(nameof(Test1));
+ callOrder[2].ShouldBe(nameof(TestAsync1));
+ this.ShouldBe(seenContext);
+ return Task.CompletedTask;
+ }
+
+ void Test2()
+ {
+ callOrder.Add(nameof(Test2));
+ callOrder.Count.ShouldBe(4);
+ callOrder[0].ShouldBe(nameof(Setup));
+ callOrder[1].ShouldBe(nameof(Test1));
+ callOrder[2].ShouldBe(nameof(TestAsync1));
+ callOrder[3].ShouldBe(nameof(Test2));
+ this.ShouldBe(seenContext);
+ }
+
+ void Test3()
+ {
+ callOrder.Add(nameof(Test3));
+ callOrder.Count.ShouldBe(5);
+ callOrder[0].ShouldBe(nameof(Setup));
+ callOrder[1].ShouldBe(nameof(Test1));
+ callOrder[2].ShouldBe(nameof(TestAsync1));
+ callOrder[3].ShouldBe(nameof(Test2));
+ callOrder[4].ShouldBe(nameof(Test3));
+
+ this.ShouldBe(seenContext);
+ }
+
+ Task TestAsync2()
+ {
+ callOrder.Add(nameof(TestAsync2));
+ callOrder.Count.ShouldBe(6);
+ callOrder[0].ShouldBe(nameof(Setup));
+ callOrder[1].ShouldBe(nameof(Test1));
+ callOrder[2].ShouldBe(nameof(TestAsync1));
+ callOrder[3].ShouldBe(nameof(Test2));
+ callOrder[4].ShouldBe(nameof(Test3));
+ callOrder[5].ShouldBe(nameof(TestAsync2));
+ this.ShouldBe(seenContext);
+ return Task.CompletedTask;
+ }
+
+ Task TestAsync3()
+ {
+ callOrder.Add(nameof(TestAsync3));
+ callOrder.Count.ShouldBe(7);
+ callOrder[0].ShouldBe(nameof(Setup));
+ callOrder[1].ShouldBe(nameof(Test1));
+ callOrder[2].ShouldBe(nameof(TestAsync1));
+ callOrder[3].ShouldBe(nameof(Test2));
+ callOrder[4].ShouldBe(nameof(Test3));
+ callOrder[5].ShouldBe(nameof(TestAsync2));
+ callOrder[6].ShouldBe(nameof(TestAsync3));
+ this.ShouldBe(seenContext);
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/tests/GettingCutAndFragmentFromRazorTestContextTest.razor b/tests/Components/TestComponentBaseTest/GettingCutAndFragmentFromRazorTestContextTest.razor
similarity index 95%
rename from tests/GettingCutAndFragmentFromRazorTestContextTest.razor
rename to tests/Components/TestComponentBaseTest/GettingCutAndFragmentFromRazorTestContextTest.razor
index 5af2ceb24..fc27ce7f0 100644
--- a/tests/GettingCutAndFragmentFromRazorTestContextTest.razor
+++ b/tests/Components/TestComponentBaseTest/GettingCutAndFragmentFromRazorTestContextTest.razor
@@ -16,7 +16,7 @@
var cut2 = GetComponentUnderTest();
Assert.True(ReferenceEquals(cut1, cut2), "Getting CUT multiple times should return the same instance");
- Assert.Equal("CUT", cut1.GetMarkup());
+ Assert.Equal("CUT", cut1.Markup);
var firstFragmentNoId1 = GetFragment();
var firstFragmentId1 = GetFragment("first");
@@ -25,13 +25,13 @@
Assert.True(ReferenceEquals(firstFragmentNoId1, firstFragmentId1), "Getting first fragment with and without id should return the same instance");
Assert.True(ReferenceEquals(firstFragmentNoId1, firstFragmentNoId2), "Getting first fragment multiple times should return the same instance");
Assert.True(ReferenceEquals(firstFragmentId1, firstFragmentId2), "Getting first fragment multiple times should return the same instance");
- Assert.Equal("first", firstFragmentNoId1.GetMarkup());
+ Assert.Equal("first", firstFragmentNoId1.Markup);
var secondFragmentId1 = GetFragment("second");
var secondFragmentId2 = GetFragment("second");
Assert.True(ReferenceEquals(secondFragmentId1, secondFragmentId2), "Getting fragment multiple times should return the same instance");
- Assert.Equal("second", secondFragmentId2.GetMarkup());
+ Assert.Equal("second", secondFragmentId2.Markup);
}
}
diff --git a/tests/LifeCycleTrackerTest.razor b/tests/Components/TestComponentBaseTest/LifeCycleTrackerTest.razor
similarity index 100%
rename from tests/LifeCycleTrackerTest.razor
rename to tests/Components/TestComponentBaseTest/LifeCycleTrackerTest.razor
diff --git a/tests/Components/TestComponentBaseTest/SnapshotTestTest.razor b/tests/Components/TestComponentBaseTest/SnapshotTestTest.razor
new file mode 100644
index 000000000..46049852e
--- /dev/null
+++ b/tests/Components/TestComponentBaseTest/SnapshotTestTest.razor
@@ -0,0 +1,19 @@
+@inherits TestComponentBase
+@using Shouldly
+
+@code {
+ class Dep1 : ITestDep { public string Name { get; } = "FOO"; }
+ class Dep2 : IAsyncTestDep { public Task GetData() => Task.FromResult("BAR"); }
+}
+
+
+
+
+
+
+
+
FOO
+
BAR
+
+
\ No newline at end of file
diff --git a/tests/Egil.RazorComponents.Testing.Library.Tests.csproj b/tests/Egil.RazorComponents.Testing.Library.Tests.csproj
index 304d81e50..45fa173bd 100644
--- a/tests/Egil.RazorComponents.Testing.Library.Tests.csproj
+++ b/tests/Egil.RazorComponents.Testing.Library.Tests.csproj
@@ -7,6 +7,17 @@
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
diff --git a/tests/EventDispatchExtensions/GeneralEventDispatchExtensionsTest.cs b/tests/EventDispatchExtensions/GeneralEventDispatchExtensionsTest.cs
index 45fc69c9c..87b715b34 100644
--- a/tests/EventDispatchExtensions/GeneralEventDispatchExtensionsTest.cs
+++ b/tests/EventDispatchExtensions/GeneralEventDispatchExtensionsTest.cs
@@ -4,6 +4,10 @@
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
+using AngleSharp;
+using AngleSharp.Dom;
+using Moq;
+using Shouldly;
using Xunit;
namespace Egil.RazorComponents.Testing.EventDispatchExtensions
@@ -22,5 +26,38 @@ public async Task CanRaiseEvents(MethodInfo helper)
await VerifyEventRaisesCorrectly(helper, EventArgs.Empty);
}
+
+ [Fact(DisplayName = "TriggerEventAsync throws element is null")]
+ public void Test001()
+ {
+ IElement elm = default!;
+ Should.Throw(() => elm.TriggerEventAsync("", EventArgs.Empty))
+ .ParamName.ShouldBe("element");
+ }
+
+ [Fact(DisplayName = "TriggerEventAsync throws if element does not contain an attribute with the blazor event-name")]
+ public void Test002()
+ {
+ var elmMock = new Mock();
+ elmMock.Setup(x => x.GetAttribute(It.IsAny())).Returns(() => null!);
+
+ Should.Throw(() => elmMock.Object.TriggerEventAsync("click", EventArgs.Empty));
+ }
+
+ [Fact(DisplayName = "TriggerEventAsync throws if element was not rendered through blazor (has a TestRendere in its context)")]
+ public void Test003()
+ {
+ var elmMock = new Mock();
+ var docMock = new Mock();
+ var ctxMock = new Mock();
+
+ elmMock.Setup(x => x.GetAttribute(It.IsAny())).Returns("1");
+ elmMock.SetupGet(x => x.Owner).Returns(docMock.Object);
+ docMock.SetupGet(x => x.Context).Returns(ctxMock.Object);
+ ctxMock.Setup(x => x.GetService()).Returns(() => null!);
+
+ Should.Throw(() => elmMock.Object.TriggerEventAsync("click", EventArgs.Empty));
+ }
+
}
}
diff --git a/tests/FixtureMethodsShouldBeCalledInExpectedOrder.razor b/tests/FixtureMethodsShouldBeCalledInExpectedOrder.razor
deleted file mode 100644
index 1d84d9a97..000000000
--- a/tests/FixtureMethodsShouldBeCalledInExpectedOrder.razor
+++ /dev/null
@@ -1,47 +0,0 @@
-@inherits TestComponentBase
-
-
-
-
-@code{
- List callOrder = new List();
- IRazorTestContext? seenContext;
-
- void Setup()
- {
- seenContext = this;
- callOrder.Add(nameof(Setup));
- callOrder.Count.ShouldBe(1);
- callOrder[0].ShouldBe(nameof(Setup));
- }
-
- void Test1()
- {
- callOrder.Add(nameof(Test1));
- callOrder.Count.ShouldBe(2);
- callOrder[0].ShouldBe(nameof(Setup));
- callOrder[1].ShouldBe(nameof(Test1));
- this.ShouldBe(seenContext);
- }
-
- void Test2()
- {
- callOrder.Add(nameof(Test2));
- callOrder.Count.ShouldBe(3);
- callOrder[0].ShouldBe(nameof(Setup));
- callOrder[1].ShouldBe(nameof(Test1));
- callOrder[2].ShouldBe(nameof(Test2));
- this.ShouldBe(seenContext);
- }
-
- void Test3()
- {
- callOrder.Add(nameof(Test3));
- callOrder.Count.ShouldBe(4);
- callOrder[0].ShouldBe(nameof(Setup));
- callOrder[1].ShouldBe(nameof(Test1));
- callOrder[2].ShouldBe(nameof(Test2));
- callOrder[3].ShouldBe(nameof(Test3));
- this.ShouldBe(seenContext);
- }
-}
\ No newline at end of file
diff --git a/tests/GlobalSuppressions.cs b/tests/GlobalSuppressions.cs
index 241c968a9..8a82ddb38 100644
--- a/tests/GlobalSuppressions.cs
+++ b/tests/GlobalSuppressions.cs
@@ -3,3 +3,5 @@
[assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "")]
[assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "")]
[assembly: SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "")]
+[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "In tests its ok to catch the general exception type")]
+[assembly: SuppressMessage("Usage", "CA2234:Pass system uri objects instead of strings", Justification = "")]
diff --git a/tests/JSInterop/MockJsRuntimeInvokeHandlerTest.cs b/tests/JSInterop/MockJsRuntimeInvokeHandlerTest.cs
deleted file mode 100644
index 3c43800c9..000000000
--- a/tests/JSInterop/MockJsRuntimeInvokeHandlerTest.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Shouldly;
-using Xunit;
-
-namespace Egil.RazorComponents.Testing.JSInterop
-{
- public class MockJsRuntimeInvokeHandlerTest
- {
- [Fact(DisplayName = "Mock returns default value in loose mode without invocation setup")]
- public async Task Test001()
- {
- var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Loose);
-
- var result = await sut.ToJsRuntime().InvokeAsync("ident", Array.Empty());
-
- result.ShouldBe(default);
- }
-
- [Fact(DisplayName = "After invocation a invocation should be visible from the Invocations list")]
- public void Test002()
- {
- var identifier = "fooFunc";
- var args = new[] { "bar", "baz" };
- using var cts = new CancellationTokenSource();
- var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Loose);
-
- sut.ToJsRuntime().InvokeAsync(identifier, cts.Token, args);
-
- var invocation = sut.Invocations[identifier].Single();
- invocation.Identifier.ShouldBe(identifier);
- invocation.Arguments.ShouldBe(args);
- invocation.CancellationToken.ShouldBe(cts.Token);
- }
-
- [Fact(DisplayName = "Mock throws exception when in strict mode and invocation has not been setup")]
- public void Test003()
- {
- var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
-
- Should.Throw(() => sut.ToJsRuntime().InvokeAsync("ident", new[] { "bar", "baz" }));
- }
-
- [Fact(DisplayName = "Mock returns task from planned invocation when one is present")]
- public async Task Test004()
- {
- var expectedResult = "HELLO WORLD";
- var ident = "fooFunc";
- var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
- sut.Setup(ident).SetResult(expectedResult);
-
- var result = await sut.ToJsRuntime().InvokeAsync(ident, Array.Empty());
-
- result.ShouldBe(expectedResult);
- }
- }
-}
diff --git a/tests/Mocking/JSInterop/JsRuntimeAssertExtensionsTest.cs b/tests/Mocking/JSInterop/JsRuntimeAssertExtensionsTest.cs
new file mode 100644
index 000000000..e0b0d71ee
--- /dev/null
+++ b/tests/Mocking/JSInterop/JsRuntimeAssertExtensionsTest.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using AngleSharp.Dom;
+using Egil.RazorComponents.Testing.Diffing;
+using Microsoft.AspNetCore.Components;
+using Microsoft.JSInterop;
+using Moq;
+using Shouldly;
+using Xunit;
+using Xunit.Sdk;
+
+namespace Egil.RazorComponents.Testing.Mocking.JSInterop
+{
+ public class JsRuntimeAssertExtensionsTest
+ {
+ [Fact(DisplayName = "VerifyNotInvoke throws if handler is null")]
+ public void Test001()
+ {
+ MockJsRuntimeInvokeHandler? handler = null;
+ Should.Throw(() => JsRuntimeAssertExtensions.VerifyNotInvoke(handler!, ""));
+ }
+
+ [Fact(DisplayName = "VerifyNotInvoke throws JsInvokeCountExpectedException if identifier " +
+ "has been invoked one or more times")]
+ public async Task Test002()
+ {
+ var identifier = "test";
+ var handler = new MockJsRuntimeInvokeHandler();
+ await handler.ToJsRuntime().InvokeVoidAsync(identifier);
+
+ Should.Throw(() => handler.VerifyNotInvoke(identifier));
+ }
+
+ [Fact(DisplayName = "VerifyNotInvoke throws JsInvokeCountExpectedException if identifier " +
+ "has been invoked one or more times, with custom error message")]
+ public async Task Test003()
+ {
+ var identifier = "test";
+ var errMsg = "HELLO WORLD";
+ var handler = new MockJsRuntimeInvokeHandler();
+ await handler.ToJsRuntime().InvokeVoidAsync(identifier);
+
+ Should.Throw(() => handler.VerifyNotInvoke(identifier, errMsg))
+ .UserMessage.ShouldEndWith(errMsg);
+ }
+
+ [Fact(DisplayName = "VerifyNotInvoke does not throw if identifier has not been invoked")]
+ public void Test004()
+ {
+ var handler = new MockJsRuntimeInvokeHandler();
+
+ handler.VerifyNotInvoke("FOOBAR");
+ }
+
+ [Fact(DisplayName = "VerifyInvoke throws if handler is null")]
+ public void Test100()
+ {
+ MockJsRuntimeInvokeHandler? handler = null;
+ Should.Throw(() => JsRuntimeAssertExtensions.VerifyInvoke(handler!, ""));
+ Should.Throw(() => JsRuntimeAssertExtensions.VerifyInvoke(handler!, "", 42));
+ }
+
+ [Fact(DisplayName = "VerifyInvoke throws invokeCount is less than 1")]
+ public void Test101()
+ {
+ var handler = new MockJsRuntimeInvokeHandler();
+
+ Should.Throw(() => handler.VerifyInvoke("", 0));
+ }
+
+ [Fact(DisplayName = "VerifyInvoke throws JsInvokeCountExpectedException when " +
+ "invocation count doesn't match the expected")]
+ public async Task Test103()
+ {
+ var identifier = "test";
+ var handler = new MockJsRuntimeInvokeHandler();
+ await handler.ToJsRuntime().InvokeVoidAsync(identifier);
+
+ var actual = Should.Throw(() => handler.VerifyInvoke(identifier, 2));
+ actual.ExpectedInvocationCount.ShouldBe(2);
+ actual.ActualInvocationCount.ShouldBe(1);
+ actual.Identifier.ShouldBe(identifier);
+ }
+
+ [Fact(DisplayName = "VerifyInvoke returns the invocation(s) if the expected count matched")]
+ public async Task Test104()
+ {
+ var identifier = "test";
+ var handler = new MockJsRuntimeInvokeHandler();
+ await handler.ToJsRuntime().InvokeVoidAsync(identifier);
+
+ var invocations = handler.VerifyInvoke(identifier, 1);
+ invocations.ShouldBeSameAs(handler.Invocations[identifier]);
+
+ var invocation = handler.VerifyInvoke(identifier);
+ invocation.ShouldBe(handler.Invocations[identifier][0]);
+ }
+
+ [Fact(DisplayName = "ShouldBeElementReferenceTo throws if actualArgument or targeted element is null")]
+ public void Test200()
+ {
+ Should.Throw(() => JsRuntimeAssertExtensions.ShouldBeElementReferenceTo(null!, null!))
+ .ParamName.ShouldBe("actualArgument");
+ Should.Throw(() => JsRuntimeAssertExtensions.ShouldBeElementReferenceTo(string.Empty, null!))
+ .ParamName.ShouldBe("expectedTargetElement");
+ }
+
+ [Fact(DisplayName = "ShouldBeElementReferenceTo throws if actualArgument is not a ElementReference")]
+ public void Test201()
+ {
+ var obj = new object();
+ Should.Throw(() => obj.ShouldBeElementReferenceTo(Mock.Of()));
+ }
+
+ [Fact(DisplayName = "ShouldBeElementReferenceTo throws if element reference does not point to the provided element")]
+ public void Test202()
+ {
+ using var htmlParser = new TestHtmlParser();
+ var elmRef = new ElementReference(Guid.NewGuid().ToString());
+ var elm = (IElement)htmlParser.Parse($"").First();
+
+ Should.Throw(() => elmRef.ShouldBeElementReferenceTo(elm));
+
+ var elmWithoutRefAttr = (IElement)htmlParser.Parse($"").First();
+
+ Should.Throw(() => elmRef.ShouldBeElementReferenceTo(elmWithoutRefAttr));
+ }
+ }
+}
diff --git a/tests/Mocking/JSInterop/JsRuntimeInvocationTest.cs b/tests/Mocking/JSInterop/JsRuntimeInvocationTest.cs
new file mode 100644
index 000000000..41c373a1a
--- /dev/null
+++ b/tests/Mocking/JSInterop/JsRuntimeInvocationTest.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Shouldly;
+using Xunit;
+
+namespace Egil.RazorComponents.Testing.Mocking.JSInterop
+{
+ public class JsRuntimeInvocationTest
+ {
+ public static IEnumerable GetEqualsTestData()
+ {
+ var token = new CancellationToken(true);
+ var args = new object[] { 1, "baz" };
+
+ var i1 = new JsRuntimeInvocation("foo", token, args);
+ var i2 = new JsRuntimeInvocation("foo", token, args);
+ var i3 = new JsRuntimeInvocation("bar", token, args);
+ var i4 = new JsRuntimeInvocation("foo", CancellationToken.None, args);
+ var i5 = new JsRuntimeInvocation("foo", token, Array.Empty());
+ var i6 = new JsRuntimeInvocation("foo", token, new object[] { 2, "woop" });
+
+ yield return new object[] { i1, i1, true };
+ yield return new object[] { i1, i2, true };
+ yield return new object[] { i1, i3, false };
+ yield return new object[] { i1, i4, false };
+ yield return new object[] { i1, i5, false };
+ yield return new object[] { i1, i6, false };
+ }
+
+ [Theory(DisplayName = "Equals operator works as expected")]
+ [MemberData(nameof(GetEqualsTestData))]
+ public void Test002(JsRuntimeInvocation left, JsRuntimeInvocation right, bool expectedResult)
+ {
+ left.Equals(right).ShouldBe(expectedResult);
+ right.Equals(left).ShouldBe(expectedResult);
+ (left == right).ShouldBe(expectedResult);
+ (left != right).ShouldNotBe(expectedResult);
+ left.Equals((object)right).ShouldBe(expectedResult);
+ right.Equals((object)left).ShouldBe(expectedResult);
+ }
+
+ [Fact(DisplayName = "Equals operator works as expected with non compatible types")]
+ public void Test003()
+ {
+ new JsRuntimeInvocation().Equals(new object()).ShouldBeFalse();
+ }
+
+ [Theory(DisplayName = "GetHashCode returns same result for equal JsRuntimeInvocations")]
+ [MemberData(nameof(GetEqualsTestData))]
+ public void Test004(JsRuntimeInvocation left, JsRuntimeInvocation right, bool expectedResult)
+ {
+ left.GetHashCode().Equals(right.GetHashCode()).ShouldBe(expectedResult);
+ }
+ }
+}
diff --git a/tests/Mocking/JSInterop/MockJsRuntimeInvokeHandlerTest.cs b/tests/Mocking/JSInterop/MockJsRuntimeInvokeHandlerTest.cs
new file mode 100644
index 000000000..9325051bc
--- /dev/null
+++ b/tests/Mocking/JSInterop/MockJsRuntimeInvokeHandlerTest.cs
@@ -0,0 +1,239 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.JSInterop;
+using Shouldly;
+using Xunit;
+
+namespace Egil.RazorComponents.Testing.Mocking.JSInterop
+{
+ public class MockJsRuntimeInvokeHandlerTest
+ {
+ [Fact(DisplayName = "Mock returns default value in loose mode without invocation setup")]
+ public async Task Test001()
+ {
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Loose);
+
+ var result = await sut.ToJsRuntime().InvokeAsync("ident", Array.Empty());
+
+ result.ShouldBe(default);
+ }
+
+ [Fact(DisplayName = "After invocation a invocation should be visible from the Invocations list")]
+ public void Test002()
+ {
+ var identifier = "fooFunc";
+ var args = new[] { "bar", "baz" };
+ using var cts = new CancellationTokenSource();
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Loose);
+
+ sut.ToJsRuntime().InvokeAsync(identifier, cts.Token, args);
+
+ var invocation = sut.Invocations[identifier].Single();
+ invocation.Identifier.ShouldBe(identifier);
+ invocation.Arguments.ShouldBe(args);
+ invocation.CancellationToken.ShouldBe(cts.Token);
+ }
+
+ [Fact(DisplayName = "Mock throws exception when in strict mode and invocation has not been setup")]
+ public async Task Test003()
+ {
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
+ var identifier = "func";
+ var args = new[] { "bar", "baz" };
+
+ var exception = await Should.ThrowAsync(sut.ToJsRuntime().InvokeVoidAsync(identifier, args).AsTask());
+ exception.Invocation.Identifier.ShouldBe(identifier);
+ exception.Invocation.Arguments.ShouldBe(args);
+
+ exception = Should.Throw(() => sut.ToJsRuntime().InvokeAsync(identifier, args));
+ exception.Invocation.Identifier.ShouldBe(identifier);
+ exception.Invocation.Arguments.ShouldBe(args);
+ }
+
+ [Fact(DisplayName = "Invocations receives before a planned invocation " +
+ "has result set receives the same result")]
+ public async Task Test005()
+ {
+ var identifier = "func";
+ var expectedResult = Guid.NewGuid();
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
+ var plannedInvoke = sut.Setup(identifier);
+
+ var jsRuntime = sut.ToJsRuntime();
+ var i1 = jsRuntime.InvokeAsync(identifier);
+ var i2 = jsRuntime.InvokeAsync(identifier);
+
+ plannedInvoke.SetResult(expectedResult);
+
+ (await i1).ShouldBe(expectedResult);
+ (await i2).ShouldBe(expectedResult);
+ }
+
+ [Fact(DisplayName = "Invocations receives after a planned invocation " +
+ "has result set does not receive the same result as " +
+ "the invocations before the result was set the first time")]
+ public async Task Test006()
+ {
+ var identifier = "func";
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
+ var plannedInvoke = sut.Setup(identifier);
+ var jsRuntime = sut.ToJsRuntime();
+
+ var expectedResult1 = Guid.NewGuid();
+ var i1 = jsRuntime.InvokeAsync(identifier);
+ plannedInvoke.SetResult(expectedResult1);
+
+ var expectedResult2 = Guid.NewGuid();
+ var i2 = jsRuntime.InvokeAsync(identifier);
+ plannedInvoke.SetResult(expectedResult2);
+
+ (await i1).ShouldBe(expectedResult1);
+ (await i2).ShouldBe(expectedResult2);
+ }
+
+ [Fact(DisplayName = "A planned invocation can be cancelled for any waiting received invocations.")]
+ public void Test007()
+ {
+ var identifier = "func";
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
+ var plannedInvoke = sut.Setup(identifier);
+ var invocation = sut.ToJsRuntime().InvokeAsync(identifier);
+
+ plannedInvoke.SetCanceled();
+
+ invocation.IsCanceled.ShouldBeTrue();
+ }
+
+ [Fact(DisplayName = "A planned invocation can throw an exception for any waiting received invocations.")]
+ public async Task Test008()
+ {
+ var identifier = "func";
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
+ var plannedInvoke = sut.Setup(identifier);
+ var invocation = sut.ToJsRuntime().InvokeAsync(identifier);
+ var expectedException = new InvalidOperationException("TADA");
+
+ plannedInvoke.SetException(expectedException);
+
+ var actual = await Should.ThrowAsync(invocation.AsTask());
+ actual.ShouldBe(expectedException);
+ invocation.IsFaulted.ShouldBeTrue();
+ }
+
+ [Fact(DisplayName = "Invocations returns all from a planned invocation")]
+ public void Test009()
+ {
+ var identifier = "func";
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
+ var plannedInvoke = sut.Setup(identifier, x => true);
+ var i1 = sut.ToJsRuntime().InvokeAsync(identifier, "first");
+ var i2 = sut.ToJsRuntime().InvokeAsync(identifier, "second");
+
+ var invocations = plannedInvoke.Invocations;
+
+ invocations.Count.ShouldBe(2);
+ invocations[0].Arguments[0].ShouldBe("first");
+ invocations[1].Arguments[0].ShouldBe("second");
+ }
+
+ [Fact(DisplayName = "Arguments used in Setup are matched with invocations")]
+ public void Test010()
+ {
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
+ var planned = sut.Setup("foo", "bar", 42);
+
+ sut.ToJsRuntime().InvokeAsync("foo", "bar", 42);
+
+ Should.Throw(
+ () => sut.ToJsRuntime().InvokeAsync("foo", "bar", 41)
+ );
+
+ planned.Invocations.Count.ShouldBe(1);
+ var invocation = planned.Invocations[0];
+ invocation.Identifier.ShouldBe("foo");
+ invocation.Arguments[0].ShouldBe("bar");
+ invocation.Arguments[1].ShouldBe(42);
+ }
+
+ [Fact(DisplayName = "Argument matcher used in Setup are matched with invocations")]
+ public void Test011()
+ {
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
+ var planned = sut.Setup("foo", args => args.Count == 1);
+
+ sut.ToJsRuntime().InvokeAsync("foo", 42);
+
+ Should.Throw(
+ () => sut.ToJsRuntime().InvokeAsync("foo", "bar", 42)
+ );
+
+ planned.Invocations.Count.ShouldBe(1);
+ var invocation = planned.Invocations[0];
+ invocation.Identifier.ShouldBe("foo");
+ invocation.Arguments.Count.ShouldBe(1);
+ invocation.Arguments[0].ShouldBe(42);
+ }
+
+ [Fact(DisplayName = "SetupVoid returns a planned invocation that does not take a result object")]
+ public async Task Test012()
+ {
+ var identifier = "func";
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
+ var plannedInvoke = sut.SetupVoid(identifier);
+
+ var invocation = sut.ToJsRuntime().InvokeVoidAsync(identifier);
+ plannedInvoke.SetVoidResult();
+
+ await invocation;
+
+ invocation.IsCompletedSuccessfully.ShouldBeTrue();
+ }
+
+ [Fact(DisplayName = "Arguments used in SetupVoid are matched with invocations")]
+ public async Task Test013()
+ {
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
+ var planned = sut.SetupVoid("foo", "bar", 42);
+
+ var i1 = sut.ToJsRuntime().InvokeVoidAsync("foo", "bar", 42);
+
+ await Should.ThrowAsync(
+ sut.ToJsRuntime().InvokeVoidAsync("foo", "bar", 41).AsTask()
+ );
+
+ planned.Invocations.Count.ShouldBe(1);
+ var invocation = planned.Invocations[0];
+ invocation.Identifier.ShouldBe("foo");
+ invocation.Arguments[0].ShouldBe("bar");
+ invocation.Arguments[1].ShouldBe(42);
+ }
+
+ [Fact(DisplayName = "Argument matcher used in SetupVoid are matched with invocations")]
+ public async Task Test014()
+ {
+ var sut = new MockJsRuntimeInvokeHandler(JsRuntimeMockMode.Strict);
+ var planned = sut.SetupVoid("foo", args => args.Count == 2);
+
+ var i1 = sut.ToJsRuntime().InvokeVoidAsync("foo", "bar", 42);
+
+ await Should.ThrowAsync(
+ sut.ToJsRuntime().InvokeVoidAsync("foo", 42).AsTask()
+ );
+
+ await Should.ThrowAsync(
+ sut.ToJsRuntime().InvokeVoidAsync("foo").AsTask()
+ );
+
+ planned.Invocations.Count.ShouldBe(1);
+ var invocation = planned.Invocations[0];
+ invocation.Identifier.ShouldBe("foo");
+ invocation.Arguments.Count.ShouldBe(2);
+ invocation.Arguments[0].ShouldBe("bar");
+ invocation.Arguments[1].ShouldBe(42);
+ }
+ }
+}
diff --git a/tests/Mocking/MockHttpExtensionsTest.cs b/tests/Mocking/MockHttpExtensionsTest.cs
new file mode 100644
index 000000000..9aac81fdf
--- /dev/null
+++ b/tests/Mocking/MockHttpExtensionsTest.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using RichardSzalay.MockHttp;
+using Shouldly;
+using Xunit;
+
+namespace Egil.RazorComponents.Testing.Mocking
+{
+ public class MockHttpExtensionsTest
+ {
+ [Fact(DisplayName = "AddMockHttp throws if the service provider is null")]
+ public void Test001()
+ {
+ TestServiceProvider provider = default!;
+
+ Should.Throw(() => provider.AddMockHttp());
+ }
+
+ [Fact(DisplayName = "AddMockHttp registers a mock HttpClient in the service provider")]
+ public void Test002()
+ {
+ using var provider = new TestServiceProvider();
+
+ var mock = provider.AddMockHttp();
+
+ provider.GetService().ShouldNotBeNull();
+ }
+
+ [Fact(DisplayName = "Capture throws if the handler is null")]
+ public void Test003()
+ {
+ MockHttpMessageHandler handler = default!;
+
+ Should.Throw(() => handler.Capture(""));
+ }
+
+ [Fact(DisplayName = "Capture returns a task, that when completed, " +
+ "provides a response to the captured url")]
+ public async Task Test004()
+ {
+ using var provider = new TestServiceProvider();
+ var mock = provider.AddMockHttp();
+ var httpClient = provider.GetService();
+ var captured = mock.Capture("/ping");
+
+ captured.SetResult("pong");
+
+ var actual = await httpClient.GetStringAsync("/ping");
+ actual.ShouldBe("\"pong\"");
+ }
+ }
+}
diff --git a/tests/RenderComponentTest.cs b/tests/RenderComponentTest.cs
new file mode 100644
index 000000000..33379530d
--- /dev/null
+++ b/tests/RenderComponentTest.cs
@@ -0,0 +1,63 @@
+using Egil.RazorComponents.Testing.EventDispatchExtensions;
+using Egil.RazorComponents.Testing.SampleComponents;
+using Shouldly;
+using Xunit;
+
+namespace Egil.RazorComponents.Testing
+{
+ public class RenderComponentTest : ComponentTestFixture
+ {
+ [Fact(DisplayName = "Nodes should return the same instance " +
+ "when a render has not resulted in any changes")]
+ public void Test003()
+ {
+ var cut = RenderComponent(ChildContent("
"));
+
+ Assert.Same(initialNodes, cut.Nodes);
+ }
+
+ [Fact(DisplayName = "Nodes should return new instance " +
+ "when a SetParametersAndRender has caused changes to DOM tree")]
+ public void Tets004()
+ {
+ var cut = RenderComponent(ChildContent("
"));
+ var initialNodes = cut.Nodes;
+
+ cut.SetParametersAndRender(ChildContent("
"));
+
+ Assert.NotSame(initialNodes, cut.Nodes);
+ cut.Find("p").ShouldNotBeNull();
+ }
+
+ [Fact(DisplayName = "Nodes should return new instance " +
+ "when a Render has caused changes to DOM tree")]
+ public void Tets005()
+ {
+ var cut = RenderComponent();
+ var initialNodes = cut.Nodes;
+
+ cut.Render();
+
+ Assert.NotSame(initialNodes, cut.Nodes);
+ }
+
+ [Fact(DisplayName = "Nodes should return new instance " +
+ "when a event handler trigger has caused changes to DOM tree")]
+ public void Tets006()
+ {
+ var cut = RenderComponent();
+ var initialNodes = cut.Nodes;
+
+ cut.Find("button").Click();
+
+ Assert.NotSame(initialNodes, cut.Nodes);
+ }
+
+
+ }
+
+}
diff --git a/tests/RenderedFragmentTest.cs b/tests/RenderedFragmentTest.cs
index 2ddaf7cc6..ac8075e5e 100644
--- a/tests/RenderedFragmentTest.cs
+++ b/tests/RenderedFragmentTest.cs
@@ -1,7 +1,7 @@
-using Egil.RazorComponents.Testing.EventDispatchExtensions;
-using Egil.RazorComponents.Testing.Extensions;
+using Egil.RazorComponents.Testing.Extensions;
using Egil.RazorComponents.Testing.SampleComponents;
using Egil.RazorComponents.Testing.SampleComponents.Data;
+using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using System;
using System.Collections.Generic;
@@ -28,104 +28,48 @@ public void Test002()
result.ShouldNotBeNull();
}
- [Fact(DisplayName = "GetNodes should return new instance when " +
+ [Fact(DisplayName = "Nodes should return new instance when " +
"async operation during OnInit causes component to re-render")]
public void Test003()
{
var testData = new AsyncNameDep();
- Services.AddService(testData);
+ Services.AddSingleton(testData);
var cut = RenderComponent();
- var initialValue = cut.GetNodes().Find("p").OuterHtml;
+ var initialValue = cut.Nodes.Find("p").OuterHtml;
WaitForNextRender(() => testData.SetResult("Steve Sanderson"));
- var steveValue = cut.GetNodes().Find("p").OuterHtml;
+ var steveValue = cut.Nodes.Find("p").OuterHtml;
steveValue.ShouldNotBe(initialValue);
}
- [Fact(DisplayName = "GetNodes should return new instance when " +
+ [Fact(DisplayName = "Nodes should return new instance when " +
"async operation/StateHasChanged during OnAfterRender causes component to re-render")]
public void Test004()
{
var invocation = Services.AddMockJsRuntime().Setup("getdata");
var cut = RenderComponent();
- var initialValue = cut.GetNodes().Find("p").OuterHtml;
+ var initialValue = cut.Nodes.Find("p").OuterHtml;
WaitForNextRender(() => invocation.SetResult("Steve Sanderson"));
- var steveValue = cut.GetNodes().Find("p").OuterHtml;
+ var steveValue = cut.Nodes.Find("p").OuterHtml;
steveValue.ShouldNotBe(initialValue);
}
- [Fact(DisplayName = "GetNodes on a components with child component returns " +
+ [Fact(DisplayName = "Nodes on a components with child component returns " +
"new instance when the child component has changes")]
public void Test005()
{
var invocation = Services.AddMockJsRuntime().Setup("getdata");
var notcut = RenderComponent(ChildContent());
var cut = RenderComponent(ChildContent());
- var initialValue = cut.GetNodes();
+ var initialValue = cut.Nodes;
WaitForNextRender(() => invocation.SetResult("Steve Sanderson"), TimeSpan.FromDays(1));
- Assert.NotSame(initialValue, cut.GetNodes());
+ Assert.NotSame(initialValue, cut.Nodes);
}
-
- }
-
- public class RenderComponentTest : ComponentTestFixture
- {
- [Fact(DisplayName = "GetNodes should return the same instance " +
- "when a render has not resulted in any changes")]
- public void Test003()
- {
- var cut = RenderComponent(ChildContent("
"));
-
- Assert.Same(initialNodes, cut.GetNodes());
- }
-
- [Fact(DisplayName = "GetNodes should return new instance " +
- "when a SetParametersAndRender has caused changes to DOM tree")]
- public void Tets004()
- {
- var cut = RenderComponent(ChildContent("
"));
- var initialNodes = cut.GetNodes();
-
- cut.SetParametersAndRender(ChildContent("
"));
-
- Assert.NotSame(initialNodes, cut.GetNodes());
- cut.Find("p").ShouldNotBeNull();
- }
-
- [Fact(DisplayName = "GetNodes should return new instance " +
- "when a Render has caused changes to DOM tree")]
- public void Tets005()
- {
- var cut = RenderComponent();
- var initialNodes = cut.GetNodes();
-
- cut.Render();
-
- Assert.NotSame(initialNodes, cut.GetNodes());
- }
-
- [Fact(DisplayName = "GetNodes should return new instance " +
- "when a event handler trigger has caused changes to DOM tree")]
- public void Tets006()
- {
- var cut = RenderComponent();
- var initialNodes = cut.GetNodes();
-
- cut.Find("button").Click();
-
- Assert.NotSame(initialNodes, cut.GetNodes());
- }
-
-
}
}
diff --git a/tests/Rendering/ComponentParameterTest.cs b/tests/Rendering/ComponentParameterTest.cs
new file mode 100644
index 000000000..291824693
--- /dev/null
+++ b/tests/Rendering/ComponentParameterTest.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Shouldly;
+using Xunit;
+
+namespace Egil.RazorComponents.Testing.Rendering
+{
+ public class ComponentParameterTest
+ {
+ public static IEnumerable GetEqualsTestData()
+ {
+ var name = "foo";
+ var value = "bar";
+ var p1 = ComponentParameter.CreateParameter(name, value);
+ var p2 = ComponentParameter.CreateParameter(name, value);
+ var p3 = ComponentParameter.CreateCascadingValue(name, value);
+ var p4 = ComponentParameter.CreateParameter(string.Empty, value);
+ var p5 = ComponentParameter.CreateParameter(name, string.Empty);
+
+ yield return new object[] { p1, p1, true };
+ yield return new object[] { p1, p2, true };
+ yield return new object[] { p3, p3, true };
+ yield return new object[] { p1, p3, false };
+ yield return new object[] { p1, p4, false };
+ yield return new object[] { p1, p5, false };
+ }
+
+ [Fact(DisplayName = "Creating a cascading value throws")]
+ public void Test001()
+ {
+ Should.Throw(() => ComponentParameter.CreateCascadingValue(null, null!));
+ Should.Throw(() => { ComponentParameter p = (null, null, true); });
+ }
+
+ [Fact(DisplayName = "Creating a regular parameter without a name throws")]
+ public void Test002()
+ {
+ Should.Throw(() => ComponentParameter.CreateParameter(null!, null));
+ Should.Throw(() => { ComponentParameter p = (null, null, false); });
+ }
+
+ [Theory(DisplayName = "Equals compares correctly")]
+ [MemberData(nameof(GetEqualsTestData))]
+ public void Test003(ComponentParameter left, ComponentParameter right, bool expectedResult)
+ {
+ left.Equals(right).ShouldBe(expectedResult);
+ right.Equals(left).ShouldBe(expectedResult);
+ (left == right).ShouldBe(expectedResult);
+ (left != right).ShouldNotBe(expectedResult);
+ left.Equals((object)right).ShouldBe(expectedResult);
+ right.Equals((object)left).ShouldBe(expectedResult);
+ }
+
+ [Fact(DisplayName = "Equals operator works as expected with non compatible types")]
+ public void Test004()
+ {
+ ComponentParameter.CreateParameter(string.Empty, string.Empty)
+ .Equals(new object())
+ .ShouldBeFalse();
+ }
+
+ [Theory(DisplayName = "GetHashCode returns same result for equal ComponentParameter")]
+ [MemberData(nameof(GetEqualsTestData))]
+ public void Test005(ComponentParameter left, ComponentParameter right, bool expectedResult)
+ {
+ left.GetHashCode().Equals(right.GetHashCode()).ShouldBe(expectedResult);
+ }
+ }
+}
diff --git a/tests/TestServiceProviderTest.cs b/tests/TestServiceProviderTest.cs
new file mode 100644
index 000000000..f16208f9d
--- /dev/null
+++ b/tests/TestServiceProviderTest.cs
@@ -0,0 +1,148 @@
+using Egil.RazorComponents.Testing;
+using Egil.RazorComponents.Testing.EventDispatchExtensions;
+using Egil.RazorComponents.Testing.Extensions;
+using Egil.RazorComponents.Testing.Mocking.JSInterop;
+using Egil.RazorComponents.Testing.SampleComponents;
+using Egil.RazorComponents.Testing.SampleComponents.Data;
+using Microsoft.Extensions.DependencyInjection;
+using Shouldly;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Xunit;
+
+namespace Egil.RazorComponents.Testing
+{
+ public class TestServiceProviderTest : ComponentTestFixture
+ {
+ class DummyService { }
+ class AnotherDummyService { }
+ class OneMoreDummyService { }
+
+ [Fact(DisplayName = "Provider initialized without a service collection has zero services by default")]
+ public void Test001()
+ {
+ using var sut = new TestServiceProvider();
+
+ sut.Count.ShouldBe(0);
+ }
+
+ [Fact(DisplayName = "Provider initialized with a service collection has the services form the provided collection")]
+ public void Test002()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton(new DummyService());
+ using var sut = new TestServiceProvider(services);
+
+ sut.Count.ShouldBe(1);
+ sut[0].ServiceType.ShouldBe(typeof(DummyService));
+ }
+
+ [Fact(DisplayName = "Services can be registered in the provider like a normal service collection")]
+ public void Test010()
+ {
+ using var sut = new TestServiceProvider();
+
+ sut.Add(new ServiceDescriptor(typeof(DummyService), new DummyService()));
+ sut.Insert(0, new ServiceDescriptor(typeof(AnotherDummyService), new AnotherDummyService()));
+ sut[1] = new ServiceDescriptor(typeof(DummyService), new DummyService());
+
+ sut.Count.ShouldBe(2);
+ sut[0].ServiceType.ShouldBe(typeof(AnotherDummyService));
+ sut[1].ServiceType.ShouldBe(typeof(DummyService));
+ }
+
+ [Fact(DisplayName = "Services can be removed in the provider like a normal service collection")]
+ public void Test011()
+ {
+ using var sut = new TestServiceProvider();
+ var descriptor = new ServiceDescriptor(typeof(DummyService), new DummyService());
+ var anotherDescriptor = new ServiceDescriptor(typeof(AnotherDummyService), new AnotherDummyService());
+ var oneMoreDescriptor = new ServiceDescriptor(typeof(OneMoreDummyService), new OneMoreDummyService());
+
+ sut.Add(descriptor);
+ sut.Add(anotherDescriptor);
+ sut.Add(oneMoreDescriptor);
+
+ sut.Remove(descriptor);
+ sut.Count.ShouldBe(2);
+
+ sut.RemoveAt(1);
+ sut.Count.ShouldBe(1);
+
+ sut.Clear();
+ sut.ShouldBeEmpty();
+ }
+
+ [Fact(DisplayName = "Misc collection methods works as expected")]
+ public void Test012()
+ {
+ using var sut = new TestServiceProvider();
+ var descriptor = new ServiceDescriptor(typeof(DummyService), new DummyService());
+ var copyToTarget = new ServiceDescriptor[1];
+ sut.Add(descriptor);
+
+ sut.IndexOf(descriptor).ShouldBe(0);
+ sut.Contains(descriptor).ShouldBeTrue();
+ sut.CopyTo(copyToTarget, 0);
+ copyToTarget[0].ShouldBe(descriptor);
+ sut.IsReadOnly.ShouldBeFalse();
+ ((IEnumerable)sut).OfType().Count().ShouldBe(1);
+ }
+
+ [Fact(DisplayName = "After the first service is requested, " +
+ "the provider does not allow changes to service collection")]
+ public void Test013()
+ {
+ var descriptor = new ServiceDescriptor(typeof(AnotherDummyService), new AnotherDummyService());
+
+ using var sut = new TestServiceProvider();
+ sut.AddSingleton(new DummyService());
+ sut.GetService();
+
+ // Try adding
+ Should.Throw(() => sut.Add(descriptor));
+ Should.Throw(() => sut.Insert(0, descriptor));
+ Should.Throw(() => sut[0] = descriptor);
+
+ // Try removing
+ Should.Throw(() => sut.Remove(descriptor));
+ Should.Throw