Validation for block level variation (#17355)

* Validation for block level variation

* Make the "missing property value" JSON path expression valid

* Update src/Umbraco.Core/Services/ContentValidationServiceBase.cs

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>

* Update src/Umbraco.Core/Services/ContentValidationServiceBase.cs

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>

* Update src/Umbraco.Infrastructure/PropertyEditors/BlockEditorValidatorBase.cs

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>

---------

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
This commit is contained in:
Kenn Jacobsen
2024-10-31 10:04:54 +01:00
committed by GitHub
parent 3ecd5b470f
commit 5fe91f837d
34 changed files with 1246 additions and 123 deletions

View File

@@ -1548,4 +1548,332 @@ public partial class BlockListElementLevelVariationTests
validateBlocks?.Invoke(value);
}
}
[Test]
public async Task Can_Publish_Valid_Properties()
{
var elementType = CreateElementTypeWithValidation();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var content = CreateContent(contentType, elementType, [], false);
var blockListValue = BlockListPropertyValue(
elementType,
[
(
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant content value" },
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid content value in Danish", Culture = "da-DK" }
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant settings value" },
new() { Alias = "variantText", Value = "Valid value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid value in Danish", Culture = "da-DK" },
},
null,
null
)
)
]
);
content.Properties["blocks"]!.SetValue(JsonSerializer.Serialize(blockListValue));
ContentService.Save(content);
var publishResult = ContentService.Publish(content, ["en-US", "da-DK"]);
Assert.IsTrue(publishResult.Success);
Assert.IsTrue(publishResult.Content.PublishedCultures.Contains("en-US"));
Assert.IsTrue(publishResult.Content.PublishedCultures.Contains("da-DK"));
}
[Test]
public async Task Can_Publish_Valid_Properties_Specific_Culture_Only()
{
var elementType = CreateElementTypeWithValidation();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var content = CreateContent(contentType, elementType, [], false);
var blockListValue = BlockListPropertyValue(
elementType,
[
(
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant content value" },
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid content value in Danish", Culture = "da-DK" }
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant settings value" },
new() { Alias = "variantText", Value = "Valid value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid value in Danish", Culture = "da-DK" },
},
null,
null
)
)
]
);
content.Properties["blocks"]!.SetValue(JsonSerializer.Serialize(blockListValue));
ContentService.Save(content);
var publishResult = ContentService.Publish(content, ["en-US"]);
Assert.IsTrue(publishResult.Success);
Assert.IsTrue(publishResult.Content.PublishedCultures.Contains("en-US"));
Assert.IsFalse(publishResult.Content.PublishedCultures.Contains("da-DK"));
}
[Test]
public async Task Can_Publish_Valid_Properties_With_Wildcard_Culture()
{
var elementType = CreateElementTypeWithValidation();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var content = CreateContent(contentType, elementType, [], false);
var blockListValue = BlockListPropertyValue(
elementType,
[
(
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant content value" },
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid content value in Danish", Culture = "da-DK" }
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant settings value" },
new() { Alias = "variantText", Value = "Valid value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid value in Danish", Culture = "da-DK" },
},
null,
null
)
)
]
);
content.Properties["blocks"]!.SetValue(JsonSerializer.Serialize(blockListValue));
ContentService.Save(content);
var publishResult = ContentService.Publish(content, ["*"]);
Assert.IsTrue(publishResult.Success);
Assert.IsTrue(publishResult.Content.PublishedCultures.Contains("en-US"));
Assert.IsTrue(publishResult.Content.PublishedCultures.Contains("da-DK"));
}
[TestCase(true)]
[TestCase(false)]
public async Task Cannot_Publish_Invalid_Invariant_Properties(bool invalidSettingsValue)
{
var elementType = CreateElementTypeWithValidation();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var content = CreateContent(contentType, elementType, [], false);
var blockListValue = BlockListPropertyValue(
elementType,
[
(
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = $"{(invalidSettingsValue ? "Valid" : "Invalid")} invariant content value" },
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid content value in Danish", Culture = "da-DK" }
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = $"{(invalidSettingsValue ? "Invalid" : "Valid")} invariant settings value" },
new() { Alias = "variantText", Value = "Valid value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid value in Danish", Culture = "da-DK" },
},
null,
null
)
)
]
);
content.Properties["blocks"]!.SetValue(JsonSerializer.Serialize(blockListValue));
ContentService.Save(content);
var publishResult = ContentService.Publish(content, ["en-US", "da-DK"]);
Assert.Multiple(() =>
{
Assert.IsFalse(publishResult.Success);
Assert.IsNotNull(publishResult.InvalidProperties);
Assert.AreEqual(1, publishResult.InvalidProperties.Count());
Assert.AreEqual("blocks", publishResult.InvalidProperties.First().Alias);
});
}
[Test]
public async Task Cannot_Publish_Missing_Invariant_Properties()
{
var elementType = CreateElementTypeWithValidation();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var content = CreateContent(contentType, elementType, [], false);
var blockListValue = BlockListPropertyValue(
elementType,
[
(
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid content value in Danish", Culture = "da-DK" }
},
new List<BlockPropertyValue>
{
new() { Alias = "variantText", Value = "Valid value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid value in Danish", Culture = "da-DK" },
},
null,
null
)
)
]
);
content.Properties["blocks"]!.SetValue(JsonSerializer.Serialize(blockListValue));
ContentService.Save(content);
var publishResult = ContentService.Publish(content, ["en-US", "da-DK"]);
Assert.Multiple(() =>
{
Assert.IsFalse(publishResult.Success);
Assert.IsNotNull(publishResult.InvalidProperties);
Assert.AreEqual(1, publishResult.InvalidProperties.Count());
Assert.AreEqual("blocks", publishResult.InvalidProperties.First().Alias);
});
}
[TestCase(true)]
[TestCase(false)]
public async Task Cannot_Publish_Invalid_Variant_Properties(bool invalidSettingsValue)
{
var elementType = CreateElementTypeWithValidation();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var content = CreateContent(contentType, elementType, [], false);
var blockListValue = BlockListPropertyValue(
elementType,
[
(
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant content value" },
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = $"{(invalidSettingsValue ? "Valid" : "Invalid")} content value in Danish", Culture = "da-DK" }
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant settings value" },
new() { Alias = "variantText", Value = "Valid settings value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = $"{(invalidSettingsValue ? "Invalid" : "Valid")} settings value in Danish", Culture = "da-DK" },
},
null,
null
)
)
]
);
content.Properties["blocks"]!.SetValue(JsonSerializer.Serialize(blockListValue));
ContentService.Save(content);
var publishResult = ContentService.Publish(content, ["en-US"]);
Assert.IsTrue(publishResult.Success);
publishResult = ContentService.Publish(content, ["da-DK"]);
Assert.Multiple(() =>
{
Assert.IsFalse(publishResult.Success);
Assert.IsNotNull(publishResult.InvalidProperties);
Assert.AreEqual(1, publishResult.InvalidProperties.Count());
Assert.AreEqual("blocks", publishResult.InvalidProperties.First().Alias);
});
}
[Test]
public async Task Cannot_Publish_Missing_Variant_Properties()
{
var elementType = CreateElementTypeWithValidation();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var content = CreateContent(contentType, elementType, [], false);
var blockListValue = BlockListPropertyValue(
elementType,
[
(
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant content value" },
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant settings value" },
new() { Alias = "variantText", Value = "Valid settings value in English", Culture = "en-US" },
},
null,
null
)
)
]
);
// make sure all blocks are exposed
blockListValue.Expose =
[
new() { ContentKey = blockListValue.ContentData[0].Key, Culture = "en-US" },
new() { ContentKey = blockListValue.ContentData[0].Key, Culture = "da-DK" },
];
content.Properties["blocks"]!.SetValue(JsonSerializer.Serialize(blockListValue));
ContentService.Save(content);
var publishResult = ContentService.Publish(content, ["en-US"]);
Assert.IsTrue(publishResult.Success);
publishResult = ContentService.Publish(content, ["da-DK"]);
Assert.Multiple(() =>
{
Assert.IsFalse(publishResult.Success);
Assert.IsNotNull(publishResult.InvalidProperties);
Assert.AreEqual(1, publishResult.InvalidProperties.Count());
Assert.AreEqual("blocks", publishResult.InvalidProperties.First().Alias);
});
}
}

