diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml index c17b75ffd..d3b0472b7 100644 --- a/.github/workflows/verification.yml +++ b/.github/workflows/verification.yml @@ -73,15 +73,35 @@ jobs: dotnet pack src/bunit/ -c release -o ${GITHUB_WORKSPACE}/packages -p:ContinuousIntegrationBuild=true dotnet pack src/bunit.template/ -c release -o ${GITHUB_WORKSPACE}/packages -p:ContinuousIntegrationBuild=true - # Excluding windows because the restore step doesnt seem to work correct. - - name: ✔ Verify template + - name: ✳ Install bUnit template if: matrix.os != 'windows-latest' run: | dotnet new --install bunit.template::${NBGV_NuGetPackageVersion} --nuget-source ${GITHUB_WORKSPACE}/packages - dotnet new bunit --no-restore -o ${GITHUB_WORKSPACE}/TemplateTest - echo '' >> ${GITHUB_WORKSPACE}/TemplateTest/Directory.Build.props - dotnet restore ${GITHUB_WORKSPACE}/TemplateTest --source ${GITHUB_WORKSPACE}/packages --source https://api.nuget.org/v3/index.json - dotnet test ${GITHUB_WORKSPACE}/TemplateTest --blame-hang --blame-hang-timeout 1m --blame-hang-dump-type none + + # Excluding windows because the restore step doesnt seem to work correct. + - name: ✔ Verify xUnit template + if: matrix.os != 'windows-latest' + run: | + dotnet new bunit --no-restore -o ${GITHUB_WORKSPACE}/TemplateTestXunit + echo '' >> ${GITHUB_WORKSPACE}/TemplateTestXunit/Directory.Build.props + dotnet restore ${GITHUB_WORKSPACE}/TemplateTestXunit --source ${GITHUB_WORKSPACE}/packages --source https://api.nuget.org/v3/index.json + dotnet test ${GITHUB_WORKSPACE}/TemplateTestXunit --blame-hang --blame-hang-timeout 1m --blame-hang-dump-type none + + - name: ✔ Verify NUnit template + if: matrix.os != 'windows-latest' + run: | + dotnet new bunit --framework nunit --no-restore -o ${GITHUB_WORKSPACE}/TemplateTestNunit + echo '' >> ${GITHUB_WORKSPACE}/TemplateTestNunit/Directory.Build.props + dotnet restore ${GITHUB_WORKSPACE}/TemplateTestNunit --source ${GITHUB_WORKSPACE}/packages --source https://api.nuget.org/v3/index.json + dotnet test ${GITHUB_WORKSPACE}/TemplateTestNunit --blame-hang --blame-hang-timeout 1m --blame-hang-dump-type none + + - name: ✔ Verify MSTest template + if: matrix.os != 'windows-latest' + run: | + dotnet new bunit --framework mstest --no-restore -o ${GITHUB_WORKSPACE}/TemplateTestMstest + echo '' >> ${GITHUB_WORKSPACE}/TemplateTestMstest/Directory.Build.props + dotnet restore ${GITHUB_WORKSPACE}/TemplateTestMstest --source ${GITHUB_WORKSPACE}/packages --source https://api.nuget.org/v3/index.json + dotnet test ${GITHUB_WORKSPACE}/TemplateTestMstest --blame-hang --blame-hang-timeout 1m --blame-hang-dump-type none # DocFx only works well on Windows currently - name: 📄 Build documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index 708979e43..8fce6ee82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,35 @@ All notable changes to **bUnit** will be documented in this file. The project ad ## [Unreleased] +This first release of 2022 includes a one fix and four additions. A huge thank you to [Steven Giesel (@linkdotnet)](https://github.com/linkdotnet) and [Denis Ekart (@denisekart)](https://github.com/denisekart) for their contributions to this release. + +Also a big shout out to **bUnit's sponsors** who helped make this release happen. + +**The higher tier sponsors are:** + +- [Progress Telerik](https://github.com/Progress-Telerik) +- [Syncfusion](https://github.com/syncfusion) +- [CTRL Informatique](https://github.com/CTRL-Informatique) + +**Other sponsors are:** + +- [Hassan Rezk Habib (@Garderoben)](https://github.com/hassanhabib) +- [Jonny Larsson (@Garderoben)](https://github.com/Garderoben) +- [Domn Werner (@domn1995)](https://github.com/domn1995) +- [Mladen Macanović (@stsrki)](https://github.com/stsrki) +- [@ChristopheDEBOVE](https://github.com/ChristopheDEBOVE) + +### Added + +- Added `FakeSignOutSessionStateManage` type in Blazor, that makes it easy to test components that use the `SignOutSessionStateManage` type. By [@linkdotnet](https://github.com/linkdotnet). +- Added a validation to `AddChildContent` method in `ComponentParameterCollectionBuilder` that will throw an exception if the component's `ChildContent` is a generic type. By [@denisekart](https://github.com/denisekart). +- Added more optional arguments for `Click` and `DoubleClick` extensions which were introduced in .NET 5 and .NET 6. By [@linkdotnet](https://github.com/linkdotnet). +- Added template support for `Nunit` and `MSTest` unit test frameworks. By [@denisekart](https://github.com/denisekart). + +### Fixed + +- Changed `GetDispatchEventTasks` for bubbling events such that handled exceptions are not rethrown later from the `WaitFor...` helpers methods. Reported by [@AndrewStrickland](https://github.com/AndrewStrickland). Fixed by [@linkdotnet](https://github.com/linkdotnet) + ## [1.4.15] - 2021-12-18 This release reintroduces `Stub` and related back into the main library, so the "preview" library `bunit.web.mock` is already obsolete. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45a4ab997..525a353d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,8 @@ bUnit accepts fixes and features. Here is what you should do when writing code f - Follow the coding conventions used throughout the bUnit project. In general, they align with the AspNetCore teams [coding guidelines](https://github.com/dotnet/aspnetcore/wiki/Engineering-guidelines#coding-guidelines). - Add, remove, or delete unit tests to cover your changes. Make sure tests are specific to the changes you are making. Tests need to be provided for every bug/feature that is completed. -- This repository follow the truck-based development approach, meaning changes should be based on the `main` branch +- This repository follow the truck-based development approach, meaning changes should be based on the `main` branch. +- Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) style commit messages. - Any code or documentation you share with the bUnit projects should fall under the projects license agreement. Here are some resources to help you get started on how to contribute code or new content: diff --git a/Directory.Build.props b/Directory.Build.props index efc9a63f5..e8c988957 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ - 3.1.1 + 3.1.22 5.0.0 6.0.0 @@ -47,14 +47,14 @@ - - + + - + diff --git a/README.md b/README.md index 3c5bf6963..3186aeb02 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,12 @@ A huge thank you to the [sponsors of my work with bUnit](https://github.com/spon Shout outs and a big thank you [to all the contributors](https://github.com/bUnit-dev/bUnit/graphs/contributors) to the library, including those that raise issues, provide input to issues, and those who send pull requests. Thank you! +These good people have contributed code or documentation to bUnit: + + + + + ## Code of conduct This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. diff --git a/docs/site/docs/getting-started/create-test-project.md b/docs/site/docs/getting-started/create-test-project.md index 981aca17c..f567af491 100644 --- a/docs/site/docs/getting-started/create-test-project.md +++ b/docs/site/docs/getting-started/create-test-project.md @@ -5,9 +5,9 @@ title: Creating a new bUnit test project # Creating a new bUnit test project -To write tests, you need a place to put them - a test project. bUnit is not a unit test runner, so a general-purpose test framework like xUnit, NUnit, or MSTest is needed in addition to bUnit in order to write and run tests. +To write tests, you need a place to put them - a test project. bUnit is not a unit test runner, so a general-purpose test framework like xUnit, NUnit, or MSTest is needed in addition to bUnit in order to write and run tests. -To use bUnit with xUnit, the easiest approach is to use the bUnit project template described in the [Create a test project with bUnit template](#creating-a-test-project-with-bunit-template) section further down the page. To create a test project manually and in a general-purpose testing frameworks agnostic way, read the following section. +To use bUnit, the easiest approach is to use the bUnit project template described in the [Create a test project with bUnit template](#creating-a-test-project-with-bunit-template) section further down the page. To create a test project manually and in a general-purpose testing frameworks agnostic way, read the following section. ## Creating a test project manually @@ -57,7 +57,7 @@ dotnet add package bunit --version #{NBGV_NuGetPackageVersion}# **3. Configure project settings** -The test projects setting needs to be set to the following: +The test projects setting needs to be set to the following: - the project's SDK needs to be set to `Microsoft.NET.Sdk.Razor` - set the `` to `net6.0` @@ -176,7 +176,7 @@ The result should be a test project with a `.csproj` that looks like this (non b ## Creating a test project with bUnit template -To skip a few steps in the guide above, use the [bUnit test project template](https://www.nuget.org/packages/bunit.template/). The bUnit project template currently only works with the xUnit general-purpose testing framework, but other frameworks will be supported in the future. +To skip a few steps in the guide above, use the [bUnit test project template](https://www.nuget.org/packages/bunit.template/). The steps for creating a test project with the bUnit template are as follows: @@ -198,11 +198,31 @@ dotnet new --install bunit.template::#{NBGV_NuGetPackageVersion}# Use the following command to create a bUnit with xUnit test project: +# [xUnit](#tab/xunit) + ```dotnetcli -dotnet new bunit -o +dotnet new bunit --framework xunit -o ``` -The `-o` option in the `dotnet new` command above is used to specify the name of the test project. +# [NUnit](#tab/nunit) + +```dotnetcli +dotnet new bunit --framework nunit -o +``` + +# [MSTest](#tab/mstest) + +```dotnetcli +dotnet new bunit --framework mstest -o +``` + +*** + +The `--framework` option in the `dotnet new` command above is used to specify the unit testing framework used by the test project. If the `--framework` option is omitted, the default test framework `xunit` will be configured. Currently supported options are the following: + +- `xunit` - [xUnit](https://xunit.net/), +- `nunit` - [NUnit](https://nunit.org/), +- `mstest` - [MSTest](https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest) **3. Add the test project to your solution** diff --git a/src/bunit.core/ComponentParameterCollectionBuilder.cs b/src/bunit.core/ComponentParameterCollectionBuilder.cs index e29d74afa..e9581b779 100644 --- a/src/bunit.core/ComponentParameterCollectionBuilder.cs +++ b/src/bunit.core/ComponentParameterCollectionBuilder.cs @@ -246,6 +246,9 @@ public ComponentParameterCollectionBuilder AddChildContent(RenderFra if (!HasChildContentParameter()) throw new ArgumentException($"The component '{typeof(TComponent)}' does not have a {ChildContent} [Parameter] attribute.", nameof(childContent)); + if (HasGenericChildContentParameter()) + throw new ArgumentException($"Calling AddChildContent on component '{typeof(TComponent)}' with a generic {ChildContent} type (RenderFragment) is not supported. Use the 'Add(p => p.ChildContent, p => {{content}})' method instead.", nameof(childContent)); + return AddParameter(ChildContent, childContent); } @@ -375,6 +378,10 @@ private static bool HasChildContentParameter() => TComponentType.GetProperty(ChildContent, BindingFlags.Public | BindingFlags.Instance) is PropertyInfo ccProp && ccProp.GetCustomAttribute(inherit: false) is not null; + private static bool HasGenericChildContentParameter() + => TComponentType.GetProperty(ChildContent, BindingFlags.Public | BindingFlags.Instance) is PropertyInfo ccProp + && ccProp.PropertyType.IsGenericType; + private ComponentParameterCollectionBuilder AddParameter(string name, [AllowNull] TValue value) { parameters.Add(ComponentParameter.CreateParameter(name, value)); diff --git a/src/bunit.core/Rendering/ITestRenderer.cs b/src/bunit.core/Rendering/ITestRenderer.cs index ce36d1bb3..c4d3bee97 100644 --- a/src/bunit.core/Rendering/ITestRenderer.cs +++ b/src/bunit.core/Rendering/ITestRenderer.cs @@ -23,7 +23,24 @@ public interface ITestRenderer /// Information that the renderer can use to update the state of the existing render tree to match the UI. /// Arguments to be passed to the event handler. /// A which will complete once all asynchronous processing related to the event has completed. - Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs); + Task DispatchEventAsync( + ulong eventHandlerId, + EventFieldInfo fieldInfo, + EventArgs eventArgs); + + /// + /// Notifies the renderer that an event has occurred. + /// + /// The value from the original event attribute. + /// Information that the renderer can use to update the state of the existing render tree to match the UI. + /// Arguments to be passed to the event handler. + /// Set to true to ignore the . + /// A which will complete once all asynchronous processing related to the event has completed. + Task DispatchEventAsync( + ulong eventHandlerId, + EventFieldInfo fieldInfo, + EventArgs eventArgs, + bool ignoreUnknownEventHandlers); /// /// Renders the . diff --git a/src/bunit.core/Rendering/TestRenderer.cs b/src/bunit.core/Rendering/TestRenderer.cs index 41a622bf5..77b9cf8b4 100644 --- a/src/bunit.core/Rendering/TestRenderer.cs +++ b/src/bunit.core/Rendering/TestRenderer.cs @@ -59,13 +59,21 @@ public IRenderedComponentBase RenderComponent(ComponentP } /// - public new Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs) + public new Task DispatchEventAsync( + ulong eventHandlerId, + EventFieldInfo fieldInfo, + EventArgs eventArgs) => DispatchEventAsync(eventHandlerId, fieldInfo, eventArgs, ignoreUnknownEventHandlers: false); + + /// + public Task DispatchEventAsync( + ulong eventHandlerId, + EventFieldInfo fieldInfo, + EventArgs eventArgs, + bool ignoreUnknownEventHandlers) { if (fieldInfo is null) throw new ArgumentNullException(nameof(fieldInfo)); - ResetUnhandledException(); - var result = Dispatcher.InvokeAsync(() => { try @@ -79,7 +87,7 @@ public IRenderedComponentBase RenderComponent(ComponentP } }); - if (result.IsFaulted && result.Exception is not null) + if (result.IsFaulted && result.Exception is not null && !(ignoreUnknownEventHandlers && result.Exception.InnerException is UnknownEventHandlerIdException)) { HandleException(result.Exception); } diff --git a/src/bunit.core/bunit.core.csproj b/src/bunit.core/bunit.core.csproj index ad090069b..016b4caec 100644 --- a/src/bunit.core/bunit.core.csproj +++ b/src/bunit.core/bunit.core.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/bunit.template/bunit.template.csproj b/src/bunit.template/bunit.template.csproj index 0a6976e6a..b2b18da37 100644 --- a/src/bunit.template/bunit.template.csproj +++ b/src/bunit.template/bunit.template.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/bunit.template/template/.template.config/dotnetcli.host.json b/src/bunit.template/template/.template.config/dotnetcli.host.json index ca2669aee..d78ed2e98 100644 --- a/src/bunit.template/template/.template.config/dotnetcli.host.json +++ b/src/bunit.template/template/.template.config/dotnetcli.host.json @@ -3,6 +3,15 @@ "skipRestore": { "longName": "no-restore", "shortName": "" + }, + "UnitTestFramework": { + "longName": "framework", + "shortName": "" } - } -} \ No newline at end of file + }, + "usageExamples": [ + "--framework xunit", + "--framework nunit", + "--framework mstest" + ] +} diff --git a/src/bunit.template/template/.template.config/template.json b/src/bunit.template/template/.template.config/template.json index 9614ffc45..47766b885 100644 --- a/src/bunit.template/template/.template.config/template.json +++ b/src/bunit.template/template/.template.config/template.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "http://json.schemastore.org/template", "author": "Egil Hansen", "classifications": [ @@ -18,75 +18,76 @@ }, "sourceName": "Company.BlazorTests1", "defaultName": "BlazorTestProject1", - "preferNameDirectory": true, - "symbols": { - "HostIdentifier": { - "type": "bind", - "binding": "HostIdentifier" - }, - "skipRestore": { - "type": "parameter", - "datatype": "bool", - "description": "If specified, skips the automatic restore of the project on create.", - "defaultValue": "false" - } - }, + "preferNameDirectory": true, + "sources": [ + { + "modifiers": [ + { + "exclude": [ "BunitTestContext.cs" ], + "condition": "(testFramework_xunit)" + } + ] + } + ], + "symbols": { + "HostIdentifier": { + "type": "bind", + "binding": "HostIdentifier" + }, + "skipRestore": { + "type": "parameter", + "datatype": "bool", + "description": "If specified, skips the automatic restore of the project on create.", + "defaultValue": "false" + }, + "UnitTestFramework": { + "type": "parameter", + "description": "The target unit testing framework for the project.", + "displayName": "Unit test framework", + "datatype": "choice", + "defaultValue": "xunit", + "replaces": "UnitTestFramework", + "choices": [ + { + "choice": "nunit", + "description": "NUnit unit testing framework", + "displayName": "NUnit" + }, + { + "choice": "xunit", + "description": "xUnit unit testing framework", + "displayName": "xUnit" + }, + { + "choice": "mstest", + "description": "MSTest unit testing framework", + "displayName": "MSTest" + } + ] + }, + "testFramework_nunit": { + "type": "computed", + "value": "UnitTestFramework == \"nunit\"" + }, + "testFramework_xunit": { + "type": "computed", + "value": "UnitTestFramework == \"xunit\"" + }, + "testFramework_mstest": { + "type": "computed", + "value": "UnitTestFramework == \"mstest\"" + } + }, "primaryOutputs": [ { "path": "Company.BlazorTests1.csproj" } ], "postActions": [ - { - "condition": "(!skipRestore)", - "description": "Restore NuGet packages required by this project.", - "manualInstructions": [ { "text": "Run 'dotnet restore'" } ], - "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", - "continueOnError": true - } - //}, - //{ - // "description": "Adding latests bUnit.Core reference", - // "actionId": "B17581D1-C5C9-4489-8F0A-004BE667B814", - // "continueOnError": true, - // "manualInstructions": [ - // { - // "text": "Manually add the 'bunit.core' reference to your project file" - // } - // ], - // "args": { - // "referenceType": "package", - // "reference": "bunit.core", - // "projectFileExtensions": ".csproj" - // } - //}, - //{ - // "description": "Adding latests bUnit.Web reference", - // "actionId": "B17581D1-C5C9-4489-8F0A-004BE667B814", - // "continueOnError": true, - // "manualInstructions": [ - // { - // "text": "Manually add the 'bunit.web' reference to your project file" - // } - // ], - // "args": { - // "referenceType": "package", - // "reference": "bunit.web", - // "projectFileExtensions": ".csproj" - // } - //}, - //{ - // "description": "Adding latests bUnit.xUnit reference", - // "actionId": "B17581D1-C5C9-4489-8F0A-004BE667B814", - // "continueOnError": true, - // "manualInstructions": [ - // { - // "text": "Manually add the 'bunit.xunit' reference to your project file" - // } - // ], - // "args": { - // "referenceType": "package", - // "reference": "bunit.xunit", - // "projectFileExtensions": ".csproj" - // } - //} + { + "condition": "(!skipRestore)", + "description": "Restore NuGet packages required by this project.", + "manualInstructions": [ { "text": "Run 'dotnet restore'" } ], + "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", + "continueOnError": true + } ] } diff --git a/src/bunit.template/template/BunitTestContext.cs b/src/bunit.template/template/BunitTestContext.cs new file mode 100644 index 000000000..cdbaba27d --- /dev/null +++ b/src/bunit.template/template/BunitTestContext.cs @@ -0,0 +1,29 @@ +using Bunit; +#if (testFramework_nunit) +using NUnit.Framework; +#elif (testFramework_mstest) +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif + +namespace Company.BlazorTests1; + +/// +/// Test context wrapper for bUnit. +/// Read more about using here. +/// +public abstract class BunitTestContext : TestContextWrapper +{ +#if (testFramework_nunit) + [SetUp] +#elif (testFramework_mstest) + [TestInitialize] +#endif + public void Setup() => TestContext = new Bunit.TestContext(); + +#if (testFramework_nunit) + [TearDown] +#elif (testFramework_mstest) + [TestCleanup] +#endif + public void TearDown() => TestContext?.Dispose(); +} diff --git a/src/bunit.template/template/Company.BlazorTests1.csproj b/src/bunit.template/template/Company.BlazorTests1.csproj index 456a86482..500c1aaac 100644 --- a/src/bunit.template/template/Company.BlazorTests1.csproj +++ b/src/bunit.template/template/Company.BlazorTests1.csproj @@ -9,21 +9,37 @@ - + + + + - + - - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + + + + + + + + + + + diff --git a/src/bunit.template/template/CounterCSharpTests.cs b/src/bunit.template/template/CounterCSharpTests.cs index 19f046721..020dfabbd 100644 --- a/src/bunit.template/template/CounterCSharpTests.cs +++ b/src/bunit.template/template/CounterCSharpTests.cs @@ -4,9 +4,22 @@ namespace Company.BlazorTests1; /// These tests are written entirely in C#. /// Learn more at https://bunit.dev/docs/getting-started/writing-tests.html#creating-basic-tests-in-cs-files /// +#if (testFramework_xunit) public class CounterCSharpTests : TestContext +#elif (testFramework_nunit) +public class CounterCSharpTests : BunitTestContext +#elif (testFramework_mstest) +[TestClass] +public class CounterCSharpTests : BunitTestContext +#endif { +#if (testFramework_xunit) [Fact] +#elif (testFramework_nunit) + [Test] +#elif (testFramework_mstest) + [TestMethod] +#endif public void CounterStartsAtZero() { // Arrange @@ -16,7 +29,13 @@ public void CounterStartsAtZero() cut.Find("p").MarkupMatches("

