Skip to content

Commit 1829408

Browse files
whymatterjnyrup
andauthored
Make Excluding() work on nested collections if root is a collection (#2135)
* #1989 Remove ContainsIndexingQualifiers check to make Excluding() work on nested collections if root is a collection * #1989 Added test case for For().Exclude() * #1989 Adapted releases.md * #1989 Refactor rename RemoveRootIndexQualifier * Update docs/_pages/releases.md Co-authored-by: Jonas Nyrup <jnyrup@users.noreply.github.com> --------- Co-authored-by: Jonas Nyrup <jnyrup@users.noreply.github.com>
1 parent 7be61e8 commit 1829408

File tree

6 files changed

+182
-31
lines changed

6 files changed

+182
-31
lines changed

Src/FluentAssertions/Equivalency/Selection/ExcludeMemberByPathSelectionRule.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ internal class ExcludeMemberByPathSelectionRule : SelectMemberByPathSelectionRul
1111
private MemberPath memberToExclude;
1212

1313
public ExcludeMemberByPathSelectionRule(MemberPath pathToExclude)
14-
: base(pathToExclude.ToString())
1514
{
1615
memberToExclude = pathToExclude;
1716
}
@@ -26,7 +25,6 @@ protected override void AddOrRemoveMembersFrom(List<IMember> selectedMembers, IN
2625
public void AppendPath(MemberPath nextPath)
2726
{
2827
memberToExclude = memberToExclude.AsParentCollectionOf(nextPath);
29-
SetSelectedPath(memberToExclude.ToString());
3028
}
3129

3230
public override string ToString()

Src/FluentAssertions/Equivalency/Selection/IncludeMemberByPathSelectionRule.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ internal class IncludeMemberByPathSelectionRule : SelectMemberByPathSelectionRul
1212
private readonly MemberPath memberToInclude;
1313

1414
public IncludeMemberByPathSelectionRule(MemberPath pathToInclude)
15-
: base(pathToInclude.ToString())
1615
{
1716
memberToInclude = pathToInclude;
1817
}

Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Collections.Generic;
32
using System.Linq;
43
using System.Text.RegularExpressions;
@@ -7,32 +6,12 @@ namespace FluentAssertions.Equivalency.Selection;
76

87
internal abstract class SelectMemberByPathSelectionRule : IMemberSelectionRule
98
{
10-
private string selectedPath;
11-
12-
protected SelectMemberByPathSelectionRule(string selectedPath)
13-
{
14-
this.selectedPath = selectedPath;
15-
}
16-
179
public virtual bool IncludesMembers => false;
1810

19-
protected void SetSelectedPath(string path)
20-
{
21-
selectedPath = path;
22-
}
23-
2411
public IEnumerable<IMember> SelectMembers(INode currentNode, IEnumerable<IMember> selectedMembers,
2512
MemberSelectionContext context)
2613
{
27-
string currentPath = currentNode.PathAndName;
28-
29-
// If we're part of a collection comparison, the selected path will not include an index,
30-
// so we need to remove it from the current node as well.
31-
if (!ContainsIndexingQualifiers(selectedPath))
32-
{
33-
currentPath = RemoveIndexQualifiers(currentPath);
34-
}
35-
14+
var currentPath = RemoveRootIndexQualifier(currentNode.PathAndName);
3615
var members = selectedMembers.ToList();
3716
AddOrRemoveMembersFrom(members, currentNode, currentPath, context);
3817

@@ -43,12 +22,7 @@ protected abstract void AddOrRemoveMembersFrom(List<IMember> selectedMembers,
4322
INode parent, string parentPath,
4423
MemberSelectionContext context);
4524

46-
private static bool ContainsIndexingQualifiers(string path)
47-
{
48-
return path.Contains('[', StringComparison.Ordinal) && path.Contains(']', StringComparison.Ordinal);
49-
}
50-
51-
private static string RemoveIndexQualifiers(string path)
25+
private static string RemoveRootIndexQualifier(string path)
5226
{
5327
Match match = new Regex(@"^\[[0-9]+]").Match(path);
5428

Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,57 @@ public void When_property_in_collection_is_excluded_it_should_not_throw()
597597
.Exclude(x => x.Number));
598598
}
599599

600+
[Fact]
601+
public void When_property_in_collection_is_excluded_it_should_not_throw_if_root_is_a_collection()
602+
{
603+
// Arrange
604+
var subject = new
605+
{
606+
Level = new
607+
{
608+
Collection = new[]
609+
{
610+
new
611+
{
612+
Number = 1,
613+
Text = "Text"
614+
},
615+
new
616+
{
617+
Number = 2,
618+
Text = "Actual"
619+
}
620+
}
621+
}
622+
};
623+
624+
var expected = new
625+
{
626+
Level = new
627+
{
628+
Collection = new[]
629+
{
630+
new
631+
{
632+
Number = 1,
633+
Text = "Text"
634+
},
635+
new
636+
{
637+
Number = 3,
638+
Text = "Actual"
639+
}
640+
}
641+
}
642+
};
643+
644+
// Act / Assert
645+
new[] { subject }.Should().BeEquivalentTo(new[] { expected },
646+
options => options
647+
.For(x => x.Level.Collection)
648+
.Exclude(x => x.Number));
649+
}
650+
600651
[Fact]
601652
public void When_collection_in_collection_is_excluded_it_should_not_throw()
602653
{

Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,30 @@ public void When_only_the_excluded_property_doesnt_match_it_should_not_throw()
622622
.Excluding(d => d.Id));
623623
}
624624

625+
[Fact]
626+
public void When_only_the_excluded_property_doesnt_match_it_should_not_throw_if_root_is_a_collection()
627+
{
628+
// Arrange
629+
var dto = new Customer
630+
{
631+
Age = 36,
632+
Birthdate = new DateTime(1973, 9, 20),
633+
Name = "John"
634+
};
635+
636+
var customer = new Customer
637+
{
638+
Age = 36,
639+
Birthdate = new DateTime(1973, 9, 20),
640+
Name = "Dennis"
641+
};
642+
643+
// Act / Assert
644+
new[] { dto }.Should().BeEquivalentTo(new[] { customer }, options => options
645+
.Excluding(d => d.Name)
646+
.Excluding(d => d.Id));
647+
}
648+
625649
[Fact]
626650
[SuppressMessage("ReSharper", "StringLiteralTypo")]
627651
public void When_excluding_members_it_should_pass_if_only_the_excluded_members_are_different()
@@ -742,6 +766,41 @@ public void When_a_deeply_nested_property_with_a_value_mismatch_is_excluded_it_s
742766
act.Should().NotThrow();
743767
}
744768

769+
[Fact]
770+
public void When_a_deeply_nested_property_with_a_value_mismatch_is_excluded_it_should_not_throw_if_root_is_a_collection()
771+
{
772+
// Arrange
773+
var subject = new Root
774+
{
775+
Text = "Root",
776+
Level = new Level1
777+
{
778+
Text = "Level1",
779+
Level = new Level2
780+
{
781+
Text = "Mismatch"
782+
}
783+
}
784+
};
785+
786+
var expected = new RootDto
787+
{
788+
Text = "Root",
789+
Level = new Level1Dto
790+
{
791+
Text = "Level1",
792+
Level = new Level2Dto
793+
{
794+
Text = "Level2"
795+
}
796+
}
797+
};
798+
799+
// Act / Assert
800+
new[] { subject }.Should().BeEquivalentTo(new[] { expected },
801+
options => options.Excluding(r => r.Level.Level.Text));
802+
}
803+
745804
[Fact]
746805
public void When_a_property_with_a_value_mismatch_is_excluded_using_a_predicate_it_should_not_throw()
747806
{
@@ -922,6 +981,74 @@ public void When_excluding_properties_via_non_array_indexers_it_should_exclude_t
922981
act.Should().NotThrow();
923982
}
924983

984+
[Fact]
985+
public void
986+
When_excluding_properties_via_non_array_indexers_it_should_exclude_the_specified_paths_if_root_is_a_collection()
987+
{
988+
// Arrange
989+
var subject = new
990+
{
991+
List = new[]
992+
{
993+
new
994+
{
995+
Foo = 1,
996+
Bar = 2
997+
},
998+
new
999+
{
1000+
Foo = 3,
1001+
Bar = 4
1002+
}
1003+
}.ToList(),
1004+
Dictionary = new Dictionary<string, ClassWithOnlyAProperty>
1005+
{
1006+
["Foo"] = new()
1007+
{
1008+
Value = 1
1009+
},
1010+
["Bar"] = new()
1011+
{
1012+
Value = 2
1013+
}
1014+
}
1015+
};
1016+
1017+
var expected = new
1018+
{
1019+
List = new[]
1020+
{
1021+
new
1022+
{
1023+
Foo = 1,
1024+
Bar = 2
1025+
},
1026+
new
1027+
{
1028+
Foo = 2,
1029+
Bar = 4
1030+
}
1031+
}.ToList(),
1032+
Dictionary = new Dictionary<string, ClassWithOnlyAProperty>
1033+
{
1034+
["Foo"] = new()
1035+
{
1036+
Value = 1
1037+
},
1038+
["Bar"] = new()
1039+
{
1040+
Value = 3
1041+
}
1042+
}
1043+
};
1044+
1045+
// Act / Assert
1046+
new[] { subject }.Should().BeEquivalentTo(new[] { expected },
1047+
options => options
1048+
.Excluding(x => x.List[1].Foo)
1049+
.Excluding(x => x.Dictionary["Bar"].Value));
1050+
}
1051+
9251052
[Fact]
9261053
public void When_excluding_properties_via_non_array_indexers_it_should_not_exclude_paths_with_different_indexes()
9271054
{

docs/_pages/releases.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ sidebar:
2020
* Improved robustness of several assertions when they're wrapped in an `AssertionScope` - [#2133](https://github.com/fluentassertions/fluentassertions/pull/2133)
2121
* The maximum depth `BeEquivalentTo` uses for recursive comparisons was 9 instead of the expected 10 - [#2145](https://github.com/fluentassertions/fluentassertions/pull/2145)
2222

23+
* Fixed `.Excluding()` and `.For().Exclude()` not working if root is a collection - [#2135](https://github.com/fluentassertions/fluentassertions/pull/2135)
24+
2325
## 6.10.0
2426

2527
### Fixes

0 commit comments

Comments
 (0)