Skip to content

Commit

Permalink
Release 6.7
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisdoomen committed May 18, 2022
2 parents 7895517 + 463edb9 commit d476154
Show file tree
Hide file tree
Showing 171 changed files with 36,757 additions and 31,857 deletions.
6 changes: 3 additions & 3 deletions Build/_build.csproj
Expand Up @@ -8,10 +8,10 @@
<NukeScriptDirectory>..\</NukeScriptDirectory>
</PropertyGroup>
<ItemGroup>
<PackageDownload Include="GitVersion.Tool" Version="[5.9.0]" />
<PackageDownload Include="GitVersion.Tool" Version="[5.10.1]" />
<PackageDownload Include="NSpec" Version="[3.1.0]" />
<PackageDownload Include="ReportGenerator" Version="[5.1.3]" />
<PackageDownload Include="ReportGenerator" Version="[5.1.4]" />
<PackageDownload Include="xunit.runner.console" Version="[2.4.1]" />
<PackageReference Include="Nuke.Common" Version="6.0.1" />
<PackageReference Include="Nuke.Common" Version="6.0.2" />
</ItemGroup>
</Project>
6 changes: 2 additions & 4 deletions README.md
Expand Up @@ -3,7 +3,7 @@
[![](https://img.shields.io/nuget/dt/FluentAssertions.svg?label=nuget%20downloads)](https://www.nuget.org/packages/FluentAssertions)
[![](https://img.shields.io/librariesio/dependents/nuget/FluentAssertions.svg?label=dependent%20libraries)](https://libraries.io/nuget/FluentAssertions)
![](https://img.shields.io/badge/release%20strategy-githubflow-orange.svg)
[![Coverage Status](https://coveralls.io/repos/github/fluentassertions/fluentassertions/badge.svg?branch=develop)](https://coveralls.io/github/fluentassertions/fluentassertions?branch=develop)
[![Coverage Status](https://coveralls.io/repos/github/fluentassertions/fluentassertions/badge.svg?branch=master)](https://coveralls.io/github/fluentassertions/fluentassertions?branch=master)

# About this project
A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit tests. Targets .NET Framework 4.7, as well as .NET Core 2.1, .NET Core 3.0, .NET 6, .NET Standard 2.0 and 2.1.
Expand All @@ -19,9 +19,7 @@ Install Visual Studio 2022 17.0+ or JetBrains Rider 2021.3 as well as the Build
# What are these Approval.Tests?
This is a special set of tests that use the [Verify](https://github.com/VerifyTests/Verify) project to verify whether you've introduced any breaking changes in the public API of the library.

If you've verified the changes and decided they are valid, you can accept them using `AcceptApiChanges.ps1` or `AcceptApiChanges.sh`. Alternatively, you can use the [Verify Support](https://plugins.jetbrains.com/plugin/17240-verify-support) plug-in to compare the changes and accept them right from inside Rider. See also the [Contribution Guidelines.](CONTRIBUTING.md).

`build.ps1`
If you've verified the changes and decided they are valid, you can accept them using `AcceptApiChanges.ps1` or `AcceptApiChanges.sh`. Alternatively, you can use the [Verify Support](https://plugins.jetbrains.com/plugin/17240-verify-support) plug-in to compare the changes and accept them right from inside Rider. See also the [Contribution Guidelines](CONTRIBUTING.md).

# Powered By
<a href="https://www.jetbrains.com/rider/"><img src="docs/assets/images/jetbrainsrider.svg" style="width:150px"/></a> <br/>and <br>
Expand Down
47 changes: 47 additions & 0 deletions Src/FluentAssertions/AssertionExtensions.cs
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Reflection;
Expand Down Expand Up @@ -385,6 +386,36 @@ public static StringCollectionAssertions Should(this IEnumerable<string> @this)
return new GenericDictionaryAssertions<TCollection, TKey, TValue>(actualValue);
}

/// <summary>
/// Returns an assertions object that provides methods for asserting the state of a <see cref="DataTableCollection"/>.
/// </summary>
[Pure]
public static GenericCollectionAssertions<DataTable> Should(this DataTableCollection actualValue)
{
return new GenericCollectionAssertions<DataTable>(
ReadOnlyNonGenericCollectionWrapper.Create(actualValue));
}

/// <summary>
/// Returns an assertions object that provides methods for asserting the state of a <see cref="DataColumnCollection"/>.
/// </summary>
[Pure]
public static GenericCollectionAssertions<DataColumn> Should(this DataColumnCollection actualValue)
{
return new GenericCollectionAssertions<DataColumn>(
ReadOnlyNonGenericCollectionWrapper.Create(actualValue));
}

/// <summary>
/// Returns an assertions object that provides methods for asserting the state of a <see cref="DataRowCollection"/>.
/// </summary>
[Pure]
public static GenericCollectionAssertions<DataRow> Should(this DataRowCollection actualValue)
{
return new GenericCollectionAssertions<DataRow>(
ReadOnlyNonGenericCollectionWrapper.Create(actualValue));
}

/// <summary>
/// Returns a <see cref="DataColumnAssertions"/> object that can be used to assert the
/// current <see cref="DataColumn"/>.
Expand Down Expand Up @@ -1028,6 +1059,22 @@ public static void Should(this TypeSelectorAssertions _)
InvalidShouldCall();
}

/// <inheritdoc cref="Should(ExecutionTimeAssertions)" />
[Obsolete("You are asserting the 'AndConstraint' itself. Remove the 'Should()' method directly following 'And'", error: true)]
public static void Should<TAssertions>(this DateTimeRangeAssertions<TAssertions> _)
where TAssertions : DateTimeAssertions<TAssertions>
{
InvalidShouldCall();
}

/// <inheritdoc cref="Should(ExecutionTimeAssertions)" />
[Obsolete("You are asserting the 'AndConstraint' itself. Remove the 'Should()' method directly following 'And'", error: true)]
public static void Should<TAssertions>(this DateTimeOffsetRangeAssertions<TAssertions> _)
where TAssertions : DateTimeOffsetAssertions<TAssertions>
{
InvalidShouldCall();
}

[DoesNotReturn]
private static void InvalidShouldCall()
{
Expand Down
8 changes: 0 additions & 8 deletions Src/FluentAssertions/Common/Guard.cs
Expand Up @@ -55,14 +55,6 @@ public static void ThrowIfArgumentContainsNull<T>(IEnumerable<T> values, string
}
}

public static void ThrowIfArgumentContainsNull<T>(IEnumerable<T> values, string paramName, string message)
{
if (values.Any(t => t is null))
{
throw new ArgumentNullException(paramName, message);
}
}

public static void ThrowIfArgumentIsEmpty<T>(IEnumerable<T> values, string paramName, string message)
{
if (!values.Any())
Expand Down
24 changes: 18 additions & 6 deletions Src/FluentAssertions/Common/MemberPath.cs
Expand Up @@ -17,6 +17,8 @@ internal class MemberPath

private string[] segments;

private static readonly MemberPathSegmentEqualityComparer MemberPathSegmentEqualityComparer = new();

public MemberPath(IMember member, string parentPath)
: this(member.ReflectedType, member.DeclaringType, parentPath.Combine(member.Name))
{
Expand Down Expand Up @@ -53,7 +55,7 @@ public bool IsSameAs(MemberPath candidate)
{
string[] candidateSegments = candidate.Segments;

return candidateSegments.SequenceEqual(Segments);
return candidateSegments.SequenceEqual(Segments, MemberPathSegmentEqualityComparer);
}

return false;
Expand All @@ -64,15 +66,22 @@ private bool IsParentOf(MemberPath candidate)
string[] candidateSegments = candidate.Segments;

return candidateSegments.Length > Segments.Length &&
candidateSegments.Take(Segments.Length).SequenceEqual(Segments);
candidateSegments.Take(Segments.Length).SequenceEqual(Segments, MemberPathSegmentEqualityComparer);
}

private bool IsChildOf(MemberPath candidate)
{
string[] candidateSegments = candidate.Segments;

return candidateSegments.Length < Segments.Length
&& candidateSegments.SequenceEqual(Segments.Take(candidateSegments.Length));
&& candidateSegments.SequenceEqual(Segments.Take(candidateSegments.Length),
MemberPathSegmentEqualityComparer);
}

public MemberPath AsParentCollectionOf(MemberPath nextPath)
{
var extendedDottedPath = dottedPath.Combine(nextPath.dottedPath, "[]");
return new MemberPath(declaringType, nextPath.reflectedType, extendedDottedPath);
}

/// <summary>
Expand All @@ -86,7 +95,7 @@ public bool IsEquivalentTo(string path)
public bool HasSameParentAs(MemberPath path)
{
return Segments.Length == path.Segments.Length
&& GetParentSegments().SequenceEqual(path.GetParentSegments());
&& GetParentSegments().SequenceEqual(path.GetParentSegments(), MemberPathSegmentEqualityComparer);
}

private IEnumerable<string> GetParentSegments() => Segments.Take(Segments.Length - 1);
Expand All @@ -96,6 +105,11 @@ public bool HasSameParentAs(MemberPath path)
/// </summary>
public bool GetContainsSpecificCollectionIndex() => dottedPath.ContainsSpecificCollectionIndex();

private string[] Segments =>
segments ??= dottedPath
.Replace("[]", "[*]", StringComparison.Ordinal)
.Split(new[] { '.', '[', ']' }, StringSplitOptions.RemoveEmptyEntries);

/// <summary>
/// Returns a copy of the current object as if it represented an un-indexed item in a collection.
/// </summary>
Expand All @@ -104,8 +118,6 @@ public MemberPath WithCollectionAsRoot()
return new MemberPath(reflectedType, declaringType, "[]." + dottedPath);
}

private string[] Segments => segments ??= dottedPath.Split(new[] { '.', '[', ']' }, StringSplitOptions.RemoveEmptyEntries);

/// <summary>
/// Returns the name of the member the current path points to without its parent path.
/// </summary>
Expand Down
49 changes: 49 additions & 0 deletions Src/FluentAssertions/Common/MemberPathSegmentEqualityComparer.cs
@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace FluentAssertions.Common
{
/// <summary>
/// Compares two segments of a <see cref="MemberPath"/>.
/// Sets the <see cref="AnyIndexQualifier"/> equal with any numeric index qualifier.
/// All other comparisons are default string equality.
/// </summary>
internal class MemberPathSegmentEqualityComparer : IEqualityComparer<string>
{
private const string AnyIndexQualifier = "*";
private static readonly Regex IndexQualifierRegex = new(@"^\d+$");

/// <summary>
/// Compares two segments of a <see cref="MemberPath"/>.
/// </summary>
/// <param name="x">Left part of the comparison.</param>
/// <param name="y">Right part of the comparison.</param>
/// <returns>True if segments are equal, false if not.</returns>
public bool Equals(string x, string y)
{
if (x == AnyIndexQualifier)
{
return IsIndexQualifier(y);
}

if (y == AnyIndexQualifier)
{
return IsIndexQualifier(x);
}

return x == y;
}

private static bool IsIndexQualifier(string segment)
=> segment == AnyIndexQualifier || IndexQualifierRegex.IsMatch(segment);

public int GetHashCode(string obj)
{
#if NETCOREAPP2_1_OR_GREATER
return obj.GetHashCode(System.StringComparison.Ordinal);
#else
return obj.GetHashCode();
#endif
}
}
}
75 changes: 75 additions & 0 deletions Src/FluentAssertions/Common/ReadOnlyNonGenericCollectionWrapper.cs
@@ -0,0 +1,75 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace FluentAssertions.Common
{
internal static class ReadOnlyNonGenericCollectionWrapper
{
public static ReadOnlyNonGenericCollectionWrapper<DataTableCollection, DataTable> Create(DataTableCollection collection)
{
return
(collection != null)
? new ReadOnlyNonGenericCollectionWrapper<DataTableCollection, DataTable>(collection)
: null;
}

public static ReadOnlyNonGenericCollectionWrapper<DataColumnCollection, DataColumn> Create(DataColumnCollection collection)
{
return
(collection != null)
? new ReadOnlyNonGenericCollectionWrapper<DataColumnCollection, DataColumn>(collection)
: null;
}

public static ReadOnlyNonGenericCollectionWrapper<DataRowCollection, DataRow> Create(DataRowCollection collection)
{
return
(collection != null)
? new ReadOnlyNonGenericCollectionWrapper<DataRowCollection, DataRow>(collection)
: null;
}
}

internal class ReadOnlyNonGenericCollectionWrapper<TCollection, TItem> : ICollection<TItem>
where TCollection : ICollection
{
public TCollection UnderlyingCollection { get; }

public ReadOnlyNonGenericCollectionWrapper(TCollection collection)
{
Guard.ThrowIfArgumentIsNull(collection, nameof(collection));

UnderlyingCollection = collection;
}

public int Count => UnderlyingCollection.Count;

public bool IsReadOnly => true;

public IEnumerator<TItem> GetEnumerator() => UnderlyingCollection.Cast<TItem>().GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => UnderlyingCollection.GetEnumerator();

public bool Contains(TItem item) => UnderlyingCollection.Cast<TItem>().Contains(item);

public void CopyTo(TItem[] array, int arrayIndex) => UnderlyingCollection.CopyTo(array, arrayIndex);

/*
Mutation is not supported, but these methods must be implemented to satisfy ICollection<T>:
* Add
* Clear
* Remove
*/

public void Add(TItem item) => throw new NotSupportedException();

public void Clear() => throw new NotSupportedException();

public bool Remove(TItem item) => throw new NotSupportedException();
}
}

0 comments on commit d476154

Please sign in to comment.