Current count: 0

"); } +#if (testFramework_xunit) [Fact] +#elif (testFramework_nunit) + [Test] +#elif (testFramework_mstest) + [TestMethod] +#endif public void ClickingButtonIncrementsCounter() { // Arrange diff --git a/src/bunit.template/template/CounterRazorTests.razor b/src/bunit.template/template/CounterRazorTests.razor index 63ba07dc1..7992eda2d 100644 --- a/src/bunit.template/template/CounterRazorTests.razor +++ b/src/bunit.template/template/CounterRazorTests.razor @@ -1,11 +1,24 @@ +@*#if (testFramework_xunit)*@ @inherits TestContext +@*#elif (testFramework_nunit)*@ +@inherits BunitTestContext +@*#elif (testFramework_mstest)*@ +@attribute [TestClass] +@inherits BunitTestContext +@*#endif*@ These tests are written entirely in razor and C# syntax. Learn more at https://bunit.dev/docs/getting-started/writing-tests.html#creating-basic-tests-in-razor-files @code { - [Fact] +@*#if (testFramework_xunit)*@ + [Fact] +@*#elif (testFramework_nunit)*@ + [Test] +@*#elif (testFramework_mstest)*@ + [TestMethod] +@*#endif*@ public void CounterStartsAtZero() { // Arrange @@ -14,8 +27,13 @@ Learn more at https://bunit.dev/docs/getting-started/writing-tests.html#creating // Assert that content of the paragraph shows counter at zero cut.Find("p").MarkupMatches(@

