Skip to content

Commit 386c5c5

Browse files
committed
Fixing exact source member matching, re: issue #34 / Removing 'Root' prefix from non-root qualified member joined names / Increasing test coverage
1 parent 9f198d6 commit 386c5c5

File tree

11 files changed

+142
-21
lines changed

11 files changed

+142
-21
lines changed

AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
<Compile Include="NonParallelTestsBase.cs" />
8080
<Compile Include="Properties\AssemblyInfo.cs" />
8181
<Compile Include="SimpleTypeConversion\WhenConvertingToStrings.cs" />
82+
<Compile Include="WhenViewingMappingPlans.cs" />
8283
</ItemGroup>
8384
<ItemGroup>
8485
<None Include="app.config" />
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
namespace AgileObjects.AgileMapper.UnitTests.NonParallel
2+
{
3+
using Shouldly;
4+
using TestClasses;
5+
using Xunit;
6+
7+
public class WhenViewingMappingPlans : NonParallelTestsBase
8+
{
9+
[Fact]
10+
public void ShouldCreateAPlanSetViaTheStaticApi()
11+
{
12+
TestThenReset(() =>
13+
{
14+
var plans = Mapper.GetPlansFor<Product>().To<ProductDto>().ToString();
15+
16+
plans.ShouldContain("Rule Set: CreateNew");
17+
plans.ShouldContain("Rule Set: Overwrite");
18+
plans.ShouldContain("Rule Set: Merge");
19+
});
20+
}
21+
22+
[Fact]
23+
public void ShouldShowAllCachedMappingPlansViaTheStaticApi()
24+
{
25+
TestThenReset(() =>
26+
{
27+
Mapper.GetPlanFor<MysteryCustomer>().ToANew<MysteryCustomerViewModel>();
28+
Mapper.GetPlansFor(new MegaProduct()).To<ProductDtoMega>();
29+
30+
var plan = Mapper.GetPlansInCache();
31+
32+
plan.ShouldContain("MysteryCustomer -> MysteryCustomerViewModel");
33+
plan.ShouldContain("MegaProduct -> ProductDtoMega");
34+
plan.ShouldContain("Rule set: CreateNew");
35+
plan.ShouldContain("Rule set: Merge");
36+
plan.ShouldContain("Rule set: Overwrite");
37+
});
38+
}
39+
}
40+
}

AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,21 @@ public void ShouldApplyDifferingTargetTypeInlineDataSourceMemberConfig()
313313
}
314314
}
315315

316+
[Fact]
317+
public void ShouldHandleANullSourceMember()
318+
{
319+
using (var mapper = Mapper.CreateNew())
320+
{
321+
var result = mapper
322+
.Map(default(PersonViewModel))
323+
.ToANew<Person>(cfg => cfg
324+
.Map((pvm, p) => "Named: " + pvm.Name)
325+
.To(p => p.Name));
326+
327+
result.ShouldBeNull();
328+
}
329+
}
330+
316331
#region Helper Members
317332

318333
private static Expression<Func<IMappingData<PublicProperty<int>, PublicField<int>>, object>> SubtractOne =>

AgileMapper.UnitTests/WhenMappingOverComplexTypeMembers.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ public void ShouldNotPopulateAMemberWithNoMatchingSource()
6565
result.Address.ShouldBeNull();
6666
}
6767

68+
// See https://github.com/agileobjects/AgileMapper/issues/34
69+
[Fact]
70+
public void ShouldNotPopulateAMemberWithANullSource()
71+
{
72+
var result = Mapper.Map(new RockModel()).Over(new PaperModel());
73+
74+
result.Paper.ShouldBeNull();
75+
}
76+
6877
[Fact]
6978
public void ShouldNotOverwriteAMemberWithNoMatchingSource()
7079
{
@@ -135,5 +144,37 @@ public void ShouldHandleANullReadOnlyNestedMemberProperty()
135144

136145
result.Value.ShouldBeNull();
137146
}
147+
148+
#region Helper Classes
149+
150+
internal class PaperModel
151+
{
152+
public int Id { get; set; }
153+
154+
public int? PaperId { get; set; }
155+
156+
public Paper Paper { get; set; }
157+
}
158+
159+
internal class RockModel
160+
{
161+
public int Id { get; set; }
162+
163+
public int? RockId { get; set; }
164+
165+
public Rock Rock { get; set; }
166+
}
167+
168+
internal class Rock
169+
{
170+
public int Id { get; set; }
171+
}
172+
173+
internal class Paper
174+
{
175+
public int Id { get; set; }
176+
}
177+
178+
#endregion
138179
}
139180
}

AgileMapper/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ internal static class Constants
1313
{
1414
public static readonly bool ReflectionNotPermitted = ReflectionExtensions.ReflectionNotPermitted;
1515

16+
public static readonly string RootMemberName = "Root";
1617
public static readonly string EnumerableElementName = "[i]";
1718

1819
public static readonly Type[] NoTypeArguments = Enumerable<Type>.EmptyArray;

AgileMapper/DataSources/ConfiguredDictionaryDataSourceFactory.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,10 @@ public override bool AppliesTo(IBasicMapperData mapperData)
2525
return false;
2626
}
2727

28-
return Matches(mapperData.TargetMember) && base.AppliesTo(mapperData);
28+
return mapperData.TargetMember.Matches(TargetDictionaryEntryMember) &&
29+
base.AppliesTo(mapperData);
2930
}
3031

