using Microsoft.CodeAnalysis; using Xunit; namespace AutoMapProperty.Tests; public class NullabilityTests { [Theory] [InlineData("string", "Nullable", true)] [InlineData("string?", "NonNullable", false)] [InlineData("string?", "Preserve", true)] [InlineData("string", "Preserve", false)] [InlineData("int", "Nullable", true)] [InlineData("int?", "Nullable", true)] [InlineData("int?", "NonNullable", false)] [InlineData("int", "NonNullable", false)] [InlineData("int?", "Preserve", true)] [InlineData("DateTime", "Nullable", true)] [InlineData("DateTime?", "NonNullable", false)] [InlineData("string[]", "Nullable", true)] [InlineData("string[]?", "NonNullable", false)] [InlineData("ICollection", "Nullable", true)] public void OverridesTopLevelNullability(string type, string mode, bool nullable) { var compilation = new GeneratorTestCompilation($$""" namespace Fixtures { public class Entity { [AutoMapProperty("Response", Nullability = AutoMapNullability.{{mode}})] public {{type}} Value { get; set; } } } """); compilation.AssertCompiles(); var generatedType = compilation.Property("Fixtures.EntityResponse", "Value").Type; Assert.Equal(nullable, generatedType.NullableAnnotation == NullableAnnotation.Annotated || generatedType is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T }); if (generatedType is INamedTypeSymbol { Name: "ICollection" } collection) Assert.Equal(NullableAnnotation.Annotated, collection.TypeArguments[0].NullableAnnotation); } [Fact] public void OverridesAreIndependentForEachDtoAndAppliedAfterTypeReplacement() { var compilation = new GeneratorTestCompilation(""" namespace Fixtures { public class Entity { [AutoMapProperty("Request", typeof(long), Nullability = AutoMapNullability.Nullable)] [AutoMapProperty("Response", typeof(long?), Nullability = AutoMapNullability.NonNullable)] [AutoMapProperty("Copy")] public int Value { get; set; } } } """); compilation.AssertCompiles(); var request = Assert.IsAssignableFrom(compilation.Property("Fixtures.EntityRequest", "Value").Type); Assert.Equal(SpecialType.System_Nullable_T, request.OriginalDefinition.SpecialType); Assert.Equal(SpecialType.System_Int64, request.TypeArguments[0].SpecialType); Assert.Equal(SpecialType.System_Int64, compilation.Property("Fixtures.EntityResponse", "Value").Type.SpecialType); Assert.Equal(SpecialType.System_Int32, compilation.Property("Fixtures.EntityCopy", "Value").Type.SpecialType); } [Fact] public void NullableValuesMapInBothDirectionsAndThroughProjectors() { var compilation = new GeneratorTestCompilation(""" namespace Fixtures { public class Entity { [AutoMapProperty("Request", Nullability = AutoMapNullability.Nullable)] public int Value { get; set; } } public static class Check { public static bool Run() { var entity = new Entity { Value = 42 }; var dto = new EntityRequest().ApplyFrom(null, entity); var projected = dto.GetProjectorFrom(null).Compile()(entity); dto.Value = 17; dto.ApplyTo(null, entity); dto.Value = null; try { dto.ApplyTo(null, entity); } catch (InvalidOperationException) { return entity.Value == 17 && projected.Value == 42; } return false; } } } """); Assert.Equal(true, compilation.Evaluate()); } [Fact] public void ValueTypeNullabilityOverridesKeepValidInitializers() { var compilation = new GeneratorTestCompilation(""" namespace Fixtures { public class Entity { [AutoMapProperty("Request", Nullability = AutoMapNullability.Nullable)] public DateTime Date { get; set; } = new DateTime(2026, 1, 2); [AutoMapProperty("Request", Nullability = AutoMapNullability.NonNullable)] public int? Count { get; set; } = null; [AutoMapProperty("Request", Nullability = AutoMapNullability.NonNullable)] public int? Total { get; set; } = default(int?); } public static class Check { public static bool Run() { var dto = new EntityRequest(); return dto.Date == new DateTime(2026, 1, 2) && dto.Count == 0 && dto.Total == 0; } } } """); Assert.Equal(true, compilation.Evaluate()); } [Fact] public void InvalidNullabilityReportsPropertyDiagnostic() { var compilation = new GeneratorTestCompilation(""" namespace Fixtures { public class Entity { [AutoMapProperty("Response", Nullability = (AutoMapNullability)99)] public string Value { get; set; } } } """); var diagnostic = Assert.Single(compilation.RunResult.Diagnostics); Assert.Equal("NEJ23", diagnostic.Id); Assert.True(diagnostic.Location.IsInSource); } }