Current count: 0

); } - - [Fact] +@*#if (testFramework_xunit)*@ + [Fact] +@*#elif (testFramework_nunit)*@ + [Test] +@*#elif (testFramework_mstest)*@ + [TestMethod] +@*#endif*@ public void ClickingButtonIncrementsCounter() { // Arrange diff --git a/src/bunit.template/template/_Imports.razor b/src/bunit.template/template/_Imports.razor index 5703c2018..054402d02 100644 --- a/src/bunit.template/template/_Imports.razor +++ b/src/bunit.template/template/_Imports.razor @@ -3,4 +3,10 @@ @using Microsoft.Extensions.DependencyInjection @using Bunit @using Bunit.TestDoubles +@*#if (testFramework_xunit)*@ @using Xunit +@*#elif (testFramework_nunit)*@ +@using NUnit.Framework +@*#elif (testFramework_mstest)*@ +@using Microsoft.VisualStudio.TestTools.UnitTesting +@*#endif*@ diff --git a/src/bunit.web.testcomponents/bunit.web.testcomponents.csproj b/src/bunit.web.testcomponents/bunit.web.testcomponents.csproj index 7055a1fdf..207937c5c 100644 --- a/src/bunit.web.testcomponents/bunit.web.testcomponents.csproj +++ b/src/bunit.web.testcomponents/bunit.web.testcomponents.csproj @@ -24,7 +24,7 @@ NOTE: This package represents experimental features of bUnit that has been super - +
diff --git a/src/bunit.web/EventDispatchExtensions/MouseEventDispatchExtensions.cs b/src/bunit.web/EventDispatchExtensions/MouseEventDispatchExtensions.cs index 72a63ea2f..4164ac107 100644 --- a/src/bunit.web/EventDispatchExtensions/MouseEventDispatchExtensions.cs +++ b/src/bunit.web/EventDispatchExtensions/MouseEventDispatchExtensions.cs @@ -252,6 +252,7 @@ public static void MouseUp(this IElement element, MouseEventArgs eventArgs) /// A task that completes when the event handler is done. public static Task MouseUpAsync(this IElement element, MouseEventArgs eventArgs) => element.TriggerEventAsync("onmouseup", eventArgs); +#if NET6_0_OR_GREATER /// /// Raises the @onclick event on , passing the provided /// properties to the event handler via a object. @@ -262,6 +263,74 @@ public static void MouseUp(this IElement element, MouseEventArgs eventArgs) /// The Y coordinate of the mouse pointer in global (screen) coordinates. /// The X coordinate of the mouse pointer in local (DOM content) coordinates. /// The Y coordinate of the mouse pointer in local (DOM content) coordinates. + /// The X coordinate of the mouse pointer relative to the whole document. + /// The Y coordinate of the mouse pointer relative to the whole document. + /// The X coordinate of the mouse pointer in relative (Target Element) coordinates. + /// The Y coordinate of the mouse pointer in relative (Target Element) coordinates. + /// + /// The button number that was pressed when the mouse event was fired: Left button=0, + /// middle button=1 (if present), right button=2. For mice configured for left handed + /// use in which the button actions are reversed the values are instead read from + /// right to left. + /// + /// + /// The buttons being pressed when the mouse event was fired: Left button=1, Right + /// button=2, Middle (wheel) button=4, 4th button (typically, "Browser Back" button)=8, + /// 5th button (typically, "Browser Forward" button)=16. If two or more buttons are + /// pressed, returns the logical sum of the values. E.g., if Left button and Right + /// button are pressed, returns 3 (=1 | 2). + /// + /// true if the control key was down when the event was fired. false otherwise. + /// true if the shift key was down when the event was fired. false otherwise. + /// true if the alt key was down when the event was fired. false otherwise. + /// true if the meta key was down when the event was fired. false otherwise. + /// Gets or sets the type of the event. + public static void Click(this IElement element, long detail = 1, double screenX = default, double screenY = default, double clientX = default, double clientY = default, double pageX = 0, double pageY = 0, double offsetX = 0, double offsetY = 0, long button = default, long buttons = default, bool ctrlKey = default, bool shiftKey = default, bool altKey = default, bool metaKey = default, string? type = default) + => _ = ClickAsync(element, new MouseEventArgs { Detail = detail, ScreenX = screenX, ScreenY = screenY, ClientX = clientX, PageX = pageX, PageY = pageY, OffsetX = offsetX, OffsetY = offsetY, ClientY = clientY, Button = button, Buttons = buttons, CtrlKey = ctrlKey, ShiftKey = shiftKey, AltKey = altKey, MetaKey = metaKey, Type = type! }); +#elif NET5_0 + /// + /// Raises the @onclick event on , passing the provided + /// properties to the event handler via a object. + /// + /// The element to raise the event on. + /// A count of consecutive clicks that happened in a short amount of time, incremented by one. + /// The X coordinate of the mouse pointer in global (screen) coordinates. + /// The Y coordinate of the mouse pointer in global (screen) coordinates. + /// The X coordinate of the mouse pointer in local (DOM content) coordinates. + /// The Y coordinate of the mouse pointer in local (DOM content) coordinates. + /// The X coordinate of the mouse pointer in relative (Target Element) coordinates. + /// The Y coordinate of the mouse pointer in relative (Target Element) coordinates. + /// + /// The button number that was pressed when the mouse event was fired: Left button=0, + /// middle button=1 (if present), right button=2. For mice configured for left handed + /// use in which the button actions are reversed the values are instead read from + /// right to left. + /// + /// + /// The buttons being pressed when the mouse event was fired: Left button=1, Right + /// button=2, Middle (wheel) button=4, 4th button (typically, "Browser Back" button)=8, + /// 5th button (typically, "Browser Forward" button)=16. If two or more buttons are + /// pressed, returns the logical sum of the values. E.g., if Left button and Right + /// button are pressed, returns 3 (=1 | 2). + /// + /// true if the control key was down when the event was fired. false otherwise. + /// true if the shift key was down when the event was fired. false otherwise. + /// true if the alt key was down when the event was fired. false otherwise. + /// true if the meta key was down when the event was fired. false otherwise. + /// Gets or sets the type of the event. + public static void Click(this IElement element, long detail = 1, double screenX = default, double screenY = default, double clientX = default, double clientY = default, double offsetX = 0, double offsetY = 0, long button = default, long buttons = default, bool ctrlKey = default, bool shiftKey = default, bool altKey = default, bool metaKey = default, string? type = default) + => _ = ClickAsync(element, new MouseEventArgs { Detail = detail, ScreenX = screenX, ScreenY = screenY, ClientX = clientX, OffsetX = offsetX, OffsetY = offsetY, ClientY = clientY, Button = button, Buttons = buttons, CtrlKey = ctrlKey, ShiftKey = shiftKey, AltKey = altKey, MetaKey = metaKey, Type = type! }); +#else + /// + /// Raises the @onclick event on , passing the provided + /// properties to the event handler via a object. + /// + /// The element to raise the event on. + /// A count of consecutive clicks that happened in a short amount of time, incremented by one. + /// The X coordinate of the mouse pointer in global (screen) coordinates. + /// The Y coordinate of the mouse pointer in global (screen) coordinates. + /// The X coordinate of the mouse pointer in local (DOM content) coordinates. + /// The Y coordinate of the mouse pointer in local (DOM content) coordinates. /// /// The button number that was pressed when the mouse event was fired: Left button=0, /// middle button=1 (if present), right button=2. For mice configured for left handed @@ -282,6 +351,8 @@ public static void MouseUp(this IElement element, MouseEventArgs eventArgs) /// Gets or sets the type of the event. public static void Click(this IElement element, long detail = 1, double screenX = default, double screenY = default, double clientX = default, double clientY = default, long button = default, long buttons = default, bool ctrlKey = default, bool shiftKey = default, bool altKey = default, bool metaKey = default, string? type = default) => _ = ClickAsync(element, new MouseEventArgs { Detail = detail, ScreenX = screenX, ScreenY = screenY, ClientX = clientX, ClientY = clientY, Button = button, Buttons = buttons, CtrlKey = ctrlKey, ShiftKey = shiftKey, AltKey = altKey, MetaKey = metaKey, Type = type! }); +#endif + /// /// Raises the @onclick event on , passing the provided @@ -301,6 +372,76 @@ public static void Click(this IElement element, MouseEventArgs eventArgs) /// A task that completes when the event handler is done. public static Task ClickAsync(this IElement element, MouseEventArgs eventArgs) => element.TriggerEventAsync("onclick", eventArgs); +#if NET6_0_OR_GREATER + /// + /// Raises the @ondblclick event on , passing the provided + /// properties to the event handler via a object. + /// + /// The element to raise the event on. + /// A count of consecutive clicks that happened in a short amount of time, incremented by one. + /// The X coordinate of the mouse pointer in global (screen) coordinates. + /// The Y coordinate of the mouse pointer in global (screen) coordinates. + /// The X coordinate of the mouse pointer in local (DOM content) coordinates. + /// The Y coordinate of the mouse pointer in local (DOM content) coordinates. + /// The X coordinate of the mouse pointer relative to the whole document. + /// The Y coordinate of the mouse pointer relative to the whole document. + /// The X coordinate of the mouse pointer in relative (Target Element) coordinates. + /// The Y coordinate of the mouse pointer in relative (Target Element) coordinates. + /// + /// The button number that was pressed when the mouse event was fired: Left button=0, + /// middle button=1 (if present), right button=2. For mice configured for left handed + /// use in which the button actions are reversed the values are instead read from + /// right to left. + /// + /// + /// The buttons being pressed when the mouse event was fired: Left button=1, Right + /// button=2, Middle (wheel) button=4, 4th button (typically, "Browser Back" button)=8, + /// 5th button (typically, "Browser Forward" button)=16. If two or more buttons are + /// pressed, returns the logical sum of the values. E.g., if Left button and Right + /// button are pressed, returns 3 (=1 | 2). + /// + /// true if the control key was down when the event was fired. false otherwise. + /// true if the shift key was down when the event was fired. false otherwise. + /// true if the alt key was down when the event was fired. false otherwise. + /// true if the meta key was down when the event was fired. false otherwise. + /// Gets or sets the type of the event. + public static void DoubleClick(this IElement element, long detail = 2, double screenX = default, double screenY = default, double clientX = default, double clientY = default, double pageX = 0, double pageY = 0, double offsetX = 0, double offsetY = 0, long button = default, long buttons = default, bool ctrlKey = default, bool shiftKey = default, bool altKey = default, bool metaKey = default, string? type = default) + => _ = DoubleClickAsync(element, new MouseEventArgs { Detail = detail, ScreenX = screenX, ScreenY = screenY, ClientX = clientX, ClientY = clientY, PageX = pageX, PageY = pageY, OffsetX = offsetX, OffsetY = offsetY, Button = button, Buttons = buttons, CtrlKey = ctrlKey, ShiftKey = shiftKey, AltKey = altKey, MetaKey = metaKey, Type = type! }); +#elif NET5_0 + /// + /// Raises the @ondblclick event on , passing the provided + /// properties to the event handler via a object. + /// + /// The element to raise the event on. + /// A count of consecutive clicks that happened in a short amount of time, incremented by one. + /// The X coordinate of the mouse pointer in global (screen) coordinates. + /// The Y coordinate of the mouse pointer in global (screen) coordinates. + /// The X coordinate of the mouse pointer in local (DOM content) coordinates. + /// The Y coordinate of the mouse pointer in local (DOM content) coordinates. + /// The X coordinate of the mouse pointer in relative (Target Element) coordinates. + /// The Y coordinate of the mouse pointer in relative (Target Element) coordinates. + /// + /// The button number that was pressed when the mouse event was fired: Left button=0, + /// middle button=1 (if present), right button=2. For mice configured for left handed + /// use in which the button actions are reversed the values are instead read from + /// right to left. + /// + /// + /// The buttons being pressed when the mouse event was fired: Left button=1, Right + /// button=2, Middle (wheel) button=4, 4th button (typically, "Browser Back" button)=8, + /// 5th button (typically, "Browser Forward" button)=16. If two or more buttons are + /// pressed, returns the logical sum of the values. E.g., if Left button and Right + /// button are pressed, returns 3 (=1 | 2). + /// + /// true if the control key was down when the event was fired. false otherwise. + /// true if the shift key was down when the event was fired. false otherwise. + /// true if the alt key was down when the event was fired. false otherwise. + /// true if the meta key was down when the event was fired. false otherwise. + /// Gets or sets the type of the event. + public static void DoubleClick(this IElement element, long detail = 2, double screenX = default, double screenY = default, double clientX = default, double clientY = default, double offsetX = 0, double offsetY = 0, long button = default, long buttons = default, bool ctrlKey = default, bool shiftKey = default, bool altKey = default, bool metaKey = default, string? type = default) + => _ = DoubleClickAsync(element, new MouseEventArgs { Detail = detail, ScreenX = screenX, ScreenY = screenY, ClientX = clientX, ClientY = clientY, OffsetX = offsetX, OffsetY = offsetY, Button = button, Buttons = buttons, CtrlKey = ctrlKey, ShiftKey = shiftKey, AltKey = altKey, MetaKey = metaKey, Type = type! }); +#else + /// /// Raises the @ondblclick event on , passing the provided /// properties to the event handler via a object. @@ -331,7 +472,7 @@ public static void Click(this IElement element, MouseEventArgs eventArgs) /// Gets or sets the type of the event. public static void DoubleClick(this IElement element, long detail = 2, double screenX = default, double screenY = default, double clientX = default, double clientY = default, long button = default, long buttons = default, bool ctrlKey = default, bool shiftKey = default, bool altKey = default, bool metaKey = default, string? type = default) => _ = DoubleClickAsync(element, new MouseEventArgs { Detail = detail, ScreenX = screenX, ScreenY = screenY, ClientX = clientX, ClientY = clientY, Button = button, Buttons = buttons, CtrlKey = ctrlKey, ShiftKey = shiftKey, AltKey = altKey, MetaKey = metaKey, Type = type! }); - +#endif /// /// Raises the @ondblclick event on , passing the provided /// to the event handler. diff --git a/src/bunit.web/EventDispatchExtensions/TriggerEventDispatchExtensions.cs b/src/bunit.web/EventDispatchExtensions/TriggerEventDispatchExtensions.cs index 35411bb76..310f59636 100644 --- a/src/bunit.web/EventDispatchExtensions/TriggerEventDispatchExtensions.cs +++ b/src/bunit.web/EventDispatchExtensions/TriggerEventDispatchExtensions.cs @@ -80,7 +80,11 @@ private static Task TriggerBubblingEventAsync(ITestRenderer renderer, IElement e return Task.WhenAll(eventTasks); } - private static List GetDispatchEventTasks(ITestRenderer renderer, IElement element, string eventName, EventArgs eventArgs) + private static List GetDispatchEventTasks( + ITestRenderer renderer, + IElement element, + string eventName, + EventArgs eventArgs) { var eventAttrName = Htmlizer.ToBlazorAttribute(eventName); var eventStopPropagationAttrName = $"{eventAttrName}:stoppropagation"; @@ -90,16 +94,8 @@ private static List GetDispatchEventTasks(ITestRenderer renderer, IElement { if (candidate.TryGetEventId(eventAttrName, out var id)) { - try - { - var info = new EventFieldInfo() { FieldValue = eventName }; - eventTasks.Add(renderer.DispatchEventAsync(id, info, eventArgs)); - } - catch (UnknownEventHandlerIdException) when (eventTasks.Count > 0) - { - // Capture and ignore NoEventHandlerException for bubbling events - // if at least one event handler has been triggered without throwing. - } + var info = new EventFieldInfo() { FieldValue = eventName }; + eventTasks.Add(renderer.DispatchEventAsync(id, info, eventArgs, ignoreUnknownEventHandlers: eventTasks.Count > 0)); } if (candidate.HasAttribute(eventStopPropagationAttrName) || candidate.EventIsDisabled(eventName)) diff --git a/src/bunit.web/JSInterop/BunitJSModuleInterop.cs b/src/bunit.web/JSInterop/BunitJSModuleInterop.cs index 4e618e6c6..ca5746844 100644 --- a/src/bunit.web/JSInterop/BunitJSModuleInterop.cs +++ b/src/bunit.web/JSInterop/BunitJSModuleInterop.cs @@ -9,6 +9,7 @@ public sealed class BunitJSModuleInterop : BunitJSInterop private readonly BunitJSInterop parent; private JSRuntimeMode? handlerMode; + /// /// Gets or sets whether this /// is running in or . @@ -18,6 +19,7 @@ public sealed class BunitJSModuleInterop : BunitJSInterop /// As soon as this is set, the mode will no longer be changed when the /// changes. /// + [SuppressMessage("Critical Bug", "S4275:Getters and setters should access the expected fields", Justification = "Analyzer bug. The property does correctly refer to the correct field(s).")] public override JSRuntimeMode Mode { get => handlerMode ?? parent.Mode; diff --git a/src/bunit.web/Rendering/RenderedComponent.cs b/src/bunit.web/Rendering/RenderedComponent.cs index 2d76e8be9..cfb80271f 100644 --- a/src/bunit.web/Rendering/RenderedComponent.cs +++ b/src/bunit.web/Rendering/RenderedComponent.cs @@ -52,7 +52,6 @@ private void SetComponentAndID(RenderEvent renderEvent) private bool TryFindComponent(RenderTreeFrameDictionary framesCollection, int parentComponentId, out int componentId, out TComponent component) { - var result = false; componentId = -1; component = default!; @@ -67,18 +66,16 @@ private bool TryFindComponent(RenderTreeFrameDictionary framesCollection, int pa { componentId = frame.ComponentId; component = c; - result = true; - break; + return true; } if (TryFindComponent(framesCollection, frame.ComponentId, out componentId, out component)) { - result = true; - break; + return true; } } } - return result; + return false; } } diff --git a/src/bunit.web/TestDoubles/Authorization/FakeAuthorizationExtensions.cs b/src/bunit.web/TestDoubles/Authorization/FakeAuthorizationExtensions.cs index 79cbdf31e..eca12e2d3 100644 --- a/src/bunit.web/TestDoubles/Authorization/FakeAuthorizationExtensions.cs +++ b/src/bunit.web/TestDoubles/Authorization/FakeAuthorizationExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; namespace Bunit.TestDoubles; @@ -16,9 +17,11 @@ public static class FakeAuthorizationExtensions public static TestAuthorizationContext AddTestAuthorization(this TestContextBase context) { if (context is null) - throw new System.ArgumentNullException(nameof(context)); + throw new ArgumentNullException(nameof(context)); context.RenderTree.TryAdd(); + context.Services.AddSingleton(); + context.Services.AddSingleton(s => s.GetRequiredService()); var authCtx = new TestAuthorizationContext(); authCtx.SetNotAuthorized(); authCtx.RegisterAuthorizationServices(context.Services); diff --git a/src/bunit.web/TestDoubles/NavigationManager/FakeSignOutSessionStateManager.cs b/src/bunit.web/TestDoubles/NavigationManager/FakeSignOutSessionStateManager.cs new file mode 100644 index 000000000..4a61b0559 --- /dev/null +++ b/src/bunit.web/TestDoubles/NavigationManager/FakeSignOutSessionStateManager.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; + +namespace Bunit.TestDoubles; + +/// +/// Represents a fake that captures calls to +/// that will help later to assert if the user was logged out +/// +public class FakeSignOutSessionStateManager : SignOutSessionStateManager +{ + /// + /// Returns true when was called, otherwise false + /// + public bool IsSignedOut { get; set; } + + /// + /// Initializes a new instance of + /// + public FakeSignOutSessionStateManager(IJSRuntime jsRuntime) : base(jsRuntime) + { + } + + /// + public override ValueTask SetSignOutState() + { + IsSignedOut = true; + return new ValueTask(); + } + + /// + public override Task ValidateSignOutState() + { + var wasSignedOut = IsSignedOut; + IsSignedOut = false; + return Task.FromResult(wasSignedOut); + } +} diff --git a/src/bunit.web/bunit.web.csproj b/src/bunit.web/bunit.web.csproj index dcfaff7b9..da072abb0 100644 --- a/src/bunit.web/bunit.web.csproj +++ b/src/bunit.web/bunit.web.csproj @@ -10,34 +10,37 @@ bunit.web bUnit.web - bUnit.web is the web specific parts of bUnit, that enables you to easily test and verify the output of Blazor (web) component. + bUnit.web is the web specific parts of bUnit, that enables you to easily test and verify the output of Blazor (web) component. - + - + - + + + - + + diff --git a/src/bunit/bunit.csproj b/src/bunit/bunit.csproj index d45cbd586..8561fdcd4 100644 --- a/src/bunit/bunit.csproj +++ b/src/bunit/bunit.csproj @@ -26,7 +26,7 @@ - + \ No newline at end of file diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index bfb1909ce..2bb73ff69 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -22,7 +22,7 @@ - + diff --git a/tests/bunit.core.tests/ComponentParameterCollectionBuilderTests.cs b/tests/bunit.core.tests/ComponentParameterCollectionBuilderTests.cs index d282b1e8b..fabe0a764 100644 --- a/tests/bunit.core.tests/ComponentParameterCollectionBuilderTests.cs +++ b/tests/bunit.core.tests/ComponentParameterCollectionBuilderTests.cs @@ -560,6 +560,26 @@ public void Test301() .ShouldBeParameter("Value", true, isCascadingValue: false); } + [Fact(DisplayName = "AddChildContent on generic child content parameter throws")] + public void Test302() + { + Action addChildContent = () => new ComponentParameterCollectionBuilder().AddChildContent("

