Add default property value converters for all value types (#14869)

* Add default property value converters for all value types

* Clean up some left-over stuff
This commit is contained in:
Kenn Jacobsen
2023-09-28 13:20:03 +02:00
committed by GitHub
parent 06272d9337
commit ce86abe8ac
14 changed files with 492 additions and 45 deletions

View File

@@ -0,0 +1,10 @@
namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// Indicates that this is a default value type property value converter (shipped with Umbraco).
/// This attribute is for internal use only. It should never be applied to custom value converters.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class DefaultValueTypePropertyValueConverterAttribute : DefaultPropertyValueConverterAttribute
{
}

View File

@@ -44,5 +44,15 @@ public class PropertyValueConverterCollection : BuilderCollectionBase<IPropertyV
=> DefaultConverters.ContainsKey(converter);
internal bool Shadows(IPropertyValueConverter shadowing, IPropertyValueConverter shadowed)
=> DefaultConverters.TryGetValue(shadowing, out Type[]? types) && types.Contains(shadowed.GetType());
{
Type shadowedType = shadowed.GetType();
// any value converter built specifically to convert purely value type bound properties can always be shadowed
if (shadowedType.GetCustomAttribute<DefaultValueTypePropertyValueConverterAttribute>(false) is not null)
{
return true;
}
return DefaultConverters.TryGetValue(shadowing, out Type[]? types) && types.Contains(shadowedType);
}
}

View File

@@ -0,0 +1,24 @@
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
[DefaultValueTypePropertyValueConverter]
public class BigintValueTypeConverter : ValueTypePropertyValueConverterBase
{
protected override string[] SupportedValueTypes => new[] { ValueTypes.Bigint };
public BigintValueTypeConverter(PropertyEditorCollection propertyEditors)
: base(propertyEditors)
{
}
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof(long);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
=> source.TryConvertTo<long>().Result;
}

View File

@@ -17,26 +17,7 @@ public class DatePickerValueConverter : PropertyValueConverterBase
=> PropertyCacheLevel.Element;
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
{
if (source == null)
{
return DateTime.MinValue;
}
// in XML a DateTime is: string - format "yyyy-MM-ddTHH:mm:ss"
// Actually, not always sometimes it is formatted in UTC style with 'Z' suffixed on the end but that is due to this bug:
// http://issues.umbraco.org/issue/U4-4145, http://issues.umbraco.org/issue/U4-3894
// We should just be using TryConvertTo instead.
if (source is string sourceString)
{
Attempt<DateTime> attempt = sourceString.TryConvertTo<DateTime>();
return attempt.Success == false ? DateTime.MinValue : attempt.Result;
}
// in the database a DateTime is: DateTime
// default value is: DateTime.MinValue
return source is DateTime ? source : DateTime.MinValue;
}
=> ParseDateTimeValue(source);
// default ConvertSourceToObject just returns source ie a DateTime value
[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")]
@@ -55,4 +36,26 @@ public class DatePickerValueConverter : PropertyValueConverterBase
return XmlConvert.ToString((DateTime)inter, XmlDateTimeSerializationMode.Unspecified);
}
internal static DateTime ParseDateTimeValue(object? source)
{
if (source == null)
{
return DateTime.MinValue;
}
// in XML a DateTime is: string - format "yyyy-MM-ddTHH:mm:ss"
// Actually, not always sometimes it is formatted in UTC style with 'Z' suffixed on the end but that is due to this bug:
// http://issues.umbraco.org/issue/U4-4145, http://issues.umbraco.org/issue/U4-3894
// We should just be using TryConvertTo instead.
if (source is string sourceString)
{
Attempt<DateTime> attempt = sourceString.TryConvertTo<DateTime>();
return attempt.Success == false ? DateTime.MinValue : attempt.Result;
}
// in the database a DateTime is: DateTime
// default value is: DateTime.MinValue
return source is DateTime dateTimeValue ? dateTimeValue : DateTime.MinValue;
}
}

View File

