From 29d47908f333e2f091486f9309e9b0ef99f56caf Mon Sep 17 00:00:00 2001 From: bUnit bot Date: Fri, 16 Sep 2022 16:22:08 +0000 Subject: [PATCH 01/11] Set version to '1.11-preview' --- version.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.json b/version.json index ec3e2ad8b..bf09bf642 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.10-preview", + "version": "1.11-preview", "assemblyVersion": { "precision": "revision" }, @@ -22,4 +22,4 @@ "pathFilters": [ "./src" ] -} +} \ No newline at end of file From b87342ff5b01bedeca60c87a86c30f98aa800a6d Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Thu, 22 Sep 2022 22:42:59 +0000 Subject: [PATCH 02/11] fix: ensure timeout timer is disposed in WaitForHelper --- .../WaitForHelpers/WaitForHelper.cs | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/bunit.core/Extensions/WaitForHelpers/WaitForHelper.cs b/src/bunit.core/Extensions/WaitForHelpers/WaitForHelper.cs index 9bcd8a277..e32dd7d73 100644 --- a/src/bunit.core/Extensions/WaitForHelpers/WaitForHelper.cs +++ b/src/bunit.core/Extensions/WaitForHelpers/WaitForHelper.cs @@ -9,6 +9,7 @@ namespace Bunit.Extensions.WaitForHelpers; /// public abstract class WaitForHelper : IDisposable { + private readonly Timer timer; private readonly TaskCompletionSource checkPassedCompletionSource; private readonly Func<(bool CheckPassed, T Content)> completeChecker; private readonly IRenderedFragmentBase renderedFragment; @@ -51,7 +52,13 @@ protected WaitForHelper( logger = renderedFragment.Services.CreateLogger>(); checkPassedCompletionSource = new TaskCompletionSource(); - WaitTask = CreateWaitTask(renderedFragment, timeout); + timer = new Timer(_ => + { + logger.LogWaiterTimedOut(renderedFragment.ComponentId); + checkPassedCompletionSource.TrySetException(new WaitForFailedException(TimeoutErrorMessage, capturedException)); + }); + WaitTask = CreateWaitTask(renderedFragment); + timer.Change(GetRuntimeTimeout(timeout), Timeout.InfiniteTimeSpan); InitializeWaiting(); } @@ -80,6 +87,7 @@ protected virtual void Dispose(bool disposing) return; isDisposed = true; + timer.Dispose(); checkPassedCompletionSource.TrySetCanceled(); renderedFragment.OnAfterRender -= OnAfterRender; logger.LogWaiterDisposed(renderedFragment.ComponentId); @@ -105,9 +113,11 @@ private void InitializeWaiting() } } - private Task CreateWaitTask(IRenderedFragmentBase renderedFragment, TimeSpan? timeout) + private Task CreateWaitTask(IRenderedFragmentBase renderedFragment) { - var renderer = renderedFragment.Services.GetRequiredService(); + var renderer = renderedFragment + .Services + .GetRequiredService(); // Two to failure conditions, that the renderer captures an unhandled // exception from a component or itself, or that the timeout is reached, @@ -115,31 +125,18 @@ private Task CreateWaitTask(IRenderedFragmentBase renderedFragment, TimeSpan? // and the continuations does not happen at the same time. var failureTask = renderer.Dispatcher.InvokeAsync(() => { - var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); - - var renderException = renderer + return renderer .UnhandledException .ContinueWith( x => Task.FromException(x.Result), CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously, - taskScheduler); - - var timeoutTask = Task.Delay(GetRuntimeTimeout(timeout)) - .ContinueWith( - x => - { - logger.LogWaiterTimedOut(renderedFragment.ComponentId); - return Task.FromException(new WaitForFailedException(TimeoutErrorMessage, capturedException)); - }, - CancellationToken.None, - TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously, - taskScheduler); - - return Task.WhenAny(renderException, timeoutTask).Unwrap(); + TaskScheduler.FromCurrentSynchronizationContext()); }).Unwrap(); - return Task.WhenAny(failureTask, checkPassedCompletionSource.Task).Unwrap(); + return Task + .WhenAny(checkPassedCompletionSource.Task, failureTask) + .Unwrap(); } private void OnAfterRender(object? sender, EventArgs args) @@ -170,7 +167,8 @@ private void OnAfterRender(object? sender, EventArgs args) if (StopWaitingOnCheckException) { - checkPassedCompletionSource.TrySetException(new WaitForFailedException(CheckThrowErrorMessage, capturedException)); + checkPassedCompletionSource.TrySetException( + new WaitForFailedException(CheckThrowErrorMessage, capturedException)); Dispose(); } } From a83f145d518843eba6eefb5257404e31795ec77e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Sep 2022 10:38:13 +0000 Subject: [PATCH 03/11] build(deps): Bump Nerdbank.GitVersioning from 3.5.109 to 3.5.113 (#864) Bumps [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) from 3.5.109 to 3.5.113. - [Release notes](https://github.com/dotnet/Nerdbank.GitVersioning/releases) - [Commits](https://github.com/dotnet/Nerdbank.GitVersioning/compare/v3.5.109...v3.5.113) --- updated-dependencies: - dependency-name: Nerdbank.GitVersioning dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Egil Hansen --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 74f317d57..4f3431aa3 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -43,7 +43,7 @@ - + From 35094337f4447c929d8bb0e0092009ccb3413804 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 15:14:31 +0000 Subject: [PATCH 04/11] build(deps): Bump Microsoft.NET.Test.Sdk from 17.3.1 to 17.3.2 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.3.1 to 17.3.2. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.3.1...v17.3.2) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tests/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 9ed6290d0..677ca95da 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -16,7 +16,7 @@ - + From 5f7fd870e729902763a8d6519743ac2ea95443a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 15:10:53 +0000 Subject: [PATCH 05/11] build(deps): Bump SonarAnalyzer.CSharp from 8.44.0.52574 to 8.46.0.54807 Bumps SonarAnalyzer.CSharp from 8.44.0.52574 to 8.46.0.54807. --- updated-dependencies: - dependency-name: SonarAnalyzer.CSharp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index fe29a2151..60b5f6fdc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -48,7 +48,7 @@ - + Date: Mon, 3 Oct 2022 18:24:07 +0200 Subject: [PATCH 06/11] build: Remove dotnet7 nightly runs --- .../workflows/verification-dotnet-nightly.yml | 64 ------------------- 1 file changed, 64 deletions(-) delete mode 100644 .github/workflows/verification-dotnet-nightly.yml diff --git a/.github/workflows/verification-dotnet-nightly.yml b/.github/workflows/verification-dotnet-nightly.yml deleted file mode 100644 index 49da31b50..000000000 --- a/.github/workflows/verification-dotnet-nightly.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: verification-dotnet-nightly - -on: - schedule: - - cron: "0 0 * * *" - workflow_dispatch: - -jobs: - verify-bunit: - name: ๐Ÿ‘Œ Verify bUnit - runs-on: ubuntu-latest - container: - image: mcr.microsoft.com/dotnet/nightly/sdk:7.0 - - steps: - - name: ๐ŸŒ› Show dotnet version - run: | - dotnet --info - - - name: ๐Ÿ›’ Checkout repository - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: ๐ŸŽจ Setup color - run: | - echo "DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION=1" >> $GITHUB_ENV - echo "TERM=xterm" >> $GITHUB_ENV - - - name: ๐Ÿ“ฆ Setup nuget nightly builds - run: | - dotnet restore --verbosity normal -s https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet7/nuget/v3/index.json -s https://api.nuget.org/v3/index.json - - - name: ๐Ÿงช Run unit tests - run: | - dotnet test ./tests/bunit.core.tests/bunit.core.tests.csproj -c release --no-restore -f net7.0 --logger:"console;verbosity=normal" --blame-hang-timeout 15s --blame-hang-dump-type full --blame-crash-dump-type full - dotnet test ./tests/bunit.web.tests/bunit.web.tests.csproj -c release --no-restore -f net7.0 --logger:"console;verbosity=normal" --blame-hang-timeout 15s --blame-hang-dump-type full --blame-crash-dump-type full - dotnet test ./tests/AngleSharpWrappers.Tests/AngleSharpWrappers.Tests.csproj -c release --no-restore -f net7.0 --logger:"console;verbosity=normal" --blame-hang-timeout 15s --blame-hang-dump-type full --blame-crash-dump-type full - - - name: ๐Ÿ“› Upload hang- and crash-dumps on test failure - if: failure() - uses: actions/upload-artifact@v3 - with: - if-no-files-found: ignore - name: test-dumps - path: | - **/*hangdump.dmp - **/*crashdump.dmp - - - name: ๐Ÿงพ Collect dotnet information - if: failure() - run: | - echo "DOTNET_VERSION=$(dotnet --version)" >> $GITHUB_ENV - echo $DOTNET_VERSION - - - name: โšก Create issue if failed - if: failure() - uses: JasonEtco/create-an-issue@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ADJECTIVE: DOTNET_VERSION - with: - filename: .github/nightly-failed.md - update_existing: true From ace36cb972938de4e76c1dd2e4fe92f125cac0df Mon Sep 17 00:00:00 2001 From: Steven Giesel Date: Mon, 3 Oct 2022 19:57:00 +0200 Subject: [PATCH 07/11] feat: Added StringSyntax support so that IDE's help out the user --- .../ComponentParameterCollectionBuilder.cs | 4 +-- src/bunit.core/ComponentParameterFactory.cs | 4 +-- src/bunit.core/Extensions/BlazorExtensions.cs | 2 +- src/bunit.core/StringSyntaxAttribute.cs | 25 +++++++++++++++++++ .../MarkupMatchesAssertExtensions.cs | 22 ++++++++-------- ...tubComponentFactoryCollectionExtensions.cs | 2 +- src/bunit.web/Rendering/BunitHtmlParser.cs | 4 +-- .../NavigationManager/NavigationHistory.cs | 10 +++++--- 8 files changed, 50 insertions(+), 23 deletions(-) create mode 100644 src/bunit.core/StringSyntaxAttribute.cs diff --git a/src/bunit.core/ComponentParameterCollectionBuilder.cs b/src/bunit.core/ComponentParameterCollectionBuilder.cs index edfb05897..013241b21 100644 --- a/src/bunit.core/ComponentParameterCollectionBuilder.cs +++ b/src/bunit.core/ComponentParameterCollectionBuilder.cs @@ -72,7 +72,7 @@ public ComponentParameterCollectionBuilder Add(Expr /// A lambda function that selects the parameter. /// The markup string to pass to the . /// This . - public ComponentParameterCollectionBuilder Add(Expression> parameterSelector, string markup) + public ComponentParameterCollectionBuilder Add(Expression> parameterSelector, [StringSyntax("Html")]string markup) => Add(parameterSelector, markup.ToMarkupRenderFragment()); /// @@ -260,7 +260,7 @@ public ComponentParameterCollectionBuilder AddChildContent(RenderFra /// /// The markup string to pass the ChildContent parameter wrapped in a . /// This . - public ComponentParameterCollectionBuilder AddChildContent(string markup) + public ComponentParameterCollectionBuilder AddChildContent([StringSyntax("Html")]string markup) => AddChildContent(markup.ToMarkupRenderFragment()); /// diff --git a/src/bunit.core/ComponentParameterFactory.cs b/src/bunit.core/ComponentParameterFactory.cs index 9488dfe5c..668a8c65f 100644 --- a/src/bunit.core/ComponentParameterFactory.cs +++ b/src/bunit.core/ComponentParameterFactory.cs @@ -137,7 +137,7 @@ public static ComponentParameter CascadingValue(object value) /// /// Markup to pass to the child content parameter. /// The . - public static ComponentParameter ChildContent(string markup) + public static ComponentParameter ChildContent([StringSyntax("Html")]string markup) { return RenderFragment(nameof(ChildContent), markup); } @@ -173,7 +173,7 @@ public static ComponentParameter ChildContent(RenderFragment renderFragment) /// Parameter name. /// Markup to pass to the render fragment parameter. /// The . - public static ComponentParameter RenderFragment(string name, string markup) + public static ComponentParameter RenderFragment(string name, [StringSyntax("Html")]string markup) { return ComponentParameter.CreateParameter(name, markup.ToMarkupRenderFragment()); } diff --git a/src/bunit.core/Extensions/BlazorExtensions.cs b/src/bunit.core/Extensions/BlazorExtensions.cs index 0f4cc45f5..daba2f7cf 100644 --- a/src/bunit.core/Extensions/BlazorExtensions.cs +++ b/src/bunit.core/Extensions/BlazorExtensions.cs @@ -10,7 +10,7 @@ public static class BlazorExtensions /// /// Markup to render. /// The . - public static RenderFragment ToMarkupRenderFragment(this string? markup) + public static RenderFragment ToMarkupRenderFragment([StringSyntax("Html")]this string? markup) { if (string.IsNullOrEmpty(markup)) return _ => { }; diff --git a/src/bunit.core/StringSyntaxAttribute.cs b/src/bunit.core/StringSyntaxAttribute.cs new file mode 100644 index 000000000..8817b14fd --- /dev/null +++ b/src/bunit.core/StringSyntaxAttribute.cs @@ -0,0 +1,25 @@ +#if !NET7_0_OR_GREATER +namespace System.Diagnostics.CodeAnalysis; + +/// Fake version of the StringSyntaxAttribute, which was introduced in .NET 7 +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] +public sealed class StringSyntaxAttribute : Attribute +{ + /// + /// Initializes the with the identifier of the syntax used. + /// + public StringSyntaxAttribute(string syntax) + { + } + + /// + /// Initializes the with the identifier of the syntax used. + /// + public StringSyntaxAttribute(string syntax, params object?[] arguments) + { + } + + /// The syntax identifier for strings containing URIs. + public const string Uri = nameof(Uri); +} +#endif diff --git a/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs b/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs index 3474c61ef..8c2f7655d 100644 --- a/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs +++ b/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs @@ -20,7 +20,7 @@ public static class MarkupMatchesAssertExtensions /// The expected markup fragment. /// A custom user message to display in case the verification fails. [AssertionMethod] - public static void MarkupMatches(this string actual, string expected, string? userMessage = null) + public static void MarkupMatches([StringSyntax("Html")]this string actual, [StringSyntax("Html")]string expected, string? userMessage = null) { if (actual is null) throw new ArgumentNullException(nameof(actual)); @@ -42,7 +42,7 @@ public static void MarkupMatches(this string actual, string expected, string? us /// The expected . /// A custom user message to display in case the verification fails. [AssertionMethod] - public static void MarkupMatches(this string actual, IRenderedFragment expected, string? userMessage = null) + public static void MarkupMatches([StringSyntax("Html")]this string actual, IRenderedFragment expected, string? userMessage = null) { if (actual is null) throw new ArgumentNullException(nameof(actual)); @@ -62,7 +62,7 @@ public static void MarkupMatches(this string actual, IRenderedFragment expected, /// The expected . /// A custom user message to display in case the verification fails. [AssertionMethod] - public static void MarkupMatches(this string actual, INodeList expected, string? userMessage = null) + public static void MarkupMatches([StringSyntax("Html")]this string actual, INodeList expected, string? userMessage = null) { if (actual is null) throw new ArgumentNullException(nameof(actual)); @@ -82,7 +82,7 @@ public static void MarkupMatches(this string actual, INodeList expected, string? /// The expected . /// A custom user message to display in case the verification fails. [AssertionMethod] - public static void MarkupMatches(this string actual, INode expected, string? userMessage = null) + public static void MarkupMatches([StringSyntax("Html")]this string actual, INode expected, string? userMessage = null) { if (actual is null) throw new ArgumentNullException(nameof(actual)); @@ -102,7 +102,7 @@ public static void MarkupMatches(this string actual, INode expected, string? use /// The expected markup. /// A custom user message to display in case the verification fails. [AssertionMethod] - public static void MarkupMatches(this IRenderedFragment actual, string expected, string? userMessage = null) + public static void MarkupMatches(this IRenderedFragment actual, [StringSyntax("Html")]string expected, string? userMessage = null) { if (actual is null) throw new ArgumentNullException(nameof(actual)); @@ -182,7 +182,7 @@ public static void MarkupMatches(this INode actual, IRenderedFragment expected, /// The expected markup. /// A custom user message to display in case the verification fails. [AssertionMethod] - public static void MarkupMatches(this INode actual, string expected, string? userMessage = null) + public static void MarkupMatches(this INode actual, [StringSyntax("Html")]string expected, string? userMessage = null) { if (actual is null) throw new ArgumentNullException(nameof(actual)); @@ -203,7 +203,7 @@ public static void MarkupMatches(this INode actual, string expected, string? use /// The expected markup. /// A custom user message to display in case the verification fails. [AssertionMethod] - public static void MarkupMatches(this INodeList actual, string expected, string? userMessage = null) + public static void MarkupMatches(this INodeList actual, [StringSyntax("Html")]string expected, string? userMessage = null) { if (actual is null) throw new ArgumentNullException(nameof(actual)); @@ -355,7 +355,7 @@ public static void MarkupMatches(this INodeList actual, RenderFragment expected, /// The expected markup fragment. /// A custom user message to display in case the verification fails. [AssertionMethod] - public static void MarkupMatches(this IEnumerable actual, string expected, string? userMessage = null) + public static void MarkupMatches(this IEnumerable actual, [StringSyntax("Html")]string expected, string? userMessage = null) { if (actual is null) throw new ArgumentNullException(nameof(actual)); @@ -473,9 +473,7 @@ private static INodeList ToNodeList(this IEnumerable elements, BunitHt using var parser = new BunitHtmlParser(); return nodesStr.ToNodeList(parser); } - else - { - return nodesStr.ToNodeList(htmlParser); - } + + return nodesStr.ToNodeList(htmlParser); } } diff --git a/src/bunit.web/Extensions/StubComponentFactoryCollectionExtensions.cs b/src/bunit.web/Extensions/StubComponentFactoryCollectionExtensions.cs index 01da2102a..011c82464 100644 --- a/src/bunit.web/Extensions/StubComponentFactoryCollectionExtensions.cs +++ b/src/bunit.web/Extensions/StubComponentFactoryCollectionExtensions.cs @@ -39,7 +39,7 @@ static Predicate CreatePredicate(Type componentTypeToStub) /// The bUnit to configure. /// Markup that will be used as render output instead of the stubbed out component. /// A . - public static ComponentFactoryCollection AddStub(this ComponentFactoryCollection factories, string replacementMarkup) where TComponent : IComponent + public static ComponentFactoryCollection AddStub(this ComponentFactoryCollection factories, [StringSyntax("Html")]string replacementMarkup) where TComponent : IComponent => AddStub(factories, b => b.AddMarkupContent(0, replacementMarkup)); /// diff --git a/src/bunit.web/Rendering/BunitHtmlParser.cs b/src/bunit.web/Rendering/BunitHtmlParser.cs index 07a7f42c1..c6edf2971 100644 --- a/src/bunit.web/Rendering/BunitHtmlParser.cs +++ b/src/bunit.web/Rendering/BunitHtmlParser.cs @@ -57,7 +57,7 @@ private BunitHtmlParser(IConfiguration angleSharpConfiguration) /// /// The markup to parse. /// The . - public INodeList Parse(string markup) + public INodeList Parse([StringSyntax("Html")]string markup) { if (markup is null) throw new ArgumentNullException(nameof(markup)); @@ -187,4 +187,4 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } -} \ No newline at end of file +} diff --git a/src/bunit.web/TestDoubles/NavigationManager/NavigationHistory.cs b/src/bunit.web/TestDoubles/NavigationManager/NavigationHistory.cs index e6b86e73c..aa13a0953 100644 --- a/src/bunit.web/TestDoubles/NavigationManager/NavigationHistory.cs +++ b/src/bunit.web/TestDoubles/NavigationManager/NavigationHistory.cs @@ -45,7 +45,7 @@ public sealed class NavigationHistory : IEquatable /// /// [SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", Justification = "Using string to align with NavigationManager")] - public NavigationHistory(string uri, Bunit.TestDoubles.NavigationOptions options) + public NavigationHistory([StringSyntax(StringSyntaxAttribute.Uri)]string uri, Bunit.TestDoubles.NavigationOptions options) { Uri = uri; Options = options; @@ -58,7 +58,7 @@ public NavigationHistory(string uri, Bunit.TestDoubles.NavigationOptions options /// /// [SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", Justification = "Using string to align with NavigationManager")] - public NavigationHistory(string uri, NavigationOptions options) + public NavigationHistory([StringSyntax(StringSyntaxAttribute.Uri)]string uri, NavigationOptions options) { Uri = uri; Options = options; @@ -74,7 +74,11 @@ public NavigationHistory(string uri, NavigationOptions options) /// /// [SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", Justification = "Using string to align with NavigationManager")] - public NavigationHistory(string uri, NavigationOptions options, NavigationState navigationState, Exception? exception = null) + public NavigationHistory( + [StringSyntax(StringSyntaxAttribute.Uri)]string uri, + NavigationOptions options, + NavigationState navigationState, + Exception? exception = null) { Uri = uri; Options = options; From 4c415ae8deac7786d90c4b5d71a2f4dbdcbf1fb0 Mon Sep 17 00:00:00 2001 From: Steven Giesel Date: Fri, 7 Oct 2022 16:18:22 +0200 Subject: [PATCH 08/11] fix: Spelling mistakes --- src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs b/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs index 8c2f7655d..4d7c2fe5e 100644 --- a/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs +++ b/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs @@ -351,7 +351,7 @@ public static void MarkupMatches(this INodeList actual, RenderFragment expected, /// the markup fragment, using the type. /// /// Thrown when the markup does not match the markup. - /// A enumerable of IElements to verifiy. + /// A enumerable of IElements to verify. /// The expected markup fragment. /// A custom user message to display in case the verification fails. [AssertionMethod] @@ -370,7 +370,7 @@ public static void MarkupMatches(this IEnumerable actual, [StringSynta /// the fragments, using the type. /// /// Thrown when the element does not match the fragments. - /// An IElement to verifiy. + /// An IElement to verify. /// The expected markup fragments. /// A custom user message to display in case the verification fails. [AssertionMethod] @@ -412,7 +412,7 @@ public static void MarkupMatches(this IElement actual, IEnumerable exp /// the fragment, using the type. /// /// Thrown when the elements does not match the fragment. - /// A list of elements to verifiy. + /// A list of elements to verify. /// The expected markup fragment. /// A custom user message to display in case the verification fails. [AssertionMethod] From 100ea0177847f66ec879ec06fda3d74ad146036c Mon Sep 17 00:00:00 2001 From: Steven Giesel Date: Wed, 12 Oct 2022 18:19:09 +0200 Subject: [PATCH 09/11] feat: Easy way to retrieve InteractiveRequestOptions (#867) feat: Easy way to retrieve InteractiveRequestOptions * fix: simplified test runner script * adopt some documentation * Apply suggestions from code review Co-authored-by: Egil Hansen --- CHANGELOG.md | 3 ++ .../test-doubles/fake-navigation-manager.md | 28 ++++++++++++ .../NavigationManager/NavigationHistory.cs | 35 ++++++++++++++- .../FakeNavigationManagerTest.cs | 45 ++++++++++++++++++- tests/run-tests.ps1 | 8 +--- 5 files changed, 111 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1aaffd9f..28ee4e258 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ All notable changes to **bUnit** will be documented in this file. The project ad ## [Unreleased] +### Added +- Added the `StateFromJson` method to the `NavigationHistory` type, to make it easy to deserialize navigation state stored as JSON during a call to `NavigationManager.NavigateTo`, e.g. as seen with the new `InteractiveRequestOptions` type available in .NET 7. By [@linkdotnet](https://github.com/linkdotnet) and [@egil](https://github.com/egil). + ## [1.10.14] - 2022-09-16 ### Added diff --git a/docs/site/docs/test-doubles/fake-navigation-manager.md b/docs/site/docs/test-doubles/fake-navigation-manager.md index a12f53b70..67783e009 100644 --- a/docs/site/docs/test-doubles/fake-navigation-manager.md +++ b/docs/site/docs/test-doubles/fake-navigation-manager.md @@ -136,4 +136,32 @@ navMan.NavigateTo("/counter"); var navigationHistory = navMan.History.Single(); Assert.Equal(NavigationState.Faulted, navigationHistory.NavigationState); Assert.NotNull(navigationHistory.Exception); +``` + +## Getting the result of `NavigationManager.NavigateToLogin` +[`NavigationManager.NavigateToLogin`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.navigationmanager.navigateto?view=aspnetcore-7.0) is a function, which was introduced with .NET 7, which allows to login dynamically. The function can also retrieve an `InteractiveRequestOptions` object, which can hold additional parameter. + +```csharp +InteractiveRequestOptions requestOptions = new() +{ + Interaction = InteractionType.SignIn, + ReturnUrl = NavigationManager.Uri, +}; +requestOptions.TryAddAdditionalParameter("prompt", "login"); +NavigationManager.NavigateToLogin("authentication/login", requestOptions); +``` + +A test could look like this: +```csharp +using var ctx = new TestContext(); +var navigationManager = ctx.Services.GetRequiredService(); + +ActionToTriggerTheNavigationManager(); + +// This helper method retrieves the InteractiveRequestOptions object +var requestOptions = navigationManager.History.Last().StateFromJson(); +Asser.NotNull(requestOptions); +Assert.Equal(requestOptions.Interaction, InteractionType.SignIn); +options.TryGetAdditionalParameter("prompt", out string prompt); +Assert.Equal(prompt, "login"); ``` \ No newline at end of file diff --git a/src/bunit.web/TestDoubles/NavigationManager/NavigationHistory.cs b/src/bunit.web/TestDoubles/NavigationManager/NavigationHistory.cs index aa13a0953..9422f7a58 100644 --- a/src/bunit.web/TestDoubles/NavigationManager/NavigationHistory.cs +++ b/src/bunit.web/TestDoubles/NavigationManager/NavigationHistory.cs @@ -1,4 +1,8 @@ +#if NET7_0_OR_GREATER +using System.Text.Json; using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +#endif namespace Bunit.TestDoubles; @@ -85,6 +89,26 @@ public NavigationHistory( State = navigationState; Exception = exception; } + + /// + /// Deserialize the content of . + /// into if it is not null. + /// + /// The type to deserialize the content of . to. + /// The used when deserializing. If not provided, is used. + /// The target type of the JSON value. + /// When . is null. + public T? StateFromJson(JsonSerializerOptions? options = null) + { + if (Options.HistoryEntryState is null) + { + throw new InvalidOperationException($"No {nameof(Options.HistoryEntryState)} has been set."); + } + + return JsonSerializer.Deserialize( + Options.HistoryEntryState, + options ?? JsonSerializerOptions.Default); + } #endif /// @@ -92,13 +116,22 @@ public NavigationHistory( public bool Equals(NavigationHistory? other) => other is not null && string.Equals(Uri, other.Uri, StringComparison.Ordinal) && Options.Equals(other.Options); #endif -#if NET6_0_OR_GREATER +#if NET6_0 public bool Equals(NavigationHistory? other) => other is not null && string.Equals(Uri, other.Uri, StringComparison.Ordinal) && Options.ForceLoad == other.Options.ForceLoad && Options.ReplaceHistoryEntry == other.Options.ReplaceHistoryEntry; #endif +#if NET7_0_OR_GREATER + public bool Equals(NavigationHistory? other) + => other is not null + && string.Equals(Uri, other.Uri, StringComparison.Ordinal) + && Options.ForceLoad == other.Options.ForceLoad + && Options.ReplaceHistoryEntry == other.Options.ReplaceHistoryEntry + && State == other.State + && Exception == other.Exception; +#endif /// public override bool Equals(object? obj) => obj is NavigationHistory other && Equals(other); diff --git a/tests/bunit.web.tests/TestDoubles/NavigationManager/FakeNavigationManagerTest.cs b/tests/bunit.web.tests/TestDoubles/NavigationManager/FakeNavigationManagerTest.cs index 3709497c0..68549436d 100644 --- a/tests/bunit.web.tests/TestDoubles/NavigationManager/FakeNavigationManagerTest.cs +++ b/tests/bunit.web.tests/TestDoubles/NavigationManager/FakeNavigationManagerTest.cs @@ -1,3 +1,6 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; + namespace Bunit.TestDoubles; using static Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers; @@ -219,6 +222,47 @@ public void Test012() entry.State.ShouldBe(NavigationState.Faulted); } + [Fact(DisplayName = "StateFromJson deserialize InteractiveRequestOptions")] + public void Test013() + { + var fakeNavigationManager = CreateFakeNavigationManager(); + var requestOptions = new InteractiveRequestOptions + { + ReturnUrl = "return", Interaction = InteractionType.SignIn, + }; + requestOptions.TryAddAdditionalParameter("library", "bunit"); + + fakeNavigationManager.NavigateToLogin("/some-url", requestOptions); + + var options = fakeNavigationManager.History.Last().StateFromJson(); + options.ShouldNotBeNull(); + options.Interaction.ShouldBe(InteractionType.SignIn); + options.ReturnUrl.ShouldBe("return"); + options.TryGetAdditionalParameter("library", out string libraryName).ShouldBeTrue(); + libraryName.ShouldBe("bunit"); + } + + [Fact(DisplayName = "Given no content in state then StateFromJson throws")] + public void Test014() + { + var fakeNavigationManager = CreateFakeNavigationManager(); + fakeNavigationManager.NavigateTo("/some-url"); + + Should.Throw( + () => fakeNavigationManager.History.Last().StateFromJson()); + } + + [Fact(DisplayName = "StateFromJson with invalid json throws")] + public void Test015() + { + var fakeNavigationManager = CreateFakeNavigationManager(); + + fakeNavigationManager.NavigateTo("/login", new NavigationOptions { HistoryEntryState = "" }); + + Should.Throw( + () => fakeNavigationManager.History.Last().StateFromJson()); + } + private class InterceptNavigateToCounterComponent : ComponentBase { protected override void BuildRenderTree(RenderTreeBuilder builder) @@ -251,7 +295,6 @@ private void InterceptNavigation(LocationChangingContext context) public class GotoExternalResourceComponent : ComponentBase { -#pragma warning disable 1998 protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenElement(0, "button"); diff --git a/tests/run-tests.ps1 b/tests/run-tests.ps1 index 51e7f05a4..3b8fe0688 100644 --- a/tests/run-tests.ps1 +++ b/tests/run-tests.ps1 @@ -10,14 +10,10 @@ for ($num = 1 ; $num -le $maxRuns ; $num++) if($filter) { - dotnet test .\bunit.core.tests\bunit.core.tests.csproj -c $mode --no-restore --no-build --blame-hang --blame-hang-timeout 100s --nologo --filter $filter --logger:"console;verbosity=normal" - dotnet test .\bunit.web.tests\bunit.web.tests.csproj -c $mode --no-restore --no-build --blame-hang --blame-hang-timeout 100s --nologo --filter $filter --logger:"console;verbosity=normal" - dotnet test .\bunit.web.testcomponents.tests\bunit.web.testcomponents.teststests.csproj -c $mode --no-restore --no-build --blame-hang --blame-hang-timeout 100s --nologo --filter $filter --logger:"console;verbosity=normal" + dotnet test ..\bunit.sln -c $mode --no-restore --no-build --blame --nologo --filter $filter } else { - dotnet test .\bunit.core.tests\bunit.core.tests.csproj -c $mode --no-restore --no-build --blame-hang --blame-hang-timeout 100s --nologo - dotnet test .\bunit.web.tests\bunit.web.tests.csproj -c $mode --no-restore --no-build --blame-hang --blame-hang-timeout 100s --nologo - dotnet test .\bunit.web.testcomponents.tests\bunit.web.testcomponents.tests.csproj -c $mode --no-restore --no-build --blame-hang --blame-hang-timeout 100s --nologo + dotnet test ..\bunit.sln -c $mode --no-restore --no-build --blame --nologo } } From 914bb538128d92066cd61a0381122742cbc3f182 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 15:09:45 +0000 Subject: [PATCH 10/11] build(deps): Bump Xunit.Combinatorial from 1.4.1 to 1.5.25 Bumps [Xunit.Combinatorial](https://github.com/AArnott/Xunit.Combinatorial) from 1.4.1 to 1.5.25. - [Release notes](https://github.com/AArnott/Xunit.Combinatorial/releases) - [Commits](https://github.com/AArnott/Xunit.Combinatorial/compare/v1.4.1...v1.5.25) --- updated-dependencies: - dependency-name: Xunit.Combinatorial dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tests/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 677ca95da..143b11772 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -20,7 +20,7 @@ - + From 2b2d98fb0dcd02a1cc5f451f140e7417e1a4a93d Mon Sep 17 00:00:00 2001 From: bUnit bot Date: Thu, 13 Oct 2022 11:09:39 +0000 Subject: [PATCH 11/11] Set version to '1.11' --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index bf09bf642..cce110611 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.11-preview", + "version": "1.11", "assemblyVersion": { "precision": "revision" },