Server side validation for property editors (multiple text strings) (#18581)
* Server-side validation for multiple text strings property editor. * Added unit tests for block list min/max server validation. * Add danish translations * Add test showing issue * Fix issue --------- Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
This commit is contained in:
@@ -120,6 +120,9 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="invalidStartNode">Valgt indhold kommer fra en ugyldig mappe.</key>
|
||||
<key alias="outOfRangeMinimum">Værdien %0% er mindre end det tilladte minimum af %1%.</key>
|
||||
<key alias="outOfRangeMaximum">Værdien %0% er større end det tilladte maksimum af %1%.</key>
|
||||
<key alias="outOfRangeSingleItemMinimum">Den ene enhed givet er mindre end det tilladte minimum af %1%.</key>
|
||||
<key alias="outOfRangeMultipleItemsMinimum">De %0% enheder givet er mindre end det tilladte minimum af %1%.</key>
|
||||
<key alias="outOfRangeMultipleItemsMaximum">De %0% enheder givet er større end det tilladte minimum af %1%.</key>
|
||||
<key alias="invalidStep">Værdien %0% passer ikke med den konfigureret trin værdi af %1% og mindste værdi af %2%.</key>
|
||||
<key alias="unexpectedRange">Værdien %0% forventes ikke at indeholde et spænd.</key>
|
||||
<key alias="invalidRange">Værdien %0% forventes at have en værdi der er større end fra værdien.</key>
|
||||
|
||||
@@ -389,6 +389,9 @@
|
||||
<key alias="duplicateUsername">Username '%0%' is already taken</key>
|
||||
<key alias="outOfRangeMinimum">The value %0% is less than the allowed minimum value of %1%</key>
|
||||
<key alias="outOfRangeMaximum">The value %0% is greater than the allowed maximum value of %1%</key>
|
||||
<key alias="outOfRangeSingleItemMinimum">The 1 item provided is less than the allowed minimum of %1%</key>
|
||||
<key alias="outOfRangeMultipleItemsMinimum">The %0% items provided are less than the allowed minimum of %1%</key>
|
||||
<key alias="outOfRangeMultipleItemsMaximum">The %0% items provided are greater than the allowed maximum of %1%</key>
|
||||
<key alias="invalidStep">The value %0% does not correspond with the configured step value of %1% and minimum value of %2%</key>
|
||||
<key alias="unexpectedRange">The value %0% is not expected to contain a range</key>
|
||||
<key alias="invalidRange">The value %0% is not expected to have a to value less than the from value</key>
|
||||
|
||||
@@ -387,6 +387,9 @@
|
||||
<key alias="duplicateUsername">Username '%0%' is already taken</key>
|
||||
<key alias="outOfRangeMinimum">The value %0% is less than the allowed minimum value of %1%</key>
|
||||
<key alias="outOfRangeMaximum">The value %0% is greater than the allowed maximum value of %1%</key>
|
||||
<key alias="outOfRangeSingleItemMinimum">The 1 item provided is less than the allowed minimum of %1%</key>
|
||||
<key alias="outOfRangeMultipleItemsMinimum">The %0% items provided are less than the allowed minimum of %1%</key>
|
||||
<key alias="outOfRangeMultipleItemsMaximum">The %0% items provided are greater than the allowed maximum of %1%</key>
|
||||
<key alias="invalidStep">The value %0% does not correspond with the configured step value of %1% and minimum value of %2%</key>
|
||||
<key alias="unexpectedRange">The value %0% is not expected to contain a range</key>
|
||||
<key alias="invalidRange">The value %0% is not expected to have a to value less than the from value</key>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
@@ -16,12 +16,22 @@ internal abstract class BlockEditorMinMaxValidatorBase<TValue, TLayout> : IValue
|
||||
where TValue : BlockValue<TLayout>, new()
|
||||
where TLayout : class, IBlockLayoutItem, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockEditorMinMaxValidatorBase{TValue, TLayout}"/> class.
|
||||
/// </summary>
|
||||
protected BlockEditorMinMaxValidatorBase(ILocalizedTextService textService) => TextService = textService;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ILocalizedTextService"/>
|
||||
/// </summary>
|
||||
protected ILocalizedTextService TextService { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext);
|
||||
|
||||
/// <summary>
|
||||
/// Validates the number of blocks are within the configured minimum and maximum values.
|
||||
/// </summary>
|
||||
protected IEnumerable<ValidationResult> ValidateNumberOfBlocks(BlockEditorData<TValue, TLayout>? blockEditorData, int? min, int? max)
|
||||
{
|
||||
var numberOfBlocks = blockEditorData?.Layout?.Count() ?? 0;
|
||||
@@ -35,8 +45,8 @@ internal abstract class BlockEditorMinMaxValidatorBase<TValue, TLayout> : IValue
|
||||
TextService.Localize(
|
||||
"validation",
|
||||
"entriesShort",
|
||||
new[] { min.ToString(), (min - numberOfBlocks).ToString(), }),
|
||||
new[] { "minCount" });
|
||||
[min.ToString(), (min - numberOfBlocks).ToString(),]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +56,8 @@ internal abstract class BlockEditorMinMaxValidatorBase<TValue, TLayout> : IValue
|
||||
TextService.Localize(
|
||||
"validation",
|
||||
"entriesExceed",
|
||||
new[] { max.ToString(), (numberOfBlocks - max).ToString(), }),
|
||||
new[] { "maxCount" });
|
||||
[max.ToString(), (numberOfBlocks - max).ToString(),]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using Umbraco.Extensions;
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a block list property editor.
|
||||
/// Represents a block list property editor.
|
||||
/// </summary>
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.BlockList,
|
||||
@@ -19,6 +19,9 @@ public class BlockListPropertyEditor : BlockListPropertyEditorBase
|
||||
{
|
||||
private readonly IIOHelper _ioHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockListPropertyEditor"/> class.
|
||||
/// </summary>
|
||||
public BlockListPropertyEditor(
|
||||
IDataValueEditorFactory dataValueEditorFactory,
|
||||
IIOHelper ioHelper,
|
||||
@@ -27,6 +30,9 @@ public class BlockListPropertyEditor : BlockListPropertyEditorBase
|
||||
: base(dataValueEditorFactory, blockValuePropertyIndexValueFactory, jsonSerializer)
|
||||
=> _ioHelper = ioHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockListPropertyEditor"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Use constructor that doesn't take PropertyEditorCollection, scheduled for removal in V15")]
|
||||
public BlockListPropertyEditor(
|
||||
IDataValueEditorFactory dataValueEditorFactory,
|
||||
@@ -38,6 +44,7 @@ public class BlockListPropertyEditor : BlockListPropertyEditorBase
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool SupportsConfigurableElements => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -50,6 +57,7 @@ public class BlockListPropertyEditor : BlockListPropertyEditorBase
|
||||
return valueEditor.MergePartialPropertyValueForCulture(sourceValue, targetValue, culture);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object? MergeVariantInvariantPropertyValue(
|
||||
object? sourceValue,
|
||||
object? targetValue,
|
||||
@@ -60,10 +68,7 @@ public class BlockListPropertyEditor : BlockListPropertyEditorBase
|
||||
return valueEditor.MergeVariantInvariantPropertyValue(sourceValue, targetValue, canUpdateInvariantData, allowedCultures);
|
||||
}
|
||||
|
||||
#region Pre Value Editor
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IConfigurationEditor CreateConfigurationEditor() =>
|
||||
new BlockListConfigurationEditor(_ioHelper);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -24,12 +24,18 @@ public abstract class BlockListPropertyEditorBase : DataEditor
|
||||
private readonly IBlockValuePropertyIndexValueFactory _blockValuePropertyIndexValueFactory;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockListPropertyEditorBase"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 15.")]
|
||||
protected BlockListPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory, IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory)
|
||||
: this(dataValueEditorFactory,blockValuePropertyIndexValueFactory, StaticServiceProvider.Instance.GetRequiredService<IJsonSerializer>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockListPropertyEditorBase"/> class.
|
||||
/// </summary>
|
||||
protected BlockListPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory, IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory, IJsonSerializer jsonSerializer)
|
||||
: base(dataValueEditorFactory)
|
||||
{
|
||||
@@ -38,21 +44,27 @@ public abstract class BlockListPropertyEditorBase : DataEditor
|
||||
SupportsReadOnly = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IPropertyIndexValueFactory PropertyIndexValueFactory => _blockValuePropertyIndexValueFactory;
|
||||
|
||||
#region Value Editor
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="BlockEditorDataConverter"/> for use with the block list editor property value editor.
|
||||
/// Instantiates a new <see cref="BlockEditorDataConverter{BlockListValue, BlockListLayoutItem}"/> for use with the block list editor property value editor.
|
||||
/// </summary>
|
||||
/// <returns>A new instance of <see cref="BlockListEditorDataConverter"/>.</returns>
|
||||
protected virtual BlockEditorDataConverter<BlockListValue, BlockListLayoutItem> CreateBlockEditorDataConverter() => new BlockListEditorDataConverter(_jsonSerializer);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IDataValueEditor CreateValueEditor() =>
|
||||
DataValueEditorFactory.Create<BlockListEditorPropertyValueEditor>(Attribute!, CreateBlockEditorDataConverter());
|
||||
|
||||
/// <summary>
|
||||
/// Defines the value editor for the block list property editors.
|
||||
/// </summary>
|
||||
internal class BlockListEditorPropertyValueEditor : BlockEditorPropertyValueEditor<BlockListValue, BlockListLayoutItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockListEditorPropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
public BlockListEditorPropertyValueEditor(
|
||||
DataEditorAttribute attribute,
|
||||
BlockEditorDataConverter<BlockListValue, BlockListLayoutItem> blockEditorDataConverter,
|
||||
@@ -78,8 +90,12 @@ public abstract class BlockListPropertyEditorBase : DataEditor
|
||||
/// <inheritdoc />
|
||||
public override IValueRequiredValidator RequiredValidator => new BlockListValueRequiredValidator(JsonSerializer);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override BlockListValue CreateWithLayout(IEnumerable<BlockListLayoutItem> layout) => new(layout);
|
||||
|
||||
/// <summary>
|
||||
/// Validates the min/max configuration for block list property editors.
|
||||
/// </summary>
|
||||
private class MinMaxValidator : BlockEditorMinMaxValidatorBase<BlockListValue, BlockListLayoutItem>
|
||||
{
|
||||
private readonly BlockEditorValues<BlockListValue, BlockListLayoutItem> _blockEditorValues;
|
||||
@@ -104,12 +120,11 @@ public abstract class BlockListPropertyEditorBase : DataEditor
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<Guid> ConfiguredElementTypeKeys()
|
||||
{
|
||||
var configuration = ConfigurationObject as BlockListConfiguration;
|
||||
return configuration?.Blocks.SelectMany(ConfiguredElementTypeKeys) ?? Enumerable.Empty<Guid>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -6,14 +6,17 @@ using Umbraco.Cms.Core.Exceptions;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a multiple text string property editor.
|
||||
/// Represents a multiple text string property editor.
|
||||
/// </summary>
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.MultipleTextstring,
|
||||
@@ -24,7 +27,7 @@ public class MultipleTextStringPropertyEditor : DataEditor
|
||||
private readonly IIOHelper _ioHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultipleTextStringPropertyEditor" /> class.
|
||||
/// Initializes a new instance of the <see cref="MultipleTextStringPropertyEditor" /> class.
|
||||
/// </summary>
|
||||
public MultipleTextStringPropertyEditor(IIOHelper ioHelper, IDataValueEditorFactory dataValueEditorFactory)
|
||||
: base(dataValueEditorFactory)
|
||||
@@ -42,37 +45,42 @@ public class MultipleTextStringPropertyEditor : DataEditor
|
||||
new MultipleTextStringConfigurationEditor(_ioHelper);
|
||||
|
||||
/// <summary>
|
||||
/// Custom value editor so we can format the value for the editor and the database
|
||||
/// Defines the value editor for the multiple text string property editor.
|
||||
/// </summary>
|
||||
internal class MultipleTextStringPropertyValueEditor : DataValueEditor
|
||||
{
|
||||
private static readonly string NewLine = "\n";
|
||||
private static readonly string[] NewLineDelimiters = { "\r\n", "\r", "\n" };
|
||||
private static readonly string _newLine = "\n";
|
||||
private static readonly string[] _newLineDelimiters = { "\r\n", "\r", "\n" };
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultipleTextStringPropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
public MultipleTextStringPropertyValueEditor(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute)
|
||||
DataEditorAttribute attribute,
|
||||
ILocalizedTextService localizedTextService)
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
{
|
||||
Validators.AddRange(new MinMaxValidator(localizedTextService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom FormatValidator is used as for multiple text strings, each string should individually be checked
|
||||
/// against the configured regular expression, rather than the JSON representing all the strings as a whole.
|
||||
/// A custom <see href="IValueFormatValidator" /> is used as for multiple text strings, each string should individually
|
||||
/// be checked against the configured regular expression, rather than the JSON representing all the strings as a whole.
|
||||
/// </summary>
|
||||
public override IValueFormatValidator FormatValidator => new MultipleTextStringFormatValidator();
|
||||
|
||||
/// <summary>
|
||||
/// The value passed in from the editor will be an array of simple objects so we'll need to parse them to get the
|
||||
/// string
|
||||
/// The value passed in from the editor will be an array of simple objects so we'll need to parse them to get the
|
||||
/// string.
|
||||
/// </summary>
|
||||
/// <param name="editorValue"></param>
|
||||
/// <param name="currentValue"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// We will also check the pre-values here, if there are more items than what is allowed we'll just trim the end
|
||||
/// We will also check the pre-values here, if there are more items than what is allowed we'll just trim the end.
|
||||
/// </remarks>
|
||||
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
|
||||
{
|
||||
@@ -93,30 +101,35 @@ public class MultipleTextStringPropertyEditor : DataEditor
|
||||
// only allow the max if over 0
|
||||
if (max > 0)
|
||||
{
|
||||
return string.Join(NewLine, value.Take(max));
|
||||
return string.Join(_newLine, value.Take(max));
|
||||
}
|
||||
|
||||
return string.Join(NewLine, value);
|
||||
return string.Join(_newLine, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object ToEditor(IProperty property, string? culture = null, string? segment = null)
|
||||
{
|
||||
var value = property.GetValue(culture, segment);
|
||||
|
||||
// The legacy property editor saved this data as new line delimited! strange but we have to maintain that.
|
||||
return value is string stringValue
|
||||
? stringValue.Split(NewLineDelimiters, StringSplitOptions.None)
|
||||
? stringValue.Split(_newLineDelimiters, StringSplitOptions.None)
|
||||
: Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom <see href="IValueFormatValidator" /> to check each string against the configured format.
|
||||
/// </summary>
|
||||
internal class MultipleTextStringFormatValidator : IValueFormatValidator
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> ValidateFormat(object? value, string valueType, string format)
|
||||
{
|
||||
if (value is not IEnumerable<string> textStrings)
|
||||
{
|
||||
return Enumerable.Empty<ValidationResult>();
|
||||
return [];
|
||||
}
|
||||
|
||||
var textStringValidator = new RegexValidator();
|
||||
@@ -129,7 +142,60 @@ public class MultipleTextStringPropertyEditor : DataEditor
|
||||
}
|
||||
}
|
||||
|
||||
return Enumerable.Empty<ValidationResult>();
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the min/max configuration for the multiple text strings property editor.
|
||||
/// </summary>
|
||||
internal class MinMaxValidator : IValueValidator
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MinMaxValidator"/> class.
|
||||
/// </summary>
|
||||
public MinMaxValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext)
|
||||
{
|
||||
if (dataTypeConfiguration is not MultipleTextStringConfiguration multipleTextStringConfiguration)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// If we have a null value, treat as an empty collection for minimum number validation.
|
||||
if (value is not IEnumerable<string> stringValues)
|
||||
{
|
||||
stringValues = [];
|
||||
}
|
||||
|
||||
var stringCount = stringValues.Count();
|
||||
|
||||
if (stringCount < multipleTextStringConfiguration.Min)
|
||||
{
|
||||
if (stringCount == 1)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "outOfRangeSingleItemMinimum", [multipleTextStringConfiguration.Min.ToString()]),
|
||||
["value"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "outOfRangeMultipleItemsMinimum", [stringCount.ToString(), multipleTextStringConfiguration.Min.ToString()]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
|
||||
if (multipleTextStringConfiguration.Max > 0 && stringCount > multipleTextStringConfiguration.Max)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "outOfRangeMultipleItemsMaximum", [stringCount.ToString(), multipleTextStringConfiguration.Max.ToString()]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Cache.PropertyEditors;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models.Blocks;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.Serialization;
|
||||
using static Umbraco.Cms.Core.PropertyEditors.BlockListPropertyEditorBase;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
|
||||
|
||||
[TestFixture]
|
||||
public class BlockListEditorPropertyValueEditorTests
|
||||
{
|
||||
[Test]
|
||||
public void Validates_Null_As_Below_Configured_Min()
|
||||
{
|
||||
var editor = CreateValueEditor();
|
||||
var result = editor.Validate(null, false, null, PropertyValidationContext.Empty());
|
||||
Assert.AreEqual(1, result.Count());
|
||||
|
||||
var validationResult = result.First();
|
||||
Assert.AreEqual($"validation_entriesShort", validationResult.ErrorMessage);
|
||||
}
|
||||
|
||||
[TestCase(0, false)]
|
||||
[TestCase(1, false)]
|
||||
[TestCase(2, true)]
|
||||
[TestCase(3, true)]
|
||||
public void Validates_Number_Of_Items_Is_Greater_Than_Or_Equal_To_Configured_Min(int numberOfBlocks, bool expectedSuccess)
|
||||
{
|
||||
var value = CreateBlocksJson(numberOfBlocks);
|
||||
var editor = CreateValueEditor();
|
||||
var result = editor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
if (expectedSuccess)
|
||||
{
|
||||
Assert.IsEmpty(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(1, result.Count());
|
||||
|
||||
var validationResult = result.First();
|
||||
Assert.AreEqual("validation_entriesShort", validationResult.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(3, true)]
|
||||
[TestCase(4, true)]
|
||||
[TestCase(5, false)]
|
||||
public void Validates_Number_Of_Items_Is_Less_Than_Or_Equal_To_Configured_Max(int numberOfBlocks, bool expectedSuccess)
|
||||
{
|
||||
var value = CreateBlocksJson(numberOfBlocks);
|
||||
var editor = CreateValueEditor();
|
||||
var result = editor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
if (expectedSuccess)
|
||||
{
|
||||
Assert.IsEmpty(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(1, result.Count());
|
||||
|
||||
var validationResult = result.First();
|
||||
Assert.AreEqual("validation_entriesExceed", validationResult.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonObject CreateBlocksJson(int numberOfBlocks)
|
||||
{
|
||||
var layoutItems = new JsonArray();
|
||||
var contentData = new JsonArray();
|
||||
for (int i = 0; i < numberOfBlocks; i++)
|
||||
{
|
||||
layoutItems.Add(CreateLayoutBlockJson());
|
||||
contentData.Add(CreateContentDataBlockJson());
|
||||
}
|
||||
|
||||
return new JsonObject
|
||||
{
|
||||
{
|
||||
"layout", new JsonObject
|
||||
{
|
||||
{ "Umbraco.BlockList", layoutItems },
|
||||
}
|
||||
},
|
||||
{ "contentData", contentData },
|
||||
};
|
||||
}
|
||||
|
||||
private static JsonObject CreateLayoutBlockJson() =>
|
||||
new()
|
||||
{
|
||||
{ "$type", "BlockListLayoutItem" },
|
||||
{ "contentKey", Guid.NewGuid() },
|
||||
};
|
||||
|
||||
private static JsonObject CreateContentDataBlockJson() =>
|
||||
new()
|
||||
{
|
||||
{ "contentTypeKey", Guid.Parse("01935a73-c86b-4521-9dcb-ad7cea402215") },
|
||||
{ "key", Guid.NewGuid() },
|
||||
{
|
||||
"values",
|
||||
new JsonArray
|
||||
{
|
||||
new JsonObject
|
||||
{
|
||||
{ "editorAlias", "Umbraco.TextBox" },
|
||||
{ "alias", "message" },
|
||||
{ "value", "Hello" },
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static BlockListEditorPropertyValueEditor CreateValueEditor()
|
||||
{
|
||||
var localizedTextServiceMock = new Mock<ILocalizedTextService>();
|
||||
localizedTextServiceMock.Setup(x => x.Localize(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<CultureInfo>(),
|
||||
It.IsAny<IDictionary<string, string>>()))
|
||||
.Returns((string key, string alias, CultureInfo culture, IDictionary<string, string> args) => $"{key}_{alias}");
|
||||
|
||||
var jsonSerializer = new SystemTextJsonSerializer();
|
||||
var languageService = Mock.Of<ILanguageService>();
|
||||
|
||||
return new BlockListEditorPropertyValueEditor(
|
||||
new DataEditorAttribute("alias"),
|
||||
new BlockListEditorDataConverter(jsonSerializer),
|
||||
new(new DataEditorCollection(() => [])),
|
||||
new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>),
|
||||
Mock.Of<IDataTypeConfigurationCache>(),
|
||||
Mock.Of<IBlockEditorElementTypeCache>(),
|
||||
localizedTextServiceMock.Object,
|
||||
new NullLogger<BlockListEditorPropertyValueEditor>(),
|
||||
Mock.Of<IShortStringHelper>(),
|
||||
jsonSerializer,
|
||||
Mock.Of<IPropertyValidationService>(),
|
||||
new BlockEditorVarianceHandler(languageService, Mock.Of<IContentTypeService>()),
|
||||
languageService,
|
||||
Mock.Of<IIOHelper>())
|
||||
{
|
||||
ConfigurationObject = new BlockListConfiguration
|
||||
{
|
||||
ValidationLimit = new BlockListConfiguration.NumberRange
|
||||
{
|
||||
Min = 2,
|
||||
Max = 4
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ using Umbraco.Cms.Core.Strings;
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
|
||||
|
||||
[TestFixture]
|
||||
public class ColorPickerValueEditorTests
|
||||
public class ColorPickerPropertyValueEditorTests
|
||||
{
|
||||
[TestCase("#ffffff", true)]
|
||||
[TestCase("#f0f0f0", false)]
|
||||
@@ -14,7 +14,7 @@ using Umbraco.Cms.Core.Strings;
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
|
||||
|
||||
[TestFixture]
|
||||
public class DecimalValueEditorTests
|
||||
public class DecimalPropertyValueEditorTests
|
||||
{
|
||||
// annoyingly we can't use decimals etc. in attributes, so we can't turn these into test cases :(
|
||||
private Dictionary<object?,object?> _valuesAndExpectedResults = new();
|
||||
@@ -14,7 +14,7 @@ using Umbraco.Cms.Core.Strings;
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
|
||||
|
||||
[TestFixture]
|
||||
public class IntegerValueEditorTests
|
||||
public class IntegerPropertyValueEditorTests
|
||||
{
|
||||
// annoyingly we can't use decimals etc. in attributes, so we can't turn these into test cases :(
|
||||
private Dictionary<object?,object?> _valuesAndExpectedResults = new();
|
||||
@@ -1,9 +1,12 @@
|
||||
using Moq;
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Nodes;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
@@ -12,7 +15,7 @@ using Umbraco.Cms.Core.Strings;
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
|
||||
|
||||
[TestFixture]
|
||||
public class MultipleTextStringValueEditorTests
|
||||
public class MultipleTextStringPropertyValueEditorTests
|
||||
{
|
||||
[Test]
|
||||
public void Can_Handle_Invalid_Values_From_Editor()
|
||||
@@ -114,6 +117,71 @@ public class MultipleTextStringValueEditorTests
|
||||
Assert.IsEmpty(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_Null_As_Below_Configured_Min()
|
||||
{
|
||||
var editor = CreateValueEditor();
|
||||
var result = editor.Validate(null, false, null, PropertyValidationContext.Empty());
|
||||
Assert.AreEqual(1, result.Count());
|
||||
|
||||
var validationResult = result.First();
|
||||
Assert.AreEqual($"validation_outOfRangeMultipleItemsMinimum", validationResult.ErrorMessage);
|
||||
}
|
||||
|
||||
[TestCase(0, false, "outOfRangeMultipleItemsMinimum")]
|
||||
[TestCase(1, false, "outOfRangeSingleItemMinimum")]
|
||||
[TestCase(2, true, "")]
|
||||
[TestCase(3, true, "")]
|
||||
public void Validates_Number_Of_Items_Is_Greater_Than_Or_Equal_To_Configured_Min(int numberOfStrings, bool expectedSuccess, string expectedValidationMessageKey)
|
||||
{
|
||||
var value = Enumerable.Range(1, numberOfStrings).Select(x => x.ToString());
|
||||
var editor = CreateValueEditor();
|
||||
var result = editor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
if (expectedSuccess)
|
||||
{
|
||||
Assert.IsEmpty(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(1, result.Count());
|
||||
|
||||
var validationResult = result.First();
|
||||
Assert.AreEqual($"validation_{expectedValidationMessageKey}", validationResult.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(3, true)]
|
||||
[TestCase(4, true)]
|
||||
[TestCase(5, false)]
|
||||
public void Validates_Number_Of_Items_Is_Less_Than_Or_Equal_To_Configured_Max(int numberOfStrings, bool expectedSuccess)
|
||||
{
|
||||
var value = Enumerable.Range(1, numberOfStrings).Select(x => x.ToString());
|
||||
var editor = CreateValueEditor();
|
||||
var result = editor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
if (expectedSuccess)
|
||||
{
|
||||
Assert.IsEmpty(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(1, result.Count());
|
||||
|
||||
var validationResult = result.First();
|
||||
Assert.AreEqual("validation_outOfRangeMultipleItemsMaximum", validationResult.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Max_Item_Validation_Respects_0_As_Unlimited()
|
||||
{
|
||||
var value = Enumerable.Range(1, 100).Select(x => x.ToString());
|
||||
var editor = CreateValueEditor();
|
||||
editor.ConfigurationObject = new MultipleTextStringConfiguration();
|
||||
|
||||
var result = editor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
Assert.IsEmpty(result);
|
||||
}
|
||||
|
||||
private static object? FromEditor(object? value, int max = 0)
|
||||
=> CreateValueEditor().FromEditor(new ContentPropertyData(value, new MultipleTextStringConfiguration { Max = max }), null);
|
||||
|
||||
@@ -129,11 +197,25 @@ public class MultipleTextStringValueEditorTests
|
||||
|
||||
private static MultipleTextStringPropertyEditor.MultipleTextStringPropertyValueEditor CreateValueEditor()
|
||||
{
|
||||
var valueEditor = new MultipleTextStringPropertyEditor.MultipleTextStringPropertyValueEditor(
|
||||
var localizedTextServiceMock = new Mock<ILocalizedTextService>();
|
||||
localizedTextServiceMock.Setup(x => x.Localize(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<CultureInfo>(),
|
||||
It.IsAny<IDictionary<string, string>>()))
|
||||
.Returns((string key, string alias, CultureInfo culture, IDictionary<string, string> args) => $"{key}_{alias}");
|
||||
return new MultipleTextStringPropertyEditor.MultipleTextStringPropertyValueEditor(
|
||||
Mock.Of<IShortStringHelper>(),
|
||||
Mock.Of<IJsonSerializer>(),
|
||||
Mock.Of<IIOHelper>(),
|
||||
new DataEditorAttribute("alias"));
|
||||
return valueEditor;
|
||||
new DataEditorAttribute("alias"),
|
||||
localizedTextServiceMock.Object)
|
||||
{
|
||||
ConfigurationObject = new MultipleTextStringConfiguration
|
||||
{
|
||||
Min = 2,
|
||||
Max = 4
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ using Umbraco.Cms.Infrastructure.Serialization;
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
|
||||
|
||||
[TestFixture]
|
||||
public class SliderValueEditorTests
|
||||
public class SliderPropertyValueEditorTests
|
||||
{
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
public static object[] InvalidCaseData = new object[]
|
||||
Reference in New Issue
Block a user