31-
public bool Matches(QualifiedMember targetMember)
32-
=> targetMember.Matches(TargetDictionaryEntryMember);
33-
3432
protected override bool MembersConflict(UserConfiguredItemBase otherItem)
3533
{
3634
return otherItem is ConfiguredDictionaryDataSourceFactory otherDictionaryItem &&

AgileMapper/Members/DictionaryTargetMember.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,9 @@ protected override QualifiedMember CreateRuntimeTypedMember(Type runtimeType)
121121

122122
public override bool Matches(IQualifiedMember otherMember)
123123
{
124-
var matches = base.Matches(otherMember);
125-
126124
if (_key == null)
127125
{
128-
return matches;
126+
return base.Matches(otherMember);
129127
}
130128

131129
return GetKeyNameOrNull() == otherMember.Name;

AgileMapper/Members/MemberExtensions.cs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Extensions;
1111
using NetStandardPolyfills;
1212
using ReadableExpressions.Extensions;
13+
using static System.StringComparison;
1314

1415
internal static class MemberExtensions
1516
{
@@ -32,12 +33,12 @@ private static string GetMemberPath(IQualifiedMember member, IQualifiedMember ro
3233
return rootTypeName;
3334
}
3435

35-
if (memberPath.StartsWith(rootMember.Name, StringComparison.Ordinal))
36+
if (memberPath.StartsWith(rootMember.Name, Ordinal))
3637
{
3738
return rootTypeName + memberPath.Substring(rootMember.Name.Length);
3839
}
3940

40-
var rootMemberNameIndex = memberPath.IndexOf("." + rootMember.Name + ".", StringComparison.Ordinal);
41+
var rootMemberNameIndex = memberPath.IndexOf("." + rootMember.Name + ".", Ordinal);
4142

4243
if (rootMemberNameIndex == -1)
4344
{
@@ -132,18 +133,33 @@ public static ICollection<string> ExtendWith(
132133
return mapperContext.NamingSettings.ExtendJoinedNames(parentJoinedNames, memberMatchingNames);
133134
}
134135

135-
public static bool CouldMatch(this IEnumerable<string> memberNames, IEnumerable<string> otherMemberNames)
136+
public static bool CouldMatch(this ICollection<string> memberNames, ICollection<string> otherMemberNames)
136137
{
138+
if (otherMemberNames.HasOne() && (otherMemberNames.First() == Constants.RootMemberName) ||
139+
memberNames.HasOne() && (memberNames.First() == Constants.RootMemberName))
140+
{
141+
return true;
142+
}
143+
137144
return otherMemberNames
138-
.Any(otherJoinedName => memberNames
139-
.Any(joinedName => otherJoinedName.StartsWith(joinedName, StringComparison.OrdinalIgnoreCase)));
145+
.Any(otherJoinedName => (otherJoinedName == Constants.RootMemberName) || memberNames
146+
.Any(joinedName => (joinedName == Constants.RootMemberName) || otherJoinedName.StartsWith(joinedName, OrdinalIgnoreCase)));
140147
}
141148

142-
public static bool Match(this IEnumerable<string> memberNames, IEnumerable<string> otherMemberNames)
149+
public static bool Match(this ICollection<string> memberNames, ICollection<string> otherMemberNames)
143150
{
144-
return memberNames
145-
.Intersect(otherMemberNames, StringComparer.OrdinalIgnoreCase)
146-
.Any();
151+
if (!memberNames.HasOne())
152+
{
153+
return memberNames
154+
.Intersect(otherMemberNames, StringComparer.OrdinalIgnoreCase)
155+
.Any();
156+
}
157+
158+
var memberName = memberNames.First();
159+
160+
return otherMemberNames.HasOne()
161+
? memberName.Equals(otherMemberNames.First(), OrdinalIgnoreCase)
162+
: otherMemberNames.Any(otherMemberName => otherMemberName.Equals(memberName, OrdinalIgnoreCase));
147163
}
148164

149165
public static TMember GetElementMember<TMember>(this TMember enumerableMember)

AgileMapper/Members/NamingSettings.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ private IEnumerable<string> EnumerateMatchingNames(Member member)
140140
{
141141
if (member.IsRoot)
142142
{
143-
yield return "Root";
143+
yield return Constants.RootMemberName;
144144
yield break;
145145
}
146146

@@ -177,8 +177,16 @@ public ICollection<string> GetJoinedNamesFor(string[][] matchingNameSets)
177177

178178
public ICollection<string> ExtendJoinedNames(ICollection<string> parentJoinedNames, string[] names)
179179
{
180+
var firstParentJoinedName = parentJoinedNames.First();
181+
182+
if (parentJoinedNames.HasOne() && (firstParentJoinedName == Constants.RootMemberName))
183+
{
184+
// Don't bother to prepend 'Root' as a joined name:
185+
return names;
186+
}
187+
180188
var isElementMember = (names.Length == 1) && (names[0] == Constants.EnumerableElementName);
181-
var wasElementMember = parentJoinedNames.First().EndsWith(Constants.EnumerableElementName, StringComparison.Ordinal);
189+
var wasElementMember = firstParentJoinedName.EndsWith(Constants.EnumerableElementName, StringComparison.Ordinal);
182190

183191
var numberOfExtendedJoinedNames = isElementMember
184192
? parentJoinedNames.Count

AgileMapper/Members/SourceMemberMatcher.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ private static bool ExactMatchingSourceMemberExists(
4747
{
4848
var sourceMember = QuerySourceMembers(
4949
parentSourceMember,
50-
m => m.Name == targetData.MapperData.TargetMember.Name).FirstOrDefault();
50+
m => targetData.MapperData.TargetMember.LeafMember.Equals(m) ||
51+
targetData.MapperData.TargetMember.JoinedNames.Match(new[] { m.Name }))
52+
.FirstOrDefault();
5153

52-
if ((sourceMember == null) ||
54+
if ((sourceMember == null) ||
5355
!TypesAreCompatible(sourceMember.Type, targetData.MapperData))
5456
{
5557
matchingMember = null;

0 commit comments

Comments
 (0)