@@ -0,0 +1,23 @@
using Umbraco.Cms.Core.Models.PublishedContent;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
[DefaultValueTypePropertyValueConverter]
public class DateTimeValueTypeConverter : ValueTypePropertyValueConverterBase
{
protected override string[] SupportedValueTypes => new[] { ValueTypes.Date, ValueTypes.DateTime };
public DateTimeValueTypeConverter(PropertyEditorCollection propertyEditors)
: base(propertyEditors)
{
}
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof(DateTime);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
=> DatePickerValueConverter.ParseDateTimeValue(source); // reuse the value conversion from the default "Umbraco.DateTime" value converter
}

View File

@@ -16,6 +16,9 @@ public class DecimalValueConverter : PropertyValueConverterBase
=> PropertyCacheLevel.Element;
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
=> ParseDecimalValue(source);
internal static decimal ParseDecimalValue(object? source)
{
if (source == null)
{
@@ -23,9 +26,9 @@ public class DecimalValueConverter : PropertyValueConverterBase
}
// is it already a decimal?
if (source is decimal)
if (source is decimal sourceDecimal)
{
return source;
return sourceDecimal;
}
// is it a double?

View File

@@ -0,0 +1,23 @@
using Umbraco.Cms.Core.Models.PublishedContent;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
[DefaultValueTypePropertyValueConverter]
public class DecimalValueTypeConverter : ValueTypePropertyValueConverterBase
{
protected override string[] SupportedValueTypes => new[] { ValueTypes.Decimal };
public DecimalValueTypeConverter(PropertyEditorCollection propertyEditors)
: base(propertyEditors)
{
}
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof(decimal);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
=> DecimalValueConverter.ParseDecimalValue(source); // reuse the value conversion from the default "Umbraco.Decimal" value converter
}

View File

@@ -0,0 +1,24 @@
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
[DefaultValueTypePropertyValueConverter]
public class IntegerValueTypeConverter : ValueTypePropertyValueConverterBase
{
protected override string[] SupportedValueTypes => new[] { ValueTypes.Integer };
public IntegerValueTypeConverter(PropertyEditorCollection propertyEditors)
: base(propertyEditors)
{
}
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof(int);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
=> source.TryConvertTo<int>().Result;
}

View File

@@ -0,0 +1,23 @@
using Umbraco.Cms.Core.Models.PublishedContent;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
[DefaultValueTypePropertyValueConverter]
public class TextStringValueTypeConverter : ValueTypePropertyValueConverterBase
{
protected override string[] SupportedValueTypes => new[] { ValueTypes.Text, ValueTypes.String };
public TextStringValueTypeConverter(PropertyEditorCollection propertyEditors)
: base(propertyEditors)
{
}
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof(string);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
=> source as string;
}

View File

@@ -0,0 +1,23 @@
using Umbraco.Cms.Core.Models.PublishedContent;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
[DefaultValueTypePropertyValueConverter]
public class TimeValueTypeConverter : ValueTypePropertyValueConverterBase
{
protected override string[] SupportedValueTypes => new[] { ValueTypes.Time };
public TimeValueTypeConverter(PropertyEditorCollection propertyEditors)
: base(propertyEditors)
{
}
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof(TimeSpan);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
=> source is DateTime dateTimeValue ? dateTimeValue.ToUniversalTime().TimeOfDay : null;
}

View File

@@ -0,0 +1,18 @@
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
public abstract class ValueTypePropertyValueConverterBase : PropertyValueConverterBase
{
private readonly PropertyEditorCollection _propertyEditors;
protected abstract string[] SupportedValueTypes { get; }
protected ValueTypePropertyValueConverterBase(PropertyEditorCollection propertyEditors)
=> _propertyEditors = propertyEditors;
public override bool IsConverter(IPublishedPropertyType propertyType)
=> _propertyEditors.TryGet(propertyType.EditorAlias, out IDataEditor? editor)
&& SupportedValueTypes.InvariantContains(editor.GetValueEditor().ValueType);
}

View File

@@ -0,0 +1,51 @@
using System.Xml.Linq;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors.DeliveryApi;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
[DefaultValueTypePropertyValueConverter]
public class XmlValueTypeConverter : ValueTypePropertyValueConverterBase, IDeliveryApiPropertyValueConverter
{
protected override string[] SupportedValueTypes => new[] { ValueTypes.Xml };
public XmlValueTypeConverter(PropertyEditorCollection propertyEditors)
: base(propertyEditors)
{
}
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof(XDocument);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
{
if (source is not string stringValue)
{
return null;
}
try
{
return XDocument.Parse(stringValue);
}
catch
{
return null;
}
}
public PropertyCacheLevel GetDeliveryApiPropertyCacheLevel(IPublishedPropertyType propertyType)
=> GetPropertyCacheLevel(propertyType);
public Type GetDeliveryApiPropertyValueType(IPublishedPropertyType propertyType)
=> typeof(string);
// System.Text.Json does not appreciate serializing XDocument because of parent/child node references. Let's settle for outputting the raw XML as a string, then.
public object? ConvertIntermediateToDeliveryApiObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview, bool expanding)
=> inter is XDocument xDocumentValue
? xDocumentValue.ToString(SaveOptions.DisableFormatting)
: null;
}

