From fda0922dccfc679ab7c4c88e0198d2996d82ad41 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 31 Aug 2024 18:48:53 +0200 Subject: [PATCH 01/15] Initial working concept of mapping properties to C# classes using reflection --- .../Properties/HasPropertiesBaseTests.cs | 194 ++++++++++++++++++ src/DotTiled/Properties/ClassProperty.cs | 31 +-- src/DotTiled/Properties/IHasProperties.cs | 59 ++++++ 3 files changed, 255 insertions(+), 29 deletions(-) create mode 100644 src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs diff --git a/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs b/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs new file mode 100644 index 0000000..4e53b57 --- /dev/null +++ b/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs @@ -0,0 +1,194 @@ +using System.Globalization; + +namespace DotTiled.Tests; + +public class HasPropertiesBaseTests +{ + private sealed class TestHasProperties(IList props) : HasPropertiesBase + { + public override IList GetProperties() => props; + } + + private sealed class MapTo + { + public bool MapToBool { get; set; } = false; + public Color MapToColor { get; set; } = Color.Parse("#00000000", CultureInfo.InvariantCulture); + public float MapToFloat { get; set; } = 0.0f; + public string MapToFile { get; set; } = ""; + public int MapToInt { get; set; } = 0; + public int MapToObject { get; set; } = 0; + public string MapToString { get; set; } = ""; + } + + [Fact] + public void GetMappedProperty_PropertyNotFound_ThrowsKeyNotFoundException() + { + // Arrange + List props = [ + new ClassProperty { + Name = "ClassInObject", + PropertyType = "MapTo", + Value = [ + new StringProperty { Name = "PropertyThatDoesNotExistInMapTo", Value = "Test" } + ], + } + ]; + var hasProperties = new TestHasProperties(props); + + // Act + var act = () => hasProperties.GetMappedProperty("ClassInObject"); + + // Assert + Assert.Throws(act); + } + + [Fact] + public void GetMappedProperty_AllBasicValidProperties_ReturnsMappedProperty() + { + // Arrange + List props = [ + new ClassProperty { + Name = "ClassInObject", + PropertyType = "MapTo", + Value = [ + new BoolProperty { Name = "MapToBool", Value = true }, + new ColorProperty { Name = "MapToColor", Value = Color.Parse("#FF0000FF", CultureInfo.InvariantCulture) }, + new FloatProperty { Name = "MapToFloat", Value = 1.0f }, + new StringProperty { Name = "MapToFile", Value = "Test" }, + new IntProperty { Name = "MapToInt", Value = 1 }, + new IntProperty { Name = "MapToObject", Value = 1 }, + new StringProperty { Name = "MapToString", Value = "Test" }, + ], + } + ]; + var hasProperties = new TestHasProperties(props); + + // Act + var mappedProperty = hasProperties.GetMappedProperty("ClassInObject"); + + // Assert + Assert.True(mappedProperty.MapToBool); + Assert.Equal(Color.Parse("#FF0000FF", CultureInfo.InvariantCulture), mappedProperty.MapToColor); + Assert.Equal(1.0f, mappedProperty.MapToFloat); + Assert.Equal("Test", mappedProperty.MapToFile); + Assert.Equal(1, mappedProperty.MapToInt); + Assert.Equal(1, mappedProperty.MapToObject); + Assert.Equal("Test", mappedProperty.MapToString); + } + + private sealed class NestedMapTo + { + public string NestedMapToString { get; set; } = ""; + public MapTo MapToInNested { get; set; } = new MapTo(); + } + + [Fact] + public void GetMappedProperty_NestedMapTo_ReturnsMappedProperty() + { + // Arrange + List props = [ + new ClassProperty { + Name = "ClassInObject", + PropertyType = "NestedMapTo", + Value = [ + new StringProperty { Name = "NestedMapToString", Value = "Test" }, + new ClassProperty { + Name = "MapToInNested", + PropertyType = "MapTo", + Value = [ + new BoolProperty { Name = "MapToBool", Value = true }, + new ColorProperty { Name = "MapToColor", Value = Color.Parse("#FF0000FF", CultureInfo.InvariantCulture) }, + new FloatProperty { Name = "MapToFloat", Value = 1.0f }, + new StringProperty { Name = "MapToFile", Value = "Test" }, + new IntProperty { Name = "MapToInt", Value = 1 }, + new IntProperty { Name = "MapToObject", Value = 1 }, + new StringProperty { Name = "MapToString", Value = "Test" }, + ], + }, + ], + } + ]; + var hasProperties = new TestHasProperties(props); + + // Act + var mappedProperty = hasProperties.GetMappedProperty("ClassInObject"); + + // Assert + Assert.Equal("Test", mappedProperty.NestedMapToString); + Assert.True(mappedProperty.MapToInNested.MapToBool); + Assert.Equal(Color.Parse("#FF0000FF", CultureInfo.InvariantCulture), mappedProperty.MapToInNested.MapToColor); + Assert.Equal(1.0f, mappedProperty.MapToInNested.MapToFloat); + Assert.Equal("Test", mappedProperty.MapToInNested.MapToFile); + Assert.Equal(1, mappedProperty.MapToInNested.MapToInt); + Assert.Equal(1, mappedProperty.MapToInNested.MapToObject); + Assert.Equal("Test", mappedProperty.MapToInNested.MapToString); + } + + private enum TestEnum + { + TestValue1, + TestValue2, + TestValue3 + } + + private sealed class EnumMapTo + { + public TestEnum EnumMapToEnum { get; set; } = TestEnum.TestValue1; + } + + [Fact] + public void GetMappedProperty_EnumProperty_ReturnsMappedProperty() + { + // Arrange + List props = [ + new ClassProperty { + Name = "ClassInObject", + PropertyType = "EnumMapTo", + Value = [ + new EnumProperty { Name = "EnumMapToEnum", PropertyType = "TestEnum", Value = new HashSet { "TestValue1" } }, + ], + } + ]; + var hasProperties = new TestHasProperties(props); + + // Act + var mappedProperty = hasProperties.GetMappedProperty("ClassInObject"); + + // Assert + Assert.Equal(TestEnum.TestValue1, mappedProperty.EnumMapToEnum); + } + + private enum TestEnumWithFlags + { + TestValue1 = 1, + TestValue2 = 2, + TestValue3 = 4 + } + + private sealed class EnumWithFlagsMapTo + { + public TestEnumWithFlags EnumWithFlagsMapToEnum { get; set; } = TestEnumWithFlags.TestValue1; + } + + [Fact] + public void GetMappedProperty_EnumWithFlagsProperty_ReturnsMappedProperty() + { + // Arrange + List props = [ + new ClassProperty { + Name = "ClassInObject", + PropertyType = "EnumWithFlagsMapTo", + Value = [ + new EnumProperty { Name = "EnumWithFlagsMapToEnum", PropertyType = "TestEnumWithFlags", Value = new HashSet { "TestValue1", "TestValue2" } }, + ], + } + ]; + var hasProperties = new TestHasProperties(props); + + // Act + var mappedProperty = hasProperties.GetMappedProperty("ClassInObject"); + + // Assert + Assert.Equal(TestEnumWithFlags.TestValue1 | TestEnumWithFlags.TestValue2, mappedProperty.EnumWithFlagsMapToEnum); + } +} diff --git a/src/DotTiled/Properties/ClassProperty.cs b/src/DotTiled/Properties/ClassProperty.cs index 2df32ee..7244aa5 100644 --- a/src/DotTiled/Properties/ClassProperty.cs +++ b/src/DotTiled/Properties/ClassProperty.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; namespace DotTiled; @@ -8,7 +6,7 @@ namespace DotTiled; /// /// Represents a class property. /// -public class ClassProperty : IHasProperties, IProperty> +public class ClassProperty : HasPropertiesBase, IProperty> { /// public required string Name { get; set; } @@ -36,30 +34,5 @@ public class ClassProperty : IHasProperties, IProperty> }; /// - public IList GetProperties() => Value; - - /// - public T GetProperty(string name) where T : IProperty - { - var property = Value.FirstOrDefault(_properties => _properties.Name == name) ?? throw new InvalidOperationException($"Property '{name}' not found."); - if (property is T prop) - { - return prop; - } - - throw new InvalidOperationException($"Property '{name}' is not of type '{typeof(T).Name}'."); - } - - /// - public bool TryGetProperty(string name, [NotNullWhen(true)] out T property) where T : IProperty - { - if (Value.FirstOrDefault(_properties => _properties.Name == name) is T prop) - { - property = prop; - return true; - } - - property = default; - return false; - } + public override IList GetProperties() => Value; } diff --git a/src/DotTiled/Properties/IHasProperties.cs b/src/DotTiled/Properties/IHasProperties.cs index 8ffd9f0..748e2b5 100644 --- a/src/DotTiled/Properties/IHasProperties.cs +++ b/src/DotTiled/Properties/IHasProperties.cs @@ -31,6 +31,8 @@ public interface IHasProperties /// The name of the property to get. /// The property with the specified name. T GetProperty(string name) where T : IProperty; + + T GetMappedProperty(string name) where T : new(); } /// @@ -69,4 +71,61 @@ public abstract class HasPropertiesBase : IHasProperties property = default; return false; } + + public T GetMappedProperty(string name) where T : new() + { + var property = GetProperty(name); + return CreateMappedInstance(property); + } + + private static object CreatedMappedInstance(Type type, ClassProperty classProperty) + { + var instance = Activator.CreateInstance(type); + + foreach (var prop in classProperty.Value) + { + if (type.GetProperty(prop.Name) == null) + throw new KeyNotFoundException($"Property '{prop.Name}' not found in '{type.Name}'."); + + switch (prop) + { + case BoolProperty boolProp: + type.GetProperty(prop.Name)?.SetValue(instance, boolProp.Value); + break; + case ColorProperty colorProp: + type.GetProperty(prop.Name)?.SetValue(instance, colorProp.Value); + break; + case FloatProperty floatProp: + type.GetProperty(prop.Name)?.SetValue(instance, floatProp.Value); + break; + case FileProperty fileProp: + type.GetProperty(prop.Name)?.SetValue(instance, fileProp.Value); + break; + case IntProperty intProp: + type.GetProperty(prop.Name)?.SetValue(instance, intProp.Value); + break; + case ObjectProperty objectProp: + type.GetProperty(prop.Name)?.SetValue(instance, objectProp.Value); + break; + case StringProperty stringProp: + type.GetProperty(prop.Name)?.SetValue(instance, stringProp.Value); + break; + case ClassProperty classProp: + var subClassProp = type.GetProperty(prop.Name); + subClassProp?.SetValue(instance, CreatedMappedInstance(subClassProp.PropertyType, classProp)); + break; + case EnumProperty enumProp: + var enumPropInClass = type.GetProperty(prop.Name); + var enumType = enumPropInClass?.PropertyType; + enumPropInClass?.SetValue(instance, Enum.Parse(enumType!, string.Join(", ", enumProp.Value))); + break; + default: + throw new ArgumentOutOfRangeException($"Unknown property type {prop.GetType().Name}"); + } + } + + return instance; + } + + private static T CreateMappedInstance(ClassProperty classProperty) where T : new() => (T)CreatedMappedInstance(typeof(T), classProperty); } From ce3d4e339c8c2cfdad1b74736037e2416784ef45 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 31 Aug 2024 19:56:44 +0200 Subject: [PATCH 02/15] Rename parts of the property mapping API and simplify --- .../Properties/HasPropertiesBaseTests.cs | 66 +++++++++++++++---- src/DotTiled/Properties/IHasProperties.cs | 39 ++++++++--- 2 files changed, 82 insertions(+), 23 deletions(-) diff --git a/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs b/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs index 4e53b57..ea7d375 100644 --- a/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs +++ b/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs @@ -21,7 +21,7 @@ public class HasPropertiesBaseTests } [Fact] - public void GetMappedProperty_PropertyNotFound_ThrowsKeyNotFoundException() + public void MapClassPropertyTo_PropertyNotFound_ThrowsKeyNotFoundException() { // Arrange List props = [ @@ -35,15 +35,25 @@ public class HasPropertiesBaseTests ]; var hasProperties = new TestHasProperties(props); - // Act - var act = () => hasProperties.GetMappedProperty("ClassInObject"); - - // Assert - Assert.Throws(act); + // Act & Assert + _ = Assert.Throws(() => hasProperties.MapClassPropertyTo("ClassInObject")); } [Fact] - public void GetMappedProperty_AllBasicValidProperties_ReturnsMappedProperty() + public void MapClassPropertyTo_PropertyIsNotClassProperty_ThrowsInvalidCastException() + { + // Arrange + List props = [ + new StringProperty { Name = "ClassInObject", Value = "Test" } + ]; + var hasProperties = new TestHasProperties(props); + + // Act & Assert + _ = Assert.Throws(() => hasProperties.MapClassPropertyTo("ClassInObject")); + } + + [Fact] + public void MapClassPropertyTo_AllBasicValidProperties_ReturnsMappedProperty() { // Arrange List props = [ @@ -64,7 +74,7 @@ public class HasPropertiesBaseTests var hasProperties = new TestHasProperties(props); // Act - var mappedProperty = hasProperties.GetMappedProperty("ClassInObject"); + var mappedProperty = hasProperties.MapClassPropertyTo("ClassInObject"); // Assert Assert.True(mappedProperty.MapToBool); @@ -83,7 +93,7 @@ public class HasPropertiesBaseTests } [Fact] - public void GetMappedProperty_NestedMapTo_ReturnsMappedProperty() + public void MapClassPropertyTo_NestedMapTo_ReturnsMappedProperty() { // Arrange List props = [ @@ -111,7 +121,7 @@ public class HasPropertiesBaseTests var hasProperties = new TestHasProperties(props); // Act - var mappedProperty = hasProperties.GetMappedProperty("ClassInObject"); + var mappedProperty = hasProperties.MapClassPropertyTo("ClassInObject"); // Assert Assert.Equal("Test", mappedProperty.NestedMapToString); @@ -137,7 +147,7 @@ public class HasPropertiesBaseTests } [Fact] - public void GetMappedProperty_EnumProperty_ReturnsMappedProperty() + public void MapClassPropertyTo_EnumProperty_ReturnsMappedProperty() { // Arrange List props = [ @@ -152,7 +162,7 @@ public class HasPropertiesBaseTests var hasProperties = new TestHasProperties(props); // Act - var mappedProperty = hasProperties.GetMappedProperty("ClassInObject"); + var mappedProperty = hasProperties.MapClassPropertyTo("ClassInObject"); // Assert Assert.Equal(TestEnum.TestValue1, mappedProperty.EnumMapToEnum); @@ -171,7 +181,7 @@ public class HasPropertiesBaseTests } [Fact] - public void GetMappedProperty_EnumWithFlagsProperty_ReturnsMappedProperty() + public void MapClassPropertyTo_EnumWithFlagsProperty_ReturnsMappedProperty() { // Arrange List props = [ @@ -186,9 +196,37 @@ public class HasPropertiesBaseTests var hasProperties = new TestHasProperties(props); // Act - var mappedProperty = hasProperties.GetMappedProperty("ClassInObject"); + var mappedProperty = hasProperties.MapClassPropertyTo("ClassInObject"); // Assert Assert.Equal(TestEnumWithFlags.TestValue1 | TestEnumWithFlags.TestValue2, mappedProperty.EnumWithFlagsMapToEnum); } + + [Fact] + public void MapPropertiesTo_WithProperties_ReturnsMappedProperty() + { + // Arrange + List props = [ + new BoolProperty { Name = "MapToBool", Value = true }, + new ColorProperty { Name = "MapToColor", Value = Color.Parse("#FF0000FF", CultureInfo.InvariantCulture) }, + new FloatProperty { Name = "MapToFloat", Value = 1.0f }, + new StringProperty { Name = "MapToFile", Value = "Test" }, + new IntProperty { Name = "MapToInt", Value = 1 }, + new IntProperty { Name = "MapToObject", Value = 1 }, + new StringProperty { Name = "MapToString", Value = "Test" }, + ]; + var hasProperties = new TestHasProperties(props); + + // Act + var mappedProperty = hasProperties.MapPropertiesTo(); + + // Assert + Assert.True(mappedProperty.MapToBool); + Assert.Equal(Color.Parse("#FF0000FF", CultureInfo.InvariantCulture), mappedProperty.MapToColor); + Assert.Equal(1.0f, mappedProperty.MapToFloat); + Assert.Equal("Test", mappedProperty.MapToFile); + Assert.Equal(1, mappedProperty.MapToInt); + Assert.Equal(1, mappedProperty.MapToObject); + Assert.Equal("Test", mappedProperty.MapToString); + } } diff --git a/src/DotTiled/Properties/IHasProperties.cs b/src/DotTiled/Properties/IHasProperties.cs index 748e2b5..cd413dd 100644 --- a/src/DotTiled/Properties/IHasProperties.cs +++ b/src/DotTiled/Properties/IHasProperties.cs @@ -32,7 +32,20 @@ public interface IHasProperties /// The property with the specified name. T GetProperty(string name) where T : IProperty; - T GetMappedProperty(string name) where T : new(); + /// + /// Maps a class property to a new instance of the specified type using reflection. + /// + /// + /// The property which you want to map to a class + /// + T MapClassPropertyTo(string name) where T : new(); + + /// + /// Maps all properties in this object to a new instance of the specified type using reflection. + /// + /// + /// + T MapPropertiesTo() where T : new(); } /// @@ -72,17 +85,25 @@ public abstract class HasPropertiesBase : IHasProperties return false; } - public T GetMappedProperty(string name) where T : new() + /// + public T MapClassPropertyTo(string name) where T : new() { - var property = GetProperty(name); - return CreateMappedInstance(property); + var classProperty = GetProperty(name); + return CreateMappedInstance(classProperty.GetProperties()); } - private static object CreatedMappedInstance(Type type, ClassProperty classProperty) + /// + public T MapPropertiesTo() where T : new() { - var instance = Activator.CreateInstance(type); + var properties = GetProperties(); + return CreateMappedInstance(properties); + } - foreach (var prop in classProperty.Value) + private static object CreatedMappedInstance(Type type, IList properties) + { + var instance = Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of '{type.Name}'."); + + foreach (var prop in properties) { if (type.GetProperty(prop.Name) == null) throw new KeyNotFoundException($"Property '{prop.Name}' not found in '{type.Name}'."); @@ -112,7 +133,7 @@ public abstract class HasPropertiesBase : IHasProperties break; case ClassProperty classProp: var subClassProp = type.GetProperty(prop.Name); - subClassProp?.SetValue(instance, CreatedMappedInstance(subClassProp.PropertyType, classProp)); + subClassProp?.SetValue(instance, CreatedMappedInstance(subClassProp.PropertyType, classProp.GetProperties())); break; case EnumProperty enumProp: var enumPropInClass = type.GetProperty(prop.Name); @@ -127,5 +148,5 @@ public abstract class HasPropertiesBase : IHasProperties return instance; } - private static T CreateMappedInstance(ClassProperty classProperty) where T : new() => (T)CreatedMappedInstance(typeof(T), classProperty); + private static T CreateMappedInstance(IList properties) where T : new() => (T)CreatedMappedInstance(typeof(T), properties); } From 4e273cd521c3757f5d7b774fedc09bd076914831 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 31 Aug 2024 19:59:20 +0200 Subject: [PATCH 03/15] Remove unecessary duplicate of API --- .../Properties/HasPropertiesBaseTests.cs | 12 ++++++------ src/DotTiled/Properties/IHasProperties.cs | 15 --------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs b/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs index ea7d375..8676156 100644 --- a/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs +++ b/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs @@ -36,7 +36,7 @@ public class HasPropertiesBaseTests var hasProperties = new TestHasProperties(props); // Act & Assert - _ = Assert.Throws(() => hasProperties.MapClassPropertyTo("ClassInObject")); + _ = Assert.Throws(() => hasProperties.GetProperty("ClassInObject").MapPropertiesTo()); } [Fact] @@ -49,7 +49,7 @@ public class HasPropertiesBaseTests var hasProperties = new TestHasProperties(props); // Act & Assert - _ = Assert.Throws(() => hasProperties.MapClassPropertyTo("ClassInObject")); + _ = Assert.Throws(() => hasProperties.GetProperty("ClassInObject").MapPropertiesTo()); } [Fact] @@ -74,7 +74,7 @@ public class HasPropertiesBaseTests var hasProperties = new TestHasProperties(props); // Act - var mappedProperty = hasProperties.MapClassPropertyTo("ClassInObject"); + var mappedProperty = hasProperties.GetProperty("ClassInObject").MapPropertiesTo(); // Assert Assert.True(mappedProperty.MapToBool); @@ -121,7 +121,7 @@ public class HasPropertiesBaseTests var hasProperties = new TestHasProperties(props); // Act - var mappedProperty = hasProperties.MapClassPropertyTo("ClassInObject"); + var mappedProperty = hasProperties.GetProperty("ClassInObject").MapPropertiesTo(); // Assert Assert.Equal("Test", mappedProperty.NestedMapToString); @@ -162,7 +162,7 @@ public class HasPropertiesBaseTests var hasProperties = new TestHasProperties(props); // Act - var mappedProperty = hasProperties.MapClassPropertyTo("ClassInObject"); + var mappedProperty = hasProperties.GetProperty("ClassInObject").MapPropertiesTo(); // Assert Assert.Equal(TestEnum.TestValue1, mappedProperty.EnumMapToEnum); @@ -196,7 +196,7 @@ public class HasPropertiesBaseTests var hasProperties = new TestHasProperties(props); // Act - var mappedProperty = hasProperties.MapClassPropertyTo("ClassInObject"); + var mappedProperty = hasProperties.GetProperty("ClassInObject").MapPropertiesTo(); // Assert Assert.Equal(TestEnumWithFlags.TestValue1 | TestEnumWithFlags.TestValue2, mappedProperty.EnumWithFlagsMapToEnum); diff --git a/src/DotTiled/Properties/IHasProperties.cs b/src/DotTiled/Properties/IHasProperties.cs index cd413dd..705a7d2 100644 --- a/src/DotTiled/Properties/IHasProperties.cs +++ b/src/DotTiled/Properties/IHasProperties.cs @@ -32,14 +32,6 @@ public interface IHasProperties /// The property with the specified name. T GetProperty(string name) where T : IProperty; - /// - /// Maps a class property to a new instance of the specified type using reflection. - /// - /// - /// The property which you want to map to a class - /// - T MapClassPropertyTo(string name) where T : new(); - /// /// Maps all properties in this object to a new instance of the specified type using reflection. /// @@ -85,13 +77,6 @@ public abstract class HasPropertiesBase : IHasProperties return false; } - /// - public T MapClassPropertyTo(string name) where T : new() - { - var classProperty = GetProperty(name); - return CreateMappedInstance(classProperty.GetProperties()); - } - /// public T MapPropertiesTo() where T : new() { From 612b5cdb3346041b52e6275e77c74a905d740db9 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 31 Aug 2024 20:02:08 +0200 Subject: [PATCH 04/15] Rename tests --- .../Properties/HasPropertiesBaseTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs b/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs index 8676156..ca173d0 100644 --- a/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs +++ b/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs @@ -21,7 +21,7 @@ public class HasPropertiesBaseTests } [Fact] - public void MapClassPropertyTo_PropertyNotFound_ThrowsKeyNotFoundException() + public void MapPropertiesTo_NestedPropertyNotFound_ThrowsKeyNotFoundException() { // Arrange List props = [ @@ -40,7 +40,7 @@ public class HasPropertiesBaseTests } [Fact] - public void MapClassPropertyTo_PropertyIsNotClassProperty_ThrowsInvalidCastException() + public void MapPropertiesTo_NestedPropertyIsNotClassProperty_ThrowsInvalidCastException() { // Arrange List props = [ @@ -53,7 +53,7 @@ public class HasPropertiesBaseTests } [Fact] - public void MapClassPropertyTo_AllBasicValidProperties_ReturnsMappedProperty() + public void MapPropertiesTo_NestedAllBasicValidProperties_ReturnsMappedProperty() { // Arrange List props = [ @@ -93,7 +93,7 @@ public class HasPropertiesBaseTests } [Fact] - public void MapClassPropertyTo_NestedMapTo_ReturnsMappedProperty() + public void MapPropertiesTo_NestedNestedMapTo_ReturnsMappedProperty() { // Arrange List props = [ @@ -147,7 +147,7 @@ public class HasPropertiesBaseTests } [Fact] - public void MapClassPropertyTo_EnumProperty_ReturnsMappedProperty() + public void MapPropertiesTo_NestedEnumProperty_ReturnsMappedProperty() { // Arrange List props = [ @@ -181,7 +181,7 @@ public class HasPropertiesBaseTests } [Fact] - public void MapClassPropertyTo_EnumWithFlagsProperty_ReturnsMappedProperty() + public void MapPropertiesTo_NestedEnumWithFlagsProperty_ReturnsMappedProperty() { // Arrange List props = [ From 53907b9c366c87c7d7c3e3f9cccc5c3f49a552ac Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Thu, 5 Sep 2024 21:02:34 +0200 Subject: [PATCH 05/15] Add overload to MapPropertiesTo with supplied factory method --- src/DotTiled/Properties/IHasProperties.cs | 28 +++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/DotTiled/Properties/IHasProperties.cs b/src/DotTiled/Properties/IHasProperties.cs index 705a7d2..40c9853 100644 --- a/src/DotTiled/Properties/IHasProperties.cs +++ b/src/DotTiled/Properties/IHasProperties.cs @@ -38,6 +38,14 @@ public interface IHasProperties /// /// T MapPropertiesTo() where T : new(); + + /// + /// Maps all properties in this object to a new instance of the specified type using reflection. + /// + /// + /// + /// + T MapPropertiesTo(Func initializer); } /// @@ -78,15 +86,14 @@ public abstract class HasPropertiesBase : IHasProperties } /// - public T MapPropertiesTo() where T : new() - { - var properties = GetProperties(); - return CreateMappedInstance(properties); - } + public T MapPropertiesTo() where T : new() => CreateMappedInstance(GetProperties()); - private static object CreatedMappedInstance(Type type, IList properties) + /// + public T MapPropertiesTo(Func initializer) => CreateMappedInstance(GetProperties(), initializer); + + private static object CreatedMappedInstance(object instance, IList properties) { - var instance = Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of '{type.Name}'."); + var type = instance.GetType(); foreach (var prop in properties) { @@ -118,7 +125,7 @@ public abstract class HasPropertiesBase : IHasProperties break; case ClassProperty classProp: var subClassProp = type.GetProperty(prop.Name); - subClassProp?.SetValue(instance, CreatedMappedInstance(subClassProp.PropertyType, classProp.GetProperties())); + subClassProp?.SetValue(instance, CreatedMappedInstance(Activator.CreateInstance(subClassProp.PropertyType), classProp.GetProperties())); break; case EnumProperty enumProp: var enumPropInClass = type.GetProperty(prop.Name); @@ -133,5 +140,8 @@ public abstract class HasPropertiesBase : IHasProperties return instance; } - private static T CreateMappedInstance(IList properties) where T : new() => (T)CreatedMappedInstance(typeof(T), properties); + private static T CreateMappedInstance(IList properties) where T : new() => + (T)CreatedMappedInstance(Activator.CreateInstance() ?? throw new InvalidOperationException($"Failed to create instance of '{typeof(T).Name}'."), properties); + + private static T CreateMappedInstance(IList properties, Func initializer) => (T)CreatedMappedInstance(initializer(), properties); } From e285f978250ecd0b4140590e3ec215a041b9979c Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Thu, 5 Sep 2024 21:15:39 +0200 Subject: [PATCH 06/15] Add some more tests to HasPropertiesBase --- .../Properties/HasPropertiesBaseTests.cs | 68 +++++++++++++++++++ src/DotTiled/Properties/IHasProperties.cs | 3 +- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs b/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs index ca173d0..4f9ab0f 100644 --- a/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs +++ b/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs @@ -9,6 +9,74 @@ public class HasPropertiesBaseTests public override IList GetProperties() => props; } + [Fact] + public void TryGetProperty_PropertyNotFound_ReturnsFalseAndOutIsNull() + { + // Arrange + var hasProperties = new TestHasProperties([]); + + // Act + var result = hasProperties.TryGetProperty("Test", out var property); + + // Assert + Assert.False(result); + Assert.Null(property); + } + + [Fact] + public void TryGetProperty_PropertyFound_ReturnsTrueAndOutIsProperty() + { + // Arrange + List props = [new BoolProperty { Name = "Test", Value = true }]; + var hasProperties = new TestHasProperties(props); + + // Act + var result = hasProperties.TryGetProperty("Test", out var property); + + // Assert + Assert.True(result); + Assert.NotNull(property); + Assert.Equal("Test", property.Name); + Assert.True(property.Value); + } + + [Fact] + public void GetProperty_PropertyNotFound_ThrowsKeyNotFoundException() + { + // Arrange + var hasProperties = new TestHasProperties([]); + + // Act & Assert + _ = Assert.Throws(() => hasProperties.GetProperty("Test")); + } + + [Fact] + public void GetProperty_PropertyFound_ReturnsProperty() + { + // Arrange + List props = [new BoolProperty { Name = "Test", Value = true }]; + var hasProperties = new TestHasProperties(props); + + // Act + var property = hasProperties.GetProperty("Test"); + + // Assert + Assert.NotNull(property); + Assert.Equal("Test", property.Name); + Assert.True(property.Value); + } + + [Fact] + public void GetProperty_PropertyIsWrongType_ThrowsInvalidCastException() + { + // Arrange + List props = [new BoolProperty { Name = "Test", Value = true }]; + var hasProperties = new TestHasProperties(props); + + // Act & Assert + _ = Assert.Throws(() => hasProperties.GetProperty("Test")); + } + private sealed class MapTo { public bool MapToBool { get; set; } = false; diff --git a/src/DotTiled/Properties/IHasProperties.cs b/src/DotTiled/Properties/IHasProperties.cs index 40c9853..03e610a 100644 --- a/src/DotTiled/Properties/IHasProperties.cs +++ b/src/DotTiled/Properties/IHasProperties.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; namespace DotTiled; @@ -72,7 +71,7 @@ public abstract class HasPropertiesBase : IHasProperties } /// - public bool TryGetProperty(string name, [NotNullWhen(true)] out T property) where T : IProperty + public bool TryGetProperty(string name, out T property) where T : IProperty { var properties = GetProperties(); if (properties.FirstOrDefault(_properties => _properties.Name == name) is T prop) From 84b55fe0057ca2df9da590901357043506bcf32a Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Thu, 5 Sep 2024 22:25:13 +0200 Subject: [PATCH 07/15] Added some new FromClass methods to construct custom class definitions, tests not done --- src/DotTiled.Tests/Assert/AssertMap.cs | 8 +- .../CustomTypes/CustomClassDefinitionTests.cs | 112 ++++++++++++++++++ .../CustomTypes/CustomClassDefinition.cs | 94 +++++++++++++++ 3 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 src/DotTiled.Tests/Properties/CustomTypes/CustomClassDefinitionTests.cs diff --git a/src/DotTiled.Tests/Assert/AssertMap.cs b/src/DotTiled.Tests/Assert/AssertMap.cs index c57a233..bc87002 100644 --- a/src/DotTiled.Tests/Assert/AssertMap.cs +++ b/src/DotTiled.Tests/Assert/AssertMap.cs @@ -5,7 +5,7 @@ namespace DotTiled.Tests; public static partial class DotTiledAssert { - private static void AssertListOrdered(IList expected, IList actual, string nameof, Action assertEqual = null) + internal static void AssertListOrdered(IList expected, IList actual, string nameof, Action assertEqual = null) { if (expected is null) { @@ -27,7 +27,7 @@ public static partial class DotTiledAssert } } - private static void AssertOptionalsEqual( + internal static void AssertOptionalsEqual( Optional expected, Optional actual, string nameof, @@ -49,7 +49,7 @@ public static partial class DotTiledAssert Assert.False(actual.HasValue, $"Expected {nameof} to not have a value"); } - private static void AssertEqual(Optional expected, Optional actual, string nameof) + internal static void AssertEqual(Optional expected, Optional actual, string nameof) { if (expected is null) { @@ -67,7 +67,7 @@ public static partial class DotTiledAssert Assert.False(actual.HasValue, $"Expected {nameof} to not have a value"); } - private static void AssertEqual(T expected, T actual, string nameof) + internal static void AssertEqual(T expected, T actual, string nameof) { if (expected == null) { diff --git a/src/DotTiled.Tests/Properties/CustomTypes/CustomClassDefinitionTests.cs b/src/DotTiled.Tests/Properties/CustomTypes/CustomClassDefinitionTests.cs new file mode 100644 index 0000000..af944f0 --- /dev/null +++ b/src/DotTiled.Tests/Properties/CustomTypes/CustomClassDefinitionTests.cs @@ -0,0 +1,112 @@ +namespace DotTiled.Tests; + +public class CustomClassDefinitionTests +{ + [Fact] + public void FromClassType_WhenTypeIsNotCustomClass_ThrowsArgumentException() + { + // Arrange + var type = typeof(string); + + // Act & Assert + Assert.Throws(() => CustomClassDefinition.FromClassType(type)); + } + + private sealed class TestClass1 + { + public string Name { get; set; } = "John Doe"; + public int Age { get; set; } = 42; + } + + private static CustomClassDefinition ExpectedTestClass1Definition => new CustomClassDefinition + { + Name = "TestClass1", + UseAs = CustomClassUseAs.All, + Members = new List + { + new StringProperty { Name = "Name", Value = "John Doe" }, + new IntProperty { Name = "Age", Value = 42 } + } + }; + + private sealed class TestClass2WithNestedClass + { + public string Name { get; set; } = "John Doe"; + public int Age { get; set; } = 42; + public TestClass1 Nested { get; set; } = new TestClass1(); + } + + private static CustomClassDefinition ExpectedTestClass2WithNestedClassDefinition => new CustomClassDefinition + { + Name = "TestClass2WithNestedClass", + UseAs = CustomClassUseAs.All, + Members = [ + new StringProperty { Name = "Name", Value = "John Doe" }, + new IntProperty { Name = "Age", Value = 42 }, + new ClassProperty + { + Name = "Nested", + PropertyType = "TestClass1", + Value = [] + } + ] + }; + + private sealed class TestClass3WithOverridenNestedClass + { + public string Name { get; set; } = "John Doe"; + public int Age { get; set; } = 42; + public TestClass1 Nested { get; set; } = new TestClass1 + { + Name = "Jane Doe" + }; + } + + private static CustomClassDefinition ExpectedTestClass3WithOverridenNestedClassDefinition => new CustomClassDefinition + { + Name = "TestClass3WithOverridenNestedClass", + UseAs = CustomClassUseAs.All, + Members = [ + new StringProperty { Name = "Name", Value = "John Doe" }, + new IntProperty { Name = "Age", Value = 42 }, + new ClassProperty + { + Name = "Nested", + PropertyType = "TestClass1", + Value = [ + new StringProperty { Name = "Name", Value = "Jane Doe" }, + ] + } + ] + }; + + private static IEnumerable<(Type, CustomClassDefinition)> GetCustomClassDefinitionTestData() + { + yield return (typeof(TestClass1), ExpectedTestClass1Definition); + yield return (typeof(TestClass2WithNestedClass), ExpectedTestClass2WithNestedClassDefinition); + yield return (typeof(TestClass3WithOverridenNestedClass), ExpectedTestClass3WithOverridenNestedClassDefinition); + } + + private static void AssertCustomClassDefinitionEqual(CustomClassDefinition expected, CustomClassDefinition actual) + { + DotTiledAssert.AssertEqual(expected.ID, actual.ID, nameof(CustomClassDefinition.ID)); + DotTiledAssert.AssertEqual(expected.Name, actual.Name, nameof(CustomClassDefinition.Name)); + DotTiledAssert.AssertEqual(expected.Color, actual.Color, nameof(CustomClassDefinition.Color)); + DotTiledAssert.AssertEqual(expected.DrawFill, actual.DrawFill, nameof(CustomClassDefinition.DrawFill)); + DotTiledAssert.AssertEqual(expected.UseAs, actual.UseAs, nameof(CustomClassDefinition.UseAs)); + DotTiledAssert.AssertProperties(expected.Members, actual.Members); + } + + public static IEnumerable CustomClassDefinitionTestData => + GetCustomClassDefinitionTestData().Select(data => new object[] { data.Item1, data.Item2 }); + [Theory] + [MemberData(nameof(CustomClassDefinitionTestData))] + public void FromClassType_WhenTypeIsCustomClass_ReturnsCustomClassDefinition(Type type, CustomClassDefinition expected) + { + // Arrange & Act + var result = CustomClassDefinition.FromClassType(type); + + // Assert + AssertCustomClassDefinitionEqual(expected, result); + } +} diff --git a/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs b/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs index 6a99f62..2c3be71 100644 --- a/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs +++ b/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; namespace DotTiled; @@ -95,4 +97,96 @@ public class CustomClassDefinition : HasPropertiesBase, ICustomTypeDefinition /// public override IList GetProperties() => Members; + + /// + /// Creates a new from the specified class type. + /// + /// The type of the class to create a custom class definition from. + /// A new instance. + /// Thrown when the specified type is not a class. + public static CustomClassDefinition FromClassType(Type type) + { + if (type == typeof(string) || !type.IsClass) + throw new ArgumentException("Type must be a class.", nameof(type)); + + return FromClass(() => Activator.CreateInstance(type)); + } + + /// + /// Creates a new from the specified instance of a class. + /// + /// The instance of the class to create a custom class definition from. + /// A new instance. + public static CustomClassDefinition FromClassInstance(dynamic instance) + { + ArgumentNullException.ThrowIfNull(instance); + return FromClass(() => instance); + } + + /// + /// Creates a new from the specified constructible class type. + /// + /// The type of the class to create a custom class definition from. + /// A new instance. + public static CustomClassDefinition FromClass() where T : class, new() => FromClass(() => new T()); + + /// + /// Creates a new from the specified factory function of a class instance. + /// + /// The type of the class to create a custom class definition from. + /// The factory function that creates an instance of the class. + /// A new instance. + public static CustomClassDefinition FromClass(Func factory) where T : class + { + var instance = factory(); + var type = instance.GetType(); + var properties = type.GetProperties(); + + return new CustomClassDefinition + { + Name = type.Name, + UseAs = CustomClassUseAs.All, + Members = properties.Select(p => ConvertPropertyInfoToIProperty(instance, p)).ToList() + }; + } + + private static IProperty ConvertPropertyInfoToIProperty(object instance, PropertyInfo propertyInfo) + { + switch (propertyInfo.PropertyType) + { + case Type t when t == typeof(bool): + return new BoolProperty { Name = propertyInfo.Name, Value = (bool)propertyInfo.GetValue(instance) }; + case Type t when t == typeof(Color): + return new ColorProperty { Name = propertyInfo.Name, Value = (Color)propertyInfo.GetValue(instance) }; + case Type t when t == typeof(float): + return new FloatProperty { Name = propertyInfo.Name, Value = (float)propertyInfo.GetValue(instance) }; + case Type t when t == typeof(string): + return new StringProperty { Name = propertyInfo.Name, Value = (string)propertyInfo.GetValue(instance) }; + case Type t when t == typeof(int): + return new IntProperty { Name = propertyInfo.Name, Value = (int)propertyInfo.GetValue(instance) }; + case Type t when t.IsClass: + return new ClassProperty { Name = propertyInfo.Name, PropertyType = t.Name, Value = GetNestedProperties(propertyInfo.PropertyType, propertyInfo.GetValue(instance)) }; + default: + break; + } + + throw new NotSupportedException($"Type '{propertyInfo.PropertyType.Name}' is not supported in custom classes."); + } + + private static List GetNestedProperties(Type type, object instance) + { + var defaultInstance = Activator.CreateInstance(type); + var properties = type.GetProperties(); + + bool IsPropertyDefaultValue(PropertyInfo propertyInfo) + { + var defaultValue = propertyInfo.GetValue(defaultInstance); + var value = propertyInfo.GetValue(instance); + return value.Equals(defaultValue); + } + + return properties + .Where(p => !IsPropertyDefaultValue(p)) + .Select(p => ConvertPropertyInfoToIProperty(instance, p)).ToList(); + } } From 762e6109044ebb8eb8100f87a5d9bd912604a561 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 7 Sep 2024 12:49:10 +0200 Subject: [PATCH 08/15] Make sure to only embed map, tileset and template files in tests binary --- src/DotTiled.Tests/DotTiled.Tests.csproj | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/DotTiled.Tests/DotTiled.Tests.csproj b/src/DotTiled.Tests/DotTiled.Tests.csproj index 45d8f5a..fe6e8c9 100644 --- a/src/DotTiled.Tests/DotTiled.Tests.csproj +++ b/src/DotTiled.Tests/DotTiled.Tests.csproj @@ -26,7 +26,12 @@ - + + + + + + From 64f66421c2db83508e0be45022bc78c8a30b3d54 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 7 Sep 2024 12:59:59 +0200 Subject: [PATCH 09/15] Add tests for FromClass for CustomClassDefinition --- .../CustomTypes/CustomClassDefinitionTests.cs | 44 ++++++++++++++----- .../CustomTypes/CustomClassDefinition.cs | 15 ++----- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/DotTiled.Tests/Properties/CustomTypes/CustomClassDefinitionTests.cs b/src/DotTiled.Tests/Properties/CustomTypes/CustomClassDefinitionTests.cs index af944f0..87fecbd 100644 --- a/src/DotTiled.Tests/Properties/CustomTypes/CustomClassDefinitionTests.cs +++ b/src/DotTiled.Tests/Properties/CustomTypes/CustomClassDefinitionTests.cs @@ -2,16 +2,6 @@ namespace DotTiled.Tests; public class CustomClassDefinitionTests { - [Fact] - public void FromClassType_WhenTypeIsNotCustomClass_ThrowsArgumentException() - { - // Arrange - var type = typeof(string); - - // Act & Assert - Assert.Throws(() => CustomClassDefinition.FromClassType(type)); - } - private sealed class TestClass1 { public string Name { get; set; } = "John Doe"; @@ -101,12 +91,42 @@ public class CustomClassDefinitionTests GetCustomClassDefinitionTestData().Select(data => new object[] { data.Item1, data.Item2 }); [Theory] [MemberData(nameof(CustomClassDefinitionTestData))] - public void FromClassType_WhenTypeIsCustomClass_ReturnsCustomClassDefinition(Type type, CustomClassDefinition expected) + public void FromClass_Type_WhenTypeIsCustomClass_ReturnsCustomClassDefinition(Type type, CustomClassDefinition expected) { // Arrange & Act - var result = CustomClassDefinition.FromClassType(type); + var result = CustomClassDefinition.FromClass(type); // Assert AssertCustomClassDefinitionEqual(expected, result); } + + [Fact] + public void FromClass_Type_WhenTypeIsNull_ThrowsArgumentNullException() + { + // Arrange + Type type = null; + + // Act & Assert + Assert.Throws(() => CustomClassDefinition.FromClass(type)); + } + + [Fact] + public void FromClass_Type_WhenTypeIsString_ThrowsArgumentException() + { + // Arrange + Type type = typeof(string); + + // Act & Assert + Assert.Throws(() => CustomClassDefinition.FromClass(type)); + } + + [Fact] + public void FromClass_Type_WhenTypeIsNotClass_ThrowsArgumentException() + { + // Arrange + Type type = typeof(int); + + // Act & Assert + Assert.Throws(() => CustomClassDefinition.FromClass(type)); + } } diff --git a/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs b/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs index 2c3be71..3e65094 100644 --- a/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs +++ b/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs @@ -104,25 +104,16 @@ public class CustomClassDefinition : HasPropertiesBase, ICustomTypeDefinition /// The type of the class to create a custom class definition from. /// A new instance. /// Thrown when the specified type is not a class. - public static CustomClassDefinition FromClassType(Type type) + public static CustomClassDefinition FromClass(Type type) { + ArgumentNullException.ThrowIfNull(type, nameof(type)); + if (type == typeof(string) || !type.IsClass) throw new ArgumentException("Type must be a class.", nameof(type)); return FromClass(() => Activator.CreateInstance(type)); } - /// - /// Creates a new from the specified instance of a class. - /// - /// The instance of the class to create a custom class definition from. - /// A new instance. - public static CustomClassDefinition FromClassInstance(dynamic instance) - { - ArgumentNullException.ThrowIfNull(instance); - return FromClass(() => instance); - } - /// /// Creates a new from the specified constructible class type. /// From 58b0ad3493c208acb87794f14275b86d2ccac122 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 7 Sep 2024 13:03:27 +0200 Subject: [PATCH 10/15] Make use of generic type parameter in generic methods --- .../CustomTypes/CustomEnumDefinitionTests.cs | 0 .../Properties/CustomTypes/CustomClassDefinition.cs | 12 ++++++++++-- .../Properties/CustomTypes/CustomEnumDefinition.cs | 6 ++++++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 src/DotTiled.Tests/Properties/CustomTypes/CustomEnumDefinitionTests.cs diff --git a/src/DotTiled.Tests/Properties/CustomTypes/CustomEnumDefinitionTests.cs b/src/DotTiled.Tests/Properties/CustomTypes/CustomEnumDefinitionTests.cs new file mode 100644 index 0000000..e69de29 diff --git a/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs b/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs index 3e65094..83e0fbe 100644 --- a/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs +++ b/src/DotTiled/Properties/CustomTypes/CustomClassDefinition.cs @@ -111,7 +111,15 @@ public class CustomClassDefinition : HasPropertiesBase, ICustomTypeDefinition if (type == typeof(string) || !type.IsClass) throw new ArgumentException("Type must be a class.", nameof(type)); - return FromClass(() => Activator.CreateInstance(type)); + var instance = Activator.CreateInstance(type); + var properties = type.GetProperties(); + + return new CustomClassDefinition + { + Name = type.Name, + UseAs = CustomClassUseAs.All, + Members = properties.Select(p => ConvertPropertyInfoToIProperty(instance, p)).ToList() + }; } /// @@ -130,7 +138,7 @@ public class CustomClassDefinition : HasPropertiesBase, ICustomTypeDefinition public static CustomClassDefinition FromClass(Func factory) where T : class { var instance = factory(); - var type = instance.GetType(); + var type = typeof(T); var properties = type.GetProperties(); return new CustomClassDefinition diff --git a/src/DotTiled/Properties/CustomTypes/CustomEnumDefinition.cs b/src/DotTiled/Properties/CustomTypes/CustomEnumDefinition.cs index e155b38..719f1ef 100644 --- a/src/DotTiled/Properties/CustomTypes/CustomEnumDefinition.cs +++ b/src/DotTiled/Properties/CustomTypes/CustomEnumDefinition.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace DotTiled; @@ -44,4 +45,9 @@ public class CustomEnumDefinition : ICustomTypeDefinition /// Whether the value should be treated as flags. /// public bool ValueAsFlags { get; set; } + + // public CustomEnumDefinition FromEnum(Type enumType) + // { + // if (!enumType.Is) + // } } From 0a77a9fec7c1a28da64f02aec139e3754d10613b Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 7 Sep 2024 13:16:29 +0200 Subject: [PATCH 11/15] Add FromEnum to CustomEnumDefinition with tests --- src/DotTiled.Tests/OptionalTests.cs | 203 ------------------ .../CustomTypes/CustomEnumDefinitionTests.cs | 110 ++++++++++ .../CustomTypes/CustomEnumDefinition.cs | 43 +++- 3 files changed, 149 insertions(+), 207 deletions(-) diff --git a/src/DotTiled.Tests/OptionalTests.cs b/src/DotTiled.Tests/OptionalTests.cs index 222c09a..5390a64 100644 --- a/src/DotTiled.Tests/OptionalTests.cs +++ b/src/DotTiled.Tests/OptionalTests.cs @@ -1,208 +1,5 @@ namespace DotTiled.Tests; -// public class OptionalTests -// { -// [Fact] -// public void HasValue_WhenValueIsSet_ReturnsTrue() -// { -// // Arrange -// var optional = new Optional(42); - -// // Act & Assert -// Assert.True(optional.HasValue); -// } - -// [Fact] -// public void HasValue_WhenValueIsNotSet_ReturnsFalse() -// { -// // Arrange -// var optional = new Optional(); - -// // Act & Assert -// Assert.False(optional.HasValue); -// } - -// [Fact] -// public void Value_WhenValueIsSet_ReturnsValue() -// { -// // Arrange -// var optional = new Optional(42); - -// // Act -// var value = optional.Value; - -// // Assert -// Assert.Equal(42, value); -// } - -// [Fact] -// public void Value_WhenValueIsNotSet_ThrowsInvalidOperationException() -// { -// // Arrange -// var optional = new Optional(); - -// // Act & Assert -// _ = Assert.Throws(() => optional.Value); -// } - -// [Fact] -// public void ImplicitConversionFromValue_CreatesOptionalWithValue() -// { -// // Arrange -// Optional optional = 42; - -// // Act & Assert -// Assert.True(optional.HasValue); -// Assert.Equal(42, optional.Value); -// } - -// [Fact] -// public void ImplicitConversionToValue_ReturnsValue() -// { -// // Arrange -// var optional = new Optional(42); - -// // Act -// int value = optional; - -// // Assert -// Assert.Equal(42, value); -// } - -// [Fact] -// public void ToString_WhenValueIsSet_ReturnsValueToString() -// { -// // Arrange -// var optional = new Optional(42); - -// // Act -// var result = optional.ToString(); - -// // Assert -// Assert.Equal("42", result); -// } - -// [Fact] -// public void ToString_WhenValueIsNotSet_ReturnsEmpty() -// { -// // Arrange -// var optional = new Optional(); - -// // Act -// var result = optional.ToString(); - -// // Assert -// Assert.Equal("Empty", result); -// } - -// [Fact] -// public void Equals_WithObject_ReturnsTrueWhenValueIsEqual() -// { -// // Arrange -// var optional = new Optional(42); - -// // Act -// var result = optional.Equals(42); - -// // Assert -// Assert.True(result); -// } - -// [Fact] -// public void Equals_WithObject_ReturnsFalseWhenValueIsNotEqual() -// { -// // Arrange -// var optional = new Optional(42); - -// // Act -// var result = optional.Equals(43); - -// // Assert -// Assert.False(result); -// } - -// [Fact] -// public void Equals_WithObject_ReturnsFalseWhenValueIsNotSet() -// { -// // Arrange -// var optional = new Optional(); - -// // Act -// var result = optional.Equals(42); - -// // Assert -// Assert.False(result); -// } - -// [Fact] -// public void Equals_WithOptional_ReturnsTrueWhenValueIsEqual() -// { -// // Arrange -// var optional1 = new Optional(42); -// var optional2 = new Optional(42); - -// // Act -// var result = optional1.Equals(optional2); - -// // Assert -// Assert.True(result); -// } - -// [Fact] -// public void Equals_WithOptional_ReturnsFalseWhenValueIsNotEqual() -// { -// // Arrange -// var optional1 = new Optional(42); -// var optional2 = new Optional(43); - -// // Act -// var result = optional1.Equals(optional2); - -// // Assert -// Assert.False(result); -// } - -// [Fact] -// public void Equals_WithOptional_ReturnsFalseWhenValueIsNotSet() -// { -// // Arrange -// var optional1 = new Optional(); -// var optional2 = new Optional(42); - -// // Act -// var result = optional1.Equals(optional2); - -// // Assert -// Assert.False(result); -// } - -// [Fact] -// public void GetHashCode_WhenValueIsSet_ReturnsValueHashCode() -// { -// // Arrange -// var optional = new Optional(42); - -// // Act -// var result = optional.GetHashCode(); - -// // Assert -// Assert.Equal(42.GetHashCode(), result); -// } - -// [Fact] -// public void GetHashCode_WhenValueIsNotSet_ReturnsZero() -// { -// // Arrange -// var optional = new Optional(); - -// // Act -// var result = optional.GetHashCode(); - -// // Assert -// Assert.Equal(0, result); -// } -// } - public class OptionalTests { // Constructor Tests diff --git a/src/DotTiled.Tests/Properties/CustomTypes/CustomEnumDefinitionTests.cs b/src/DotTiled.Tests/Properties/CustomTypes/CustomEnumDefinitionTests.cs index e69de29..347836a 100644 --- a/src/DotTiled.Tests/Properties/CustomTypes/CustomEnumDefinitionTests.cs +++ b/src/DotTiled.Tests/Properties/CustomTypes/CustomEnumDefinitionTests.cs @@ -0,0 +1,110 @@ +namespace DotTiled.Tests; + +public class CustomEnumDefinitionTests +{ + private static void AssertCustomEnumDefinitionEqual(CustomEnumDefinition expected, CustomEnumDefinition actual) + { + DotTiledAssert.AssertEqual(expected.ID, actual.ID, nameof(CustomEnumDefinition.ID)); + DotTiledAssert.AssertEqual(expected.Name, actual.Name, nameof(CustomEnumDefinition.Name)); + DotTiledAssert.AssertEqual(expected.StorageType, actual.StorageType, nameof(CustomEnumDefinition.StorageType)); + DotTiledAssert.AssertEqual(expected.ValueAsFlags, actual.ValueAsFlags, nameof(CustomEnumDefinition.ValueAsFlags)); + DotTiledAssert.AssertListOrdered(expected.Values, actual.Values, nameof(CustomEnumDefinition.Values)); + } + + [Fact] + public void FromEnum_Type_WhenTypeIsNotEnum_ThrowsArgumentException() + { + // Arrange + var type = typeof(string); + + // Act & Assert + Assert.Throws(() => CustomEnumDefinition.FromEnum(type)); + } + + private enum TestEnum1 { Value1, Value2, Value3 } + + [Fact] + public void FromEnum_Type_WhenTypeIsEnum_ReturnsCustomEnumDefinition() + { + // Arrange + var type = typeof(TestEnum1); + var expected = new CustomEnumDefinition + { + ID = 0, + Name = "TestEnum1", + StorageType = CustomEnumStorageType.Int, + Values = ["Value1", "Value2", "Value3"], + ValueAsFlags = false + }; + + // Act + var result = CustomEnumDefinition.FromEnum(type); + + // Assert + AssertCustomEnumDefinitionEqual(expected, result); + } + + [Flags] + private enum TestEnum2 { Value1, Value2, Value3 } + + [Fact] + public void FromEnum_Type_WhenEnumIsFlags_ReturnsCustomEnumDefinition() + { + // Arrange + var type = typeof(TestEnum2); + var expected = new CustomEnumDefinition + { + ID = 0, + Name = "TestEnum2", + StorageType = CustomEnumStorageType.Int, + Values = ["Value1", "Value2", "Value3"], + ValueAsFlags = true + }; + + // Act + var result = CustomEnumDefinition.FromEnum(type); + + // Assert + AssertCustomEnumDefinitionEqual(expected, result); + } + + [Fact] + public void FromEnum_T_WhenTypeIsEnum_ReturnsCustomEnumDefinition() + { + // Arrange + var expected = new CustomEnumDefinition + { + ID = 0, + Name = "TestEnum1", + StorageType = CustomEnumStorageType.Int, + Values = ["Value1", "Value2", "Value3"], + ValueAsFlags = false + }; + + // Act + var result = CustomEnumDefinition.FromEnum(); + + // Assert + AssertCustomEnumDefinitionEqual(expected, result); + } + + [Fact] + public void FromEnum_T_WhenEnumIsFlags_ReturnsCustomEnumDefinition() + { + // Arrange + var expected = new CustomEnumDefinition + { + ID = 0, + Name = "TestEnum2", + StorageType = CustomEnumStorageType.Int, + Values = ["Value1", "Value2", "Value3"], + ValueAsFlags = true + }; + + // Act + var result = CustomEnumDefinition.FromEnum(); + + // Assert + AssertCustomEnumDefinitionEqual(expected, result); + } +} diff --git a/src/DotTiled/Properties/CustomTypes/CustomEnumDefinition.cs b/src/DotTiled/Properties/CustomTypes/CustomEnumDefinition.cs index 719f1ef..72593d9 100644 --- a/src/DotTiled/Properties/CustomTypes/CustomEnumDefinition.cs +++ b/src/DotTiled/Properties/CustomTypes/CustomEnumDefinition.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace DotTiled; @@ -46,8 +47,42 @@ public class CustomEnumDefinition : ICustomTypeDefinition /// public bool ValueAsFlags { get; set; } - // public CustomEnumDefinition FromEnum(Type enumType) - // { - // if (!enumType.Is) - // } + /// + /// Creates a custom enum definition from the specified enum type. + /// + /// + /// + public static CustomEnumDefinition FromEnum() where T : Enum + { + var type = typeof(T); + var isFlags = type.GetCustomAttributes(typeof(FlagsAttribute), false).Length != 0; + + return new CustomEnumDefinition + { + Name = type.Name, + StorageType = CustomEnumStorageType.Int, + Values = Enum.GetNames(type).ToList(), + ValueAsFlags = isFlags + }; + } + + /// + /// Creates a custom enum definition from the specified enum type. + /// + /// + public static CustomEnumDefinition FromEnum(Type type) + { + if (!type.IsEnum) + throw new ArgumentException("Type must be an enum.", nameof(type)); + + var isFlags = type.GetCustomAttributes(typeof(FlagsAttribute), false).Length != 0; + + return new CustomEnumDefinition + { + Name = type.Name, + StorageType = CustomEnumStorageType.Int, + Values = Enum.GetNames(type).ToList(), + ValueAsFlags = isFlags + }; + } } From d23eec443345b0b623266920e94502892f5f5a02 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 7 Sep 2024 13:23:32 +0200 Subject: [PATCH 12/15] Move test data and current tests into UnitTests --- src/DotTiled.Tests/DotTiled.Tests.csproj | 12 ++--- src/DotTiled.Tests/Serialization/TestData.cs | 47 ----------------- .../Maps}/default-map/default-map.cs | 0 .../Maps}/default-map/default-map.tmj | 0 .../Maps}/default-map/default-map.tmx | 0 .../map-external-tileset-multi.cs | 0 .../map-external-tileset-multi.tmj | 0 .../map-external-tileset-multi.tmx | 0 .../multi-tileset.tsj | 0 .../multi-tileset.tsx | 0 .../map-external-tileset-multi/tileset.png | Bin .../map-external-tileset-wangset.cs | 0 .../map-external-tileset-wangset.tmj | 0 .../map-external-tileset-wangset.tmx | 0 .../map-external-tileset-wangset/tileset.png | Bin .../wangset-tileset.tsj | 0 .../wangset-tileset.tsx | 0 .../map-with-class-and-props.cs | 0 .../map-with-class-and-props.tmj | 0 .../map-with-class-and-props.tmx | 0 .../Maps}/map-with-class/map-with-class.cs | 0 .../Maps}/map-with-class/map-with-class.tmj | 0 .../Maps}/map-with-class/map-with-class.tmx | 0 .../map-with-common-props.cs | 0 .../map-with-common-props.tmj | 0 .../map-with-common-props.tmx | 0 .../map-with-custom-type-props.cs | 0 .../map-with-custom-type-props.tmj | 0 .../map-with-custom-type-props.tmx | 0 .../propertytypes.json | 0 .../map-with-deep-props.cs | 0 .../map-with-deep-props.tmj | 0 .../map-with-deep-props.tmx | 0 .../map-with-embedded-tileset.cs | 0 .../map-with-embedded-tileset.tmj | 0 .../map-with-embedded-tileset.tmx | 0 .../map-with-embedded-tileset/tileset.png | Bin .../map-with-external-tileset.cs | 0 .../map-with-external-tileset.tmj | 0 .../map-with-external-tileset.tmx | 0 .../map-with-external-tileset/tileset.png | Bin .../map-with-external-tileset/tileset.tsj | 0 .../map-with-external-tileset/tileset.tsx | 0 .../map-with-flippingflags.cs | 0 .../map-with-flippingflags.tmj | 0 .../map-with-flippingflags.tmx | 0 .../Maps}/map-with-flippingflags/tileset.png | Bin .../Maps}/map-with-flippingflags/tileset.tsj | 0 .../Maps}/map-with-flippingflags/tileset.tsx | 0 .../map-with-many-layers.cs | 0 .../map-with-many-layers.tmj | 0 .../map-with-many-layers.tmx | 0 .../Maps}/map-with-many-layers/poly.tj | 0 .../Maps}/map-with-many-layers/poly.tx | 0 .../Maps}/map-with-many-layers/tileset.png | Bin .../Maps}/map-with-many-layers/tileset.tsj | 0 .../Maps}/map-with-many-layers/tileset.tsx | 0 .../{ => UnitTests}/OptionalTests.cs | 0 .../CustomTypes/CustomClassDefinitionTests.cs | 0 .../CustomTypes/CustomEnumDefinitionTests.cs | 0 .../Properties/HasPropertiesBaseTests.cs | 0 .../DefaultResourceCacheTests.cs | 0 .../Serialization/LoaderTests.cs | 12 ----- .../Serialization/MapReaderTests.cs | 0 .../UnitTests/Serialization/TestData.cs | 49 ++++++++++++++++++ .../Serialization/Tmj/TmjMapReaderTests.cs | 0 .../Serialization/Tmx/TmxMapReaderTests.cs | 0 67 files changed, 55 insertions(+), 65 deletions(-) delete mode 100644 src/DotTiled.Tests/Serialization/TestData.cs rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/default-map/default-map.cs (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/default-map/default-map.tmj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/default-map/default-map.tmx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-external-tileset-multi/map-external-tileset-multi.cs (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-external-tileset-multi/map-external-tileset-multi.tmj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-external-tileset-multi/map-external-tileset-multi.tmx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-external-tileset-multi/multi-tileset.tsj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-external-tileset-multi/multi-tileset.tsx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-external-tileset-multi/tileset.png (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-external-tileset-wangset/map-external-tileset-wangset.cs (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-external-tileset-wangset/map-external-tileset-wangset.tmj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-external-tileset-wangset/map-external-tileset-wangset.tmx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-external-tileset-wangset/tileset.png (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-external-tileset-wangset/wangset-tileset.tsj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-external-tileset-wangset/wangset-tileset.tsx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-class-and-props/map-with-class-and-props.cs (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-class-and-props/map-with-class-and-props.tmj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-class-and-props/map-with-class-and-props.tmx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-class/map-with-class.cs (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-class/map-with-class.tmj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-class/map-with-class.tmx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-common-props/map-with-common-props.cs (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-common-props/map-with-common-props.tmj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-common-props/map-with-common-props.tmx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-custom-type-props/map-with-custom-type-props.cs (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-custom-type-props/map-with-custom-type-props.tmj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-custom-type-props/map-with-custom-type-props.tmx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-custom-type-props/propertytypes.json (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-deep-props/map-with-deep-props.cs (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-deep-props/map-with-deep-props.tmj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-deep-props/map-with-deep-props.tmx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-embedded-tileset/map-with-embedded-tileset.cs (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-embedded-tileset/map-with-embedded-tileset.tmj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-embedded-tileset/map-with-embedded-tileset.tmx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-embedded-tileset/tileset.png (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-external-tileset/map-with-external-tileset.cs (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-external-tileset/map-with-external-tileset.tmj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-external-tileset/map-with-external-tileset.tmx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-external-tileset/tileset.png (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-external-tileset/tileset.tsj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-external-tileset/tileset.tsx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-flippingflags/map-with-flippingflags.cs (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-flippingflags/map-with-flippingflags.tmj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-flippingflags/map-with-flippingflags.tmx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-flippingflags/tileset.png (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-flippingflags/tileset.tsj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-flippingflags/tileset.tsx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-many-layers/map-with-many-layers.cs (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-many-layers/map-with-many-layers.tmj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-many-layers/map-with-many-layers.tmx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-many-layers/poly.tj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-many-layers/poly.tx (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-many-layers/tileset.png (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-many-layers/tileset.tsj (100%) rename src/DotTiled.Tests/{Serialization/TestData/Map => TestData/Maps}/map-with-many-layers/tileset.tsx (100%) rename src/DotTiled.Tests/{ => UnitTests}/OptionalTests.cs (100%) rename src/DotTiled.Tests/{ => UnitTests}/Properties/CustomTypes/CustomClassDefinitionTests.cs (100%) rename src/DotTiled.Tests/{ => UnitTests}/Properties/CustomTypes/CustomEnumDefinitionTests.cs (100%) rename src/DotTiled.Tests/{ => UnitTests}/Properties/HasPropertiesBaseTests.cs (100%) rename src/DotTiled.Tests/{ => UnitTests}/Serialization/DefaultResourceCacheTests.cs (100%) rename src/DotTiled.Tests/{ => UnitTests}/Serialization/LoaderTests.cs (95%) rename src/DotTiled.Tests/{ => UnitTests}/Serialization/MapReaderTests.cs (100%) create mode 100644 src/DotTiled.Tests/UnitTests/Serialization/TestData.cs rename src/DotTiled.Tests/{ => UnitTests}/Serialization/Tmj/TmjMapReaderTests.cs (100%) rename src/DotTiled.Tests/{ => UnitTests}/Serialization/Tmx/TmxMapReaderTests.cs (100%) diff --git a/src/DotTiled.Tests/DotTiled.Tests.csproj b/src/DotTiled.Tests/DotTiled.Tests.csproj index fe6e8c9..552a423 100644 --- a/src/DotTiled.Tests/DotTiled.Tests.csproj +++ b/src/DotTiled.Tests/DotTiled.Tests.csproj @@ -26,12 +26,12 @@ - - - - - - + + + + + + diff --git a/src/DotTiled.Tests/Serialization/TestData.cs b/src/DotTiled.Tests/Serialization/TestData.cs deleted file mode 100644 index 582bd70..0000000 --- a/src/DotTiled.Tests/Serialization/TestData.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Xml; - -namespace DotTiled.Tests; - -public static partial class TestData -{ - public static XmlReader GetXmlReaderFor(string testDataFile) - { - var fullyQualifiedTestDataFile = $"DotTiled.Tests.{ConvertPathToAssemblyResourcePath(testDataFile)}"; - using var stream = typeof(TestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) - ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); - - using var stringReader = new StreamReader(stream); - var xml = stringReader.ReadToEnd(); - var xmlStringReader = new StringReader(xml); - return XmlReader.Create(xmlStringReader); - } - - public static string GetRawStringFor(string testDataFile) - { - var fullyQualifiedTestDataFile = $"DotTiled.Tests.{ConvertPathToAssemblyResourcePath(testDataFile)}"; - using var stream = typeof(TestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) - ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); - - using var stringReader = new StreamReader(stream); - return stringReader.ReadToEnd(); - } - - private static string ConvertPathToAssemblyResourcePath(string path) => - path.Replace("/", ".").Replace("\\", ".").Replace(" ", "_"); - - public static IEnumerable MapTests => - [ - ["Serialization/TestData/Map/default_map/default-map", (string f) => DefaultMap(), Array.Empty()], - ["Serialization/TestData/Map/map_with_common_props/map-with-common-props", (string f) => MapWithCommonProps(), Array.Empty()], - ["Serialization/TestData/Map/map_with_custom_type_props/map-with-custom-type-props", (string f) => MapWithCustomTypeProps(), MapWithCustomTypePropsCustomTypeDefinitions()], - ["Serialization/TestData/Map/map_with_embedded_tileset/map-with-embedded-tileset", (string f) => MapWithEmbeddedTileset(), Array.Empty()], - ["Serialization/TestData/Map/map_with_external_tileset/map-with-external-tileset", (string f) => MapWithExternalTileset(f), Array.Empty()], - ["Serialization/TestData/Map/map_with_flippingflags/map-with-flippingflags", (string f) => MapWithFlippingFlags(f), Array.Empty()], - ["Serialization/TestData/Map/map_external_tileset_multi/map-external-tileset-multi", (string f) => MapExternalTilesetMulti(f), Array.Empty()], - ["Serialization/TestData/Map/map_external_tileset_wangset/map-external-tileset-wangset", (string f) => MapExternalTilesetWangset(f), Array.Empty()], - ["Serialization/TestData/Map/map_with_many_layers/map-with-many-layers", (string f) => MapWithManyLayers(f), Array.Empty()], - ["Serialization/TestData/Map/map_with_deep_props/map-with-deep-props", (string f) => MapWithDeepProps(), MapWithDeepPropsCustomTypeDefinitions()], - ["Serialization/TestData/Map/map_with_class/map-with-class", (string f) => MapWithClass(), MapWithClassCustomTypeDefinitions()], - ["Serialization/TestData/Map/map_with_class_and_props/map-with-class-and-props", (string f) => MapWithClassAndProps(), MapWithClassAndPropsCustomTypeDefinitions()], - ]; -} diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.cs b/src/DotTiled.Tests/TestData/Maps/default-map/default-map.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.cs rename to src/DotTiled.Tests/TestData/Maps/default-map/default-map.cs diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmj b/src/DotTiled.Tests/TestData/Maps/default-map/default-map.tmj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmj rename to src/DotTiled.Tests/TestData/Maps/default-map/default-map.tmj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmx b/src/DotTiled.Tests/TestData/Maps/default-map/default-map.tmx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmx rename to src/DotTiled.Tests/TestData/Maps/default-map/default-map.tmx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.cs b/src/DotTiled.Tests/TestData/Maps/map-external-tileset-multi/map-external-tileset-multi.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.cs rename to src/DotTiled.Tests/TestData/Maps/map-external-tileset-multi/map-external-tileset-multi.cs diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.tmj b/src/DotTiled.Tests/TestData/Maps/map-external-tileset-multi/map-external-tileset-multi.tmj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.tmj rename to src/DotTiled.Tests/TestData/Maps/map-external-tileset-multi/map-external-tileset-multi.tmj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.tmx b/src/DotTiled.Tests/TestData/Maps/map-external-tileset-multi/map-external-tileset-multi.tmx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.tmx rename to src/DotTiled.Tests/TestData/Maps/map-external-tileset-multi/map-external-tileset-multi.tmx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/multi-tileset.tsj b/src/DotTiled.Tests/TestData/Maps/map-external-tileset-multi/multi-tileset.tsj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/multi-tileset.tsj rename to src/DotTiled.Tests/TestData/Maps/map-external-tileset-multi/multi-tileset.tsj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/multi-tileset.tsx b/src/DotTiled.Tests/TestData/Maps/map-external-tileset-multi/multi-tileset.tsx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/multi-tileset.tsx rename to src/DotTiled.Tests/TestData/Maps/map-external-tileset-multi/multi-tileset.tsx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/tileset.png b/src/DotTiled.Tests/TestData/Maps/map-external-tileset-multi/tileset.png similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/tileset.png rename to src/DotTiled.Tests/TestData/Maps/map-external-tileset-multi/tileset.png diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.cs b/src/DotTiled.Tests/TestData/Maps/map-external-tileset-wangset/map-external-tileset-wangset.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.cs rename to src/DotTiled.Tests/TestData/Maps/map-external-tileset-wangset/map-external-tileset-wangset.cs diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.tmj b/src/DotTiled.Tests/TestData/Maps/map-external-tileset-wangset/map-external-tileset-wangset.tmj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.tmj rename to src/DotTiled.Tests/TestData/Maps/map-external-tileset-wangset/map-external-tileset-wangset.tmj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.tmx b/src/DotTiled.Tests/TestData/Maps/map-external-tileset-wangset/map-external-tileset-wangset.tmx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.tmx rename to src/DotTiled.Tests/TestData/Maps/map-external-tileset-wangset/map-external-tileset-wangset.tmx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/tileset.png b/src/DotTiled.Tests/TestData/Maps/map-external-tileset-wangset/tileset.png similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/tileset.png rename to src/DotTiled.Tests/TestData/Maps/map-external-tileset-wangset/tileset.png diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/wangset-tileset.tsj b/src/DotTiled.Tests/TestData/Maps/map-external-tileset-wangset/wangset-tileset.tsj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/wangset-tileset.tsj rename to src/DotTiled.Tests/TestData/Maps/map-external-tileset-wangset/wangset-tileset.tsj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/wangset-tileset.tsx b/src/DotTiled.Tests/TestData/Maps/map-external-tileset-wangset/wangset-tileset.tsx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/wangset-tileset.tsx rename to src/DotTiled.Tests/TestData/Maps/map-external-tileset-wangset/wangset-tileset.tsx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-class-and-props/map-with-class-and-props.cs b/src/DotTiled.Tests/TestData/Maps/map-with-class-and-props/map-with-class-and-props.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-class-and-props/map-with-class-and-props.cs rename to src/DotTiled.Tests/TestData/Maps/map-with-class-and-props/map-with-class-and-props.cs diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-class-and-props/map-with-class-and-props.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-class-and-props/map-with-class-and-props.tmj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-class-and-props/map-with-class-and-props.tmj rename to src/DotTiled.Tests/TestData/Maps/map-with-class-and-props/map-with-class-and-props.tmj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-class-and-props/map-with-class-and-props.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-class-and-props/map-with-class-and-props.tmx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-class-and-props/map-with-class-and-props.tmx rename to src/DotTiled.Tests/TestData/Maps/map-with-class-and-props/map-with-class-and-props.tmx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-class/map-with-class.cs b/src/DotTiled.Tests/TestData/Maps/map-with-class/map-with-class.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-class/map-with-class.cs rename to src/DotTiled.Tests/TestData/Maps/map-with-class/map-with-class.cs diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-class/map-with-class.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-class/map-with-class.tmj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-class/map-with-class.tmj rename to src/DotTiled.Tests/TestData/Maps/map-with-class/map-with-class.tmj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-class/map-with-class.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-class/map-with-class.tmx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-class/map-with-class.tmx rename to src/DotTiled.Tests/TestData/Maps/map-with-class/map-with-class.tmx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.cs b/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.cs rename to src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.cs diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.tmj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.tmj rename to src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.tmj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.tmx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.tmx rename to src/DotTiled.Tests/TestData/Maps/map-with-common-props/map-with-common-props.tmx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.cs b/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props/map-with-custom-type-props.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.cs rename to src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props/map-with-custom-type-props.cs diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props/map-with-custom-type-props.tmj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.tmj rename to src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props/map-with-custom-type-props.tmj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props/map-with-custom-type-props.tmx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.tmx rename to src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props/map-with-custom-type-props.tmx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/propertytypes.json b/src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props/propertytypes.json similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/propertytypes.json rename to src/DotTiled.Tests/TestData/Maps/map-with-custom-type-props/propertytypes.json diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-deep-props/map-with-deep-props.cs b/src/DotTiled.Tests/TestData/Maps/map-with-deep-props/map-with-deep-props.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-deep-props/map-with-deep-props.cs rename to src/DotTiled.Tests/TestData/Maps/map-with-deep-props/map-with-deep-props.cs diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-deep-props/map-with-deep-props.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-deep-props/map-with-deep-props.tmj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-deep-props/map-with-deep-props.tmj rename to src/DotTiled.Tests/TestData/Maps/map-with-deep-props/map-with-deep-props.tmj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-deep-props/map-with-deep-props.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-deep-props/map-with-deep-props.tmx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-deep-props/map-with-deep-props.tmx rename to src/DotTiled.Tests/TestData/Maps/map-with-deep-props/map-with-deep-props.tmx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.cs b/src/DotTiled.Tests/TestData/Maps/map-with-embedded-tileset/map-with-embedded-tileset.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.cs rename to src/DotTiled.Tests/TestData/Maps/map-with-embedded-tileset/map-with-embedded-tileset.cs diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-embedded-tileset/map-with-embedded-tileset.tmj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.tmj rename to src/DotTiled.Tests/TestData/Maps/map-with-embedded-tileset/map-with-embedded-tileset.tmj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-embedded-tileset/map-with-embedded-tileset.tmx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.tmx rename to src/DotTiled.Tests/TestData/Maps/map-with-embedded-tileset/map-with-embedded-tileset.tmx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/tileset.png b/src/DotTiled.Tests/TestData/Maps/map-with-embedded-tileset/tileset.png similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/tileset.png rename to src/DotTiled.Tests/TestData/Maps/map-with-embedded-tileset/tileset.png diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.cs b/src/DotTiled.Tests/TestData/Maps/map-with-external-tileset/map-with-external-tileset.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.cs rename to src/DotTiled.Tests/TestData/Maps/map-with-external-tileset/map-with-external-tileset.cs diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-external-tileset/map-with-external-tileset.tmj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.tmj rename to src/DotTiled.Tests/TestData/Maps/map-with-external-tileset/map-with-external-tileset.tmj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-external-tileset/map-with-external-tileset.tmx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.tmx rename to src/DotTiled.Tests/TestData/Maps/map-with-external-tileset/map-with-external-tileset.tmx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.png b/src/DotTiled.Tests/TestData/Maps/map-with-external-tileset/tileset.png similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.png rename to src/DotTiled.Tests/TestData/Maps/map-with-external-tileset/tileset.png diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.tsj b/src/DotTiled.Tests/TestData/Maps/map-with-external-tileset/tileset.tsj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.tsj rename to src/DotTiled.Tests/TestData/Maps/map-with-external-tileset/tileset.tsj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.tsx b/src/DotTiled.Tests/TestData/Maps/map-with-external-tileset/tileset.tsx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.tsx rename to src/DotTiled.Tests/TestData/Maps/map-with-external-tileset/tileset.tsx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.cs b/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.cs rename to src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.cs diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.tmj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.tmj rename to src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.tmj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.tmx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.tmx rename to src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/map-with-flippingflags.tmx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.png b/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/tileset.png similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.png rename to src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/tileset.png diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.tsj b/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/tileset.tsj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.tsj rename to src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/tileset.tsj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.tsx b/src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/tileset.tsx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.tsx rename to src/DotTiled.Tests/TestData/Maps/map-with-flippingflags/tileset.tsx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.cs b/src/DotTiled.Tests/TestData/Maps/map-with-many-layers/map-with-many-layers.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.cs rename to src/DotTiled.Tests/TestData/Maps/map-with-many-layers/map-with-many-layers.cs diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmj b/src/DotTiled.Tests/TestData/Maps/map-with-many-layers/map-with-many-layers.tmj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmj rename to src/DotTiled.Tests/TestData/Maps/map-with-many-layers/map-with-many-layers.tmj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmx b/src/DotTiled.Tests/TestData/Maps/map-with-many-layers/map-with-many-layers.tmx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmx rename to src/DotTiled.Tests/TestData/Maps/map-with-many-layers/map-with-many-layers.tmx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/poly.tj b/src/DotTiled.Tests/TestData/Maps/map-with-many-layers/poly.tj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/poly.tj rename to src/DotTiled.Tests/TestData/Maps/map-with-many-layers/poly.tj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/poly.tx b/src/DotTiled.Tests/TestData/Maps/map-with-many-layers/poly.tx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/poly.tx rename to src/DotTiled.Tests/TestData/Maps/map-with-many-layers/poly.tx diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.png b/src/DotTiled.Tests/TestData/Maps/map-with-many-layers/tileset.png similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.png rename to src/DotTiled.Tests/TestData/Maps/map-with-many-layers/tileset.png diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.tsj b/src/DotTiled.Tests/TestData/Maps/map-with-many-layers/tileset.tsj similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.tsj rename to src/DotTiled.Tests/TestData/Maps/map-with-many-layers/tileset.tsj diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.tsx b/src/DotTiled.Tests/TestData/Maps/map-with-many-layers/tileset.tsx similarity index 100% rename from src/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.tsx rename to src/DotTiled.Tests/TestData/Maps/map-with-many-layers/tileset.tsx diff --git a/src/DotTiled.Tests/OptionalTests.cs b/src/DotTiled.Tests/UnitTests/OptionalTests.cs similarity index 100% rename from src/DotTiled.Tests/OptionalTests.cs rename to src/DotTiled.Tests/UnitTests/OptionalTests.cs diff --git a/src/DotTiled.Tests/Properties/CustomTypes/CustomClassDefinitionTests.cs b/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomClassDefinitionTests.cs similarity index 100% rename from src/DotTiled.Tests/Properties/CustomTypes/CustomClassDefinitionTests.cs rename to src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomClassDefinitionTests.cs diff --git a/src/DotTiled.Tests/Properties/CustomTypes/CustomEnumDefinitionTests.cs b/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomEnumDefinitionTests.cs similarity index 100% rename from src/DotTiled.Tests/Properties/CustomTypes/CustomEnumDefinitionTests.cs rename to src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomEnumDefinitionTests.cs diff --git a/src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs b/src/DotTiled.Tests/UnitTests/Properties/HasPropertiesBaseTests.cs similarity index 100% rename from src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs rename to src/DotTiled.Tests/UnitTests/Properties/HasPropertiesBaseTests.cs diff --git a/src/DotTiled.Tests/Serialization/DefaultResourceCacheTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/DefaultResourceCacheTests.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/DefaultResourceCacheTests.cs rename to src/DotTiled.Tests/UnitTests/Serialization/DefaultResourceCacheTests.cs diff --git a/src/DotTiled.Tests/Serialization/LoaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs similarity index 95% rename from src/DotTiled.Tests/Serialization/LoaderTests.cs rename to src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs index 4706445..daf0733 100644 --- a/src/DotTiled.Tests/Serialization/LoaderTests.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs @@ -1,5 +1,4 @@ using System.Numerics; -using System.Runtime.CompilerServices; using DotTiled.Serialization; using NSubstitute; @@ -245,15 +244,4 @@ public class LoaderTests // Assert resourceReader.DidNotReceive().Read("template.tx"); } - - private static string WhereAmI([CallerFilePath] string callerFilePath = "") => callerFilePath; - - [Fact] - public void Test1() - { - var basePath = Path.GetDirectoryName(WhereAmI())!; - var mapPath = Path.Combine(basePath, "TestData/Map/map-with-external-tileset/map-with-external-tileset.tmx"); - var loader = Loader.Default(); - loader.LoadMap(mapPath); - } } diff --git a/src/DotTiled.Tests/Serialization/MapReaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/MapReaderTests.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/MapReaderTests.cs rename to src/DotTiled.Tests/UnitTests/Serialization/MapReaderTests.cs diff --git a/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs b/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs new file mode 100644 index 0000000..eb13942 --- /dev/null +++ b/src/DotTiled.Tests/UnitTests/Serialization/TestData.cs @@ -0,0 +1,49 @@ +using System.Xml; + +namespace DotTiled.Tests; + +public static partial class TestData +{ + public static XmlReader GetXmlReaderFor(string testDataFile) + { + var fullyQualifiedTestDataFile = $"DotTiled.Tests.{ConvertPathToAssemblyResourcePath(testDataFile)}"; + using var stream = typeof(TestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) + ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); + + using var stringReader = new StreamReader(stream); + var xml = stringReader.ReadToEnd(); + var xmlStringReader = new StringReader(xml); + return XmlReader.Create(xmlStringReader); + } + + public static string GetRawStringFor(string testDataFile) + { + var fullyQualifiedTestDataFile = $"DotTiled.Tests.{ConvertPathToAssemblyResourcePath(testDataFile)}"; + using var stream = typeof(TestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) + ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); + + using var stringReader = new StreamReader(stream); + return stringReader.ReadToEnd(); + } + + private static string ConvertPathToAssemblyResourcePath(string path) => + path.Replace("/", ".").Replace("\\", ".").Replace(" ", "_"); + + private static string GetMapPath(string mapName) => $"TestData/Maps/{mapName.Replace('-', '_')}/{mapName}"; + + public static IEnumerable MapTests => + [ + [GetMapPath("default-map"), (string f) => DefaultMap(), Array.Empty()], + [GetMapPath("map-with-common-props"), (string f) => MapWithCommonProps(), Array.Empty()], + [GetMapPath("map-with-custom-type-props"), (string f) => MapWithCustomTypeProps(), MapWithCustomTypePropsCustomTypeDefinitions()], + [GetMapPath("map-with-embedded-tileset"), (string f) => MapWithEmbeddedTileset(), Array.Empty()], + [GetMapPath("map-with-external-tileset"), (string f) => MapWithExternalTileset(f), Array.Empty()], + [GetMapPath("map-with-flippingflags"), (string f) => MapWithFlippingFlags(f), Array.Empty()], + [GetMapPath("map-external-tileset-multi"), (string f) => MapExternalTilesetMulti(f), Array.Empty()], + [GetMapPath("map-external-tileset-wangset"), (string f) => MapExternalTilesetWangset(f), Array.Empty()], + [GetMapPath("map-with-many-layers"), (string f) => MapWithManyLayers(f), Array.Empty()], + [GetMapPath("map-with-deep-props"), (string f) => MapWithDeepProps(), MapWithDeepPropsCustomTypeDefinitions()], + [GetMapPath("map-with-class"), (string f) => MapWithClass(), MapWithClassCustomTypeDefinitions()], + [GetMapPath("map-with-class-and-props"), (string f) => MapWithClassAndProps(), MapWithClassAndPropsCustomTypeDefinitions()], + ]; +} diff --git a/src/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/Tmj/TmjMapReaderTests.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs rename to src/DotTiled.Tests/UnitTests/Serialization/Tmj/TmjMapReaderTests.cs diff --git a/src/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/Tmx/TmxMapReaderTests.cs similarity index 100% rename from src/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs rename to src/DotTiled.Tests/UnitTests/Serialization/Tmx/TmxMapReaderTests.cs From e0252d263e58c734ade3d2c99399aa7d1b448431 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 7 Sep 2024 15:47:59 +0200 Subject: [PATCH 13/15] Added test that creates class definition from C# class and uses to load map --- .../Assert/AssertCustomTypes.cs | 23 +++ .../CustomTypes/FromTypeUsedInLoaderTests.cs | 180 ++++++++++++++++++ .../CustomTypes/CustomClassDefinitionTests.cs | 12 +- .../CustomTypes/CustomEnumDefinitionTests.cs | 17 +- 4 files changed, 208 insertions(+), 24 deletions(-) create mode 100644 src/DotTiled.Tests/Assert/AssertCustomTypes.cs create mode 100644 src/DotTiled.Tests/IntegrationTests/CustomTypes/FromTypeUsedInLoaderTests.cs diff --git a/src/DotTiled.Tests/Assert/AssertCustomTypes.cs b/src/DotTiled.Tests/Assert/AssertCustomTypes.cs new file mode 100644 index 0000000..19eae5b --- /dev/null +++ b/src/DotTiled.Tests/Assert/AssertCustomTypes.cs @@ -0,0 +1,23 @@ +namespace DotTiled.Tests; + +public static partial class DotTiledAssert +{ + internal static void AssertCustomClassDefinitionEqual(CustomClassDefinition expected, CustomClassDefinition actual) + { + AssertEqual(expected.ID, actual.ID, nameof(CustomClassDefinition.ID)); + AssertEqual(expected.Name, actual.Name, nameof(CustomClassDefinition.Name)); + AssertEqual(expected.Color, actual.Color, nameof(CustomClassDefinition.Color)); + AssertEqual(expected.DrawFill, actual.DrawFill, nameof(CustomClassDefinition.DrawFill)); + AssertEqual(expected.UseAs, actual.UseAs, nameof(CustomClassDefinition.UseAs)); + AssertProperties(expected.Members, actual.Members); + } + + internal static void AssertCustomEnumDefinitionEqual(CustomEnumDefinition expected, CustomEnumDefinition actual) + { + AssertEqual(expected.ID, actual.ID, nameof(CustomEnumDefinition.ID)); + AssertEqual(expected.Name, actual.Name, nameof(CustomEnumDefinition.Name)); + AssertEqual(expected.StorageType, actual.StorageType, nameof(CustomEnumDefinition.StorageType)); + AssertEqual(expected.ValueAsFlags, actual.ValueAsFlags, nameof(CustomEnumDefinition.ValueAsFlags)); + AssertListOrdered(expected.Values, actual.Values, nameof(CustomEnumDefinition.Values)); + } +} diff --git a/src/DotTiled.Tests/IntegrationTests/CustomTypes/FromTypeUsedInLoaderTests.cs b/src/DotTiled.Tests/IntegrationTests/CustomTypes/FromTypeUsedInLoaderTests.cs new file mode 100644 index 0000000..dae9464 --- /dev/null +++ b/src/DotTiled.Tests/IntegrationTests/CustomTypes/FromTypeUsedInLoaderTests.cs @@ -0,0 +1,180 @@ +using DotTiled.Serialization; +using NSubstitute; + +namespace DotTiled.Tests; + +public class FromTypeUsedInLoaderTests +{ + private sealed class TestClass + { + public string Name { get; set; } = "John Doe"; + public int Age { get; set; } = 42; + } + + [Fact] + public void LoadMap_MapHasClassAndClassIsDefined_ReturnsCorrectMap() + { + // Arrange + var resourceReader = Substitute.For(); + resourceReader.Read("map.tmx").Returns( + """ + + + + + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0 + + + + """); + var classDefinition = CustomClassDefinition.FromClass(); + var loader = Loader.DefaultWith( + resourceReader: resourceReader, + customTypeDefinitions: [classDefinition]); + var expectedMap = new Map + { + Class = "TestClass", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = new Color { R = 0, G = 0, B = 0, A = 0 }, + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + GlobalTileIDs = new Optional([ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + ]), + FlippingFlags = new Optional([ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ]) + } + } + ], + Properties = [ + new StringProperty { Name = "Name", Value = "John Doe" }, + new IntProperty { Name = "Age", Value = 42 } + ] + }; + + // Act + var result = loader.LoadMap("map.tmx"); + + // Assert + DotTiledAssert.AssertMap(expectedMap, result); + } + + [Fact] + public void LoadMap_MapHasClassAndClassIsDefinedAndFieldIsOverridenFromDefault_ReturnsCorrectMap() + { + // Arrange + var resourceReader = Substitute.For(); + resourceReader.Read("map.tmx").Returns( + """ + + + + + + + + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0 + + + + """); + var classDefinition = CustomClassDefinition.FromClass(); + var loader = Loader.DefaultWith( + resourceReader: resourceReader, + customTypeDefinitions: [classDefinition]); + var expectedMap = new Map + { + Class = "TestClass", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = new Color { R = 0, G = 0, B = 0, A = 0 }, + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + GlobalTileIDs = new Optional([ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + ]), + FlippingFlags = new Optional([ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ]) + } + } + ], + Properties = [ + new StringProperty { Name = "Name", Value = "John Doe" }, + new IntProperty { Name = "Age", Value = 42 } + ] + }; + + // Act + var result = loader.LoadMap("map.tmx"); + + // Assert + DotTiledAssert.AssertMap(expectedMap, result); + } +} diff --git a/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomClassDefinitionTests.cs b/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomClassDefinitionTests.cs index 87fecbd..7e920e3 100644 --- a/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomClassDefinitionTests.cs +++ b/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomClassDefinitionTests.cs @@ -77,16 +77,6 @@ public class CustomClassDefinitionTests yield return (typeof(TestClass3WithOverridenNestedClass), ExpectedTestClass3WithOverridenNestedClassDefinition); } - private static void AssertCustomClassDefinitionEqual(CustomClassDefinition expected, CustomClassDefinition actual) - { - DotTiledAssert.AssertEqual(expected.ID, actual.ID, nameof(CustomClassDefinition.ID)); - DotTiledAssert.AssertEqual(expected.Name, actual.Name, nameof(CustomClassDefinition.Name)); - DotTiledAssert.AssertEqual(expected.Color, actual.Color, nameof(CustomClassDefinition.Color)); - DotTiledAssert.AssertEqual(expected.DrawFill, actual.DrawFill, nameof(CustomClassDefinition.DrawFill)); - DotTiledAssert.AssertEqual(expected.UseAs, actual.UseAs, nameof(CustomClassDefinition.UseAs)); - DotTiledAssert.AssertProperties(expected.Members, actual.Members); - } - public static IEnumerable CustomClassDefinitionTestData => GetCustomClassDefinitionTestData().Select(data => new object[] { data.Item1, data.Item2 }); [Theory] @@ -97,7 +87,7 @@ public class CustomClassDefinitionTests var result = CustomClassDefinition.FromClass(type); // Assert - AssertCustomClassDefinitionEqual(expected, result); + DotTiledAssert.AssertCustomClassDefinitionEqual(expected, result); } [Fact] diff --git a/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomEnumDefinitionTests.cs b/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomEnumDefinitionTests.cs index 347836a..c342f77 100644 --- a/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomEnumDefinitionTests.cs +++ b/src/DotTiled.Tests/UnitTests/Properties/CustomTypes/CustomEnumDefinitionTests.cs @@ -2,15 +2,6 @@ namespace DotTiled.Tests; public class CustomEnumDefinitionTests { - private static void AssertCustomEnumDefinitionEqual(CustomEnumDefinition expected, CustomEnumDefinition actual) - { - DotTiledAssert.AssertEqual(expected.ID, actual.ID, nameof(CustomEnumDefinition.ID)); - DotTiledAssert.AssertEqual(expected.Name, actual.Name, nameof(CustomEnumDefinition.Name)); - DotTiledAssert.AssertEqual(expected.StorageType, actual.StorageType, nameof(CustomEnumDefinition.StorageType)); - DotTiledAssert.AssertEqual(expected.ValueAsFlags, actual.ValueAsFlags, nameof(CustomEnumDefinition.ValueAsFlags)); - DotTiledAssert.AssertListOrdered(expected.Values, actual.Values, nameof(CustomEnumDefinition.Values)); - } - [Fact] public void FromEnum_Type_WhenTypeIsNotEnum_ThrowsArgumentException() { @@ -41,7 +32,7 @@ public class CustomEnumDefinitionTests var result = CustomEnumDefinition.FromEnum(type); // Assert - AssertCustomEnumDefinitionEqual(expected, result); + DotTiledAssert.AssertCustomEnumDefinitionEqual(expected, result); } [Flags] @@ -65,7 +56,7 @@ public class CustomEnumDefinitionTests var result = CustomEnumDefinition.FromEnum(type); // Assert - AssertCustomEnumDefinitionEqual(expected, result); + DotTiledAssert.AssertCustomEnumDefinitionEqual(expected, result); } [Fact] @@ -85,7 +76,7 @@ public class CustomEnumDefinitionTests var result = CustomEnumDefinition.FromEnum(); // Assert - AssertCustomEnumDefinitionEqual(expected, result); + DotTiledAssert.AssertCustomEnumDefinitionEqual(expected, result); } [Fact] @@ -105,6 +96,6 @@ public class CustomEnumDefinitionTests var result = CustomEnumDefinition.FromEnum(); // Assert - AssertCustomEnumDefinitionEqual(expected, result); + DotTiledAssert.AssertCustomEnumDefinitionEqual(expected, result); } } From 9b665efe18d9a5ce3dd20c74afc9f0ce1feff51f Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 7 Sep 2024 15:54:51 +0200 Subject: [PATCH 14/15] Add more tests to Loader for custom types --- .../UnitTests/Serialization/LoaderTests.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs index daf0733..d54ab9f 100644 --- a/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs @@ -244,4 +244,72 @@ public class LoaderTests // Assert resourceReader.DidNotReceive().Read("template.tx"); } + + [Fact] + public void LoadMap_MapHasClassAndLoaderHasNoCustomTypes_ThrowsException() + { + // Arrange + var resourceReader = Substitute.For(); + resourceReader.Read("map.tmx").Returns( + """ + + + + + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0 + + + + """); + + var resourceCache = Substitute.For(); + var customTypeDefinitions = Enumerable.Empty(); + var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions); + + // Act & Assert + Assert.Throws(() => loader.LoadMap("map.tmx")); + } + + [Fact] + public void LoadMap_MapHasClassAndLoaderHasCustomTypeWithSameName_ReturnsMapWithPropertiesFromCustomClass() + { + // Arrange + var resourceReader = Substitute.For(); + resourceReader.Read("map.tmx").Returns( + """ + + + + + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0 + + + + """); + var resourceCache = Substitute.For(); + var customClassDefinition = new CustomClassDefinition + { + Name = "TestClass", + UseAs = CustomClassUseAs.All, + Members = [ + new StringProperty { Name = "Test1", Value = "Hello" }, + new IntProperty { Name = "Test2", Value = 42 } + ] + }; + var loader = new Loader(resourceReader, resourceCache, [customClassDefinition]); + + // Act + var result = loader.LoadMap("map.tmx"); + + // Assert + DotTiledAssert.AssertProperties(customClassDefinition.Members, result.Properties); + } } From b3be5c3735731674388722b07ac2421ee7273290 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 7 Sep 2024 18:44:46 +0200 Subject: [PATCH 15/15] Add docs on how to map properties to classes --- docs/docfx.json | 5 +- docs/docs/essentials/custom-properties.md | 147 +++++++++++++++++----- 2 files changed, 121 insertions(+), 31 deletions(-) diff --git a/docs/docfx.json b/docs/docfx.json index cb678c9..744755c 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -42,6 +42,9 @@ "_appTitle": "DotTiled", "_enableSearch": true, "pdf": false - } + }, + "xref": [ + "https://learn.microsoft.com/en-us/dotnet/.xrefmap.json" + ] } } \ No newline at end of file diff --git a/docs/docs/essentials/custom-properties.md b/docs/docs/essentials/custom-properties.md index b18ba7f..9fc19aa 100644 --- a/docs/docs/essentials/custom-properties.md +++ b/docs/docs/essentials/custom-properties.md @@ -66,11 +66,14 @@ Tiled supports a variety of property types, which are represented in the DotTile - `object` - - `string` - -In addition to these primitive property types, [Tiled also supports more complex property types](https://doc.mapeditor.org/en/stable/manual/custom-properties/#custom-types). These custom property types are defined in Tiled according to the linked documentation, and to work with them in DotTiled, you *must* define their equivalences as a . You must then provide a resolving function to a defined type given a custom type name, as it is defined in Tiled. +In addition to these primitive property types, [Tiled also supports more complex property types](https://doc.mapeditor.org/en/stable/manual/custom-properties/#custom-types). These custom property types are defined in Tiled according to the linked documentation, and to work with them in DotTiled, you *must* define their equivalences as a . This is because of how Tiled handles default values for custom property types, and DotTiled needs to know these defaults to be able to populate the properties correctly. ## Custom types -Tiled allows you to define custom property types that can be used in your maps. These custom property types can be of type `class` or `enum`. DotTiled supports custom property types by allowing you to define the equivalent in C# and then providing a custom type resolver function that will return the equivalent definition given a custom type name. +Tiled allows you to define custom property types that can be used in your maps. These custom property types can be of type `class` or `enum`. DotTiled supports custom property types by allowing you to define the equivalent in C#. This section will guide you through how to define custom property types in DotTiled and how to map properties in loaded maps to C# classes or enums. + +> [!NOTE] +> In the future, DotTiled could provide a way to configure the use of custom property types such that they aren't necessary to be defined, given that you have set the `Resolve object types and properties` setting in Tiled. ### Class properties @@ -88,14 +91,39 @@ var monsterSpawnerDefinition = new CustomClassDefinition Name = "MonsterSpawner", UseAs = CustomClassUseAs.All, // Not really validated by DotTiled Members = [ // Make sure that the default values match the Tiled UI - new BoolProperty { Name = "enabled", Value = true }, - new IntProperty { Name = "maxSpawnAmount", Value = 10 }, - new IntProperty { Name = "minSpawnAmount", Value = 0 }, - new StringProperty { Name = "monsterNames", Value = "" } + new BoolProperty { Name = "Enabled", Value = true }, + new IntProperty { Name = "MaxSpawnAmount", Value = 10 }, + new IntProperty { Name = "MinSpawnAmount", Value = 0 }, + new StringProperty { Name = "MonsterNames", Value = "" } ] }; ``` +Luckily, you don't have to manually define these custom class definitions, even though you most definitively can for scenarios that require it. DotTiled provides a way to automatically generate these definitions for you from a C# class. This is done by using the method, or one of its overloads. This method will generate a from a given C# class, and you can then use this definition when loading your maps. + +```csharp +class MonsterSpawner +{ + public bool Enabled { get; set; } = true; + public int MaxSpawnAmount { get; set; } = 10; + public int MinSpawnAmount { get; set; } = 0; + public string MonsterNames { get; set; } = ""; +} + +// ... + +// These are all valid ways to create your custom class definitions from a C# class +// The first two require the class to have a default, parameterless constructor +var monsterSpawnerDefinition1 = CustomClassDefinition.FromClass(); +var monsterSpawnerDefinition2 = CustomClassDefinition.FromClass(typeof(MonsterSpawner)); +var monsterSpawnerDefinition3 = CustomClassDefinition.FromClass(() => new MonsterSpawner +{ + Enabled = false // This will use the property values in the instance created by a factory method as the default values +}); +``` + +The last one is especially useful if you have classes that may not have parameterless constructors, or if you want to provide custom default values for the properties. Finally, the generated custom class definition will be identical to the one defined manually in the first example. + ### Enum properties Tiled also allows you to define custom property types that work as enums. Similarly to `class` properties, you must define the equivalent in DotTiled as a . You can then return the corresponding definition in the resolving function. @@ -110,7 +138,7 @@ The equivalent definition in DotTiled would look like the following: var entityTypeDefinition = new CustomEnumDefinition { Name = "EntityType", - StorageType = CustomEnumStorageType.String, + StorageType = CustomEnumStorageType.Int, ValueAsFlags = false, Values = [ "Bomb", @@ -121,23 +149,9 @@ var entityTypeDefinition = new CustomEnumDefinition }; ``` -### [Future] Automatically map custom property `class` types to C# classes - -In the future, DotTiled will support automatically mapping custom property `class` types to C# classes. This will allow you to define a C# class that matches the structure of the `class` property in Tiled, and DotTiled will automatically map the properties of the `class` property to the properties of the C# class. This will make working with `class` properties much easier and more intuitive. - -The idea is to expand on the interface with a method like `GetMappedProperty(string propertyName)`, where `T` is a class that matches the structure of the `class` property in Tiled. - -This functionality would be accompanied by a way to automatically create a matching given a C# class or enum. Something like this would then be possible: +Similarly to custom class definitions, you can also automatically generate custom enum definitions from C# enums. This is done by using the method, or one of its overloads. This method will generate a from a given C# enum, and you can then use this definition when loading your maps. ```csharp -class MonsterSpawner -{ - public bool Enabled { get; set; } = true; - public int MaxSpawnAmount { get; set; } = 10; - public int MinSpawnAmount { get; set; } = 0; - public string MonsterNames { get; set; } = ""; -} - enum EntityType { Bomb, @@ -146,16 +160,89 @@ enum EntityType Chair } -var monsterSpawnerDefinition = CustomClassDefinition.FromClass(); -var entityTypeDefinition = CustomEnumDefinition.FromEnum(); - // ... -var map = LoadMap(); -var monsterSpawner = map.GetMappedProperty("monsterSpawnerPropertyInMap"); -var entityType = map.GetMappedProperty("entityTypePropertyInMap"); +// These are both valid ways to create your custom enum definitions from a C# enum +var entityTypeDefinition1 = CustomEnumDefinition.FromEnum(); +var entityTypeDefinition2 = CustomEnumDefinition.FromEnum(typeof(EntityType)); ``` -Finally, it might be possible to also make some kind of exporting functionality for . Given a collection of custom type definitions, DotTiled could generate a corresponding `propertytypes.json` file that you then can import into Tiled. This would make it so that you only have to define your custom property types once (in C#) and then import them into Tiled to use them in your maps. +The generated custom enum definition will be identical to the one defined manually in the first example. -Depending on implementation this might become something that can inhibit native AOT compilation due to potential reflection usage. Source generators could be used to mitigate this, but it is not yet clear how this will be implemented. \ No newline at end of file +For enum definitions, the can be used to indicate that the enum should be treated as a flags enum. This will make it so the enum definition will have `ValueAsFlags = true` and the enum values will be treated as flags when working with them in DotTiled. + +## Mapping properties to C# classes or enums + +So far, we have only discussed how to define custom property types in DotTiled, and why they are needed. However, the most important part is how you can map properties inside your maps to their corresponding C# classes or enums. + +The interface has two overloads of the method. These methods allow you to map a collection of properties to a given C# class. Let's look at an example: + +```csharp +// Define a few Tiled compatible custom types +enum EntityType +{ + Player, + Enemy, + Collectible, + Explosive +} + +class EntityData +{ + public EntityType Type { get; set; } = EntityType.Player; + public int Health { get; set; } = 100; + public string Name { get; set; } = "Unnamed Entity"; +} + +var entityTypeDef = CustomEnumDefinition.FromEnum(); +var entityDataDef = CustomClassDefinition.FromClass(); +``` + +The above gives us two custom type definitions that we can supply to our map loader. Given a map that looks like this: + +```xml + + + + + + 1,1,1,1,1, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0 + + + + + + + + + + + + + +``` + +We can see that there is an ellipse object in the map that has the type `EntityData` and it has set the three properties to some value other than their defaults. After having loaded this map, we can map the properties of the object to the `EntityData` class like this: + +```csharp +var map = LoadMap([entityTypeDef, entityDataDef]); // Load the map somehow, using DotTiled.Loader or similar + +// Retrieve the object layer from the map in some way +var objectLayer = map.Layers.Skip(1).First() as ObjectLayer; + +// Retrieve the object from the object layer +var entityObject = objectLayer.Objects.First(); + +// Map the properties of the object to the EntityData class +var entityData = entityObject.MapPropertiesTo(); +``` + +The above snippet will map the properties of the object to the `EntityData` class using reflection based on the property names. The `entityData` object will now have the properties set to the values found in the object in the map. If a property is not found in the object, the default value of the property in the `EntityData` class will be used. It will even map the nested enum to its corresponding value in C#. This will work for several levels of depth as it performs this kind of mapping recursively. only supports mapping to classes that have a default, parameterless constructor. + +### [Future] Exporting custom types + +It might be possible to also make some kind of exporting functionality for . Given a collection of custom type definitions, DotTiled could generate a corresponding `propertytypes.json` file that you then can import into Tiled. This would make it so that you only have to define your custom property types once (in C#) and then import them into Tiled to use them in your maps. \ No newline at end of file