diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MultipleTextStringPropertyEditor.cs index 9fe8dbe4ec..714d1e624b 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MultipleTextStringPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MultipleTextStringPropertyEditor.cs @@ -89,21 +89,6 @@ public class MultipleTextStringPropertyEditor : DataEditor return null; } - if (!(editorValue.DataTypeConfiguration is MultipleTextStringConfiguration config)) - { - throw new PanicException( - $"editorValue.DataTypeConfiguration is {editorValue.DataTypeConfiguration?.GetType()} but must be {typeof(MultipleTextStringConfiguration)}"); - } - - var max = config.Max; - - // The legacy property editor saved this data as new line delimited! strange but we have to maintain that. - // only allow the max if over 0 - if (max > 0) - { - return string.Join(_newLine, value.Take(max)); - } - return string.Join(_newLine, value); } @@ -114,9 +99,12 @@ public class MultipleTextStringPropertyEditor : DataEditor // 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) + ? SplitPropertyValue(stringValue) : Array.Empty(); } + + internal static string[] SplitPropertyValue(string propertyValue) + => propertyValue.Split(_newLineDelimiters, StringSplitOptions.None); } /// @@ -166,13 +154,13 @@ public class MultipleTextStringPropertyEditor : DataEditor yield break; } - // If we have a null value, treat as an empty collection for minimum number validation. - if (value is not IEnumerable stringValues) - { - stringValues = []; - } - - var stringCount = stringValues.Count(); + // Handle both a newline delimited string and an IEnumerable as the value (see: https://github.com/umbraco/Umbraco-CMS/pull/18936). + // If we have a null value, treat as a string count of zero for minimum number validation. + var stringCount = value is string stringValue + ? MultipleTextStringPropertyValueEditor.SplitPropertyValue(stringValue).Length + : value is IEnumerable strings + ? strings.Count() + : 0; if (stringCount < multipleTextStringConfiguration.Min) { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultipleTextStringPropertyValueEditorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultipleTextStringPropertyValueEditorTests.cs index c3a814075a..e7544fe08d 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultipleTextStringPropertyValueEditorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultipleTextStringPropertyValueEditorTests.cs @@ -82,6 +82,20 @@ public class MultipleTextStringPropertyValueEditorTests Assert.AreEqual("The First Value\nThe Second Value\nThe Third Value", fromEditor); } + [Test] + public void Can_Parse_More_Items_Than_Allowed_From_Editor() + { + var valueEditor = CreateValueEditor(); + var fromEditor = valueEditor.FromEditor(new ContentPropertyData(new[] { "One", "Two", "Three", "Four", "Five" }, new MultipleTextStringConfiguration { Max = 4 }), null) as string; + Assert.AreEqual("One\nTwo\nThree\nFour\nFive", fromEditor); + + var validationResults = valueEditor.Validate(fromEditor, false, null, PropertyValidationContext.Empty()); + Assert.AreEqual(1, validationResults.Count()); + + var validationResult = validationResults.First(); + Assert.AreEqual($"validation_outOfRangeMultipleItemsMaximum", validationResult.ErrorMessage); + } + [Test] public void Can_Parse_Single_Value_To_Editor() { @@ -150,6 +164,27 @@ public class MultipleTextStringPropertyValueEditorTests } } + [TestCase("", false)] + [TestCase("one", false)] + [TestCase("one\ntwo", true)] + [TestCase("one\ntwo\nthree", true)] + public void Validates_Number_Of_Items_Is_Greater_Than_Or_Equal_To_Configured_Min_Raw_Property_Value(string value, bool expectedSuccess) + { + 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_outOfRangeSingleItemMinimum", validationResult.ErrorMessage); + } + } + [TestCase(3, true)] [TestCase(4, true)] [TestCase(5, false)] @@ -171,6 +206,36 @@ public class MultipleTextStringPropertyValueEditorTests } } + [TestCase("one\ntwo\nthree", true)] + [TestCase("one\ntwo\nthree\nfour", true)] + [TestCase("one\ntwo\nthree\nfour\nfive", false)] + public void Validates_Number_Of_Items_Is_Less_Than_Or_Equal_To_Configured_Max_Raw_Property_Value(string value, bool expectedSuccess) + { + 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); + } + } + + [TestCase("one\ntwo\nthree")] + [TestCase("one\rtwo\rthree")] + [TestCase("one\r\ntwo\r\nthree")] + public void Can_Parse_Supported_Property_Value_Delimiters(string value) + { + var editor = CreateValueEditor(); + var result = editor.Validate(value, false, null, PropertyValidationContext.Empty()); + Assert.IsEmpty(result); + } + [Test] public void Max_Item_Validation_Respects_0_As_Unlimited() {