View File

@@ -1,11 +1,7 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Moq;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
@@ -116,18 +112,12 @@ public class PropertyEditorValueConverterTests
}
[TestCase("1", 1)]
[TestCase("1", 1)]
[TestCase("0", 0)]
[TestCase("0", 0)]
[TestCase(null, 0)]
[TestCase(null, 0)]
[TestCase("-1", -1)]
[TestCase("-1", -1)]
[TestCase("1.65", 1.65)]
[TestCase("1.65", 1.65)]
[TestCase("-1.65", -1.65)]
[TestCase("-1.65", -1.65)]
public void CanConvertDecimalAliasPropertyEditor(object value, double expected)
public void CanConvertDecimalAliasPropertyEditor(object value, decimal expected)
{
var converter = new DecimalValueConverter();
var inter = converter.ConvertSourceToIntermediate(null, null, value, false);
@@ -136,18 +126,19 @@ public class PropertyEditorValueConverterTests
Assert.AreEqual(expected, result);
}
[Test]
public void CanConvertManifestBasedPropertyWithValueTypeJson()
[TestCase("100", 100)]
[TestCase("0", 0)]
[TestCase(null, 0)]
[TestCase("-100", -100)]
[TestCase("1.65", 2)]
[TestCase("-1.65", -2)]
[TestCase("something something", 0)]
public void CanConvertIntegerAliasPropertyEditor(object value, int expected)
{
var valueEditor = Mock.Of<IDataValueEditor>(x => x.ValueType == ValueTypes.Json);
var dataEditor = Mock.Of<IDataEditor>(x => x.GetValueEditor() == valueEditor);
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => new[] { dataEditor }));
var propertyType = Mock.Of<IPublishedPropertyType>(x => x.EditorAlias == "My.Custom.Json");
var converter = new IntegerValueConverter();
var inter = converter.ConvertSourceToIntermediate(null, null, value, false);
var result = converter.ConvertIntermediateToObject(null, null, PropertyCacheLevel.Unknown, inter, false);
var valueConverter = new JsonValueConverter(propertyEditors, Mock.Of<ILogger<JsonValueConverter>>());
var inter = valueConverter.ConvertSourceToIntermediate(Mock.Of<IPublishedElement>(), propertyType, "{\"message\": \"Hello, JSON\"}", false);
var result = valueConverter.ConvertIntermediateToObject(Mock.Of<IPublishedElement>(), propertyType, PropertyCacheLevel.Element, inter, false) as JObject;
Assert.IsNotNull(result);
Assert.AreEqual("Hello, JSON", result["message"]!.Value<string>());
Assert.AreEqual(expected, result);
}
}

View File

