diff --git a/src/Umbraco.Tests/Web/Validation/ContentModelValidatorTests.cs b/src/Umbraco.Tests/Web/Validation/ContentModelValidatorTests.cs index 1bf558605c..29a8f9ebb7 100644 --- a/src/Umbraco.Tests/Web/Validation/ContentModelValidatorTests.cs +++ b/src/Umbraco.Tests/Web/Validation/ContentModelValidatorTests.cs @@ -98,20 +98,22 @@ namespace Umbraco.Tests.Web.Validation public void TestSerializer() { var nestedLevel2 = new NestedValidationResults(); - nestedLevel2.AddElementTypeValidationResults( - new ValidationResultCollection( - new ValidationResult("error2-1", new[] { "level2" }), - new ValidationResult("error2-2", new[] { "level2" }))); + var elementTypeResult2 = new ElementTypeValidationResult("type2"); + var propertyTypeResult2 = new PropertyTypeValidationResult("prop2"); + propertyTypeResult2.ValidationResults.Add(new ValidationResult("error2-1", new[] { "level2" })); + propertyTypeResult2.ValidationResults.Add(new ValidationResult("error2-2", new[] { "level2" })); + elementTypeResult2.ValidationResults.Add(propertyTypeResult2); + nestedLevel2.ValidationResults.Add(elementTypeResult2); var nestedLevel1 = new NestedValidationResults(); - nestedLevel1.AddElementTypeValidationResults( - new ValidationResultCollection( - new ValidationResult("error1-1", new[] { "level1" }), - nestedLevel2)); + var elementTypeResult1 = new ElementTypeValidationResult("type1"); + var propertyTypeResult1 = new PropertyTypeValidationResult("prop1"); + propertyTypeResult1.ValidationResults.Add(new ValidationResult("error1-1", new[] { "level1" })); + propertyTypeResult1.ValidationResults.Add(nestedLevel2); // This is a nested result within the level 1 + elementTypeResult1.ValidationResults.Add(propertyTypeResult1); + nestedLevel1.ValidationResults.Add(elementTypeResult1); - var propValidationResult = new PropertyValidationResult(nestedLevel1); - - var serialized = JsonConvert.SerializeObject(propValidationResult, Formatting.Indented, new ValidationResultConverter()); + var serialized = JsonConvert.SerializeObject(nestedLevel1, Formatting.Indented, new ValidationResultConverter()); Console.WriteLine(serialized); } @@ -125,36 +127,26 @@ namespace Umbraco.Tests.Web.Validation var content = MockedContent.CreateTextpageContent(_contentType, "test", -1); - // TODO: Ok now test with a 3rd/4th level complex nested editor - - // const string complexValue = @"[{ - // ""key"": ""c8df5136-d606-41f0-9134-dea6ae0c2fd9"", - // ""name"": ""Hello world"", - // ""ncContentTypeAlias"": """ + ContentTypeAlias + @""", - // ""title"": ""Hello world"" - // }, { - // ""key"": ""f916104a-4082-48b2-a515-5c4bf2230f38"", - // ""name"": ""Super nested"", - // ""ncContentTypeAlias"": """ + ContentTypeAlias + @""", - // ""title"": ""Hi there!"" - // } - //]"; + // TODO: Ok now test with a 4th level complex nested editor const string complexValue = @"[{ ""key"": ""c8df5136-d606-41f0-9134-dea6ae0c2fd9"", ""name"": ""Hello world"", ""ncContentTypeAlias"": """ + ContentTypeAlias + @""", - ""title"": ""Hello world"" + ""title"": ""Hello world"", + ""bodyText"": ""The world is round"" }, { ""key"": ""f916104a-4082-48b2-a515-5c4bf2230f38"", ""name"": ""Super nested"", ""ncContentTypeAlias"": """ + ContentTypeAlias + @""", ""title"": ""Hi there!"", + ""bodyText"": ""Well hello there"", ""complex"" : [{ ""key"": ""77E15DE9-1C79-47B2-BC60-4913BC4D4C6A"", ""name"": ""I am a sub nested content"", ""ncContentTypeAlias"": """ + ContentTypeAlias + @""", - ""title"": ""Hello up there :)"" + ""title"": ""Hello up there :)"", + ""bodyText"": ""Hello way up there on a different level"" }] } ]"; @@ -234,19 +226,50 @@ namespace Umbraco.Tests.Web.Validation var jsonNestedError = JsonConvert.DeserializeObject(nestedError.ErrorMessage); Assert.AreEqual(JTokenType.Array, jsonNestedError["nestedValidation"].Type); var nestedValidation = (JArray)jsonNestedError["nestedValidation"]; - Assert.AreEqual(2, nestedValidation.Count); // there are 2 because there are 2 nested content rows + AssertNestedValidation(nestedValidation, 2); // there are 2 because there are 2 nested content rows + } + + private void AssertNestedValidation(JArray nestedValidation, int rows) + { + Assert.AreEqual(rows, nestedValidation.Count); foreach (var rowErrors in nestedValidation) { - var elementTypeErrors = (JArray)rowErrors; // this is an array of errors for the nested content row (element type) - Assert.AreEqual(2, elementTypeErrors.Count); - foreach (var elementTypeErr in elementTypeErrors) + Assert.AreEqual(JTokenType.Object, rowErrors.Type); + var elementTypeErrors = (JObject)rowErrors; // this is a dictionary of element type alias -> dictionary of errors -> prop alias -> array errors + Assert.AreEqual(1, elementTypeErrors.Count); // there is 1 element type in error + foreach (var elementTypeAliasToErrors in elementTypeErrors) { - Assert.IsNotEmpty(elementTypeErr["errorMessage"].Value()); - Assert.AreEqual(1, elementTypeErr["memberNames"].Value().Count); + Assert.AreEqual("textPage", elementTypeAliasToErrors.Key); + var propErrors = (JObject)elementTypeAliasToErrors.Value; + + foreach (var propAliasToErrors in propErrors) + { + Assert.AreEqual(JTokenType.Array, propAliasToErrors.Value.Type); + var propTypeErrors = (JArray)propAliasToErrors.Value; + + foreach (var propError in propTypeErrors) + { + var nested = propError["nestedValidation"]; + if (nested != null) + { + // recurse + AssertNestedValidation((JArray)nested, 1); // we know this is 1 row + continue; + } + + Assert.IsNotEmpty(propError["errorMessage"].Value()); + Assert.AreEqual(1, propError["memberNames"].Value().Count); + } + + } + } + + } } + [HideFromTypeFinder] [DataEditor("complexTest", "test", "test")] public class ComplexTestEditor : NestedContentPropertyEditor diff --git a/src/Umbraco.Web/PropertyEditors/ComplexEditorValidator.cs b/src/Umbraco.Web/PropertyEditors/ComplexEditorValidator.cs index 8efa89b240..16a3357cbb 100644 --- a/src/Umbraco.Web/PropertyEditors/ComplexEditorValidator.cs +++ b/src/Umbraco.Web/PropertyEditors/ComplexEditorValidator.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.PropertyEditors var result = new NestedValidationResults(); foreach(var rowResult in rowResults) { - result.AddElementTypeValidationResults(rowResult); + result.ValidationResults.Add(rowResult); } return result.Yield(); } @@ -55,30 +55,36 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - protected IEnumerable GetNestedValidationResults(IEnumerable elements) + protected IEnumerable GetNestedValidationResults(IEnumerable elements) { foreach (var row in elements) { - var nestedValidation = new List(); + var elementTypeValidationResult = new ElementTypeValidationResult(row.ElementTypeAlias); foreach (var prop in row.PropertyTypeValidation) { + var propValidationResult = new PropertyTypeValidationResult(prop.PropertyType.Alias); + foreach (var validationResult in _propertyValidationService.ValidatePropertyValue(prop.PropertyType, prop.PostedValue)) { - nestedValidation.Add(validationResult); + // add the result to the property results + propValidationResult.ValidationResults.Add(validationResult); } + + // add the property results to the element type results + elementTypeValidationResult.ValidationResults.Add(propValidationResult); } - if (nestedValidation.Count > 0) + if (elementTypeValidationResult.ValidationResults.Count > 0) { - yield return new ValidationResultCollection(nestedValidation.ToArray()); + yield return elementTypeValidationResult; } } } public class PropertyTypeValidationModel { - public PropertyTypeValidationModel(object postedValue, PropertyType propertyType) + public PropertyTypeValidationModel(PropertyType propertyType, object postedValue) { PostedValue = postedValue ?? throw new ArgumentNullException(nameof(postedValue)); PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType)); @@ -90,9 +96,17 @@ namespace Umbraco.Web.PropertyEditors public class ElementTypeValidationModel { + public ElementTypeValidationModel(string elementTypeAlias) + { + ElementTypeAlias = elementTypeAlias; + } + private List _list = new List(); + public IEnumerable PropertyTypeValidation => _list; + public string ElementTypeAlias { get; } + public void AddPropertyTypeValidation(PropertyTypeValidationModel propValidation) => _list.Add(propValidation); } } diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 5bcb5cec0f..4f5616e3d7 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -269,10 +269,11 @@ namespace Umbraco.Web.PropertyEditors { foreach (var row in _nestedContentValues.GetPropertyValues(value)) { - var elementValidation = new ElementTypeValidationModel(); + var elementValidation = new ElementTypeValidationModel(row.ContentTypeAlias); foreach (var prop in row.PropertyValues) { - elementValidation.AddPropertyTypeValidation(new PropertyTypeValidationModel(prop.Value.Value, prop.Value.PropertyType)); + elementValidation.AddPropertyTypeValidation( + new PropertyTypeValidationModel(prop.Value.PropertyType, prop.Value.Value)); } yield return elementValidation; } diff --git a/src/Umbraco.Web/PropertyEditors/Validation/NestedValidationResults.cs b/src/Umbraco.Web/PropertyEditors/Validation/NestedValidationResults.cs index 9ae11c1c38..26a47602db 100644 --- a/src/Umbraco.Web/PropertyEditors/Validation/NestedValidationResults.cs +++ b/src/Umbraco.Web/PropertyEditors/Validation/NestedValidationResults.cs @@ -3,15 +3,28 @@ using System.ComponentModel.DataAnnotations; namespace Umbraco.Web.PropertyEditors.Validation { - public class ValidationResultCollection : ValidationResult + public class PropertyTypeValidationResult : ValidationResult { - public ValidationResultCollection(params ValidationResult[] nested) + public PropertyTypeValidationResult(string propertyTypeAlias) : base(string.Empty) { - ValidationResults = new List(nested); + PropertyTypeAlias = propertyTypeAlias; } - public IList ValidationResults { get; } + public IList ValidationResults { get; } = new List(); + public string PropertyTypeAlias { get; } + } + + public class ElementTypeValidationResult : ValidationResult + { + public ElementTypeValidationResult(string elementTypeAlias) + : base(string.Empty) + { + ElementTypeAlias = elementTypeAlias; + } + + public IList ValidationResults { get; } = new List(); + public string ElementTypeAlias { get; } } /// @@ -27,11 +40,6 @@ namespace Umbraco.Web.PropertyEditors.Validation { } - public void AddElementTypeValidationResults(ValidationResultCollection resultCollection) - { - ValidationResults.Add(resultCollection); - } - - public IList ValidationResults { get; } = new List(); + public IList ValidationResults { get; } = new List(); } } diff --git a/src/Umbraco.Web/PropertyEditors/Validation/ValidationResultConverter.cs b/src/Umbraco.Web/PropertyEditors/Validation/ValidationResultConverter.cs index 2be90a6bcc..cb5e71b060 100644 --- a/src/Umbraco.Web/PropertyEditors/Validation/ValidationResultConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/Validation/ValidationResultConverter.cs @@ -42,16 +42,25 @@ namespace Umbraco.Web.PropertyEditors.Validation jo.Add("nestedValidation", obj); jo.WriteTo(writer); } - else if (validationResult is ValidationResultCollection resultCollection && resultCollection.ValidationResults.Count > 0) + else if (validationResult is ElementTypeValidationResult elementTypeValidationResult && elementTypeValidationResult.ValidationResults.Count > 0) { - var ja = new JArray(); - foreach (var result in resultCollection.ValidationResults) - { - // recurse to write out the ValidationResult - var obj = JObject.FromObject(result, camelCaseSerializer); - ja.Add(obj); + var joElementType = new JObject(); + var joPropertyType = new JObject(); + // loop over property validations + foreach (var propTypeResult in elementTypeValidationResult.ValidationResults) + { + var ja = new JArray(); + foreach (var result in propTypeResult.ValidationResults) + { + // recurse to get the validation result object and add to the array + var obj = JObject.FromObject(result, camelCaseSerializer); + ja.Add(obj); + } + // create a dictionary entry + joPropertyType.Add(propTypeResult.PropertyTypeAlias, ja); } - ja.WriteTo(writer); + joElementType.Add(elementTypeValidationResult.ElementTypeAlias, joPropertyType); + joElementType.WriteTo(writer); } else {