item

"); + + addChildContent.ShouldThrow(); + } + + [Fact(DisplayName = "Add with generic child content works")] + public void Test303() + { + var sut = new ComponentParameterCollectionBuilder(); + + sut.Add(p => p.ChildContent, p => "

item

"); + + sut.Build() + .ShouldHaveSingleItem() + .ShouldBeParameter?>(nameof(TemplatedChildContent.ChildContent), false); + } + private class Params : ComponentBase { [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Public for testing purposes")] @@ -600,4 +620,9 @@ private class InheritedParamsWithOverride : ParamsBase private class InheritedParamsWithoutOverride : InheritedParamsWithOverride { } + + private class TemplatedChildContent : ComponentBase + { + [Parameter] public RenderFragment? ChildContent { get; set; } + } } diff --git a/tests/bunit.core.tests/Rendering/TestRendererTest.cs b/tests/bunit.core.tests/Rendering/TestRendererTest.cs index be4ebf4e2..6bf3b6515 100644 --- a/tests/bunit.core.tests/Rendering/TestRendererTest.cs +++ b/tests/bunit.core.tests/Rendering/TestRendererTest.cs @@ -342,8 +342,7 @@ public void Test101() var cut = RenderComponent(parameters => parameters.Add(p => p.EitherOr, Task.Delay(1))); - var h1 = cut.Find("h1"); - cut.WaitForAssertion(() => h1.TextContent.ShouldBe("SECOND")); + cut.WaitForAssertion(() => cut.Find("h1").TextContent.ShouldBe("SECOND")); } [Fact(DisplayName = "Can render component that awaits completed task in OnInitializedAsync")] @@ -399,6 +398,21 @@ public async Task Test202() firstExceptionReported.ShouldNotBe(secondException); } + [Fact(DisplayName = "UnhandledException has a reference to latest unhandled exception thrown by a component during OnAfterRenderAsync")] + public void Test203() + { + // Arrange + var planned = JSInterop.SetupVoid("foo"); + RenderComponent(); + + // Act + planned.SetVoidResult(); // <-- After here the `OnAfterRenderAsync` progresses and throws an exception. + + // Assert + planned.VerifyInvoke("foo"); + Renderer.UnhandledException.Result.ShouldBeOfType(); + } + internal class NoChildNoParams : ComponentBase { public const string MARKUP = "hello world"; @@ -482,4 +496,15 @@ protected override async Task OnInitializedAsync() internal sealed class AsyncOperationThrowsException : Exception { } } + + internal class AsyncAfterRenderThrows : ComponentBase + { + [Inject] private IJSRuntime JSRuntime { get; set; } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await JSRuntime.InvokeVoidAsync("foo"); + throw new InvalidOperationException(); + } + } } diff --git a/tests/bunit.core.tests/ShouldlyExtensions.cs b/tests/bunit.core.tests/ShouldlyExtensions.cs index f07951bce..980b4707e 100644 --- a/tests/bunit.core.tests/ShouldlyExtensions.cs +++ b/tests/bunit.core.tests/ShouldlyExtensions.cs @@ -2,13 +2,13 @@ namespace Bunit; public static class ShouldlyExtensions { - public static void ShouldSatisfyAllConditions(this T actual, params Action[] conditions) + public static void ShouldSatisfyAllConditions(this TCondition actual, params Action[] conditions) { var conds = conditions.Select(x => (Action)(() => x.Invoke(actual))).ToArray(); ShouldSatisfyAllConditionsTestExtensions.ShouldSatisfyAllConditions(actual, conds); } - public static void ShouldBeParameter(this ComponentParameter parameter, string? name, [AllowNull] T value, bool isCascadingValue) + public static void ShouldBeParameter(this ComponentParameter parameter, string? name, [AllowNull] TValue value, bool isCascadingValue) { parameter.ShouldSatisfyAllConditions( x => x.Name.ShouldBe(name), @@ -16,13 +16,13 @@ public static void ShouldBeParameter(this ComponentParameter parameter, strin x => x.IsCascadingValue.ShouldBe(isCascadingValue)); } - public static T ShouldBeParameter(this ComponentParameter parameter, string? name, bool isCascadingValue) + public static TValue ShouldBeParameter(this ComponentParameter parameter, string? name, bool isCascadingValue) { parameter.ShouldSatisfyAllConditions( x => x.Name.ShouldBe(name), - x => x.Value.ShouldBeOfType(), + x => x.Value.ShouldBeOfType(), x => x.Value.ShouldNotBeNull(), x => x.IsCascadingValue.ShouldBe(isCascadingValue)); - return (T)parameter.Value!; + return (TValue)parameter.Value!; } } diff --git a/tests/bunit.testassets/SampleComponents/DispatcherException.razor b/tests/bunit.testassets/SampleComponents/DispatcherException.razor new file mode 100644 index 000000000..778e83562 --- /dev/null +++ b/tests/bunit.testassets/SampleComponents/DispatcherException.razor @@ -0,0 +1,9 @@ +@if(show) +{ +
+ +
+} +@code { + bool show = true; +} diff --git a/tests/bunit.testassets/SampleComponents/SignOutSessionManagerLoginDisplay.razor b/tests/bunit.testassets/SampleComponents/SignOutSessionManagerLoginDisplay.razor new file mode 100644 index 000000000..98c8342e3 --- /dev/null +++ b/tests/bunit.testassets/SampleComponents/SignOutSessionManagerLoginDisplay.razor @@ -0,0 +1,21 @@ +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@inject SignOutSessionStateManager signOutManager + + + + + + + Log in + + + +@code { + private async Task BeginSignOut(MouseEventArgs args) + { + await signOutManager.SetSignOutState(); + } +} diff --git a/tests/bunit.testassets/bunit.testassets.csproj b/tests/bunit.testassets/bunit.testassets.csproj index 778d28daa..b4bfdbc5d 100644 --- a/tests/bunit.testassets/bunit.testassets.csproj +++ b/tests/bunit.testassets/bunit.testassets.csproj @@ -17,12 +17,16 @@ + + + + diff --git a/tests/bunit.web.tests/BlazorE2E/ComponentRenderingTest.cs b/tests/bunit.web.tests/BlazorE2E/ComponentRenderingTest.cs index d4b152a77..31a551153 100644 --- a/tests/bunit.web.tests/BlazorE2E/ComponentRenderingTest.cs +++ b/tests/bunit.web.tests/BlazorE2E/ComponentRenderingTest.cs @@ -587,4 +587,15 @@ public void CanPatchRenderTreeToMatchLatestDOMState() Assert.Equal(2, completeLIs.Count); Assert.True(completeLIs[0].QuerySelector(".item-isdone").HasAttribute("checked")); } + + [Fact] + public void CanHandleRemovedParentObjects() + { + var cut = RenderComponent(); + + cut.Find("button").Click(); + + cut.WaitForState(() => !cut.FindAll("div").Any()); + cut.FindAll("div").Count.ShouldBe(0); + } } diff --git a/tests/bunit.web.tests/TestDoubles/FakeSignOutSessionStateManagerTest.cs b/tests/bunit.web.tests/TestDoubles/FakeSignOutSessionStateManagerTest.cs new file mode 100644 index 000000000..fbe71d8bc --- /dev/null +++ b/tests/bunit.web.tests/TestDoubles/FakeSignOutSessionStateManagerTest.cs @@ -0,0 +1,29 @@ +namespace Bunit.TestDoubles; + +public class FakeSignOutSessionStateManagerTest : TestContext +{ + [Theory, AutoData] + public void ShouldSignOut(string randomUserName) + { + this.AddTestAuthorization().SetAuthorized(randomUserName); + var cut = RenderComponent(); + + cut.Find("button").Click(); + + Services.GetService()! + .IsSignedOut + .ShouldBeTrue(); + } + + [Fact] + public void ShouldReturnSignOutStateOnValidateSignOutState() + { + var cut = new FakeSignOutSessionStateManager(Mock.Of()); + cut.SetSignOutState(); + + var wasValidate = cut.ValidateSignOutState().Result; + + wasValidate.ShouldBeTrue(); + cut.IsSignedOut.ShouldBeFalse(); + } +} diff --git a/version.json b/version.json index 8319f67c1..b4dd20966 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.4", + "version": "1.5", "assemblyVersion": { "precision": "revision" },