2021-06-30 21:16:28 +03:00
using System.Collections.Generic ;
using System.Linq ;
2021-04-17 10:14:39 +03:00
using MapTo.Tests.Extensions ;
2021-02-07 13:49:42 +03:00
using MapTo.Tests.Infrastructure ;
2021-06-30 21:16:28 +03:00
using Microsoft.CodeAnalysis.CSharp ;
2021-04-17 10:14:39 +03:00
using Microsoft.CodeAnalysis.CSharp.Syntax ;
using Shouldly ;
2021-02-07 13:49:42 +03:00
using Xunit ;
using Xunit.Abstractions ;
using static MapTo . Tests . Common ;
namespace MapTo.Tests
{
public class MappedClassesTests
{
private readonly ITestOutputHelper _output ;
public MappedClassesTests ( ITestOutputHelper output )
{
_output = output ;
}
2021-06-30 21:16:28 +03:00
[Theory]
[MemberData(nameof(SecondaryConstructorCheckData))]
public void When_SecondaryConstructorExists_Should_NotGenerateOne ( string source , LanguageVersion languageVersion )
2021-02-07 13:49:42 +03:00
{
// Arrange
2021-06-30 21:16:28 +03:00
source = source . Trim ( ) ;
2021-02-07 13:49:42 +03:00
// Act
2021-06-30 21:16:28 +03:00
var ( compilation , diagnostics ) = CSharpGenerator . GetOutputCompilation ( source , analyzerConfigOptions : DefaultAnalyzerOptions , languageVersion : languageVersion ) ;
2021-02-07 13:49:42 +03:00
// Assert
diagnostics . ShouldBeSuccessful ( ) ;
2021-06-30 21:16:28 +03:00
compilation
. GetGeneratedSyntaxTree ( "DestinationClass" )
. ShouldNotBeNull ( )
. GetRoot ( )
. DescendantNodes ( )
. OfType < ConstructorDeclarationSyntax > ( )
. Count ( )
. ShouldBe ( 1 ) ;
2021-02-07 13:49:42 +03:00
}
2021-06-30 21:16:28 +03:00
public static IEnumerable < object [ ] > SecondaryConstructorCheckData = > new List < object [ ] >
2021-04-17 10:14:39 +03:00
{
2021-06-30 21:16:28 +03:00
new object [ ]
{
@ "
2021-04-17 10:14:39 +03:00
using MapTo ;
namespace Test.Data.Models
{
public class SourceClass { public string Prop1 { get ; set ; } }
[MapFrom(typeof(SourceClass))]
public partial class DestinationClass
{
public DestinationClass ( SourceClass source ) : this ( new MappingContext ( ) , source ) { }
public string Prop1 { get ; set ; }
}
}
2021-06-30 21:16:28 +03:00
",
LanguageVersion . CSharp7_3
} ,
new object [ ]
{
@ "
using MapTo ;
namespace Test.Data.Models
{
public record SourceClass ( string Prop1 ) ;
[MapFrom(typeof(SourceClass))]
public partial record DestinationClass ( string Prop1 )
{
public DestinationClass ( SourceClass source ) : this ( new MappingContext ( ) , source ) { }
}
}
",
LanguageVersion . CSharp9
}
} ;
[Theory]
[MemberData(nameof(SecondaryCtorWithoutPrivateCtorData))]
public void When_SecondaryConstructorExistsButDoNotReferencePrivateConstructor_Should_ReportError ( string source , LanguageVersion languageVersion )
{
// Arrange
source = source . Trim ( ) ;
2021-04-17 10:14:39 +03:00
// Act
2021-06-30 21:16:28 +03:00
var ( compilation , diagnostics ) = CSharpGenerator . GetOutputCompilation ( source , analyzerConfigOptions : DefaultAnalyzerOptions , languageVersion : languageVersion ) ;
2021-06-29 09:14:00 +03:00
2021-04-17 10:14:39 +03:00
// Assert
2021-06-30 21:16:28 +03:00
var constructorSyntax = compilation . SyntaxTrees
. First ( )
2021-04-17 10:14:39 +03:00
. GetRoot ( )
. DescendantNodes ( )
. OfType < ConstructorDeclarationSyntax > ( )
2021-06-30 21:16:28 +03:00
. Single ( ) ;
diagnostics . ShouldNotBeSuccessful ( DiagnosticsFactory . MissingConstructorArgument ( constructorSyntax ) ) ;
2021-04-17 10:14:39 +03:00
}
2021-06-29 09:14:00 +03:00
2021-06-30 21:16:28 +03:00
public static IEnumerable < object [ ] > SecondaryCtorWithoutPrivateCtorData = > new List < object [ ] >
2021-04-17 10:14:39 +03:00
{
2021-06-30 21:16:28 +03:00
new object [ ]
{
@ "
2021-04-17 10:14:39 +03:00
using MapTo ;
namespace Test.Data.Models
{
public class SourceClass { public string Prop1 { get ; set ; } }
[MapFrom(typeof(SourceClass))]
public partial class DestinationClass
{
public DestinationClass ( SourceClass source ) { }
public string Prop1 { get ; set ; }
}
}
2021-06-30 21:16:28 +03:00
",
LanguageVersion . CSharp7_3
} ,
new object [ ]
{
@ "
using MapTo ;
namespace Test.Data.Models
{
public record SourceClass ( string Prop1 ) ;
2021-06-29 09:14:00 +03:00
2021-06-30 21:16:28 +03:00
[MapFrom(typeof(SourceClass))]
public partial record DestinationClass ( string Prop1 )
{
public DestinationClass ( SourceClass source ) : this ( "" invalid "" ) { }
}
}
",
LanguageVersion . CSharp9
}
} ;
2021-04-17 10:14:39 +03:00
2021-06-23 11:13:19 +03:00
[Fact]
public void When_PropertyNameIsTheSameAsClassName_Should_MapAccordingly ( )
{
// Arrange
var source = @ "
namespace Sale
{
public class Sale { public Sale Prop1 { get ; set ; } }
}
namespace SaleModel
{
using MapTo ;
using Sale ;
[MapFrom(typeof(Sale))]
public partial class SaleModel
{
[MapProperty(SourcePropertyName = nameof(global::Sale.Sale.Prop1))]
public Sale Sale { get ; set ; }
}
}
".Trim();
// Act
2021-06-30 21:16:28 +03:00
var ( _ , diagnostics ) = CSharpGenerator . GetOutputCompilation ( source , analyzerConfigOptions : DefaultAnalyzerOptions ) ;
2021-06-23 11:13:19 +03:00
// Assert
diagnostics . ShouldBeSuccessful ( ) ;
}
2021-07-02 11:12:11 +03:00
[Theory]
[MemberData(nameof(SameSourceAndDestinationTypeNameData))]
public void When_SourceAndDestinationNamesAreTheSame_Should_MapAccordingly ( string source , LanguageVersion languageVersion )
{
// Arrange
source = source . Trim ( ) ;
// Act
var ( _ , diagnostics ) = CSharpGenerator . GetOutputCompilation ( source , analyzerConfigOptions : DefaultAnalyzerOptions , languageVersion : languageVersion ) ;
// Assert
diagnostics . ShouldBeSuccessful ( ) ;
}
public static IEnumerable < object > SameSourceAndDestinationTypeNameData = > new List < object >
{
new object [ ]
{
@ "
namespace Test
{
public class TypeName { public int Prop2 { get ; set ; } }
}
namespace Test2
{
using MapTo ;
[MapFrom(typeof(Test.TypeName))]
public partial class TypeName
{
[MapProperty(SourcePropertyName=""Prop2"")]
public int Prop1 { get ; set ; }
}
} ",
LanguageVersion . CSharp7_3
} ,
new object [ ]
{
@ "
namespace Test
{
public record TypeName ( int Prop2 ) ;
}
namespace Test2
{
using MapTo ;
[MapFrom(typeof(Test.TypeName))]
public partial record TypeName ( [ MapProperty ( SourcePropertyName = "" Prop2 "" ) ] int Prop1 ) ;
} ",
LanguageVersion . CSharp9
} ,
new object [ ]
{
@ "
namespace Test
{
using System.Collections.Generic ;
public class SourceType2 { public int Id { get ; set ; } }
public class SourceType
{
public int Id { get ; set ; }
public List < SourceType2 > Prop1 { get ; set ; }
}
}
namespace Test2
{
using MapTo ;
using System.Collections.Generic ;
[MapFrom(typeof(Test.SourceType2))]
public partial class SourceType2 { public int Id { get ; set ; } }
[MapFrom(typeof(Test.SourceType))]
public partial class SourceType
{
public int Id { get ; set ; }
public IReadOnlyList < SourceType2 > Prop1 { get ; set ; }
}
} ",
LanguageVersion . CSharp7_3
} ,
new object [ ]
{
@ "
namespace Test
{
using System.Collections.Generic ;
public record SourceType ( int Id , List < SourceType2 > Prop1 ) ;
public record SourceType2 ( int Id ) ;
}
namespace Test2
{
using MapTo ;
using System.Collections.Generic ;
[MapFrom(typeof(Test.SourceType2))]
public partial record SourceType2 ( int Id ) ;
[MapFrom(typeof(Test.SourceType))]
public partial record SourceType ( int Id , IReadOnlyList < SourceType2 > Prop1 ) ;
} ",
LanguageVersion . CSharp9
} ,
new object [ ]
{
@ "
namespace Test
{
using System.Collections.Generic ;
public record SourceType1 ( int Id ) ;
public record SourceType2 ( int Id , List < SourceType1 > Prop1 ) ;
}
namespace Test
{
using MapTo ;
using System.Collections.Generic ;
[MapFrom(typeof(Test.SourceType1))]
public partial record SourceType3 ( int Id ) ;
[MapFrom(typeof(Test.SourceType2))]
public partial record SourceType4 ( int Id , IReadOnlyList < SourceType3 > Prop1 ) ;
} ",
LanguageVersion . CSharp9
}
} ;
2021-06-30 21:16:28 +03:00
[Theory]
[MemberData(nameof(VerifyMappedTypesData))]
public void VerifyMappedTypes ( string [ ] sources , LanguageVersion languageVersion )
{
// Arrange
// Act
var ( compilation , diagnostics ) = CSharpGenerator . GetOutputCompilation ( sources , analyzerConfigOptions : DefaultAnalyzerOptions , languageVersion : languageVersion ) ;
// Assert
diagnostics . ShouldBeSuccessful ( ) ;
_output . WriteLine ( compilation . PrintSyntaxTree ( ) ) ;
}
public static IEnumerable < object [ ] > VerifyMappedTypesData = > new List < object [ ] >
{
new object [ ] { new [ ] { MainSourceClass , NestedSourceClass , MainDestinationClass , NestedDestinationClass } , LanguageVersion . CSharp7_3 } ,
new object [ ] { new [ ] { MainSourceRecord , NestedSourceRecord , MainDestinationRecord , NestedDestinationRecord } , LanguageVersion . CSharp9 }
} ;
[Fact]
public void VerifySelfReferencingRecords ( )
{
// Arrange
var source = @ "
namespace Tests.Data.Models
2021-02-07 13:49:42 +03:00
{
2021-06-30 21:16:28 +03:00
using System.Collections.Generic ;
public record Employee ( int Id , string EmployeeCode , Manager Manager ) ;
2021-02-07 13:49:42 +03:00
2021-06-30 21:16:28 +03:00
public record Manager ( int Id , string EmployeeCode , Manager Manager , int Level , List < Employee > Employees ) : Employee ( Id , EmployeeCode , Manager ) ;
}
2021-02-07 13:49:42 +03:00
2021-06-30 21:16:28 +03:00
namespace Tests.Data.ViewModels
{
using System.Collections.Generic ;
using Tests.Data.Models ;
using MapTo ;
[MapFrom(typeof(Employee))]
public partial record EmployeeViewModel ( int Id , string EmployeeCode , ManagerViewModel Manager ) ;
[MapFrom(typeof(Manager))]
public partial record ManagerViewModel ( int Id , string EmployeeCode , ManagerViewModel Manager , int Level , List < EmployeeViewModel > Employees ) : EmployeeViewModel ( Id , EmployeeCode , Manager ) ;
2021-02-07 13:49:42 +03:00
}
".Trim();
2021-06-30 21:16:28 +03:00
// Act
var ( compilation , diagnostics ) = CSharpGenerator . GetOutputCompilation ( source , analyzerConfigOptions : DefaultAnalyzerOptions , languageVersion : LanguageVersion . CSharp9 ) ;
// Assert
diagnostics . ShouldBeSuccessful ( ) ;
_output . WriteLine ( compilation . PrintSyntaxTree ( ) ) ;
}
2021-02-07 13:49:42 +03:00
private static string MainSourceClass = > @ "
using System ;
namespace Test.Data.Models
{
public class User
{
public int Id { get ; set ; }
public DateTimeOffset RegisteredAt { get ; set ; }
public Profile Profile { get ; set ; }
}
}
".Trim();
2021-06-30 21:16:28 +03:00
private static string NestedSourceClass = > @ "
namespace Test.Data.Models
2021-02-07 13:49:42 +03:00
{
2021-06-30 21:16:28 +03:00
public class Profile
2021-02-07 13:49:42 +03:00
{
2021-06-30 21:16:28 +03:00
public string FirstName { get ; set ; }
2021-02-07 13:49:42 +03:00
2021-06-30 21:16:28 +03:00
public string LastName { get ; set ; }
public string FullName = > $"" { FirstName } { LastName } "" ;
2021-02-07 13:49:42 +03:00
}
}
".Trim();
2021-06-30 21:16:28 +03:00
2021-02-07 13:49:42 +03:00
private static string MainDestinationClass = > @ "
using System ;
using MapTo ;
using Test.Data.Models ;
namespace Test.ViewModels
{
[MapFrom(typeof(User))]
public partial class UserViewModel
{
[MapProperty(SourcePropertyName = nameof(User.Id))]
[MapTypeConverter(typeof(IdConverter))]
public string Key { get ; }
public DateTimeOffset RegisteredAt { get ; set ; }
// [IgnoreProperty]
public ProfileViewModel Profile { get ; set ; }
private class IdConverter : ITypeConverter < int , string >
{
public string Convert ( int source , object [ ] converterParameters ) = > $"" { source : X } "" ;
}
}
}
".Trim();
2021-06-30 21:16:28 +03:00
private static string NestedDestinationClass = > @ "
using MapTo ;
using Test.Data.Models ;
namespace Test.ViewModels
{
[MapFrom(typeof(Profile))]
public partial class ProfileViewModel
{
public string FirstName { get ; }
public string LastName { get ; }
}
}
".Trim();
private static string MainSourceRecord = > BuildSourceRecord ( "public record User(int Id, DateTimeOffset RegisteredAt, Profile Profile);" ) ;
private static string MainDestinationRecord = > BuildDestinationRecord ( @ "
[MapFrom(typeof(User))]
public partial record UserViewModel (
[MapProperty(SourcePropertyName = nameof(User.Id))]
[MapTypeConverter(typeof(UserViewModel.IdConverter))]
string Key ,
DateTimeOffset RegisteredAt )
{
private class IdConverter : ITypeConverter < int , string >
{
public string Convert ( int source , object [ ] converterParameters ) = > $"" { source : X } "" ;
}
} ");
private static string NestedSourceRecord = > BuildSourceRecord ( "public record Profile(string FirstName, string LastName) { public string FullName => $\"{FirstName} {LastName}\"; }" ) ;
private static string NestedDestinationRecord = > BuildDestinationRecord ( "[MapFrom(typeof(Profile))] public partial record ProfileViewModel(string FirstName, string LastName);" ) ;
private static string BuildSourceRecord ( string record )
{
return $ @ "
using System ;
namespace RecordTest.Data.Models
{ {
{ record }
} }
".Trim();
}
private static string BuildDestinationRecord ( string record )
{
return $ @ "
using System ;
using MapTo ;
using RecordTest.Data.Models ;
namespace RecordTest.ViewModels
{ {
{ record }
} }
".Trim();
}
2021-02-07 13:49:42 +03:00
}
}