Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ dotnet_diagnostic.SA1116.severity = none
dotnet_diagnostic.SA1117.severity = none
# SA1200: Using directive should appear within a namespace declaration
dotnet_diagnostic.SA1200.severity = none

# Purpose: Use string. Empty for empty strings
# Reason: There's no performance difference. See https://medium.com/@dk.kravtsov/string-empty-vs-in-c-70c64971161f
dotnet_diagnostic.SA1122.severity = none

# SA1124: Do not use regions
dotnet_diagnostic.SA1124.severity = none
# SA1201: A property should not follow a method
Expand Down
4 changes: 0 additions & 4 deletions Build/Build.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using LibGit2Sharp;
using Microsoft.Build.Tasks;
using Nuke.Common;
using Nuke.Common.CI.GitHubActions;
using Nuke.Common.Execution;
Expand Down
1 change: 0 additions & 1 deletion FluentAssertions.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
Tests\Default.testsettings = Tests\Default.testsettings
Directory.Build.props = Directory.Build.props
Src\JetBrainsAnnotations.cs = Src\JetBrainsAnnotations.cs
nuget.config = nuget.config
README.md = README.md
docs\_pages\releases.md = docs\_pages\releases.md
Expand Down
14 changes: 7 additions & 7 deletions FluentAssertions.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeEditing/Intellisense/CodeCompletion/AutoCompleteBasicCompletion/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeEditing/Intellisense/LookupWindow/ShowSignatures/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeEditing/TypingAssist/FormatBlockOnRBrace/@EntryValue">False</s:Boolean>
Expand Down Expand Up @@ -143,7 +143,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=NAMESPACE_005FALIAS/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>

<s:String x:Key="/Default/Environment/Editor/MatchingBraceHighlighting/Style/@EntryValue">OUTLINE</s:String>
<s:String x:Key="/Default/Environment/Hierarchy/PsiConfigurationSettingsKey/LocationType/@EntryValue">SOLUTION_FOLDER</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
Expand All @@ -161,17 +161,17 @@
<s:Int64 x:Key="/Default/Environment/UnitTesting/ParallelProcessesCount/@EntryValue">4</s:Int64>
<s:Boolean x:Key="/Default/Environment/UnitTesting/ShadowCopy/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/@KeyIndexDefined">True</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Field/=behavior/@KeyIndexDefined">True</s:Boolean>
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Field/=behavior/Order/@EntryValue">1</s:Int64>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Field/=scenario/@KeyIndexDefined">True</s:Boolean>
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Field/=scenario/Order/@EntryValue">0</s:Int64>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Field/=behavior/@KeyIndexDefined">False</s:Boolean>

<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Field/=scenario/@KeyIndexDefined">False</s:Boolean>