View File

@@ -0,0 +1,515 @@
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
public partial class BlockListElementLevelVariationTests
{
private IContentValidationService ContentValidationService => GetRequiredService<IContentValidationService>();
[Test]
public async Task Can_Validate_Invalid_Properties()
{
var elementType = CreateElementTypeWithValidation();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var blockListValue = BlockListPropertyValue(
elementType,
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Invalid invariant content value" },
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Invalid content value in Danish", Culture = "da-DK" },
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant settings value" },
new() { Alias = "variantText", Value = "Invalid settings value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid settings value in Danish", Culture = "da-DK" },
},
null,
null));
var result = await ContentValidationService.ValidatePropertiesAsync(
new ContentCreateModel
{
ContentTypeKey = contentType.Key,
Variants =
[
new VariantModel { Name = "Name en-US", Properties = [], Culture = "en-US", Segment = null },
new VariantModel { Name = "Name da-DK", Properties = [], Culture = "da-DK", Segment = null }
],
InvariantProperties =
[
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
]
},
contentType);
var errors = result.ValidationErrors.ToArray();
Assert.Multiple(() =>
{
Assert.AreEqual(3, errors.Length);
Assert.IsTrue(errors.All(error => error.Alias == "blocks" && error.Culture == null && error.Segment == null));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[0].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[2].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[1].value"));
});
}
[Test]
public async Task Can_Validate_Invalid_Properties_Nested_Blocks()
{
var (rootElementType, nestedElementType) = await CreateElementTypeWithValidationAndNestedBlocksAsync();
var rootBlockListDataType = await CreateBlockListDataType(rootElementType);
var contentType = CreateContentType(ContentVariation.Culture, rootBlockListDataType);
var blockListValue = BlockListPropertyValue(
rootElementType,
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new()
{
Alias = "nestedBlocks",
Value = BlockListPropertyValue(
nestedElementType,
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Invalid nested invariant content value" },
new() { Alias = "variantText", Value = "Valid nested content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Invalid nested content value in Danish", Culture = "da-DK" },
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid nested invariant settings value" },
new() { Alias = "variantText", Value = "Invalid nested settings value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid nested settings value in Danish", Culture = "da-DK" },
},
null,
null))
},
new() { Alias = "invariantText", Value = "Invalid invariant content value" },
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Invalid content value in Danish", Culture = "da-DK" },
},
new List<BlockPropertyValue>
{
new()
{
Alias = "nestedBlocks",
Value = BlockListPropertyValue(
nestedElementType,
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid nested invariant content value" },
new() { Alias = "variantText", Value = "Invalid nested content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid nested content value in Danish", Culture = "da-DK" },
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Invalid nested invariant settings value" },
new() { Alias = "variantText", Value = "Valid nested settings value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Invalid nested settings value in Danish", Culture = "da-DK" },
},
null,
null))
},
new() { Alias = "invariantText", Value = "Valid invariant content value" },
new() { Alias = "variantText", Value = "Invalid content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid content value in Danish", Culture = "da-DK" },
},
null,
null));
var result = await ContentValidationService.ValidatePropertiesAsync(
new ContentCreateModel
{
ContentTypeKey = contentType.Key,
Variants =
[
new VariantModel { Name = "Name en-US", Properties = [], Culture = "en-US", Segment = null },
new VariantModel { Name = "Name da-DK", Properties = [], Culture = "da-DK", Segment = null }
],
InvariantProperties =
[
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
]
},
contentType);
var errors = result.ValidationErrors.ToArray();
Assert.Multiple(() =>
{
Assert.AreEqual(9, errors.Length);
Assert.IsTrue(errors.All(error => error.Alias == "blocks" && error.Culture == null && error.Segment == null));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[0].value.contentData[0].values[0].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[0].value.contentData[0].values[2].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[0].value.settingsData[0].values[1].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[1].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[3].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[0].value.contentData[0].values[1].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[0].value.settingsData[0].values[0].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[0].value.settingsData[0].values[2].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[2].value"));
});
}
[Test]
public async Task Can_Validate_Invalid_Properties_Specific_Culture_Only()
{
var elementType = CreateElementTypeWithValidation();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var blockListValue = BlockListPropertyValue(
elementType,
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Invalid invariant content value" },
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Invalid content value in Danish", Culture = "da-DK" },
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant settings value" },
new() { Alias = "variantText", Value = "Invalid settings value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid settings value in Danish", Culture = "da-DK" },
},
null,
null));
var result = await ContentValidationService.ValidatePropertiesAsync(
new ContentCreateModel
{
ContentTypeKey = contentType.Key,
Variants =
[
new VariantModel { Name = "Name en-US", Properties = [], Culture = "en-US", Segment = null },
new VariantModel { Name = "Name da-DK", Properties = [], Culture = "da-DK", Segment = null }
],
InvariantProperties =
[
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
]
},
contentType,
new[] { "en-US" });
var errors = result.ValidationErrors.ToArray();
Assert.Multiple(() =>
{
Assert.AreEqual(2, errors.Length);
Assert.IsTrue(errors.All(error => error.Alias == "blocks" && error.Culture == null && error.Segment == null));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[0].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[1].value"));
});
}
[Test]
public async Task Can_Validate_Invalid_Properties_With_Wildcard_Culture()
{
var elementType = CreateElementTypeWithValidation();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var blockListValue = BlockListPropertyValue(
elementType,
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Invalid invariant content value" },
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Invalid content value in Danish", Culture = "da-DK" },
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant settings value" },
new() { Alias = "variantText", Value = "Invalid settings value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid settings value in Danish", Culture = "da-DK" },
},
null,
null));
var result = await ContentValidationService.ValidatePropertiesAsync(
new ContentCreateModel
{
ContentTypeKey = contentType.Key,
Variants =
[
new VariantModel { Name = "Name en-US", Properties = [], Culture = "en-US", Segment = null },
new VariantModel { Name = "Name da-DK", Properties = [], Culture = "da-DK", Segment = null }
],
InvariantProperties =
[
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
]
},
contentType,
["*"]);
var errors = result.ValidationErrors.ToArray();
Assert.Multiple(() =>
{
Assert.AreEqual(3, errors.Length);
Assert.IsTrue(errors.All(error => error.Alias == "blocks" && error.Culture == null && error.Segment == null));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[0].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[2].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[1].value"));
});
}
[Test]
public async Task Can_Validate_Missing_Properties()
{
var elementType = CreateElementTypeWithValidation();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var blockListValue = BlockListPropertyValue(
elementType,
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
// missing the mandatory "invariantText" (invariant) and "variantText" (in Danish)
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
},
new List<BlockPropertyValue>
{
// missing the mandatory "variantText" (in English)
new() { Alias = "invariantText", Value = "Valid invariant settings value" },
new() { Alias = "variantText", Value = "Valid settings value in Danish", Culture = "da-DK" },
},
null,
null));
// make sure all blocks are exposed
blockListValue.Expose =
[
new() { ContentKey = blockListValue.ContentData[0].Key, Culture = "en-US" },
new() { ContentKey = blockListValue.ContentData[0].Key, Culture = "da-DK" },
];
var result = await ContentValidationService.ValidatePropertiesAsync(
new ContentCreateModel
{
ContentTypeKey = contentType.Key,
Variants =
[
new VariantModel { Name = "Name en-US", Properties = [], Culture = "en-US", Segment = null },
new VariantModel { Name = "Name da-DK", Properties = [], Culture = "da-DK", Segment = null }
],
InvariantProperties =
[
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
]
},
contentType);
var errors = result.ValidationErrors.ToArray();
Assert.Multiple(() =>
{
Assert.AreEqual(3, errors.Length);
Assert.IsTrue(errors.All(error => error.Alias == "blocks" && error.Culture == null && error.Segment == null));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[?(@.alias == 'invariantText' && @.culture == null && @.segment == null)].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[?(@.alias == 'variantText' && @.culture == 'da-DK' && @.segment == null)].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[?(@.alias == 'variantText' && @.culture == 'en-US' && @.segment == null)].value"));
});
}
[Test]
public async Task Can_Validate_Missing_Properties_Nested_Blocks_Specific_Culture_Only()
{
var (rootElementType, nestedElementType) = await CreateElementTypeWithValidationAndNestedBlocksAsync();
var rootBlockListDataType = await CreateBlockListDataType(rootElementType);
var contentType = CreateContentType(ContentVariation.Culture, rootBlockListDataType);
var nestedContentBlocks = BlockListPropertyValue(
nestedElementType,
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
// missing the mandatory "invariantText" (invariant) and "variantText" (in Danish)
new() { Alias = "variantText", Value = "Valid nested content value in English", Culture = "en-US" },
},
new List<BlockPropertyValue>
{
// missing the mandatory "variantText" (in English)
new() { Alias = "invariantText", Value = "Valid nested invariant settings value" },
new() { Alias = "variantText", Value = "Valid nested settings value in Danish", Culture = "da-DK" },
},
null,
null));
var nestedSettingsBlocks = BlockListPropertyValue(
nestedElementType,
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
// missing the mandatory "variantText" (in English)
new() { Alias = "invariantText", Value = "Valid nested invariant content value" },
new() { Alias = "variantText", Value = "Valid nested content value in Danish", Culture = "da-DK" },
},
new List<BlockPropertyValue>
{
// missing the mandatory "invariantText" (invariant) and "variantText" (in Danish)
new() { Alias = "variantText", Value = "Valid nested settings value in English", Culture = "en-US" },
},
null,
null));
// make sure all nested blocks are exposed
nestedContentBlocks.Expose =
[
new() { ContentKey = nestedContentBlocks.ContentData[0].Key, Culture = "en-US" },
new() { ContentKey = nestedContentBlocks.ContentData[0].Key, Culture = "da-DK" },
];
nestedSettingsBlocks.Expose =
[
new() { ContentKey = nestedSettingsBlocks.ContentData[0].Key, Culture = "en-US" },
new() { ContentKey = nestedSettingsBlocks.ContentData[0].Key, Culture = "da-DK" },
];
var blockListValue = BlockListPropertyValue(
rootElementType,
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new()
{
Alias = "nestedBlocks",
Value = nestedContentBlocks
},
// missing the mandatory "variantText" (in both English and Danish)
new() { Alias = "invariantText", Value = "Valid root invariant content value" }
},
new List<BlockPropertyValue>
{
new()
{
Alias = "nestedBlocks",
Value = nestedSettingsBlocks
},
// missing the mandatory "invariantText"
new() { Alias = "variantText", Value = "Valid root settings value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "Valid root settings value in Danish", Culture = "da-DK" }
},
null,
null));
// make sure all root blocks are exposed
blockListValue.Expose =
[
new() { ContentKey = blockListValue.ContentData[0].Key, Culture = "en-US" },
new() { ContentKey = blockListValue.ContentData[0].Key, Culture = "da-DK" },
];
var result = await ContentValidationService.ValidatePropertiesAsync(
new ContentCreateModel
{
ContentTypeKey = contentType.Key,
Variants =
[
new VariantModel { Name = "Name en-US", Properties = [], Culture = "en-US", Segment = null },
new VariantModel { Name = "Name da-DK", Properties = [], Culture = "da-DK", Segment = null }
],
InvariantProperties =
[
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
]
},
contentType,
new[] { "da-DK" });
var errors = result.ValidationErrors.ToArray();
Assert.Multiple(() =>
{
Assert.AreEqual(6, errors.Length);
Assert.IsTrue(errors.All(error => error.Alias == "blocks" && error.Culture == null && error.Segment == null));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[0].value.contentData[0].values[?(@.alias == 'invariantText' && @.culture == null && @.segment == null)].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[0].value.contentData[0].values[?(@.alias == 'variantText' && @.culture == 'da-DK' && @.segment == null)].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".contentData[0].values[?(@.alias == 'variantText' && @.culture == 'da-DK' && @.segment == null)].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[0].value.settingsData[0].values[?(@.alias == 'invariantText' && @.culture == null && @.segment == null)].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[0].value.settingsData[0].values[?(@.alias == 'variantText' && @.culture == 'da-DK' && @.segment == null)].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[?(@.alias == 'invariantText' && @.culture == null && @.segment == null)].value"));
});
}
[Test]
public async Task Does_Not_Validate_Unexposed_Blocks()
{
var elementType = CreateElementTypeWithValidation();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var blockListValue = BlockListPropertyValue(
elementType,
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant content value" },
new() { Alias = "variantText", Value = "Valid content value in English", Culture = "en-US" },
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant settings value" },
new() { Alias = "variantText", Value = "Valid settings value in English", Culture = "en-US" },
},
null,
null));
// only expose the block in English
blockListValue.Expose =
[
new() { ContentKey = blockListValue.ContentData[0].Key, Culture = "en-US" },
];
var result = await ContentValidationService.ValidatePropertiesAsync(
new ContentCreateModel
{
ContentTypeKey = contentType.Key,
Variants =
[
new VariantModel { Name = "Name en-US", Properties = [], Culture = "en-US", Segment = null },
new VariantModel { Name = "Name da-DK", Properties = [], Culture = "da-DK", Segment = null }
],
InvariantProperties =
[
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
]
},
contentType,
["da-DK"]);
Assert.IsEmpty(result.ValidationErrors);
}
}

