Perform value detection at all property value levels (#20067)

* Perform value detection at all property value levels

* Added null cases for tests.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
Kenn Jacobsen
2025-09-04 08:38:11 +02:00
committed by GitHub
parent de227993de
commit aab3f6d10f
2 changed files with 170 additions and 4 deletions

View File

@@ -96,13 +96,23 @@ internal sealed class PublishedProperty : PublishedPropertyBase
_content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, _propertyTypeAlias, ref culture, ref segment);
var value = GetSourceValue(culture, segment);
var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source);
if (hasValue.HasValue)
var isValue = PropertyType.IsValue(value, PropertyValueLevel.Source);
if (isValue.HasValue)
{
return hasValue.Value;
return isValue.Value;
}
return PropertyType.IsValue(GetInterValue(culture, segment), PropertyValueLevel.Object) ?? false;
value = GetInterValue(culture, segment);
isValue = PropertyType.IsValue(value, PropertyValueLevel.Inter);
if (isValue.HasValue)
{
return isValue.Value;
}
value = GetValue(culture, segment);
isValue = PropertyType.IsValue(value, PropertyValueLevel.Object);
return isValue ?? false;
}
public override object? GetSourceValue(string? culture = null, string? segment = null)

View File

@@ -0,0 +1,156 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Infrastructure.HybridCache;
using Umbraco.Cms.Infrastructure.HybridCache.Factories;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
internal sealed class PropertyValueLevelDetectionTests : UmbracoIntegrationTestWithContent
{
private IPublishedContentFactory PublishedContentFactory => GetRequiredService<IPublishedContentFactory>();
private IPublishedValueFallback PublishedValueFallback => GetRequiredService<IPublishedValueFallback>();
private IPublishedContentTypeFactory PublishedContentTypeFactory => GetRequiredService<IPublishedContentTypeFactory>();
protected override void CustomTestSetup(IUmbracoBuilder builder)
{
var requestCache = new DictionaryAppCache();
var appCaches = new AppCaches(
NoAppCache.Instance,
requestCache,
new IsolatedCaches(type => NoAppCache.Instance));
builder.Services.AddUnique(appCaches);
builder.PropertyValueConverters()
.Remove<global::Umbraco.Cms.Core.PropertyEditors.ValueConverters.ContentPickerValueConverter>()
.Append<PropertyValueLevelDetectionTestsConverter>();
}
[TestCase("validSourceLevel", true)]
[TestCase("validInterLevel", true)]
[TestCase("validObjectLevel", true)]
[TestCase("invalidSourceLevel", false)]
[TestCase("invalidInterLevel", false)]
[TestCase("invalidObjectLevel", false)]
[TestCase("nullSourceLevel", false)]
[TestCase("nullInterLevel", false)]
[TestCase("nullObjectLevel", false)]
[TestCase("somethingElse", false)]
public void Can_Detect_Property_Value_At_All_Levels_For_Document(string titleValue, bool expectHasValue)
{
var contentCacheNode = new ContentCacheNode
{
Id = Textpage.Id,
Key = Textpage.Key,
ContentTypeId = Textpage.ContentType.Id,
CreateDate = Textpage.CreateDate,
CreatorId = Textpage.CreatorId,
SortOrder = Textpage.SortOrder,
Data = new ContentData(
Textpage.Name,
"text-page",
Textpage.VersionId,
Textpage.UpdateDate,
Textpage.WriterId,
Textpage.TemplateId,
true,
new Dictionary<string, PropertyData[]>
{
{
"title", new[]
{
new PropertyData
{
Value = titleValue,
Culture = string.Empty,
Segment = string.Empty,
},
}
},
},
null),
};
var result = PublishedContentFactory.ToIPublishedContent(contentCacheNode, false);
Assert.IsNotNull(result);
Assert.AreEqual(expectHasValue, result.HasValue("title"));
// NOTE: the .Value() extensions always end up returning the source value, no matter if the property value
// converter returns false from .HasValue()... this is the case both at property and at content level
var value = result.Value<string>(PublishedValueFallback, "title");
Assert.AreEqual(titleValue, value);
}
[TestCase("validSourceLevel", true)]
[TestCase("validInterLevel", true)]
[TestCase("validObjectLevel", true)]
[TestCase("invalidSourceLevel", false)]
[TestCase("invalidInterLevel", false)]
[TestCase("invalidObjectLevel", false)]
[TestCase("nullSourceLevel", false)]
[TestCase("nullInterLevel", false)]
[TestCase("nullObjectLevel", false)]
[TestCase("somethingElse", false)]
public async Task Can_Detect_Property_Value_At_All_Levels_For_Element(string titleValue, bool expectHasValue)
{
var elementType = ContentTypeBuilder.CreateSimpleContentType("umbElement");
elementType.IsElement = true;
await ContentTypeService.UpdateAsync(elementType, Constants.Security.SuperUserKey);
var publishedElementType = PublishedContentTypeFactory.CreateContentType(elementType);
var element = new PublishedElement(
publishedElementType,
Guid.NewGuid(),
new Dictionary<string, object> { { "title", titleValue } },
false,
new VariationContext());
Assert.AreEqual(expectHasValue, element.HasValue("title"));
// NOTE: the .Value() extensions always end up returning the source value, no matter if the property value
// converter returns false from .HasValue()... this is the case both at property and at content level
var value = element.Value<string>(PublishedValueFallback, "title");
Assert.AreEqual(titleValue, value);
}
[HideFromTypeFinder]
public class PropertyValueLevelDetectionTestsConverter : PropertyValueConverterBase
{
public override bool IsConverter(IPublishedPropertyType propertyType)
=> propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TextBox;
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof(string);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.None;
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
=> source as string;
public override bool? IsValue(object? value, PropertyValueLevel level)
=> level switch
{
PropertyValueLevel.Source when value?.ToString() is "validSourceLevel" => true,
PropertyValueLevel.Source when value?.ToString() is "invalidSourceLevel" => false,
PropertyValueLevel.Source when value?.ToString() is "nullSourceLevel" => null,
PropertyValueLevel.Inter when value?.ToString() is "validInterLevel" => true,
PropertyValueLevel.Inter when value?.ToString() is "invalidInterLevel" => false,
PropertyValueLevel.Source when value?.ToString() is "nullInternalLevel" => null,
PropertyValueLevel.Object when value?.ToString() is "validObjectLevel" => true,
PropertyValueLevel.Object when value?.ToString() is "invalidObjectLevel" => false,
PropertyValueLevel.Source when value?.ToString() is "nullObjectlLevel" => null,
_ => null,
};
}
}