<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Shortcut/@EntryValue">aaa</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Description/@EntryValue">Arrange-Act-Assert</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Text/@EntryValue">[Fact]
public void $END$()
{
// Arrange


// Act

Expand Down
100 changes: 75 additions & 25 deletions Src/FluentAssertions/AndWhichConstraint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,84 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions.Common;
using FluentAssertions.Execution;
using FluentAssertions.Formatting;

namespace FluentAssertions;

/// <summary>
/// Constraint which can be returned from an assertion which matches a condition and which will allow
/// further matches to be performed on the matched condition as well as the parent constraint.
/// Provides a <see cref="Which"/> property that can be used in chained assertions where the prior assertions returns a
/// single object that the assertion continues on.
/// </summary>
/// <typeparam name="TParentConstraint">The type of the original constraint that was matched</typeparam>
/// <typeparam name="TMatchedElement">The type of the matched object which the parent constraint matched</typeparam>
public class AndWhichConstraint<TParentConstraint, TMatchedElement> : AndConstraint<TParentConstraint>
public class AndWhichConstraint<TParent, TSubject> : AndConstraint<TParent>
{
private readonly Lazy<TMatchedElement> matchedConstraint;
private readonly AssertionChain assertionChain;
private readonly string pathPostfix;
private readonly Lazy<TSubject> getSubject;

public AndWhichConstraint(TParentConstraint parentConstraint, TMatchedElement matchedConstraint)
: base(parentConstraint)
/// <summary>
/// Creates an object that allows continuing an assertion executed through <paramref name="parent"/> and
/// which resulted in a single <paramref name="subject"/>.
/// </summary>
public AndWhichConstraint(TParent parent, TSubject subject)
: base(parent)
{
getSubject = new Lazy<TSubject>(() => subject);
}

/// <summary>
/// Creates an object that allows continuing an assertion executed through <paramref name="parent"/> and
/// which resulted in a single <paramref name="subject"/> on an existing <paramref name="assertionChain"/>, but where
/// the previous caller identifier is post-fixed with <paramref name="pathPostfix"/>.
/// </summary>
public AndWhichConstraint(TParent parent, TSubject subject, AssertionChain assertionChain, string pathPostfix = "")
: base(parent)
{
this.matchedConstraint =
new Lazy<TMatchedElement>(() => matchedConstraint);
getSubject = new Lazy<TSubject>(() => subject);

this.assertionChain = assertionChain;
this.pathPostfix = pathPostfix;
}

public AndWhichConstraint(TParentConstraint parentConstraint, IEnumerable<TMatchedElement> matchedConstraint)
: base(parentConstraint)
/// <summary>
/// Creates an object that allows continuing an assertion executed through <paramref name="parent"/> and
/// which resulted in a potential collection of objects through <paramref name="subjects"/>.
/// </summary>
/// <remarks>
/// If <paramref name="subjects"/> contains more than one object, a clear exception is thrown.
/// </remarks>
public AndWhichConstraint(TParent parent, IEnumerable<TSubject> subjects)
: base(parent)
{
this.matchedConstraint =
new Lazy<TMatchedElement>(
() => SingleOrDefault(matchedConstraint));
getSubject = new Lazy<TSubject>(() => Single(subjects));
}

private static TMatchedElement SingleOrDefault(
IEnumerable<TMatchedElement> matchedConstraint)
/// <summary>
/// Creates an object that allows continuing an assertion executed through <paramref name="parent"/> and
/// which resulted in a potential collection of objects through <paramref name="subjects"/> on an
/// existing <paramref name="assertionChain"/>, but where
/// the previous caller identifier is post-fixed with <paramref name="pathPostfix"/>.
/// </summary>
/// <remarks>
/// If <paramref name="subjects"/> contains more than one object, a clear exception is thrown.
/// </remarks>
public AndWhichConstraint(TParent parent, IEnumerable<TSubject> subjects, AssertionChain assertionChain, string pathPostfix)
: base(parent)
{
TMatchedElement[] matchedElements = matchedConstraint.ToArray();
getSubject = new Lazy<TSubject>(() => Single(subjects));

this.assertionChain = assertionChain;
this.pathPostfix = pathPostfix;
}

private static TSubject Single(IEnumerable<TSubject> subjects)
{
TSubject[] matchedElements = subjects.ToArray();

if (matchedElements.Length > 1)
{
string foundObjects = string.Join(Environment.NewLine,
matchedElements.Select(
ele => "\t" + Formatter.ToString(ele)));
matchedElements.Select(ele => "\t" + Formatter.ToString(ele)));

string message = "More than one object found. FluentAssertions cannot determine which object is meant."
+ $" Found objects:{Environment.NewLine}{foundObjects}";
Expand All @@ -54,13 +93,24 @@ private static TMatchedElement SingleOrDefault(
/// <summary>
/// Returns the single result of a prior assertion that is used to select a nested or collection item.
/// </summary>
public TMatchedElement Which => matchedConstraint.Value;
/// <remarks>
/// Just a convenience property that returns the same value as <see cref="Which"/>.
/// </remarks>
public TSubject Subject => Which;

/// <summary>
/// Returns the single result of a prior assertion that is used to select a nested or collection item.
/// </summary>
/// <remarks>
/// Just a convenience property that returns the same value as <see cref="Which"/>.
/// </remarks>
public TMatchedElement Subject => Which;
public TSubject Which
{
get
{
if (pathPostfix is not null and not "")
{
assertionChain.WithCallerPostfix(pathPostfix).ReuseOnce();
}

return getSubject.Value;
}
}
}
Loading