2020-12-24 13:05:29 +03:00
|
|
|
using System;
|
2020-12-19 10:55:47 +03:00
|
|
|
using System.Linq;
|
2020-12-19 12:05:49 +03:00
|
|
|
using System.Text;
|
2020-12-24 13:05:29 +03:00
|
|
|
using MapTo.Extensions;
|
2020-12-22 20:35:20 +03:00
|
|
|
using MapToTests;
|
|
|
|
using Microsoft.CodeAnalysis;
|
2020-12-19 10:55:47 +03:00
|
|
|
using Shouldly;
|
|
|
|
using Xunit;
|
|
|
|
using Xunit.Abstractions;
|
|
|
|
|
2020-12-22 20:35:20 +03:00
|
|
|
namespace MapTo.Tests
|
2020-12-19 10:55:47 +03:00
|
|
|
{
|
|
|
|
public class Tests
|
|
|
|
{
|
2020-12-24 13:05:29 +03:00
|
|
|
private const int Indent1 = 4;
|
|
|
|
private const int Indent2 = Indent1 * 2;
|
|
|
|
private const int Indent3 = Indent1 * 3;
|
|
|
|
|
2020-12-21 19:34:53 +03:00
|
|
|
public Tests(ITestOutputHelper output)
|
|
|
|
{
|
|
|
|
_output = output;
|
|
|
|
}
|
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
private readonly ITestOutputHelper _output;
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-24 13:05:29 +03:00
|
|
|
private static readonly string ExpectedAttribute = $@"{SourceBuilder.GeneratedFilesHeader}
|
2020-12-19 10:55:47 +03:00
|
|
|
using System;
|
|
|
|
|
|
|
|
namespace MapTo
|
2020-12-24 13:05:29 +03:00
|
|
|
{{
|
2020-12-19 10:55:47 +03:00
|
|
|
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
|
|
|
public sealed class MapFromAttribute : Attribute
|
2020-12-24 13:05:29 +03:00
|
|
|
{{
|
2020-12-19 10:55:47 +03:00
|
|
|
public MapFromAttribute(Type sourceType)
|
2020-12-24 13:05:29 +03:00
|
|
|
{{
|
2020-12-19 10:55:47 +03:00
|
|
|
SourceType = sourceType;
|
2020-12-24 13:05:29 +03:00
|
|
|
}}
|
2020-12-19 10:55:47 +03:00
|
|
|
|
2020-12-24 13:05:29 +03:00
|
|
|
public Type SourceType {{ get; }}
|
|
|
|
}}
|
|
|
|
}}";
|
|
|
|
|
|
|
|
private record SourceGeneratorOptions(
|
|
|
|
bool UseMapToNamespace = false,
|
|
|
|
string SourceClassNamespace = "Test.Models",
|
|
|
|
int ClassPropertiesCount = 3,
|
|
|
|
int SourceClassPropertiesCount = 3,
|
|
|
|
Action<StringBuilder> PropertyBuilder = null,
|
|
|
|
Action<StringBuilder> SourcePropertyBuilder = null);
|
|
|
|
|
|
|
|
private static string GetSourceText(SourceGeneratorOptions options = null)
|
2020-12-19 10:55:47 +03:00
|
|
|
{
|
2020-12-24 13:05:29 +03:00
|
|
|
const string ns = "Test";
|
|
|
|
options ??= new SourceGeneratorOptions();
|
|
|
|
var hasDifferentSourceNamespace = options.SourceClassNamespace != ns;
|
2020-12-21 19:34:53 +03:00
|
|
|
var builder = new StringBuilder();
|
2020-12-24 13:05:29 +03:00
|
|
|
|
|
|
|
if (options.UseMapToNamespace)
|
|
|
|
{
|
|
|
|
builder.AppendFormat("using {0};", SourceBuilder.NamespaceName).AppendLine();
|
|
|
|
}
|
|
|
|
|
|
|
|
builder
|
|
|
|
.AppendFormat("using {0};", options.SourceClassNamespace)
|
|
|
|
.AppendLine()
|
|
|
|
.AppendLine();
|
|
|
|
|
|
|
|
builder
|
|
|
|
.AppendFormat("namespace {0}", ns)
|
|
|
|
.AppendOpeningBracket();
|
|
|
|
|
|
|
|
if (hasDifferentSourceNamespace && options.UseMapToNamespace)
|
|
|
|
{
|
|
|
|
builder
|
|
|
|
.PadLeft(Indent1)
|
|
|
|
.AppendFormat("using {0};", options.SourceClassNamespace)
|
|
|
|
.AppendLine()
|
|
|
|
.AppendLine();
|
|
|
|
}
|
|
|
|
|
|
|
|
builder
|
|
|
|
.PadLeft(Indent1)
|
|
|
|
.AppendLine(options.UseMapToNamespace ? "[MapTo.MapFrom(typeof(Baz))]" : "[MapFrom(typeof(Baz))]")
|
|
|
|
.PadLeft(Indent1).Append("public partial class Foo")
|
|
|
|
.AppendOpeningBracket(Indent1);
|
|
|
|
|
|
|
|
for (var i = 1; i <= options.ClassPropertiesCount; i++)
|
|
|
|
{
|
|
|
|
builder
|
|
|
|
.PadLeft(Indent2)
|
|
|
|
.AppendLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
|
|
|
}
|
|
|
|
|
|
|
|
options.PropertyBuilder?.Invoke(builder);
|
|
|
|
|
|
|
|
builder
|
|
|
|
.AppendClosingBracket(Indent1, padNewLine: false)
|
|
|
|
.AppendClosingBracket()
|
|
|
|
.AppendLine()
|
|
|
|
.AppendLine();
|
|
|
|
|
|
|
|
builder
|
|
|
|
.AppendFormat("namespace {0}", options.SourceClassNamespace)
|
|
|
|
.AppendOpeningBracket()
|
|
|
|
.PadLeft(Indent1).Append("public class Baz")
|
|
|
|
.AppendOpeningBracket(Indent1);
|
|
|
|
|
|
|
|
for (var i = 1; i <= options.SourceClassPropertiesCount; i++)
|
|
|
|
{
|
|
|
|
builder
|
|
|
|
.PadLeft(Indent2)
|
|
|
|
.AppendLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
|
|
|
}
|
|
|
|
|
|
|
|
options.SourcePropertyBuilder?.Invoke(builder);
|
|
|
|
|
|
|
|
builder
|
|
|
|
.AppendClosingBracket(Indent1, padNewLine: false)
|
|
|
|
.AppendClosingBracket();
|
2020-12-21 19:34:53 +03:00
|
|
|
|
|
|
|
return builder.ToString();
|
2020-12-19 10:55:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
2020-12-21 19:34:53 +03:00
|
|
|
public void VerifyMapToAttribute()
|
2020-12-19 10:55:47 +03:00
|
|
|
{
|
|
|
|
// Arrange
|
|
|
|
const string source = "";
|
|
|
|
|
|
|
|
// Act
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
// Assert
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
2020-12-21 19:34:53 +03:00
|
|
|
compilation.SyntaxTrees.ShouldContain(c => c.ToString() == ExpectedAttribute);
|
2020-12-19 10:55:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public void When_MapToAttributeFound_Should_GenerateTheClass()
|
|
|
|
{
|
|
|
|
// Arrange
|
|
|
|
const string source = @"
|
2020-12-21 13:20:29 +03:00
|
|
|
using MapTo;
|
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
namespace Test
|
|
|
|
{
|
2020-12-21 13:20:29 +03:00
|
|
|
[MapFrom(typeof(Baz))]
|
2020-12-19 10:55:47 +03:00
|
|
|
public partial class Foo
|
|
|
|
{
|
2020-12-22 20:35:20 +03:00
|
|
|
public int Prop1 { get; set; }
|
2020-12-19 10:55:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
public class Baz
|
|
|
|
{
|
|
|
|
public int Prop1 { get; set; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
";
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
const string expectedResult = @"
|
|
|
|
// <auto-generated />
|
2020-12-19 11:08:19 +03:00
|
|
|
using System;
|
2020-12-19 10:55:47 +03:00
|
|
|
|
|
|
|
namespace Test
|
|
|
|
{
|
|
|
|
public partial class Foo
|
|
|
|
{
|
2020-12-21 18:53:44 +03:00
|
|
|
public Foo(Test.Baz baz)
|
2020-12-19 10:55:47 +03:00
|
|
|
{
|
2020-12-19 11:08:19 +03:00
|
|
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
2020-12-22 20:35:20 +03:00
|
|
|
|
|
|
|
Prop1 = baz.Prop1;
|
2020-12-19 10:55:47 +03:00
|
|
|
}
|
|
|
|
";
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
// Act
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
// Assert
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
2020-12-19 11:08:19 +03:00
|
|
|
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
2020-12-19 10:55:47 +03:00
|
|
|
}
|
2020-12-22 20:35:20 +03:00
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public void When_MapToAttributeFoundWithoutMatchingProperties_Should_ReportError()
|
|
|
|
{
|
|
|
|
// Arrange
|
|
|
|
var expectedDiagnostic = Diagnostics.NoMatchingPropertyFoundError(Location.None, "Foo", "Baz");
|
|
|
|
const string source = @"
|
|
|
|
using MapTo;
|
|
|
|
|
|
|
|
namespace Test
|
|
|
|
{
|
|
|
|
[MapFrom(typeof(Baz))]
|
|
|
|
public partial class Foo { }
|
|
|
|
|
|
|
|
public class Baz { public int Prop1 { get; set; } }
|
|
|
|
}
|
|
|
|
";
|
|
|
|
|
|
|
|
// Act
|
|
|
|
var (_, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
var error = diagnostics.FirstOrDefault(d => d.Id == expectedDiagnostic.Id);
|
|
|
|
error.ShouldNotBeNull();
|
|
|
|
}
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
[Fact]
|
|
|
|
public void When_MapToAttributeWithNamespaceFound_Should_GenerateTheClass()
|
|
|
|
{
|
|
|
|
// Arrange
|
|
|
|
const string source = @"
|
|
|
|
namespace Test
|
|
|
|
{
|
2020-12-21 13:20:29 +03:00
|
|
|
[MapTo.MapFrom(typeof(Baz))]
|
2020-12-22 20:35:20 +03:00
|
|
|
public partial class Foo { public int Prop1 { get; set; } }
|
2020-12-19 10:55:47 +03:00
|
|
|
|
2020-12-22 20:35:20 +03:00
|
|
|
public class Baz { public int Prop1 { get; set; } }
|
2020-12-19 10:55:47 +03:00
|
|
|
}
|
|
|
|
";
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
const string expectedResult = @"
|
|
|
|
// <auto-generated />
|
2020-12-19 11:08:19 +03:00
|
|
|
using System;
|
2020-12-19 10:55:47 +03:00
|
|
|
|
|
|
|
namespace Test
|
|
|
|
{
|
|
|
|
public partial class Foo
|
|
|
|
{
|
2020-12-21 18:53:44 +03:00
|
|
|
public Foo(Test.Baz baz)
|
2020-12-19 10:55:47 +03:00
|
|
|
{
|
2020-12-19 11:08:19 +03:00
|
|
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
2020-12-22 20:35:20 +03:00
|
|
|
|
|
|
|
Prop1 = baz.Prop1;
|
2020-12-19 10:55:47 +03:00
|
|
|
}
|
|
|
|
";
|
2020-12-21 19:34:53 +03:00
|
|
|
|
|
|
|
// Act
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
|
|
|
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public void When_NoMapToAttributeFound_Should_GenerateOnlyTheAttribute()
|
|
|
|
{
|
|
|
|
// Arrange
|
|
|
|
const string source = "";
|
|
|
|
|
|
|
|
// Act
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
2020-12-24 13:05:29 +03:00
|
|
|
compilation.SyntaxTrees
|
|
|
|
.Select(s => s.ToString())
|
|
|
|
.Where(s => !string.IsNullOrWhiteSpace(s.ToString()))
|
|
|
|
.All(s => s.Contains(": Attribute"))
|
|
|
|
.ShouldBeTrue();
|
2020-12-21 19:34:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
2020-12-22 20:35:20 +03:00
|
|
|
public void When_SourceTypeHasDifferentNamespace_Should_NotAddToUsings()
|
2020-12-21 19:34:53 +03:00
|
|
|
{
|
|
|
|
// Arrange
|
2020-12-24 13:05:29 +03:00
|
|
|
var source = GetSourceText(new SourceGeneratorOptions(SourceClassNamespace: "Bazaar"));
|
2020-12-21 19:34:53 +03:00
|
|
|
|
|
|
|
const string expectedResult = @"
|
|
|
|
// <auto-generated />
|
|
|
|
using System;
|
2020-12-22 20:35:20 +03:00
|
|
|
|
|
|
|
namespace Test
|
2020-12-21 19:34:53 +03:00
|
|
|
";
|
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
// Act
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
// Assert
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
2020-12-19 11:08:19 +03:00
|
|
|
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
2020-12-19 10:55:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public void When_SourceTypeHasMatchingProperties_Should_CreateConstructorAndAssignSrcToDest()
|
|
|
|
{
|
|
|
|
// Arrange
|
|
|
|
var source = GetSourceText();
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
const string expectedResult = @"
|
|
|
|
public partial class Foo
|
|
|
|
{
|
2020-12-21 18:53:44 +03:00
|
|
|
public Foo(Test.Models.Baz baz)
|
2020-12-19 10:55:47 +03:00
|
|
|
{
|
2020-12-19 11:08:19 +03:00
|
|
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
2020-12-22 20:35:20 +03:00
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
Prop1 = baz.Prop1;
|
|
|
|
Prop2 = baz.Prop2;
|
|
|
|
Prop3 = baz.Prop3;
|
|
|
|
}
|
|
|
|
";
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
// Act
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 10:55:47 +03:00
|
|
|
// Assert
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
|
|
|
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
|
|
|
}
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 11:08:19 +03:00
|
|
|
[Fact]
|
|
|
|
public void When_SourceTypeHasMatchingProperties_Should_CreateFromStaticMethod()
|
|
|
|
{
|
|
|
|
// Arrange
|
|
|
|
var source = GetSourceText();
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 11:08:19 +03:00
|
|
|
const string expectedResult = @"
|
2020-12-21 18:53:44 +03:00
|
|
|
public static Foo From(Test.Models.Baz baz)
|
2020-12-19 11:08:19 +03:00
|
|
|
{
|
|
|
|
return baz == null ? null : new Foo(baz);
|
|
|
|
}
|
|
|
|
";
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 11:08:19 +03:00
|
|
|
// Act
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 11:08:19 +03:00
|
|
|
// Assert
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
|
|
|
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
|
|
|
}
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 12:53:31 +03:00
|
|
|
[Fact]
|
|
|
|
public void When_SourceTypeHasMatchingProperties_Should_GenerateToExtensionMethodOnSourceType()
|
|
|
|
{
|
|
|
|
// Arrange
|
|
|
|
var source = GetSourceText();
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 12:53:31 +03:00
|
|
|
const string expectedResult = @"
|
2020-12-22 20:35:20 +03:00
|
|
|
public static partial class BazToFooExtensions
|
2020-12-19 12:53:31 +03:00
|
|
|
{
|
2020-12-21 18:53:44 +03:00
|
|
|
public static Foo ToFoo(this Test.Models.Baz baz)
|
2020-12-19 12:53:31 +03:00
|
|
|
{
|
2020-12-21 18:53:44 +03:00
|
|
|
return baz == null ? null : new Foo(baz);
|
2020-12-19 12:53:31 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
";
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 12:53:31 +03:00
|
|
|
// Act
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
2020-12-21 19:34:53 +03:00
|
|
|
|
2020-12-19 12:53:31 +03:00
|
|
|
// Assert
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
|
|
|
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
|
|
|
}
|
2020-12-24 13:05:29 +03:00
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public void VerifyIgnorePropertyAttribute()
|
|
|
|
{
|
|
|
|
// Arrange
|
|
|
|
const string source = "";
|
|
|
|
var expectedAttribute = $@"
|
|
|
|
{SourceBuilder.GeneratedFilesHeader}
|
|
|
|
using System;
|
|
|
|
|
|
|
|
namespace MapTo
|
|
|
|
{{
|
|
|
|
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
|
|
|
public sealed class IgnorePropertyAttribute : Attribute {{ }}
|
|
|
|
}}
|
|
|
|
".Trim();
|
|
|
|
|
|
|
|
// Act
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
|
|
|
compilation.SyntaxTrees.ShouldContain(c => c.ToString() == expectedAttribute);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public void When_IgnorePropertyAttributeIsSpecified_Should_NotGenerateMappingsForThatProperty()
|
|
|
|
{
|
|
|
|
// Arrange
|
|
|
|
var source = GetSourceText(new SourceGeneratorOptions(
|
|
|
|
UseMapToNamespace: true,
|
|
|
|
PropertyBuilder: builder =>
|
|
|
|
{
|
|
|
|
builder
|
|
|
|
.PadLeft(Indent2).AppendLine("[IgnoreProperty]")
|
|
|
|
.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }");
|
|
|
|
},
|
|
|
|
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }")));
|
|
|
|
|
|
|
|
var expectedResult = @"
|
|
|
|
public partial class Foo
|
|
|
|
{
|
|
|
|
public Foo(Test.Models.Baz baz)
|
|
|
|
{
|
|
|
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
|
|
|
|
|
|
|
Prop1 = baz.Prop1;
|
|
|
|
Prop2 = baz.Prop2;
|
|
|
|
Prop3 = baz.Prop3;
|
|
|
|
}
|
|
|
|
".Trim();
|
|
|
|
|
|
|
|
// Act
|
|
|
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
diagnostics.ShouldBeSuccessful();
|
|
|
|
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult);
|
|
|
|
}
|
2020-12-19 10:55:47 +03:00
|
|
|
}
|
2020-12-21 19:34:53 +03:00
|
|
|
}
|