diff --git a/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs index f9cfcf01dd..e9b2d5ee7f 100644 --- a/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs @@ -1,7 +1,8 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors.Validators; namespace Umbraco.Cms.Core.PropertyEditors; @@ -10,8 +11,17 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// public class TextboxConfigurationEditor : ConfigurationEditor { + /// + /// Initializes a new instance of the class. + /// public TextboxConfigurationEditor(IIOHelper ioHelper) : base(ioHelper) { + const int MinChars = 1; + const int MaxChars = 512; + Fields.Add(new ConfigurationField(new IntegerValidator(MinChars, MaxChars)) + { + Key = "maxChars", + }); } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs index 5027bd69ef..1bc20377bc 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs @@ -5,19 +5,52 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.Validators; /// -/// A validator that validates that the value is a valid integer +/// A validator that validates that the value is a valid integer. /// public sealed class IntegerValidator : IValueValidator { + private readonly int? _minValue; + private readonly int? _maxValue; + + /// + /// Initializes a new instance of the class. + /// + public IntegerValidator() + { + } + + /// + /// Initializes a new instance of the class with minimum and/or maximum values. + /// + public IntegerValidator(int? minValue, int? maxValue) + { + _minValue = minValue; + _maxValue = maxValue; + } + /// public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext) { - if (value != null && value.ToString() != string.Empty) + if (value == null || value.ToString() == string.Empty) { - Attempt result = value.TryConvertTo(); - if (result.Success == false) + yield break; + } + + Attempt result = value.TryConvertTo(); + if (result.Success == false) + { + yield return new ValidationResult("The value " + value + " is not a valid integer", ["value"]); + } + else + { + if (_minValue.HasValue && result.Result < _minValue.Value) { - yield return new ValidationResult("The value " + value + " is not a valid integer", new[] { "value" }); + yield return new ValidationResult("The value " + value + " is less than the minimum allowed value of " + _minValue.Value, ["value"]); + } + + if (_maxValue.HasValue && result.Result > _maxValue.Value) + { + yield return new ValidationResult("The value " + value + " is greater than the maximum allowed value of " + _maxValue.Value, ["value"]); } } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/Umbraco.TextBox.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/Umbraco.TextBox.ts index c67d1a6cf2..66766ff4f4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/Umbraco.TextBox.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/Umbraco.TextBox.ts @@ -11,8 +11,12 @@ export const manifest: ManifestPropertyEditorSchema = { { alias: 'maxChars', label: 'Maximum allowed characters', - description: 'If empty, 512 character limit', propertyEditorUiAlias: 'Umb.PropertyEditorUi.Integer', + config: [ + { alias: 'min', value: 1 }, + { alias: 'max', value: 512 }, + { alias: 'placeholder', value: '512' }, + ], }, ], defaultData: [ diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 55ac1a3b7e..9ad80cae9c 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -8,7 +8,7 @@ "hasInstallScript": true, "dependencies": { "@umbraco/json-models-builders": "^2.0.42", - "@umbraco/playwright-testhelpers": "^17.0.8", + "@umbraco/playwright-testhelpers": "^17.0.9", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -67,9 +67,9 @@ } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "17.0.8", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-17.0.8.tgz", - "integrity": "sha512-LUVBdsweiS0WpE1F9YTQejmSxdtgEvbcmLHX57e2S2AbNkdVuR8cJ0rYd9TqSKtNU8ckwnk6YRtVikegU0D64w==", + "version": "17.0.9", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-17.0.9.tgz", + "integrity": "sha512-8y2/Rjskhf5gcF+ebJfxUCCkHTcGmKQqtVMLg9hRKVjhI+3bs642+GJL0tK4MPvoSK64JhfiKmo+IhI5zrtokg==", "license": "MIT", "dependencies": { "@umbraco/json-models-builders": "2.0.42", diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 9cbb0c6fc7..6cb5e91d98 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -23,7 +23,7 @@ }, "dependencies": { "@umbraco/json-models-builders": "^2.0.42", - "@umbraco/playwright-testhelpers": "^17.0.8", + "@umbraco/playwright-testhelpers": "^17.0.9", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts index 7f97bfc383..3deb42a7fd 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts @@ -1,4 +1,4 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'TestDataType'; @@ -68,7 +68,7 @@ test('can change property editor in a data type', {tag: '@smoke'}, async ({umbra const updatedEditorName = 'Text Area'; const updatedEditorAlias = 'Umbraco.TextArea'; const updatedEditorUiAlias = 'Umb.PropertyEditorUi.TextArea'; - const maxChars = 999; + const maxChars = 500; await umbracoApi.dataType.createTextstringDataType(dataTypeName, maxChars); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy(); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/IntegerValidatorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/IntegerValidatorTests.cs new file mode 100644 index 0000000000..c8ba552e89 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/IntegerValidatorTests.cs @@ -0,0 +1,74 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; +using Umbraco.Cms.Core.Models.Validation; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.Validators; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors.Validators; + +[TestFixture] +public class IntegerValidatorTests +{ + [TestCase(null, true)] + [TestCase("", true)] + [TestCase(" ", false)] + [TestCase("x", false)] + [TestCase("99", true)] + public void Validates_Integer(object? value, bool expectedSuccess) + { + var validator = CreateValidator(); + var result = validator.Validate(value, ValueTypes.Integer, null, PropertyValidationContext.Empty()); + if (expectedSuccess) + { + Assert.IsEmpty(result); + } + else + { + Assert.AreEqual(1, result.Count()); + + var validationResult = result.First(); + Assert.IsTrue(validationResult.ErrorMessage.Contains("is not a valid integer")); + } + } + + public enum RangeResult + { + Success, + BelowMin, + AboveMax, + } + + [TestCase("5", RangeResult.BelowMin)] + [TestCase("10", RangeResult.Success)] + [TestCase("15", RangeResult.Success)] + [TestCase("20", RangeResult.Success)] + [TestCase("25", RangeResult.AboveMax)] + public void Validates_Integer_Within_Range(object? value, RangeResult expectedResult) + { + var validator = CreateValidator(10, 20); + var result = validator.Validate(value, ValueTypes.Integer, null, PropertyValidationContext.Empty()); + if (expectedResult == RangeResult.Success) + { + Assert.IsEmpty(result); + } + else + { + Assert.AreEqual(1, result.Count()); + + var validationResult = result.First(); + if (expectedResult == RangeResult.BelowMin) + { + Assert.IsTrue(validationResult.ErrorMessage.Contains("less than the minimum allowed value")); + } + else if (expectedResult == RangeResult.AboveMax) + { + Assert.IsTrue(validationResult.ErrorMessage.Contains("greater than the maximum allowed value")); + } + } + } + + private static IntegerValidator CreateValidator(int? min = null, int? max = null) => + min.HasValue is false && max.HasValue is false ? new IntegerValidator() : new IntegerValidator(min, max); +}