MapTo/test/BlueWest.MapTo.Tests/MapToTests.cs

601 lines
20 KiB
C#

using System.Collections.Generic;
using System.Linq;
using MapTo.Sources;
using MapTo.Tests.Extensions;
using MapTo.Tests.Infrastructure;
using Shouldly;
using Xunit;
using static MapTo.Extensions.GeneratorExecutionContextExtensions;
using static MapTo.Tests.Common;
namespace MapTo.Tests
{
public class MapToTests
{
private static readonly string ExpectedAttribute = $@"{Constants.GeneratedFilesHeader}
using System;
namespace MapTo
{{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class MapFromAttribute : Attribute
{{
public MapFromAttribute(Type sourceType)
{{
SourceType = sourceType;
}}
public Type SourceType {{ get; }}
}}
}}";
[Fact]
public void VerifyMapToAttribute()
{
// Arrange
const string source = "";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.ShouldContainSource(MapFromAttributeSource.AttributeClassName, ExpectedAttribute);
}
[Fact]
public void When_FoundMatchingPropertyNameWithDifferentTypes_Should_ReportError()
{
// Arrange
var source = GetSourceText(new SourceGeneratorOptions(
true,
PropertyBuilder: builder => { builder.WriteLine("public string Prop4 { get; set; }"); },
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
var expectedError = DiagnosticsFactory.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("Prop4", compilation));
diagnostics.ShouldNotBeSuccessful(expectedError);
}
[Fact]
public void When_MappingsModifierOptionIsSetToInternal_Should_GenerateThoseMethodsWithInternalAccessModifier()
{
// Arrange
var source = GetSourceText();
var configOptions = new Dictionary<string, string>
{
[GetBuildPropertyName(nameof(SourceGenerationOptions.GeneratedMethodsAccessModifier))] = "Internal",
[GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false"
};
var expectedExtension = @"
internal static partial class BazToFooExtensions
{
internal static Foo ToFoo(this Test.Models.Baz baz)
{
return baz == null ? null : new Foo(baz);
}
}".Trim();
var expectedFactory = @"
internal static Foo From(Test.Models.Baz baz)
{
return baz == null ? null : MappingContext.Create<Test.Models.Baz, Foo>(baz);
}".Trim();
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: configOptions);
// Assert
diagnostics.ShouldBeSuccessful();
var syntaxTree = compilation.SyntaxTrees.Last().ToString();
syntaxTree.ShouldContain(expectedFactory);
syntaxTree.ShouldContain(expectedExtension);
}
[Fact]
public void When_MapToAttributeFound_Should_GenerateTheClass()
{
// Arrange
const string source = @"
using MapTo;
namespace Test
{
[MapFrom(typeof(Baz))]
public partial class Foo
{
public int Prop1 { get; set; }
}
public class Baz
{
public int Prop1 { get; set; }
}
}
";
const string expectedResult = @"
// <auto-generated />
using MapTo;
using System;
namespace Test
{
partial class Foo
{
public Foo(Test.Baz baz)
: this(new MappingContext(), baz) { }
private protected Foo(MappingContext context, Test.Baz baz)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (baz == null) throw new ArgumentNullException(nameof(baz));
context.Register(baz, this);
Prop1 = baz.Prop1;
}
";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
}
[Fact]
public void When_MapToAttributeFoundWithoutMatchingProperties_Should_ReportError()
{
// Arrange
const string source = @"
using MapTo;
namespace Test
{
[MapFrom(typeof(Baz))]
public partial class Foo { }
public class Baz { public int Prop1 { get; set; } }
}
";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
// Assert
var fooType = compilation.GetTypeByMetadataName("Test.Foo");
fooType.ShouldNotBeNull();
var bazType = compilation.GetTypeByMetadataName("Test.Baz");
bazType.ShouldNotBeNull();
var expectedDiagnostic = DiagnosticsFactory.NoMatchingPropertyFoundError(fooType.Locations.Single(), fooType, bazType);
var error = diagnostics.FirstOrDefault(d => d.Id == expectedDiagnostic.Id);
error.ShouldNotBeNull();
}
[Fact]
public void When_MapToAttributeWithNamespaceFound_Should_GenerateTheClass()
{
// Arrange
const string source = @"
namespace Test
{
[MapTo.MapFrom(typeof(Baz))]
public partial class Foo { public int Prop1 { get; set; } }
public class Baz { public int Prop1 { get; set; } }
}
";
const string expectedResult = @"
// <auto-generated />
using MapTo;
using System;
namespace Test
{
partial class Foo
{
public Foo(Test.Baz baz)
: this(new MappingContext(), baz) { }
private protected Foo(MappingContext context, Test.Baz baz)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (baz == null) throw new ArgumentNullException(nameof(baz));
context.Register(baz, this);
Prop1 = baz.Prop1;
}
";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
}
[Fact]
public void When_NoMapToAttributeFound_Should_GenerateOnlyTheAttribute()
{
// Arrange
const string source = "";
var expectedTypes = new[]
{
//IgnorePropertyAttributeSource.AttributeName,
MapFromAttributeSource.AttributeName,
ITypeConverterSource.InterfaceName,
MapPropertyAttributeSource.AttributeName
};
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees
.Select(s => s.ToString())
.Where(s => !string.IsNullOrWhiteSpace(s.ToString()))
.All(s => expectedTypes.Any(s.Contains))
.ShouldBeTrue();
}
[Fact]
public void When_SourceTypeHasDifferentNamespace_Should_NotAddToUsings()
{
// Arrange
var source = GetSourceText(new SourceGeneratorOptions(SourceClassNamespace: "Bazaar"));
const string expectedResult = @"
// <auto-generated />
using MapTo;
using System;
namespace Test
{
";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
}
[Fact]
public void When_SourceTypeHasMatchingProperties_Should_CreateConstructorAndAssignSrcToDest()
{
// Arrange
var source = GetSourceText();
const string expectedResult = @"
partial class Foo
{
public Foo(Test.Models.Baz baz)
: this(new MappingContext(), baz) { }
private protected Foo(MappingContext context, Test.Models.Baz baz)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (baz == null) throw new ArgumentNullException(nameof(baz));
context.Register(baz, this);
Prop1 = baz.Prop1;
Prop2 = baz.Prop2;
Prop3 = baz.Prop3;
}
";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
}
[Fact]
public void When_SourceTypeHasMatchingProperties_Should_CreateFromStaticMethod()
{
// Arrange
var source = GetSourceText();
const string expectedResult = @"
public static Foo From(Test.Models.Baz baz)
{
return baz == null ? null : MappingContext.Create<Test.Models.Baz, Foo>(baz);
}
";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
}
[Fact]
public void When_SourceTypeHasMatchingProperties_Should_GenerateToExtensionMethodOnSourceType()
{
// Arrange
var source = GetSourceText();
const string expectedResult = @"
public static partial class BazToFooExtensions
{
public static Foo ToFoo(this Test.Models.Baz baz)
{
return baz == null ? null : new Foo(baz);
}
}
";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
}
[Fact]
public void When_HasNestedObjectPropertyTypeHasMapFromAttribute_Should_UseContinueToMap()
{
// Arrange
var source = GetSourceText(new SourceGeneratorOptions(
SourceClassNamespace: "Test",
PropertyBuilder: b => b.WriteLine("public B InnerProp1 { get; }"),
SourcePropertyBuilder: b => b.WriteLine("public A InnerProp1 { get; }")));
source += @"
namespace Test
{
public class A { public int Prop1 { get; } }
[MapTo.MapFrom(typeof(A))]
public partial class B { public int Prop1 { get; }}
}
".Trim();
var expectedResult = @"
partial class Foo
{
public Foo(Test.Baz baz)
: this(new MappingContext(), baz) { }
private protected Foo(MappingContext context, Test.Baz baz)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (baz == null) throw new ArgumentNullException(nameof(baz));
context.Register(baz, this);
Prop1 = baz.Prop1;
Prop2 = baz.Prop2;
Prop3 = baz.Prop3;
InnerProp1 = context.MapFromWithContext<A, B>(baz.InnerProp1);
}
".Trim();
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.ToArray()[^2].ShouldContainPartialSource(expectedResult);
}
[Fact]
public void When_HasNestedObjectPropertyTypeDoesNotHaveMapFromAttribute_Should_ReportError()
{
// Arrange
var source = GetSourceText(new SourceGeneratorOptions(
SourceClassNamespace: "Test",
PropertyBuilder: b => b.WriteLine("public FooInner1 InnerProp1 { get; }"),
SourcePropertyBuilder: b => b.WriteLine("public BazInner1 InnerProp1 { get; }")));
source += @"
namespace Test
{
public class FooInner1 { public int Prop1 { get; } }
public partial class BazInner1 { public int Prop1 { get; }}
}
".Trim();
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
var expectedError = DiagnosticsFactory.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("InnerProp1", compilation));
diagnostics.ShouldNotBeSuccessful(expectedError);
}
[Fact]
public void When_HasNestedObjectPropertyTypeHasMapFromAttributeToDifferentType_Should_ReportError()
{
// Arrange
var source = GetSourceText(new SourceGeneratorOptions(
SourceClassNamespace: "Test",
PropertyBuilder: b => b.WriteLine("public FooInner1 InnerProp1 { get; }"),
SourcePropertyBuilder: b => b.WriteLine("public BazInner1 InnerProp1 { get; }")));
source += @"
namespace Test
{
public class FooInner1 { public int Prop1 { get; } }
public class FooInner2 { public int Prop1 { get; } }
[MapTo.MapFrom(typeof(FooInner2))]
public partial class BazInner1 { public int Prop1 { get; }}
}
".Trim();
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
var expectedError = DiagnosticsFactory.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("InnerProp1", compilation));
diagnostics.ShouldNotBeSuccessful(expectedError);
}
[Fact]
public void When_SourceTypeEnumerableProperties_Should_CreateConstructorAndAssignSrcToDest()
{
// Arrange
var source = GetSourceText(new SourceGeneratorOptions(
Usings: new[] { "System.Collections.Generic"},
PropertyBuilder: builder => builder.WriteLine("public IEnumerable<int> Prop4 { get; }"),
SourcePropertyBuilder: builder => builder.WriteLine("public IEnumerable<int> Prop4 { get; }")));
const string expectedResult = @"
partial class Foo
{
public Foo(Test.Models.Baz baz)
: this(new MappingContext(), baz) { }
private protected Foo(MappingContext context, Test.Models.Baz baz)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (baz == null) throw new ArgumentNullException(nameof(baz));
context.Register(baz, this);
Prop1 = baz.Prop1;
Prop2 = baz.Prop2;
Prop3 = baz.Prop3;
Prop4 = baz.Prop4;
}
";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
}
[Fact]
public void When_DestinationTypeHasBaseClass_Should_CallBaseConstructor()
{
// Arrange
var sources = GetEmployeeManagerSourceText();
const string expectedResult = @"
private protected ManagerViewModel(MappingContext context, Test.Data.Models.Manager manager) : base(context, manager)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (manager == null) throw new ArgumentNullException(nameof(manager));
context.Register(manager, this);
Level = manager.Level;
Employees = manager.Employees.Select(context.MapFromWithContext<Test.Data.Models.Employee, EmployeeViewModel>).ToList();
}
";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
}
[Fact]
public void When_SourceTypeHasEnumerablePropertiesWithMapFromAttribute_Should_CreateANewEnumerableWithMappedObjects()
{
// Arrange
var sources = GetEmployeeManagerSourceText();
const string expectedResult = @"
// <auto-generated />
using MapTo;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test.ViewModels
{
partial class ManagerViewModel
{
public ManagerViewModel(Test.Data.Models.Manager manager)
: this(new MappingContext(), manager) { }
private protected ManagerViewModel(MappingContext context, Test.Data.Models.Manager manager) : base(context, manager)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (manager == null) throw new ArgumentNullException(nameof(manager));
context.Register(manager, this);
Level = manager.Level;
Employees = manager.Employees.Select(context.MapFromWithContext<Test.Data.Models.Employee, EmployeeViewModel>).ToList();
}
";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
}
[Fact]
public void When_SourceTypeHasEnumerablePropertiesWithMapFromAttributeInDifferentNamespaces_Should_CreateANewEnumerableWithMappedObjectsAndImportNamespace()
{
// Arrange
var sources = GetEmployeeManagerSourceText(useDifferentViewModelNamespace: true);
const string expectedResult = @"
using MapTo;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test.ViewModels2
{
partial class ManagerViewModel
{
public ManagerViewModel(Test.Data.Models.Manager manager)
: this(new MappingContext(), manager) { }
private protected ManagerViewModel(MappingContext context, Test.Data.Models.Manager manager) : base(context, manager)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (manager == null) throw new ArgumentNullException(nameof(manager));
context.Register(manager, this);
Level = manager.Level;
Employees = manager.Employees.Select(context.MapFromWithContext<Test.Data.Models.Employee, Test.ViewModels.EmployeeViewModel>).ToList();
}
";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
}
}
}