Color Picker: Validate uniqueness of selected colors (#20431)

* Added unique color checker to color picker.

* Added Unittest for duplicates

* optimized for codescene

* removed the bump and simplified the function

* Fixed behaviour for duplicate checks so unit test passes.
A little refactoring.

* Adds continue so invalid colors aren't checked for duplicates.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
Erik Leusink
2025-10-09 11:50:11 +02:00
committed by GitHub
parent 1fe7931d07
commit 767894b723
2 changed files with 68 additions and 5 deletions

View File

@@ -10,8 +10,11 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors;
internal sealed class ColorPickerConfigurationEditor : ConfigurationEditor<ColorPickerConfiguration>
internal sealed partial class ColorPickerConfigurationEditor : ConfigurationEditor<ColorPickerConfiguration>
{
/// <summary>
/// Initializes a new instance of the <see cref="ColorPickerConfigurationEditor"/> class.
/// </summary>
public ColorPickerConfigurationEditor(IIOHelper ioHelper, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer)
: base(ioHelper)
{
@@ -19,13 +22,17 @@ internal sealed class ColorPickerConfigurationEditor : ConfigurationEditor<Color
items.Validators.Add(new ColorListValidator(configurationEditorJsonSerializer));
}
internal sealed class ColorListValidator : IValueValidator
internal sealed partial class ColorListValidator : IValueValidator
{
private readonly IConfigurationEditorJsonSerializer _configurationEditorJsonSerializer;
/// <summary>
/// Initializes a new instance of the <see cref="ColorListValidator"/> class.
/// </summary>
public ColorListValidator(IConfigurationEditorJsonSerializer configurationEditorJsonSerializer)
=> _configurationEditorJsonSerializer = configurationEditorJsonSerializer;
/// <inheritdoc/>
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext)
{
var stringValue = value?.ToString();
@@ -46,17 +53,53 @@ internal sealed class ColorPickerConfigurationEditor : ConfigurationEditor<Color
if (items is null)
{
yield return new ValidationResult($"The configuration value {stringValue} is not a valid color picker configuration", new[] { "items" });
yield return new ValidationResult($"The configuration value {stringValue} is not a valid color picker configuration", ["items"]);
yield break;
}
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var duplicates = new List<string>();
foreach (ColorPickerConfiguration.ColorPickerItem item in items)
{
if (Regex.IsMatch(item.Value, "^([0-9a-f]{3}|[0-9a-f]{6})$", RegexOptions.IgnoreCase) == false)
if (ColorPattern().IsMatch(item.Value) == false)
{
yield return new ValidationResult($"The value {item.Value} is not a valid hex color", new[] { "items" });
yield return new ValidationResult($"The value {item.Value} is not a valid hex color", ["items"]);
continue;
}
var normalized = Normalize(item.Value);
if (seen.Add(normalized) is false)
{
duplicates.Add(normalized);
}
}
if (duplicates.Count > 0)
{
yield return new ValidationResult(
$"Duplicate color values are not allowed: {string.Join(", ", duplicates)}",
["items"]);
}
}
private static string Normalize(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
var normalizedValue = value.Trim().ToLowerInvariant();
if (normalizedValue.Length == 3)
{
normalizedValue = $"{normalizedValue[0]}{normalizedValue[0]}{normalizedValue[1]}{normalizedValue[1]}{normalizedValue[2]}{normalizedValue[2]}";
}
return normalizedValue;
}
[GeneratedRegex("^([0-9a-f]{3}|[0-9a-f]{6})$", RegexOptions.IgnoreCase, "en-GB")]
private static partial Regex ColorPattern();
}
}

View File

@@ -58,4 +58,24 @@ public class ColorListValidatorTest
PropertyValidationContext.Empty());
Assert.AreEqual(2, result.Count());
}
[Test]
public void Validates_Color_Vals_Are_Unique()
{
var validator = new ColorPickerConfigurationEditor.ColorListValidator(ConfigurationEditorJsonSerializer());
var result =
validator.Validate(
new JsonArray(
JsonNode.Parse("""{"value": "FFFFFF", "label": "One"}"""),
JsonNode.Parse("""{"value": "000000", "label": "Two"}"""),
JsonNode.Parse("""{"value": "FF00AA", "label": "Three"}"""),
JsonNode.Parse("""{"value": "fff", "label": "Four"}"""),
JsonNode.Parse("""{"value": "000000", "label": "Five"}"""),
JsonNode.Parse("""{"value": "F0A", "label": "Six"}""")),
null,
null,
PropertyValidationContext.Empty());
Assert.AreEqual(1, result.Count());
Assert.IsTrue(result.First().ErrorMessage.Contains("ffffff, 000000, ff00aa"));
}
}