Skip to content

Commit

Permalink
Merge pull request #889 from bUnit-dev/release/v1.11
Browse files Browse the repository at this point in the history
Release of new minor version v1.11
  • Loading branch information
egil authored Oct 13, 2022
2 parents b175d86 + 2b2d98f commit 72fae79
Show file tree
Hide file tree
Showing 18 changed files with 189 additions and 125 deletions.
64 changes: 0 additions & 64 deletions .github/workflows/verification-dotnet-nightly.yml

This file was deleted.

3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<!-- Shared code analyzers used for all projects in the solution -->
<ItemGroup Label="Code Analyzers">
<PackageReference Include="AsyncFixer" Version="1.6.0" PrivateAssets="All" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.44.0.52574" PrivateAssets="All" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.46.0.54807" PrivateAssets="All" />
</ItemGroup>

<ItemGroup Label="Implicit usings"
Expand Down
28 changes: 28 additions & 0 deletions docs/site/docs/test-doubles/fake-navigation-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<FakeNavigationManager>();

ActionToTriggerTheNavigationManager();

// This helper method retrieves the InteractiveRequestOptions object
var requestOptions = navigationManager.History.Last().StateFromJson<InteractiveRequestOptions>();
Asser.NotNull(requestOptions);
Assert.Equal(requestOptions.Interaction, InteractionType.SignIn);
options.TryGetAdditionalParameter("prompt", out string prompt);
Assert.Equal(prompt, "login");
```
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Nerdbank.GitVersioning" Version="3.5.109" PrivateAssets="All" />
<PackageReference Include="Nerdbank.GitVersioning" Version="3.5.113" PrivateAssets="All" />

</ItemGroup>

Expand Down
4 changes: 2 additions & 2 deletions src/bunit.core/ComponentParameterCollectionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public ComponentParameterCollectionBuilder<TComponent> Add<TChildComponent>(Expr
/// <param name="parameterSelector">A lambda function that selects the parameter.</param>
/// <param name="markup">The markup string to pass to the <see cref="RenderFragment"/>.</param>
/// <returns>This <see cref="ComponentParameterCollectionBuilder{TComponent}"/>.</returns>
public ComponentParameterCollectionBuilder<TComponent> Add(Expression<Func<TComponent, RenderFragment?>> parameterSelector, string markup)
public ComponentParameterCollectionBuilder<TComponent> Add(Expression<Func<TComponent, RenderFragment?>> parameterSelector, [StringSyntax("Html")]string markup)
=> Add(parameterSelector, markup.ToMarkupRenderFragment());

/// <summary>
Expand Down Expand Up @@ -260,7 +260,7 @@ public ComponentParameterCollectionBuilder<TComponent> AddChildContent(RenderFra
/// </summary>
/// <param name="markup">The markup string to pass the ChildContent parameter wrapped in a <see cref="RenderFragment"/>.</param>
/// <returns>This <see cref="ComponentParameterCollectionBuilder{TComponent}"/>.</returns>
public ComponentParameterCollectionBuilder<TComponent> AddChildContent(string markup)
public ComponentParameterCollectionBuilder<TComponent> AddChildContent([StringSyntax("Html")]string markup)
=> AddChildContent(markup.ToMarkupRenderFragment());

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/bunit.core/ComponentParameterFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public static ComponentParameter CascadingValue(object value)
/// </summary>
/// <param name="markup">Markup to pass to the child content parameter.</param>
/// <returns>The <see cref="ComponentParameter"/>.</returns>
public static ComponentParameter ChildContent(string markup)
public static ComponentParameter ChildContent([StringSyntax("Html")]string markup)
{
return RenderFragment(nameof(ChildContent), markup);
}
Expand Down Expand Up @@ -173,7 +173,7 @@ public static ComponentParameter ChildContent(RenderFragment renderFragment)
/// <param name="name">Parameter name.</param>
/// <param name="markup">Markup to pass to the render fragment parameter.</param>
/// <returns>The <see cref="ComponentParameter"/>.</returns>
public static ComponentParameter RenderFragment(string name, string markup)
public static ComponentParameter RenderFragment(string name, [StringSyntax("Html")]string markup)
{
return ComponentParameter.CreateParameter(name, markup.ToMarkupRenderFragment());
}
Expand Down
2 changes: 1 addition & 1 deletion src/bunit.core/Extensions/BlazorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public static class BlazorExtensions
/// </summary>
/// <param name="markup">Markup to render.</param>
/// <returns>The <see cref="RenderFragment"/>.</returns>
public static RenderFragment ToMarkupRenderFragment(this string? markup)
public static RenderFragment ToMarkupRenderFragment([StringSyntax("Html")]this string? markup)
{
if (string.IsNullOrEmpty(markup))
return _ => { };
Expand Down
42 changes: 20 additions & 22 deletions src/bunit.core/Extensions/WaitForHelpers/WaitForHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Bunit.Extensions.WaitForHelpers;
/// </summary>
public abstract class WaitForHelper<T> : IDisposable
{
private readonly Timer timer;
private readonly TaskCompletionSource<T> checkPassedCompletionSource;
private readonly Func<(bool CheckPassed, T Content)> completeChecker;
private readonly IRenderedFragmentBase renderedFragment;
Expand Down Expand Up @@ -51,7 +52,13 @@ protected WaitForHelper(

logger = renderedFragment.Services.CreateLogger<WaitForHelper<T>>();
checkPassedCompletionSource = new TaskCompletionSource<T>();
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();
}
Expand Down Expand Up @@ -80,6 +87,7 @@ protected virtual void Dispose(bool disposing)
return;

isDisposed = true;
timer.Dispose();
checkPassedCompletionSource.TrySetCanceled();
renderedFragment.OnAfterRender -= OnAfterRender;
logger.LogWaiterDisposed(renderedFragment.ComponentId);
Expand All @@ -105,41 +113,30 @@ private void InitializeWaiting()
}
}

private Task<T> CreateWaitTask(IRenderedFragmentBase renderedFragment, TimeSpan? timeout)
private Task<T> CreateWaitTask(IRenderedFragmentBase renderedFragment)
{
var renderer = renderedFragment.Services.GetRequiredService<ITestRenderer>();
var renderer = renderedFragment
.Services
.GetRequiredService<ITestRenderer>();

// Two to failure conditions, that the renderer captures an unhandled
// exception from a component or itself, or that the timeout is reached,
// are executed on the renderes scheduler, to ensure that OnAfterRender
// 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<T>(x.Result),
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously,
taskScheduler);

var timeoutTask = Task.Delay(GetRuntimeTimeout(timeout))
.ContinueWith(
x =>
{
logger.LogWaiterTimedOut(renderedFragment.ComponentId);
return Task.FromException<T>(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)
Expand Down Expand Up @@ -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();
}
}
Expand Down
25 changes: 25 additions & 0 deletions src/bunit.core/StringSyntaxAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#if !NET7_0_OR_GREATER
namespace System.Diagnostics.CodeAnalysis;

/// <summary>Fake version of the StringSyntaxAttribute, which was introduced in .NET 7</summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class StringSyntaxAttribute : Attribute
{
/// <summary>
/// Initializes the <see cref="StringSyntaxAttribute"/> with the identifier of the syntax used.
/// </summary>
public StringSyntaxAttribute(string syntax)
{
}

/// <summary>
/// Initializes the <see cref="StringSyntaxAttribute"/> with the identifier of the syntax used.
/// </summary>
public StringSyntaxAttribute(string syntax, params object?[] arguments)
{
}

/// <summary>The syntax identifier for strings containing URIs.</summary>
public const string Uri = nameof(Uri);
}
#endif
28 changes: 13 additions & 15 deletions src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static class MarkupMatchesAssertExtensions
/// <param name="expected">The expected markup fragment.</param>
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
[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));
Expand All @@ -42,7 +42,7 @@ public static void MarkupMatches(this string actual, string expected, string? us
/// <param name="expected">The expected <see cref="IRenderedFragment"/>.</param>
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
[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));
Expand All @@ -62,7 +62,7 @@ public static void MarkupMatches(this string actual, IRenderedFragment expected,
/// <param name="expected">The expected <see cref="INodeList"/>.</param>
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
[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));
Expand All @@ -82,7 +82,7 @@ public static void MarkupMatches(this string actual, INodeList expected, string?
/// <param name="expected">The expected <see cref="INode"/>.</param>
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
[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));
Expand All @@ -102,7 +102,7 @@ public static void MarkupMatches(this string actual, INode expected, string? use
/// <param name="expected">The expected markup.</param>
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
[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));
Expand Down Expand Up @@ -182,7 +182,7 @@ public static void MarkupMatches(this INode actual, IRenderedFragment expected,
/// <param name="expected">The expected markup.</param>
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
[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));
Expand All @@ -203,7 +203,7 @@ public static void MarkupMatches(this INode actual, string expected, string? use
/// <param name="expected">The expected markup.</param>
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
[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));
Expand Down Expand Up @@ -351,11 +351,11 @@ public static void MarkupMatches(this INodeList actual, RenderFragment expected,
/// the <paramref name="expected"/> markup fragment, using the <see cref="HtmlComparer"/> type.
/// </summary>
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> markup does not match the <paramref name="expected"/> markup.</exception>
/// <param name="actual">A enumerable of IElements to verifiy.</param>
/// <param name="actual">A enumerable of IElements to verify.</param>
/// <param name="expected">The expected markup fragment.</param>
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
[AssertionMethod]
public static void MarkupMatches(this IEnumerable<IElement> actual, string expected, string? userMessage = null)
public static void MarkupMatches(this IEnumerable<IElement> actual, [StringSyntax("Html")]string expected, string? userMessage = null)
{
if (actual is null)
throw new ArgumentNullException(nameof(actual));
Expand All @@ -370,7 +370,7 @@ public static void MarkupMatches(this IEnumerable<IElement> actual, string expec
/// the <paramref name="expected"/> fragments, using the <see cref="HtmlComparer"/> type.
/// </summary>
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> element does not match the <paramref name="expected"/> fragments.</exception>
/// <param name="actual">An IElement to verifiy.</param>
/// <param name="actual">An IElement to verify.</param>
/// <param name="expected">The expected markup fragments.</param>
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
[AssertionMethod]
Expand Down Expand Up @@ -412,7 +412,7 @@ public static void MarkupMatches(this IElement actual, IEnumerable<IElement> exp
/// the <paramref name="expected"/> fragment, using the <see cref="HtmlComparer"/> type.
/// </summary>
/// <exception cref="HtmlEqualException">Thrown when the <paramref name="actual"/> elements does not match the <paramref name="expected"/> fragment.</exception>
/// <param name="actual">A list of elements to verifiy.</param>
/// <param name="actual">A list of elements to verify.</param>
/// <param name="expected">The expected markup fragment.</param>
/// <param name="userMessage">A custom user message to display in case the verification fails.</param>
[AssertionMethod]
Expand Down Expand Up @@ -473,9 +473,7 @@ private static INodeList ToNodeList(this IEnumerable<IElement> elements, BunitHt
using var parser = new BunitHtmlParser();
return nodesStr.ToNodeList(parser);
}
else
{
return nodesStr.ToNodeList(htmlParser);
}

return nodesStr.ToNodeList(htmlParser);
}
}
Loading

0 comments on commit 72fae79

Please sign in to comment.