View File

@@ -146,6 +146,63 @@ public partial class BlockListElementLevelVariationTests : BlockEditorElementVar
return GetPublishedContent(content.Key);
}
private IContentType CreateElementTypeWithValidation()
{
var elementType = CreateElementType(ContentVariation.Culture);
foreach (var propertyType in elementType.PropertyTypes)
{
propertyType.Mandatory = true;
propertyType.ValidationRegExp = "^Valid.*$";
}
ContentTypeService.Save(elementType);
return elementType;
}
private async Task<(IContentType RootElementType, IContentType NestedElementType)> CreateElementTypeWithValidationAndNestedBlocksAsync()
{
var nestedElementType = CreateElementTypeWithValidation();
var nestedBlockListDataType = await CreateBlockListDataType(nestedElementType);
var rootElementType = new ContentTypeBuilder()
.WithAlias("myRootElementType")
.WithName("My Root Element Type")
.WithIsElement(true)
.WithContentVariation(ContentVariation.Culture)
.AddPropertyType()
.WithAlias("invariantText")
.WithName("Invariant text")
.WithMandatory(true)
.WithValidationRegExp("^Valid.*$")
.WithDataTypeId(Constants.DataTypes.Textbox)
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox)
.WithValueStorageType(ValueStorageType.Nvarchar)
.WithVariations(ContentVariation.Nothing)
.Done()
.AddPropertyType()
.WithAlias("variantText")
.WithName("Variant text")
.WithMandatory(true)
.WithValidationRegExp("^Valid.*$")
.WithDataTypeId(Constants.DataTypes.Textbox)
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox)
.WithValueStorageType(ValueStorageType.Nvarchar)
.WithVariations(ContentVariation.Culture)
.Done()
.AddPropertyType()
.WithAlias("nestedBlocks")
.WithName("Nested blocks")
.WithDataTypeId(nestedBlockListDataType.Id)
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.BlockList)
.WithValueStorageType(ValueStorageType.Ntext)
.WithVariations(ContentVariation.Nothing)
.Done()
.Build();
ContentTypeService.Save(rootElementType);
return (rootElementType, nestedElementType);
}
private class BlockProperty
{
public BlockProperty(IList<BlockPropertyValue> blockContentValues, IList<BlockPropertyValue> blockSettingsValues, string? culture, string? segment)

View File

@@ -102,6 +102,10 @@ public class ContentValidationServiceTests : UmbracoIntegrationTestWithContent
{ "alias": "text", "value": "Valid nested setting text)" }
]
}
],
"expose": [
{ "contentKey": "f36cebfa-d03b-4451-9e60-4bf32c5b1e2f", "culture": null, "segment": null },
{ "contentKey": "b8173e4a-0618-475c-8277-c3c6af68bee6", "culture": null, "segment": null }
]
}
}
@@ -130,6 +134,10 @@ public class ContentValidationServiceTests : UmbracoIntegrationTestWithContent
{ "alias": "text", "value": "Invalid root setting text (ref #3)" }
]
}
],
"expose": [
{ "contentKey": "9addc377-c02c-4db0-88c2-73b933704f7b", "culture": null, "segment": null },
{ "contentKey": "3af93b5b-5e40-4c64-b142-2564309fc4c7", "culture": null, "segment": null }
]
}
"""

View File

@@ -167,6 +167,9 @@
<Compile Update="Umbraco.Infrastructure\PropertyEditors\BlockListElementLevelVariationTests.Publishing.cs">
<DependentUpon>BlockListElementLevelVariationTests.cs</DependentUpon>
</Compile>
<Compile Update="Umbraco.Infrastructure\PropertyEditors\BlockListElementLevelVariationTests.Validation.cs">
<DependentUpon>BlockListElementLevelVariationTests.cs</DependentUpon>
</Compile>
<Compile Update="Umbraco.Core\Services\DocumentNavigationServiceTests.Copy.cs">
<DependentUpon>DocumentNavigationServiceTests.cs</DependentUpon>
</Compile>

View File

@@ -3,6 +3,7 @@
using System.Text.Json.Nodes;
using NUnit.Framework;
using Umbraco.Cms.Core.Models.Validation;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Infrastructure.Serialization;
@@ -23,7 +24,8 @@ public class ColorListValidatorTest
validator.Validate(
"hello",
null,
null);
null,
PropertyValidationContext.Empty());
Assert.AreEqual(1, result.Count());
}
@@ -35,7 +37,8 @@ public class ColorListValidatorTest
validator.Validate(
new JsonArray("hello", "world"),
null,
null);
null,
PropertyValidationContext.Empty());
Assert.AreEqual(1, result.Count());
}
@@ -51,7 +54,8 @@ public class ColorListValidatorTest
JsonNode.Parse("""{"value": "ABC", "label": "Three"}"""),
JsonNode.Parse("""{"value": "1234567", "label": "Four"}""")),
null,
null);
null,
PropertyValidationContext.Empty());
Assert.AreEqual(2, result.Count());
}
}

View File

@@ -3,6 +3,7 @@
using System.Text.Json.Nodes;
using NUnit.Framework;
using Umbraco.Cms.Core.Models.Validation;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Infrastructure.Serialization;
@@ -22,7 +23,8 @@ public class EnsureUniqueValuesValidatorTest
var result = validator.Validate(
"hello",
null,
null);
null,
PropertyValidationContext.Empty());
Assert.AreEqual(1, result.Count());
}
@@ -34,7 +36,8 @@ public class EnsureUniqueValuesValidatorTest
validator.Validate(
new JsonArray("hello", "world"),
null,
null);
null,
PropertyValidationContext.Empty());
Assert.AreEqual(0, result.Count());
}
@@ -46,7 +49,8 @@ public class EnsureUniqueValuesValidatorTest
validator.Validate(
new JsonArray("one", "two", "three"),
null,
null);
null,
PropertyValidationContext.Empty());
Assert.AreEqual(0, result.Count());
}
@@ -58,7 +62,8 @@ public class EnsureUniqueValuesValidatorTest
validator.Validate(
new JsonArray("one", "one"),
null,
null);
null,
PropertyValidationContext.Empty());
Assert.AreEqual(1, result.Count());
}
@@ -70,7 +75,8 @@ public class EnsureUniqueValuesValidatorTest
validator.Validate(
new JsonArray("one", "two", "three", "one", "two"),
null,
null);
null,
PropertyValidationContext.Empty());
Assert.AreEqual(2, result.Count());
}
@@ -82,7 +88,8 @@ public class EnsureUniqueValuesValidatorTest
validator.Validate(
null,
null,
null);
null,
PropertyValidationContext.Empty());
Assert.AreEqual(0, result.Count());
}
@@ -95,7 +102,8 @@ public class EnsureUniqueValuesValidatorTest
validator.Validate(
value,
null,
null);
null,
PropertyValidationContext.Empty());
Assert.AreEqual(0, result.Count());
}
@@ -108,7 +116,8 @@ public class EnsureUniqueValuesValidatorTest
validator.Validate(
value,
null,
null);
null,
PropertyValidationContext.Empty());
Assert.AreEqual(0, result.Count());
}
@@ -121,7 +130,8 @@ public class EnsureUniqueValuesValidatorTest
validator.Validate(
value,
null,
null);
null,
PropertyValidationContext.Empty());
Assert.AreEqual(0, result.Count());
}
}