@@ -0,0 +1,221 @@
using System.Xml.Linq;
using Microsoft.Extensions.Logging;
using Moq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
[TestFixture]
public class PropertyEditorValueTypeConverterTests
{
[TestCase("2023-09-26 13:14:15", true)]
[TestCase("2023-09-26T13:14:15", true)]
[TestCase("2023-09-26T00:00:00", true)]
[TestCase("2023-09-26", true)]
[TestCase("", false)]
[TestCase("Hello, world!", false)]
[TestCase(123456, false)]
[TestCase(null, false)]
public void CanConvertDateValueTypePropertyEditor(object? date, bool expectedSuccess)
{
var expectedResult = expectedSuccess ? DateTime.Parse((date as string)!) : DateTime.MinValue;
var supportedValueTypes = new[] { ValueTypes.DateTime, ValueTypes.Date };
foreach (var valueType in supportedValueTypes)
{
var converter = new DateTimeValueTypeConverter(ValueTypePropertyEditorCollection(valueType));
var propertyType = PropertyType();
Assert.IsTrue(converter.IsConverter(propertyType));
var result = converter.ConvertSourceToIntermediate(null, propertyType, date, false);
Assert.AreEqual(expectedResult, result);
}
}
[TestCase("1", 1)]
[TestCase("0", 0)]
[TestCase(null, 0)]
[TestCase("", 0)]
[TestCase("Hello, world!", 0)]
[TestCase("-1", -1)]
[TestCase("1.65", 1.65)]
[TestCase("-1.65", -1.65)]
public void CanConvertDecimalValueTypePropertyEditor(object value, decimal expected)
{
var propertyEditors = ValueTypePropertyEditorCollection(ValueTypes.Decimal);
var propertyType = PropertyType();
var converter = new DecimalValueTypeConverter(propertyEditors);
var inter = converter.ConvertSourceToIntermediate(Mock.Of<IPublishedElement>(), propertyType, value, false);
Assert.IsTrue(converter.IsConverter(propertyType));
var result = converter.ConvertIntermediateToObject(Mock.Of<IPublishedElement>(), propertyType, PropertyCacheLevel.Element, inter, false);
Assert.IsTrue(result is decimal);
Assert.AreEqual(expected, result);
}
[TestCase("100", 100)]
[TestCase("0", 0)]
[TestCase(null, 0)]
[TestCase("", 0)]
[TestCase("Hello, world!", 0)]
[TestCase("-100", -100)]
[TestCase("1.65", 2)]
[TestCase("-1.65", -2)]
public void CanConvertIntegerValueTypePropertyEditor(object value, int expected)
{
var propertyEditors = ValueTypePropertyEditorCollection(ValueTypes.Integer);
var propertyType = PropertyType();
var converter = new IntegerValueTypeConverter(propertyEditors);
var inter = converter.ConvertSourceToIntermediate(Mock.Of<IPublishedElement>(), propertyType, value, false);
Assert.IsTrue(converter.IsConverter(propertyType));
var result = converter.ConvertIntermediateToObject(Mock.Of<IPublishedElement>(), propertyType, PropertyCacheLevel.Element, inter, false);
Assert.IsTrue(result is int);
Assert.AreEqual(expected, result);
}
[TestCase("100", 100)]
[TestCase("0", 0)]
[TestCase(null, 0)]
[TestCase("", 0)]
[TestCase("Hello, world!", 0)]
[TestCase("-100", -100)]
[TestCase("1.65", 2)]
[TestCase("-1.65", -2)]
public void CanConvertBigintValueTypePropertyEditor(object value, long expected)
{
var propertyEditors = ValueTypePropertyEditorCollection(ValueTypes.Bigint);
var propertyType = PropertyType();
var converter = new BigintValueTypeConverter(propertyEditors);
var inter = converter.ConvertSourceToIntermediate(Mock.Of<IPublishedElement>(), propertyType, value, false);
Assert.IsTrue(converter.IsConverter(propertyType));
var result = converter.ConvertIntermediateToObject(Mock.Of<IPublishedElement>(), propertyType, PropertyCacheLevel.Element, inter, false);
Assert.IsTrue(result is long);
Assert.AreEqual(expected, result);
}
[TestCase("100", "100")]
[TestCase("0", "0")]
[TestCase(null, null)]
[TestCase("", "")]
[TestCase("Hello, world!", "Hello, world!")]
[TestCase(-100, null)]
[TestCase(1.65, null)]
public void CanConvertTextAndStringValueTypePropertyEditor(object? value, string? expected)
{
var scenarios = new[] { ValueTypes.Text, ValueTypes.String };
foreach (var scenario in scenarios)
{
var propertyEditors = ValueTypePropertyEditorCollection(scenario);
var propertyType = PropertyType();
var converter = new TextStringValueTypeConverter(propertyEditors);
var inter = converter.ConvertSourceToIntermediate(Mock.Of<IPublishedElement>(), propertyType, value, false);
Assert.IsTrue(converter.IsConverter(propertyType));
var result = converter.ConvertIntermediateToObject(Mock.Of<IPublishedElement>(), propertyType, PropertyCacheLevel.Element, inter, false);
Assert.AreEqual(expected, result);
}
}
[TestCase("2023-01-01T03:04:00Z","03:04:00")]
[TestCase("2023-01-01T13:14:00Z", "13:14:00")]
[TestCase("2023-01-01T13:14:15Z", "13:14:15")]
[TestCase("2023-01-01T13:14:15.678Z", "13:14:15.678")]
[TestCase("", null)]
[TestCase("Hello, world!", null)]
[TestCase(123456, null)]
[TestCase(null, null)]
public void CanConvertTimeValueTypePropertyEditor(object? value, object? expectedTime)
{
var sourceValue = expectedTime is not null ? DateTime.Parse((value as string)!) : value;
TimeSpan? expectedResult = expectedTime is not null ? TimeSpan.Parse((expectedTime as string)!) : null;
var propertyEditors = ValueTypePropertyEditorCollection(ValueTypes.Time);
var converter = new TimeValueTypeConverter(propertyEditors);
var propertyType = PropertyType();
Assert.IsTrue(converter.IsConverter(propertyType));
var result = converter.ConvertSourceToIntermediate(null, propertyType, sourceValue, false);
Assert.AreEqual(expectedResult, result);
}
[TestCase("<root>test</root>", true)]
[TestCase("<root><child>child 1</child><child>child 2</child></root>", true)]
[TestCase("<root><child>malformed XML<child><root>", false)]
[TestCase("", false)]
[TestCase("Hello, world!", false)]
[TestCase(123456, false)]
[TestCase(null, false)]
public void CanConvertXmlValueTypePropertyEditor(object? value, bool expectsSuccess)
{
var propertyEditors = ValueTypePropertyEditorCollection(ValueTypes.Xml);
var converter = new XmlValueTypeConverter(propertyEditors);
var propertyType = PropertyType();
Assert.IsTrue(converter.IsConverter(propertyType));
var result = converter.ConvertSourceToIntermediate(null, propertyType, value, false) as XDocument;
if (expectsSuccess)
{
Assert.IsNotNull(result);
Assert.AreEqual(value, result.ToString(SaveOptions.DisableFormatting));
}
else
{
Assert.IsNull(result);
}
}
[TestCase("{\"message\":\"Hello, JSON\"}", true)]
[TestCase("{\"nested\":{\"message\":\"Hello, Nested\"}}", true)]
[TestCase("{\"nested\":{\"invalid JSON", false)]
[TestCase("", false)]
[TestCase("Hello, world!", false)]
[TestCase(123456, false)]
[TestCase(null, false)]
public void CanConvertJsonValueTypePropertyEditor(object? source, bool expectsSuccess)
{
var propertyEditors = ValueTypePropertyEditorCollection(ValueTypes.Json);
var converter = new JsonValueConverter(propertyEditors, Mock.Of<ILogger<JsonValueConverter>>());
var propertyType = PropertyType();
Assert.IsTrue(converter.IsConverter(propertyType));
var result = converter.ConvertSourceToIntermediate(null, propertyType, source, false) as JToken;
if (expectsSuccess)
{
Assert.IsNotNull(result);
Assert.AreEqual(source, result.ToString(Formatting.None));
}
else
{
Assert.IsNull(result);
}
}
private static PropertyEditorCollection ValueTypePropertyEditorCollection(string valueType)
{
var valueEditor = Mock.Of<IDataValueEditor>(x => x.ValueType == valueType);
var dataEditor = Mock.Of<IDataEditor>(x => x.GetValueEditor() == valueEditor && x.Alias == "My.Custom.Alias" && x.Type == EditorType.PropertyValue);
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => new[] { dataEditor }));
return propertyEditors;
}
private static IPublishedPropertyType PropertyType() => Mock.Of<IPublishedPropertyType>(x => x.EditorAlias == "My.Custom.Alias");
}