Compare commits

...
6 changed files with 277 additions and 11 deletions
@@ -23,7 +23,10 @@ public abstract class BlockEditorValidatorBase<TValue, TLayout> : ComplexEditorV
var elementTypeValidation = new List<ElementTypeValidationModel>();
var isWildcardCulture = validationContext.Culture == "*";
var validationContextCulture = isWildcardCulture ? null : validationContext.Culture.NullOrWhiteSpaceAsNull();
elementTypeValidation.AddRange(GetBlockEditorDataValidation(blockEditorData, validationContextCulture, validationContext.Segment));
foreach (var segment in validationContext.SegmentsBeingValidated.DefaultIfEmpty(null))
{
elementTypeValidation.AddRange(GetBlockEditorDataValidation(blockEditorData, validationContextCulture, segment));
}
if (validationContextCulture is null)
{
@@ -119,12 +122,9 @@ public abstract class BlockEditorValidatorBase<TValue, TLayout> : ComplexEditorV
continue;
}
if (segment != "*")
if (segment != "*" && blockPropertyValue.Segment.InvariantEquals(segment) is false)
{
if (propertyType.VariesBySegment() != (segment is not null) || blockPropertyValue.Segment.InvariantEquals(segment) is false)
{
continue;
}
continue;
}
elementValidation.AddPropertyTypeValidation(
@@ -33,7 +33,7 @@ public sealed class BlockEditorConverter
_blockEditorVarianceHandler = blockEditorVarianceHandler;
}
public IPublishedElement? ConvertToElement(IPublishedElement owner, BlockItemData data, PropertyCacheLevel referenceCacheLevel, bool preview)
public IPublishedElement? ConvertToElement(IPublishedElement owner, BlockItemData data, IEnumerable<BlockItemVariation> variations, PropertyCacheLevel referenceCacheLevel, bool preview)
{
// Only convert element types - content types will cause an exception when PublishedModelFactory creates the model
IPublishedContentType? publishedContentType = _publishedContentTypeCache.Get(PublishedItemType.Element, data.ContentTypeKey);
@@ -48,6 +48,10 @@ public sealed class BlockEditorConverter
.PropertyTypes
.ToDictionary(propertyType => propertyType.Alias);
var variationKeys = variations
.Select(v => VariationKey(v.Culture, v.Segment))
.ToArray();
var propertyValues = new Dictionary<string, object?>();
foreach (BlockPropertyValue property in data.Values)
{
@@ -72,6 +76,11 @@ public sealed class BlockEditorConverter
? variationContext.Segment
: null;
if (expectedSegment is not null && variationKeys.Contains(VariationKey(expectedCulture, expectedSegment)) is false)
{
expectedSegment = null;
}
if (alignedProperty.Culture.NullOrWhiteSpaceAsNull().InvariantEquals(expectedCulture.NullOrWhiteSpaceAsNull())
&& alignedProperty.Segment.NullOrWhiteSpaceAsNull().InvariantEquals(expectedSegment.NullOrWhiteSpaceAsNull()))
{
@@ -95,6 +104,8 @@ public sealed class BlockEditorConverter
element = _publishedModelFactory.CreateModel(element);
return element;
string VariationKey(string? culture, string? segment) => $"{culture}:{segment}";
}
public Type GetModelType(Guid contentTypeKey)
@@ -137,7 +137,8 @@ internal abstract class BlockPropertyValueCreatorBase<TBlockModel, TBlockItemMod
continue;
}
IPublishedElement? element = BlockEditorConverter.ConvertToElement(owner, data, referenceCacheLevel, preview);
IEnumerable<BlockItemVariation> variations = converted.BlockValue.Expose.Where(e => e.ContentKey == data.Key);
IPublishedElement? element = BlockEditorConverter.ConvertToElement(owner, data, variations, referenceCacheLevel, preview);
if (element == null)
{
continue;
@@ -154,7 +155,7 @@ internal abstract class BlockPropertyValueCreatorBase<TBlockModel, TBlockItemMod
: null;
if (expose.Any(v =>
v.ContentKey == element.Key && v.Culture == expectedBlockVariationCulture &&
v.Segment == expectedBlockVariationSegment) is false)
(v.Segment == expectedBlockVariationSegment || v.Segment is null)) is false)
{
continue;
}
@@ -172,6 +173,9 @@ internal abstract class BlockPropertyValueCreatorBase<TBlockModel, TBlockItemMod
var settingsPublishedElements = new Dictionary<Guid, IPublishedElement>();
var validSettingsElementTypes = blockConfigMap.Values.Select(x => x.SettingsElementTypeKey)
.Where(x => x.HasValue).Distinct().ToList();
var contentSettingsKeyMapping = converted.References
.Where(r => r.SettingsKey.HasValue)
.ToDictionary(c => c.SettingsKey!.Value, c => c.ContentKey);
foreach (BlockItemData data in converted.BlockValue.SettingsData)
{
if (!validSettingsElementTypes.Contains(data.ContentTypeKey))
@@ -179,7 +183,13 @@ internal abstract class BlockPropertyValueCreatorBase<TBlockModel, TBlockItemMod
continue;
}
IPublishedElement? element = BlockEditorConverter.ConvertToElement(owner, data, referenceCacheLevel, preview);
if (contentSettingsKeyMapping.TryGetValue(data.Key, out Guid contentKey) is false)
{
continue;
}
IEnumerable<BlockItemVariation> variations = converted.BlockValue.Expose.Where(e => e.ContentKey == contentKey);
IPublishedElement? element = BlockEditorConverter.ConvertToElement(owner, data, variations, referenceCacheLevel, preview);
if (element is null)
{
continue;
@@ -206,7 +206,6 @@ export class UmbDocumentTypeWorkspaceViewSettingsElement extends UmbLitElement i
#renderVaryBySegmentProperty() {
if (!this._useSegments) return nothing;
if (this._isElement) return nothing;
return html`
<umb-property-layout
@@ -2023,4 +2023,132 @@ internal partial class BlockListElementLevelVariationTests
Assert.IsEmpty(value);
}
}
[Test]
public async Task Missing_Segment_Performs_Fallback_To_Default_Segment()
{
var elementType = CreateElementType(ContentVariation.Segment);
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Segment, blockListDataType);
var content = CreateContent(
contentType,
elementType,
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "The invariant content value" },
new() { Alias = "variantText", Value = "The variant content value (Default)" },
new() { Alias = "variantText", Value = "The variant content value (Segment 2)", Segment = "s2" }
},
[],
true);
AssertPropertyValues(null, "The invariant content value", "The variant content value (Default)");
AssertPropertyValues("s1", "The invariant content value", "The variant content value (Default)");
AssertPropertyValues("s2", "The invariant content value", "The variant content value (Segment 2)");
var blockListValue = JsonSerializer.Deserialize<BlockListValue>((string)content.Properties["blocks"]!.GetValue()!);
blockListValue.ContentData[0].Values.Add(new BlockPropertyValue { Alias = "variantText", Value = "The variant content value (Segment 1)", Segment = "s1" });
blockListValue.Expose =
[
new() { ContentKey = blockListValue.ContentData[0].Key },
new() { ContentKey = blockListValue.ContentData[0].Key, Segment = "s1" },
new() { ContentKey = blockListValue.ContentData[0].Key, Segment = "s2" },
];
content.Properties["blocks"]!.SetValue(JsonSerializer.Serialize(blockListValue));
ContentService.Save(content);
PublishContent(content, contentType);
AssertPropertyValues(null, "The invariant content value", "The variant content value (Default)");
AssertPropertyValues("s1", "The invariant content value", "The variant content value (Segment 1)");
AssertPropertyValues("s2", "The invariant content value", "The variant content value (Segment 2)");
void AssertPropertyValues(string? segment, string expectedInvariantContentValue, string expectedVariantContentValue)
{
SetVariationContext(null, segment);
var publishedContent = GetPublishedContent(content.Key);
var value = publishedContent.Value<BlockListModel>("blocks");
Assert.IsNotNull(value);
Assert.AreEqual(1, value.Count);
var blockListItem = value.First();
Assert.AreEqual(2, blockListItem.Content.Properties.Count());
Assert.Multiple(() =>
{
Assert.AreEqual(expectedInvariantContentValue, blockListItem.Content.Value<string>("invariantText"));
Assert.AreEqual(expectedVariantContentValue, blockListItem.Content.Value<string>("variantText"));
});
}
}
[Test]
public async Task Missing_Property_Value_For_Existing_Segment_Does_Not_Perform_Fallback_To_Default_Segment()
{
var elementType = CreateElementType(ContentVariation.Segment);
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Segment, blockListDataType);
var content = CreateContent(contentType, elementType, [], false);
var blockListValue = BlockListPropertyValue(
elementType,
[
(
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "The invariant content value" },
new() { Alias = "variantText", Value = "The variant content value (Default)" },
new() { Alias = "variantText", Value = "The variant content value (Segment 2)", Segment = "s2"}
},
[],
null,
null)
)
]);
blockListValue.Expose =
[
new() { ContentKey = blockListValue.ContentData[0].Key },
new() { ContentKey = blockListValue.ContentData[0].Key, Segment = "s1" },
new() { ContentKey = blockListValue.ContentData[0].Key, Segment = "s2" },
];
content.Properties["blocks"]!.SetValue(JsonSerializer.Serialize(blockListValue));
ContentService.Save(content);
PublishContent(content, contentType);
AssertPropertyValues(null, "The invariant content value", "The variant content value (Default)");
AssertPropertyValues("s1", "The invariant content value", string.Empty);
AssertPropertyValues("s2", "The invariant content value", "The variant content value (Segment 2)");
void AssertPropertyValues(string? segment, string expectedInvariantContentValue, string expectedVariantContentValue)
{
SetVariationContext(null, segment);
var publishedContent = GetPublishedContent(content.Key);
var value = publishedContent.Value<BlockListModel>("blocks");
Assert.IsNotNull(value);
Assert.AreEqual(1, value.Count);
var blockListItem = value.First();
Assert.AreEqual(2, blockListItem.Content.Properties.Count());
Assert.Multiple(() =>
{
Assert.AreEqual(expectedInvariantContentValue, blockListItem.Content.Value<string>("invariantText"));
Assert.AreEqual(expectedVariantContentValue, blockListItem.Content.Value<string>("variantText"));
});
}
}
}
@@ -64,6 +64,63 @@ internal partial class BlockListElementLevelVariationTests
});
}
[Test]
public async Task Can_Validate_Invalid_Properties_Segment_Variant()
{
var elementType = CreateElementTypeWithValidation(ContentVariation.Segment);
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Segment, 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 (Default)", Segment = null },
new() { Alias = "variantText", Value = "Invalid content value (S1)", Segment = "s1" },
new() { Alias = "variantText", Value = "Valid content value (S2)", Segment = "s2" },
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "Valid invariant settings value" },
new() { Alias = "variantText", Value = "Invalid content value (Default)", Segment = null },
new() { Alias = "variantText", Value = "Valid content value (S1)", Segment = "s1" },
new() { Alias = "variantText", Value = "Invalid content value (S2)", Segment = "s2" },
},
null,
null));
var result = await ContentValidationService.ValidatePropertiesAsync(
new ContentCreateModel
{
ContentTypeKey = contentType.Key,
Variants =
[
new VariantModel { Name = "Name", Segment = null },
new VariantModel { Name = "Name", Segment = "s1" },
new VariantModel { Name = "Name", Segment = "s2" },
],
Properties =
[
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
]
},
contentType);
var errors = result.ValidationErrors.ToArray();
Assert.Multiple(() =>
{
Assert.AreEqual(4, 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"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[3].value"));
});
}
[Test]
public async Task Can_Validate_Invalid_Properties_Nested_Blocks()
{
@@ -348,6 +405,67 @@ internal partial class BlockListElementLevelVariationTests
});
}
[Test]
public async Task Can_Validate_Missing_Properties_Segment_Variant()
{
var elementType = CreateElementTypeWithValidation(ContentVariation.Segment);
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Segment, blockListDataType);
var blockListValue = BlockListPropertyValue(
elementType,
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue>
{
// missing the mandatory "invariantText" (invariant) and "variantText" (for s2)
new() { Alias = "variantText", Value = "Valid content value (S1)", Segment = "s1" },
},
new List<BlockPropertyValue>
{
// missing the mandatory "variantText" (for s1)
new() { Alias = "invariantText", Value = "Valid invariant settings value" },
new() { Alias = "variantText", Value = "Valid settings value in Danish", Segment = "s2" },
},
null,
null));
// make sure all blocks are exposed
blockListValue.Expose =
[
new() { ContentKey = blockListValue.ContentData[0].Key, Segment = null },
new() { ContentKey = blockListValue.ContentData[0].Key, Segment = "s1" },
new() { ContentKey = blockListValue.ContentData[0].Key, Segment = "s2" },
];
var result = await ContentValidationService.ValidatePropertiesAsync(
new ContentCreateModel
{
ContentTypeKey = contentType.Key,
Variants =
[
new VariantModel { Name = "Name", Segment = null },
new VariantModel { Name = "Name", Segment = "s1" },
new VariantModel { Name = "Name", Segment = "s2" },
],
Properties =
[
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 == null && @.segment == 's2')].value"));
Assert.IsNotNull(errors.FirstOrDefault(error => error.JsonPath == ".settingsData[0].values[?(@.alias == 'variantText' && @.culture == null && @.segment == 's1')].value"));
});
}
[Test]
[ConfigureBuilder(ActionName = nameof(ConfigureAllowEditInvariantFromNonDefaultTrue))]
public async Task Can_Validate_Missing_Properties_Nested_Blocks_Specific_Culture_Only_With_AllowEditInvariantFromNonDefault()