2021-01-30 11:59:39 +03:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using MapTo.Sources;
|
|
|
|
|
using MapTo.Tests.Extensions;
|
|
|
|
|
using MapTo.Tests.Infrastructure;
|
|
|
|
|
using Microsoft.CodeAnalysis;
|
2021-04-09 09:48:23 +03:00
|
|
|
|
using Microsoft.CodeAnalysis.CSharp;
|
2021-01-30 11:59:39 +03:00
|
|
|
|
using Xunit;
|
|
|
|
|
using static MapTo.Tests.Common;
|
|
|
|
|
|
|
|
|
|
namespace MapTo.Tests
|
|
|
|
|
{
|
|
|
|
|
public class MapTypeConverterTests
|
|
|
|
|
{
|
|
|
|
|
[Fact]
|
|
|
|
|
public void VerifyMapTypeConverterAttribute()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
const string source = "";
|
|
|
|
|
var expectedInterface = $@"
|
|
|
|
|
{Constants.GeneratedFilesHeader}
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
|
|
|
|
|
namespace MapTo
|
|
|
|
|
{{
|
|
|
|
|
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
|
|
|
|
public sealed class MapTypeConverterAttribute : Attribute
|
|
|
|
|
{{
|
|
|
|
|
public MapTypeConverterAttribute(Type converter, object[] converterParameters = null)
|
|
|
|
|
{{
|
|
|
|
|
Converter = converter;
|
|
|
|
|
ConverterParameters = converterParameters;
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
public Type Converter {{ get; }}
|
|
|
|
|
|
|
|
|
|
public object[] ConverterParameters {{ get; }}
|
|
|
|
|
}}
|
|
|
|
|
}}
|
|
|
|
|
".Trim();
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
|
|
|
|
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
|
|
|
|
}
|
2021-01-30 12:09:58 +03:00
|
|
|
|
|
2021-01-30 11:59:39 +03:00
|
|
|
|
[Fact]
|
|
|
|
|
public void VerifyMapTypeConverterAttributeWithNullableOptionOn()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
const string source = "";
|
|
|
|
|
var expectedInterface = $@"
|
|
|
|
|
{Constants.GeneratedFilesHeader}
|
|
|
|
|
#nullable enable
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
|
|
|
|
|
namespace MapTo
|
|
|
|
|
{{
|
|
|
|
|
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
|
|
|
|
public sealed class MapTypeConverterAttribute : Attribute
|
|
|
|
|
{{
|
|
|
|
|
public MapTypeConverterAttribute(Type converter, object[]? converterParameters = null)
|
|
|
|
|
{{
|
|
|
|
|
Converter = converter;
|
|
|
|
|
ConverterParameters = converterParameters;
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
public Type Converter {{ get; }}
|
|
|
|
|
|
|
|
|
|
public object[]? ConverterParameters {{ get; }}
|
|
|
|
|
}}
|
|
|
|
|
}}
|
|
|
|
|
".Trim();
|
|
|
|
|
|
|
|
|
|
// Act
|
2021-04-09 09:48:23 +03:00
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable, languageVersion: LanguageVersion.CSharp8);
|
2021-01-30 11:59:39 +03:00
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
|
|
|
|
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2021-01-30 12:09:58 +03:00
|
|
|
|
public void VerifyTypeConverterInterface()
|
2021-01-30 11:59:39 +03:00
|
|
|
|
{
|
|
|
|
|
// Arrange
|
2021-01-30 12:09:58 +03:00
|
|
|
|
const string source = "";
|
|
|
|
|
var expectedInterface = $@"
|
|
|
|
|
{Constants.GeneratedFilesHeader}
|
2021-01-30 11:59:39 +03:00
|
|
|
|
|
2021-01-30 12:09:58 +03:00
|
|
|
|
namespace MapTo
|
|
|
|
|
{{
|
|
|
|
|
public interface ITypeConverter<in TSource, out TDestination>
|
|
|
|
|
{{
|
|
|
|
|
TDestination Convert(TSource source, object[] converterParameters);
|
|
|
|
|
}}
|
|
|
|
|
}}
|
|
|
|
|
".Trim();
|
2021-01-30 11:59:39 +03:00
|
|
|
|
|
2021-01-30 12:09:58 +03:00
|
|
|
|
// Act
|
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
|
|
|
|
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
2021-01-30 11:59:39 +03:00
|
|
|
|
}
|
2021-01-30 12:09:58 +03:00
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void VerifyTypeConverterInterfaceWithNullableOptionOn()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
const string source = "";
|
|
|
|
|
var expectedInterface = $@"
|
|
|
|
|
{Constants.GeneratedFilesHeader}
|
|
|
|
|
#nullable enable
|
|
|
|
|
|
|
|
|
|
namespace MapTo
|
|
|
|
|
{{
|
|
|
|
|
public interface ITypeConverter<in TSource, out TDestination>
|
|
|
|
|
{{
|
|
|
|
|
TDestination Convert(TSource source, object[]? converterParameters);
|
|
|
|
|
}}
|
|
|
|
|
}}
|
2021-01-30 11:59:39 +03:00
|
|
|
|
".Trim();
|
|
|
|
|
|
|
|
|
|
// Act
|
2021-04-09 09:48:23 +03:00
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable, languageVersion: LanguageVersion.CSharp8);
|
2021-01-30 11:59:39 +03:00
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
2021-01-30 12:09:58 +03:00
|
|
|
|
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
2021-01-30 11:59:39 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2021-01-30 12:09:58 +03:00
|
|
|
|
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterAndItsParametersToAssignProperties()
|
2021-01-30 11:59:39 +03:00
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var source = GetSourceText(new SourceGeneratorOptions(
|
|
|
|
|
true,
|
|
|
|
|
PropertyBuilder: builder =>
|
|
|
|
|
{
|
|
|
|
|
builder
|
2021-01-30 12:09:58 +03:00
|
|
|
|
.WriteLine("[MapTypeConverter(typeof(Prop4Converter), new object[]{\"G\", 'C', 10})]")
|
|
|
|
|
.WriteLine("public string Prop4 { get; set; }");
|
2021-01-30 11:59:39 +03:00
|
|
|
|
},
|
2021-01-30 12:09:58 +03:00
|
|
|
|
SourcePropertyBuilder: builder => builder.WriteLine("public long Prop4 { get; set; }")));
|
2021-01-30 11:59:39 +03:00
|
|
|
|
|
|
|
|
|
source += @"
|
|
|
|
|
namespace Test
|
|
|
|
|
{
|
|
|
|
|
using MapTo;
|
|
|
|
|
|
2021-01-30 12:09:58 +03:00
|
|
|
|
public class Prop4Converter: ITypeConverter<long, string>
|
2021-01-30 11:59:39 +03:00
|
|
|
|
{
|
2021-01-30 12:09:58 +03:00
|
|
|
|
public string Convert(long source, object[] converterParameters) => source.ToString(converterParameters[0] as string);
|
2021-01-30 11:59:39 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
";
|
|
|
|
|
|
2021-01-30 12:09:58 +03:00
|
|
|
|
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, new object[] { \"G\", 'C', 10 });";
|
|
|
|
|
|
2021-01-30 11:59:39 +03:00
|
|
|
|
// Act
|
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
|
|
|
|
|
|
|
|
// Assert
|
2021-01-30 12:09:58 +03:00
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
2021-02-13 14:27:32 +03:00
|
|
|
|
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedSyntax);
|
2021-01-30 11:59:39 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterToAssignProperties()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var source = GetSourceText(new SourceGeneratorOptions(
|
|
|
|
|
true,
|
|
|
|
|
PropertyBuilder: builder =>
|
|
|
|
|
{
|
|
|
|
|
builder
|
2021-01-30 12:09:58 +03:00
|
|
|
|
.WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
|
|
|
|
.WriteLine("public long Prop4 { get; set; }");
|
2021-01-30 11:59:39 +03:00
|
|
|
|
},
|
2021-01-30 12:09:58 +03:00
|
|
|
|
SourcePropertyBuilder: builder => builder.WriteLine("public string Prop4 { get; set; }")));
|
2021-01-30 11:59:39 +03:00
|
|
|
|
|
|
|
|
|
source += @"
|
|
|
|
|
namespace Test
|
|
|
|
|
{
|
|
|
|
|
using MapTo;
|
|
|
|
|
|
|
|
|
|
public class Prop4Converter: ITypeConverter<string, long>
|
|
|
|
|
{
|
|
|
|
|
public long Convert(string source, object[] converterParameters) => long.Parse(source);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
";
|
|
|
|
|
|
|
|
|
|
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, null);";
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
2021-02-13 14:27:32 +03:00
|
|
|
|
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedSyntax);
|
2021-01-30 11:59:39 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2021-01-30 12:09:58 +03:00
|
|
|
|
public void When_FoundMatchingPropertyNameWithDifferentImplicitlyConvertibleType_Should_GenerateTheProperty()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var source = GetSourceText(new SourceGeneratorOptions(
|
|
|
|
|
true,
|
|
|
|
|
PropertyBuilder: builder => { builder.WriteLine("public long Prop4 { get; set; }"); },
|
|
|
|
|
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
|
|
|
|
|
|
|
|
|
var expectedResult = @"
|
|
|
|
|
partial class Foo
|
|
|
|
|
{
|
2021-02-17 11:32:23 +03:00
|
|
|
|
public Foo(Baz baz)
|
2021-04-09 09:48:23 +03:00
|
|
|
|
: this(new MappingContext(), baz) { }
|
|
|
|
|
|
|
|
|
|
private protected Foo(MappingContext context, Baz baz)
|
2021-01-30 12:09:58 +03:00
|
|
|
|
{
|
2021-04-09 09:48:23 +03:00
|
|
|
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
2021-01-30 12:09:58 +03:00
|
|
|
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
|
|
|
|
|
2021-04-09 09:48:23 +03:00
|
|
|
|
context.Register(baz, this);
|
|
|
|
|
|
2021-01-30 12:09:58 +03:00
|
|
|
|
Prop1 = baz.Prop1;
|
|
|
|
|
Prop2 = baz.Prop2;
|
|
|
|
|
Prop3 = baz.Prop3;
|
|
|
|
|
Prop4 = baz.Prop4;
|
|
|
|
|
}
|
|
|
|
|
".Trim();
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
2021-02-13 14:27:32 +03:00
|
|
|
|
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
2021-01-30 12:09:58 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void When_FoundMatchingPropertyNameWithIncorrectConverterType_ShouldReportError()
|
2021-01-30 11:59:39 +03:00
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var source = GetSourceText(new SourceGeneratorOptions(
|
|
|
|
|
true,
|
|
|
|
|
PropertyBuilder: builder =>
|
|
|
|
|
{
|
|
|
|
|
builder
|
2021-01-30 12:09:58 +03:00
|
|
|
|
.WriteLine("[IgnoreProperty]")
|
|
|
|
|
.WriteLine("public long IgnoreMe { get; set; }")
|
|
|
|
|
.WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
|
|
|
|
.WriteLine("public long Prop4 { get; set; }");
|
2021-01-30 11:59:39 +03:00
|
|
|
|
},
|
2021-01-30 12:09:58 +03:00
|
|
|
|
SourcePropertyBuilder: builder => builder.WriteLine("public string Prop4 { get; set; }")));
|
2021-01-30 11:59:39 +03:00
|
|
|
|
|
|
|
|
|
source += @"
|
|
|
|
|
namespace Test
|
|
|
|
|
{
|
|
|
|
|
using MapTo;
|
|
|
|
|
|
2021-01-30 12:09:58 +03:00
|
|
|
|
public class Prop4Converter: ITypeConverter<string, int>
|
2021-01-30 11:59:39 +03:00
|
|
|
|
{
|
2021-01-30 12:09:58 +03:00
|
|
|
|
public int Convert(string source, object[] converterParameters) => int.Parse(source);
|
2021-01-30 11:59:39 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
";
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
|
|
|
|
|
|
|
|
// Assert
|
2021-01-30 12:09:58 +03:00
|
|
|
|
var expectedError = DiagnosticProvider.InvalidTypeConverterGenericTypesError(GetSourcePropertySymbol("Prop4", compilation), GetSourcePropertySymbol("Prop4", compilation, "Baz"));
|
|
|
|
|
diagnostics.ShouldBeUnsuccessful(expectedError);
|
2021-01-30 11:59:39 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|