Property Editors: Improve Missing Property Editor UI and allow save/publish (#20104)

* Initial implementation of non existing property editor

* Adjust `MissingPropertyEditor` to not require registering in PropertyEditorCollection

* Add `MissingPropertyEditor.name` back

* Remove unused dependencies from DataTypeService

* Removed reference to non existing property

* Add parameterless constructor back to MissingPropertyEditor

* Add validation error on document open to property with missing editor

* Update labels

* Removed public editor alias const

* Update src/Umbraco.Web.UI.Client/src/packages/property-editors/missing/manifests.ts

* Add test that checks whether the new MissingPropertyEditor is returned when an editor is not found

* Also check if the editor UI alias is correct in the test

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Share property editor instances between properties

* Only store missing property editors in memory in `ContentMapDefinition.MapValueViewModels()`

* Add value converter for the missing property editor to always return a string (same as the Label did previously)

* Small improvements to code block

* Adjust property validation to accept missing property editors

* Return the current value when trying to update a property with a missing editor

Same logic as for when the property is readonly.

* Fix failing unit tests

* Small fix

* Add unit test

* Remove client validation

* UI adjustments

* Adjustments from code review

* Adjust test

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Laura Neto
2025-09-18 08:55:58 +02:00
committed by GitHub
parent 061be01e89
commit fd0ccc529b
11 changed files with 167 additions and 123 deletions

View File

@@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Dictionary;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Validation;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.PropertyEditors.Validators;
using Umbraco.Cms.Core.Serialization;
@@ -27,16 +28,14 @@ public class PropertyValidationServiceTests
private void MockObjects(out PropertyValidationService validationService, out IDataType dt)
{
var dataTypeService = new Mock<IDataTypeService>();
var dataType = Mock.Of<IDataType>(
x => x.ConfigurationObject == string.Empty // irrelevant but needs a value
&& x.DatabaseType == ValueStorageType.Nvarchar
&& x.EditorAlias == Constants.PropertyEditors.Aliases.TextBox);
var dataType = Mock.Of<IDataType>(x => x.ConfigurationObject == string.Empty // irrelevant but needs a value
&& x.DatabaseType == ValueStorageType.Nvarchar
&& x.EditorAlias == Constants.PropertyEditors.Aliases.TextBox);
dataTypeService.Setup(x => x.GetDataType(It.IsAny<int>())).Returns(() => dataType);
dt = dataType;
// new data editor that returns a TextOnlyValueEditor which will do the validation for the properties
var dataEditor = Mock.Of<IDataEditor>(
x => x.Alias == Constants.PropertyEditors.Aliases.TextBox);
var dataEditor = Mock.Of<IDataEditor>(x => x.Alias == Constants.PropertyEditors.Aliases.TextBox);
Mock.Get(dataEditor).Setup(x => x.GetValueEditor(It.IsAny<object>()))
.Returns(new CustomTextOnlyValueEditor(
new DataEditorAttribute(Constants.PropertyEditors.Aliases.TextBox),
@@ -44,7 +43,15 @@ public class PropertyValidationServiceTests
new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory()),
Mock.Of<IIOHelper>()));
var propEditors = new PropertyEditorCollection(new DataEditorCollection(() => new[] { dataEditor }));
var languageService = new Mock<ILanguageService>();
languageService
.Setup(s => s.GetDefaultIsoCodeAsync())
.ReturnsAsync(() => "en-US");
var propEditors = new PropertyEditorCollection(new DataEditorCollection(() => [dataEditor]));
var contentSettings = new Mock<IOptions<ContentSettings>>();
contentSettings.Setup(x => x.Value).Returns(new ContentSettings());
validationService = new PropertyValidationService(
propEditors,
@@ -52,8 +59,8 @@ public class PropertyValidationServiceTests
Mock.Of<ILocalizedTextService>(),
new ValueEditorCache(),
Mock.Of<ICultureDictionary>(),
Mock.Of<ILanguageService>(),
Mock.Of<IOptions<ContentSettings>>());
languageService.Object,
contentSettings.Object);
}
[Test]
@@ -279,6 +286,23 @@ public class PropertyValidationServiceTests
Assert.AreEqual(4, invalid.Length);
}
[TestCase(null)]
[TestCase(24)]
[TestCase("test")]
[TestCase("{\"test\": true}")]
public void ValidatePropertyValue_Always_Returns_No_Validation_Errors_For_Missing_Editor(object? value)
{
MockObjects(out var validationService, out _);
var p1 = new PropertyType(ShortStringHelper, "Missing.Alias", ValueStorageType.Ntext)
{
Variations = ContentVariation.Nothing,
};
var result = validationService.ValidatePropertyValue(p1, value, PropertyValidationContext.Empty());
Assert.AreEqual(0, result.Count());
}
// used so we can inject a mock - we should fix the base class DataValueEditor to be able to have the ILocalizedTextField passed
// in to create the Requried and Regex validators so we aren't using singletons
private class CustomTextOnlyValueEditor : TextOnlyValueEditor