From e3d1bea39421386d9e9ef9e987598cbad8be8a22 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 23 Oct 2024 09:46:39 -0700 Subject: [PATCH] Add tests for struct access --- Fluid.Tests/Domain/Person.cs | 9 ++++++- Fluid.Tests/Domain/Shape.cs | 9 +++++++ Fluid.Tests/MemberAccessStrategyTests.cs | 34 ++++++++++++++++++++++++ Fluid/MemberAccessStrategyExtensions.cs | 14 +++++++--- 4 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 Fluid.Tests/Domain/Shape.cs diff --git a/Fluid.Tests/Domain/Person.cs b/Fluid.Tests/Domain/Person.cs index 5267a58e..4120db08 100644 --- a/Fluid.Tests/Domain/Person.cs +++ b/Fluid.Tests/Domain/Person.cs @@ -4,7 +4,7 @@ public class Person { public string Firstname { get; set; } public string Lastname { get; set; } - + public Colors EyesColor { get; set; } public Address Address { get; set; } } @@ -13,4 +13,11 @@ public class Address public string City { get; set; } public string State { get; set; } } + + public enum Colors + { + Blue, + Red, + Yellow + } } diff --git a/Fluid.Tests/Domain/Shape.cs b/Fluid.Tests/Domain/Shape.cs new file mode 100644 index 00000000..560fec5f --- /dev/null +++ b/Fluid.Tests/Domain/Shape.cs @@ -0,0 +1,9 @@ +using System.Drawing; + +namespace Fluid.Tests.Domain +{ + public class Shape + { + public Point Coordinates { get; set; } + } +} diff --git a/Fluid.Tests/MemberAccessStrategyTests.cs b/Fluid.Tests/MemberAccessStrategyTests.cs index fa4ea5c1..ff431753 100644 --- a/Fluid.Tests/MemberAccessStrategyTests.cs +++ b/Fluid.Tests/MemberAccessStrategyTests.cs @@ -2,7 +2,9 @@ using Fluid.Tests.Domain; using Fluid.Values; using Newtonsoft.Json.Linq; +using System; using System.Collections.Generic; +using System.Drawing; using System.Threading.Tasks; using Xunit; @@ -239,6 +241,38 @@ public void ShouldUseDictionaryAsModel() Assert.Equal("Bill Gates", template.Render(new TemplateContext(model, options))); } + + [Fact] + public void ShouldResolveEnums() + { + var options = new TemplateOptions(); + options.MemberAccessStrategy.Register(); + + var john = new Person { Firstname = "John", EyesColor = Colors.Yellow }; + + var template = _parser.Parse("{{Firstname}} {{EyesColor}}"); + Assert.Equal("John 2", template.Render(new TemplateContext(john, options, false))); + } + + [Fact] + public void ShouldResolveStructs() + { + // We can't create an open delegate on a Struc (dotnet limitation?), so instead create custom delegates + // https://sharplab.io/#v2:EYLgtghglgdgNAFxAJwK7wCYgNQB8ACATAAwCwAUEQIwX7EAE+VAdACLIQDusA5gNwUKANwjJ6ABwCSMAGYB7egF56CAJ7iApnJkAKAApzYCAJTMA4hoR7kczcjU6ARAA1HxgeRFiMhJROny5pYWCACylgAWchg6pgDCyBoQCBqsGgA2GjzJGjpqmto6+ACsADwGRnD0RgB8xu4UQA== + + var options = new TemplateOptions(); + options.MemberAccessStrategy.Register(); + options.MemberAccessStrategy.Register(nameof(Point.X), new DelegateAccessor((point, name, context) => point.X)); + options.MemberAccessStrategy.Register(nameof(Point.Y), new DelegateAccessor((point, name, context) => point.Y)); + + var circle = new Shape + { + Coordinates = new Point(1, 2) + }; + + var template = _parser.Parse("{{Coordinates.X}} {{Coordinates.Y}}"); + Assert.Equal("1 2", template.Render(new TemplateContext(circle, options, false))); + } } public class Class1 diff --git a/Fluid/MemberAccessStrategyExtensions.cs b/Fluid/MemberAccessStrategyExtensions.cs index 19ebaf39..d607fa3d 100644 --- a/Fluid/MemberAccessStrategyExtensions.cs +++ b/Fluid/MemberAccessStrategyExtensions.cs @@ -31,27 +31,35 @@ internal static Dictionary GetTypeMembers(Type type, Me } if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Task<>)) + { list[memberNameStrategy(propertyInfo)] = new AsyncDelegateAccessor(async (o, n) => { var asyncValue = (Task)propertyInfo.GetValue(o); await asyncValue.ConfigureAwait(false); return (object)((dynamic)asyncValue).Result; }); + } else + { list[memberNameStrategy(propertyInfo)] = new PropertyInfoAccessor(propertyInfo); + } } foreach (var fieldInfo in key.Type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance)) { if (fieldInfo.FieldType.IsGenericType && fieldInfo.FieldType.GetGenericTypeDefinition() == typeof(Task<>)) + { list[memberNameStrategy(fieldInfo)] = new AsyncDelegateAccessor(async (o, n) => { var asyncValue = (Task)fieldInfo.GetValue(o); await asyncValue.ConfigureAwait(false); return (object)((dynamic)asyncValue).Result; }); + } else + { list[memberNameStrategy(fieldInfo)] = new DelegateAccessor((o, n) => fieldInfo.GetValue(o)); + } } return list; @@ -76,7 +84,7 @@ internal static IMemberAccessor GetNamedAccessor(Type type, string name, MemberN /// /// The type to register. /// The . - public static void Register(this MemberAccessStrategy strategy) where T : class + public static void Register(this MemberAccessStrategy strategy) { Register(strategy, typeof(T)); } @@ -97,7 +105,7 @@ public static void Register(this MemberAccessStrategy strategy, Type type) /// The type to register. /// The . /// The names of the properties in the type to register. - public static void Register(this MemberAccessStrategy strategy, params string[] names) where T : class + public static void Register(this MemberAccessStrategy strategy, params string[] names) { strategy.Register(typeof(T), names); } @@ -108,7 +116,7 @@ public static void Register(this MemberAccessStrategy strategy, params string /// The type to register. /// The . /// The property's expressions in the type to register. - public static void Register(this MemberAccessStrategy strategy, params Expression>[] names) where T : class + public static void Register(this MemberAccessStrategy strategy, params Expression>[] names) { strategy.Register(names.Select(ExpressionHelper.GetPropertyName).ToArray()); }