Fix variant invariancy with limited language (#17707)

* Add a way to configure IUmbracobuilder on a per testcase basis

* New logic for invariantVariantMerging

* bugfix

* Undo formatting changes

* Undo more automatic formatting

* Last automatic formatting correction

* Cleanup ConfigureBuilderAttribute

* Made propertyEditor tests internal
This commit is contained in:
Sven Geusens
2025-01-06 14:58:00 +01:00
committed by GitHub
parent 99f572837a
commit 1cd9e3e83f
25 changed files with 720 additions and 79 deletions

View File

@@ -202,4 +202,10 @@ public class DataEditor : IDataEditor
/// <inheritdoc />
public virtual object? MergePartialPropertyValueForCulture(object? sourceValue, object? targetValue, string? culture) => sourceValue;
public virtual object? MergeVariantInvariantPropertyValue(
object? sourceValue,
object? targetValue,
bool canUpdateInvariantData,
HashSet<string> allowedCultures) => sourceValue;
}

View File

@@ -65,4 +65,7 @@ public interface IDataEditor : IDiscoverable
/// <param name="culture">The culture (or null for invariant).</param>
/// <returns>The result of the merge operation.</returns>
object? MergePartialPropertyValueForCulture(object? sourceValue, object? targetValue, string? culture) => sourceValue;
object? MergeVariantInvariantPropertyValue(object? sourceValue, object? targetValue,
bool canUpdateInvariantData, HashSet<string> allowedCultures) => sourceValue;
}

View File

@@ -130,50 +130,74 @@ internal sealed class ContentEditingService
ILanguage? defaultLanguage = await _languageService.GetDefaultLanguageAsync();
foreach (var culture in contentWithPotentialUnallowedChanges.EditedCultures ?? contentWithPotentialUnallowedChanges.PublishedCultures)
var disallowedCultures = (contentWithPotentialUnallowedChanges.EditedCultures ??
contentWithPotentialUnallowedChanges.PublishedCultures)
.Where(culture => allowedCultures.Contains(culture) is false).ToList();
var allowedToEditDefaultLanguage = allowedCultures.Contains(defaultLanguage?.IsoCode ?? string.Empty);
var variantProperties = new List<IProperty>();
var invariantWithVariantSupportProperties = new List<(IProperty Property, IDataEditor DataEditor)>();
var invariantProperties = new List<IProperty>();
// group properties in processing groups
foreach (IProperty property in contentWithPotentialUnallowedChanges.Properties)
{
if (allowedCultures.Contains(culture))
if (property.PropertyType.VariesByCulture())
{
continue;
variantProperties.Add(property);
}
// else override the updates values with the original values.
foreach (IProperty property in contentWithPotentialUnallowedChanges.Properties)
else if (_propertyEditorCollection.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor) && dataEditor.CanMergePartialPropertyValues(property.PropertyType))
{
// if the property varies by culture, simply overwrite the edited property value with the current property value
if (property.PropertyType.VariesByCulture())
{
var currentValue = existingContent?.Properties.First(x => x.Alias == property.Alias).GetValue(culture, null, false);
invariantWithVariantSupportProperties.Add((property, dataEditor));
}
else
{
invariantProperties.Add(property);
}
}
// if the property varies by culture, simply overwrite the edited property value with the current property value for every culture
foreach (IProperty property in variantProperties)
{
foreach (var culture in disallowedCultures)
{
var currentValue = existingContent?.Properties.First(x => x.Alias == property.Alias)
.GetValue(culture, null, false);
property.SetValue(currentValue, culture, null);
continue;
}
// if the property does not vary by culture and the data editor supports variance within invariant property values,
// we need perform a merge between the edited property value and the current property value
if (_propertyEditorCollection.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor) && dataEditor.CanMergePartialPropertyValues(property.PropertyType))
{
var currentValue = existingContent?.Properties.First(x => x.Alias == property.Alias).GetValue(null, null, false);
var editedValue = contentWithPotentialUnallowedChanges.Properties.First(x => x.Alias == property.Alias).GetValue(null, null, false);
var mergedValue = dataEditor.MergePartialPropertyValueForCulture(currentValue, editedValue, culture);
// If we are not allowed to edit invariant properties, overwrite the edited property value with the current property value.
if (_contentSettings.AllowEditInvariantFromNonDefault is false && culture == defaultLanguage?.IsoCode)
{
mergedValue = dataEditor.MergePartialPropertyValueForCulture(currentValue, mergedValue, null);
}
property.SetValue(mergedValue, null, null);
}
// If property does not support merging, we still need to overwrite if we are not allowed to edit invariant properties.
else if (_contentSettings.AllowEditInvariantFromNonDefault is false && culture == defaultLanguage?.IsoCode)
{
var currentValue = existingContent?.Properties.First(x => x.Alias == property.Alias).GetValue(null, null, false);
property.SetValue(currentValue, null, null);
}
}
}
// If property does not support merging, we still need to overwrite if we are not allowed to edit invariant properties.
if (_contentSettings.AllowEditInvariantFromNonDefault is false && allowedToEditDefaultLanguage is false)
{
foreach (IProperty property in invariantProperties)
{
var currentValue = existingContent?.Properties.First(x => x.Alias == property.Alias)
.GetValue(null, null, false);
property.SetValue(currentValue, null, null);
}
}
// if the property does not vary by culture and the data editor supports variance within invariant property values,
// we need perform a merge between the edited property value and the current property value
foreach ((IProperty Property, IDataEditor DataEditor) propertyWithEditor in invariantWithVariantSupportProperties)
{
var currentValue = existingContent?.Properties.First(x => x.Alias == propertyWithEditor.Property.Alias)
.GetValue(null, null, false);
var editedValue = contentWithPotentialUnallowedChanges.Properties
.First(x => x.Alias == propertyWithEditor.Property.Alias).GetValue(null, null, false);
// update the editedValue with a merged value of invariant data and allowed culture data using the currentValue as a fallback.
var mergedValue = propertyWithEditor.DataEditor.MergeVariantInvariantPropertyValue(
currentValue,
editedValue,
_contentSettings.AllowEditInvariantFromNonDefault || (defaultLanguage is not null && allowedCultures.Contains(defaultLanguage.IsoCode)),
allowedCultures);
propertyWithEditor.Property.SetValue(mergedValue, null, null);
}
return contentWithPotentialUnallowedChanges;
}
@@ -233,7 +257,8 @@ internal sealed class ContentEditingService
public async Task<Attempt<IContent?, ContentEditingOperationStatus>> CopyAsync(Guid key, Guid? parentKey, bool relateToOriginal, bool includeDescendants, Guid userKey)
=> await HandleCopyAsync(key, parentKey, relateToOriginal, includeDescendants, userKey);
public async Task<ContentEditingOperationStatus> SortAsync(Guid? parentKey, IEnumerable<SortingModel> sortingModels, Guid userKey)
public async Task<ContentEditingOperationStatus> SortAsync(Guid? parentKey, IEnumerable<SortingModel> sortingModels,
Guid userKey)
=> await HandleSortAsync(parentKey, sortingModels, userKey);
private async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCulturesAndPropertiesAsync(

View File

@@ -36,6 +36,16 @@ public class BlockGridPropertyEditor : BlockGridPropertyEditorBase
return valueEditor.MergePartialPropertyValueForCulture(sourceValue, targetValue, culture);
}
public override object? MergeVariantInvariantPropertyValue(
object? sourceValue,
object? targetValue,
bool canUpdateInvariantData,
HashSet<string> allowedCultures)
{
var valueEditor = (BlockGridEditorPropertyValueEditor)GetValueEditor();
return valueEditor.MergeVariantInvariantPropertyValue(sourceValue, targetValue, canUpdateInvariantData,allowedCultures);
}
#region Pre Value Editor
protected override IConfigurationEditor CreateConfigurationEditor() => new BlockGridConfigurationEditor(_ioHelper);

View File

@@ -50,6 +50,16 @@ public class BlockListPropertyEditor : BlockListPropertyEditorBase
return valueEditor.MergePartialPropertyValueForCulture(sourceValue, targetValue, culture);
}
public override object? MergeVariantInvariantPropertyValue(
object? sourceValue,
object? targetValue,
bool canUpdateInvariantData,
HashSet<string> allowedCultures)
{
var valueEditor = (BlockListEditorPropertyValueEditor)GetValueEditor();
return valueEditor.MergeVariantInvariantPropertyValue(sourceValue, targetValue, canUpdateInvariantData, allowedCultures);
}
#region Pre Value Editor
protected override IConfigurationEditor CreateConfigurationEditor() =>

View File

@@ -10,6 +10,7 @@ using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors;
@@ -259,6 +260,178 @@ public abstract class BlockValuePropertyValueEditorBase<TValue, TLayout> : DataV
}
}
/// <summary>
/// Updates the invariant data in the source with the invariant data in the value if allowed
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
/// <param name="canUpdateInvariantData"></param>
/// <returns></returns>
internal virtual BlockEditorData<TValue, TLayout>? UpdateSourceInvariantData(BlockEditorData<TValue, TLayout>? source, BlockEditorData<TValue, TLayout>? target, bool canUpdateInvariantData)
{
if (source is null && target is null)
{
return null;
}
if (source is null)
{
return MergeNewInvariant(target!, canUpdateInvariantData);
}
if (target is null)
{
return MergeRemovalInvariant(source, canUpdateInvariantData);
}
return MergeInvariant(source, target, canUpdateInvariantData);
}
internal virtual object? MergeVariantInvariantPropertyValue(
object? sourceValue,
object? targetValue,
bool canUpdateInvariantData,
HashSet<string> allowedCultures)
{
BlockEditorData<TValue, TLayout>? source = BlockEditorValues.DeserializeAndClean(sourceValue);
BlockEditorData<TValue, TLayout>? target = BlockEditorValues.DeserializeAndClean(targetValue);
source = UpdateSourceInvariantData(source, target, canUpdateInvariantData);
if (source is null && target is null)
{
return null;
}
if (source is null && target?.Layout is not null)
{
source = new BlockEditorData<TValue, TLayout>([], CreateWithLayout(target.Layout));
}
else if (target is null && source?.Layout is not null)
{
target = new BlockEditorData<TValue, TLayout>([], CreateWithLayout(source.Layout));
}
// at this point the layout should have been merged or fallback created
if (source is null || target is null)
{
throw new ArgumentException("invalid sourceValue or targetValue");
}
// remove all the blocks that are no longer part of the layout
target.BlockValue.ContentData.RemoveAll(contentBlock =>
target.Layout!.Any(layoutItem => layoutItem.ContentKey == contentBlock.Key) is false);
target.BlockValue.SettingsData.RemoveAll(settingsBlock =>
target.Layout!.Any(layoutItem => layoutItem.SettingsKey == settingsBlock.Key) is false);
CleanupVariantValues(source.BlockValue.ContentData, target.BlockValue.ContentData, canUpdateInvariantData, allowedCultures);
CleanupVariantValues(source.BlockValue.SettingsData, target.BlockValue.SettingsData, canUpdateInvariantData, allowedCultures);
return _jsonSerializer.Serialize(target.BlockValue);
}
private void CleanupVariantValues(
List<BlockItemData> sourceBlockItems,
List<BlockItemData> targetBlockItems,
bool canUpdateInvariantData,
HashSet<string> allowedCultures)
{
// merge the source values into the target values for culture
foreach (BlockItemData targetBlockItem in targetBlockItems)
{
BlockItemData? sourceBlockItem = sourceBlockItems.FirstOrDefault(i => i.Key == targetBlockItem.Key);
var valuesToRemove = new List<BlockPropertyValue>();
foreach (BlockPropertyValue targetBlockPropertyValue in targetBlockItem.Values)
{
BlockPropertyValue? sourceBlockPropertyValue =
sourceBlockItem?.Values.FirstOrDefault(v => v.Culture == targetBlockPropertyValue.Culture);
// todo double check if this path can have an invariant value, but it shouldn't right???
// => it can be a null culture, but we shouldn't do anything? as the invariant section should have done it already
if ((targetBlockPropertyValue.Culture is null && canUpdateInvariantData == false)
|| (targetBlockPropertyValue.Culture is not null && allowedCultures.Contains(targetBlockPropertyValue.Culture) is false))
{
// not allowed to update this culture, set the value back to the source
if (sourceBlockPropertyValue is null)
{
valuesToRemove.Add(targetBlockPropertyValue);
}
else
{
targetBlockPropertyValue.Value = sourceBlockPropertyValue.Value;
}
continue;
}
// is this another editor that supports partial merging? i.e. blocks within blocks.
IDataEditor? mergingDataEditor = null;
var shouldPerformPartialMerge = targetBlockPropertyValue.PropertyType is not null
&& _propertyEditors.TryGet(targetBlockPropertyValue.PropertyType.PropertyEditorAlias, out mergingDataEditor)
&& mergingDataEditor.CanMergePartialPropertyValues(targetBlockPropertyValue.PropertyType);
if (shouldPerformPartialMerge is false)
{
continue;
}
// marge subdata
targetBlockPropertyValue.Value = mergingDataEditor!.MergeVariantInvariantPropertyValue(
sourceBlockPropertyValue?.Value,
targetBlockPropertyValue.Value,
canUpdateInvariantData,
allowedCultures);
}
foreach (BlockPropertyValue value in valuesToRemove)
{
targetBlockItem.Values.Remove(value);
}
}
}
private BlockEditorData<TValue, TLayout>? MergeNewInvariant(BlockEditorData<TValue, TLayout> target, bool canUpdateInvariantData)
{
if (canUpdateInvariantData is false)
{
// source value was null and not allowed to update the structure which is invariant => nothing remains
return null;
}
// create a new source object based on the target value that only has the invariant data (structure)
return target.Layout is not null
? new BlockEditorData<TValue, TLayout>([], CreateWithLayout(target.Layout))
: null;
}
private BlockEditorData<TValue, TLayout>? MergeRemovalInvariant(BlockEditorData<TValue, TLayout> source, bool canUpdateInvariantData)
{
if (canUpdateInvariantData)
{
// if the structure is removed, everything is gone anyway
return null;
}
// create a new target object based on the source value that only has the invariant data (structure)
return source.Layout is not null
? new BlockEditorData<TValue, TLayout>([], CreateWithLayout(source.Layout))
: null;
}
private BlockEditorData<TValue, TLayout> MergeInvariant(BlockEditorData<TValue, TLayout> source, BlockEditorData<TValue, TLayout> target, bool canUpdateInvariantData)
{
if (canUpdateInvariantData)
{
source.BlockValue.Layout = target.BlockValue.Layout;
source.BlockValue.Expose = target.BlockValue.Expose;
}
return source;
}
internal virtual object? MergePartialPropertyValueForCulture(object? sourceValue, object? targetValue, string? culture)
{
if (sourceValue is null)

View File

@@ -60,6 +60,16 @@ public class RichTextPropertyEditor : DataEditor
return valueEditor.MergePartialPropertyValueForCulture(sourceValue, targetValue, culture);
}
public override object? MergeVariantInvariantPropertyValue(
object? sourceValue,
object? targetValue,
bool canUpdateInvariantData,
HashSet<string> allowedCultures)
{
var valueEditor = (RichTextPropertyValueEditor)GetValueEditor();
return valueEditor.MergeVariantInvariantPropertyValue(sourceValue, targetValue, canUpdateInvariantData,allowedCultures);
}
/// <summary>
/// Create a custom value editor
/// </summary>

View File

@@ -0,0 +1,30 @@
using NUnit.Framework;
namespace Umbraco.Cms.Tests.Integration.Attributes;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ConfigureBuilderAttribute : Attribute
{
public string ActionName { get; set; }
public void Execute(IUmbracoBuilder builder)
{
// todo allow to find methods from parents
Type.GetType(TestContext.CurrentContext.Test.ClassName).GetMethods().First(method => method.Name == ActionName)
.Invoke(null, [builder]);
}
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ConfigureBuilderTestCaseAttribute : Attribute
{
public string ActionName { get; set; }
public int IndexOfParameter { get; set; }
public void Execute(IUmbracoBuilder builder)
{
Type.GetType(TestContext.CurrentContext.Test.ClassName).GetMethods().First(method => method.Name == ActionName)
.Invoke(null, [builder, TestContext.CurrentContext.Test.Arguments[IndexOfParameter]]);
}
}

View File

@@ -0,0 +1,44 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NUnit.Framework;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.Attributes;
public class ConfigureBuilderAttributeTests : UmbracoIntegrationTest
{
private const string TestTelemetryId = "IdSetbyTestAttribute";
public static void Configure(IUmbracoBuilder builder)
{
builder.Services.Configure<GlobalSettings>(config =>
config.Id = TestTelemetryId);
}
public static void ConfigureWithValue(IUmbracoBuilder builder, string telemetryId)
{
builder.Services.Configure<GlobalSettings>(config =>
config.Id = telemetryId);
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[ConfigureBuilder(ActionName = nameof(Configure))]
public void MethodAttributeOverwritesSetupForAllCases(int testValue)
{
var settings = GetRequiredService<IOptions<GlobalSettings>>().Value;
Assert.AreEqual(TestTelemetryId, settings.Id);
}
[TestCase(1, "IdOne")]
[TestCase(2, "IdTwo")]
[TestCase(3, "IdThree")]
[ConfigureBuilderTestCase(ActionName = nameof(ConfigureWithValue), IndexOfParameter = 1)]
public void CaseAttributeOverwritesSetupForSpecificCase(int testValue, string telemetryId)
{
var settings = GetRequiredService<IOptions<GlobalSettings>>().Value;
Assert.AreEqual(telemetryId, settings.Id);
}
}

View File

@@ -1,6 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.BlockEditorBackwardsCompatibilityTests</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.BlockEditorElementVariationTestBase</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.BlockGridElementLevelVariationTests</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.BlockListElementLevelVariationTests</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.BlockListPropertyEditorTests</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.PropertyIndexValueFactoryTests</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.RichTextEditorPastedImagesTests</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.RichTextElementLevelVariationTests</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors.RichTextPropertyEditorTests</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.TemplateServiceTests.Deleting_Master_Template_Also_Deletes_Children</Target>

View File

@@ -19,6 +19,7 @@ using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Cms.Persistence.Sqlite;
using Umbraco.Cms.Persistence.SqlServer;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Integration.Attributes;
using Umbraco.Cms.Tests.Integration.DependencyInjection;
using Umbraco.Cms.Tests.Integration.Extensions;
@@ -181,10 +182,32 @@ public abstract class UmbracoIntegrationTest : UmbracoIntegrationTestBase
services.AddMvc();
CustomTestSetup(builder);
ExecuteBuilderAttributes(builder);
builder.Build();
}
private void ExecuteBuilderAttributes(IUmbracoBuilder builder)
{
// todo better errors
// execute builder attributes defined on method
foreach (ConfigureBuilderAttribute builderAttribute in Type.GetType(TestContext.CurrentContext.Test.ClassName)
.GetMethods().First(m => m.Name == TestContext.CurrentContext.Test.MethodName)
.GetCustomAttributes(typeof(ConfigureBuilderAttribute), true))
{
builderAttribute.Execute(builder);
}
// execute builder attributes defined on method with param value passtrough from testcase
foreach (ConfigureBuilderTestCaseAttribute builderAttribute in Type.GetType(TestContext.CurrentContext.Test.ClassName)
.GetMethods().First(m => m.Name == TestContext.CurrentContext.Test.MethodName)
.GetCustomAttributes(typeof(ConfigureBuilderTestCaseAttribute), true))
{
builderAttribute.Execute(builder);
}
}
/// <summary>
/// Hook for altering UmbracoBuilder setup
/// </summary>

View File

@@ -14,7 +14,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class BlockEditorBackwardsCompatibilityTests : UmbracoIntegrationTest
internal class BlockEditorBackwardsCompatibilityTests : UmbracoIntegrationTest
{
private IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();

View File

@@ -25,10 +25,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public abstract class BlockEditorElementVariationTestBase : UmbracoIntegrationTest
internal abstract class BlockEditorElementVariationTestBase : UmbracoIntegrationTest
{
protected List<string> TestsRequiringAllowEditInvariantFromNonDefault { get; set; } = new();
protected ILanguageService LanguageService => GetRequiredService<ILanguageService>();
protected IContentService ContentService => GetRequiredService<IContentService>();
@@ -64,9 +62,6 @@ public abstract class BlockEditorElementVariationTestBase : UmbracoIntegrationTe
builder.Services.AddUnique(mockHttpContextAccessor.Object);
builder.AddUmbracoHybridCache();
builder.Services.Configure<ContentSettings>(config =>
config.AllowEditInvariantFromNonDefault = TestsRequiringAllowEditInvariantFromNonDefault.Contains(TestContext.CurrentContext.Test.Name));
builder.AddNotificationHandler<ContentTreeChangeNotification, ContentTreeChangeDistributedCacheNotificationHandler>();
builder.Services.AddUnique<IServerMessenger, ContentEventsTests.LocalServerMessenger>();
}
@@ -79,19 +74,6 @@ public abstract class BlockEditorElementVariationTestBase : UmbracoIntegrationTe
{
var publishResult = ContentService.Publish(content, culturesToPublish);
Assert.IsTrue(publishResult.Success);
// ContentCacheRefresher.JsonPayload[] payloads =
// [
// new ContentCacheRefresher.JsonPayload
// {
// ChangeTypes = TreeChangeTypes.RefreshNode,
// Key = content.Key,
// Id = content.Id,
// Blueprint = false
// }
// ];
DocumentCacheService.RefreshContentAsync(content);
}

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
// NOTE: These tests are in place to ensure that element level variation works for Block Grid. Element level variation
// is tested more in-depth for Block List (see BlockListElementLevelVariationTests), but since the actual
// implementation is shared between Block List and Block Grid, we won't repeat all those tests here.
public class BlockGridElementLevelVariationTests : BlockEditorElementVariationTestBase
internal class BlockGridElementLevelVariationTests : BlockEditorElementVariationTestBase
{
private IJsonSerializer JsonSerializer => GetRequiredService<IJsonSerializer>();

View File

@@ -1,5 +1,7 @@
using NUnit.Framework;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Models.ContentEditing;
@@ -7,13 +9,15 @@ using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Integration.Attributes;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
public partial class BlockListElementLevelVariationTests
internal partial class BlockListElementLevelVariationTests
{
[TestCase(true)]
[TestCase(false)]
[ConfigureBuilder(ActionName = nameof(ConfigureAllowEditInvariantFromNonDefaultTrue))]
public async Task Can_Handle_Limited_User_Access_To_Languages_With_AllowEditInvariantFromNonDefault(bool updateWithLimitedUserAccess)
{
await LanguageService.CreateAsync(
@@ -78,6 +82,7 @@ public partial class BlockListElementLevelVariationTests
content.Properties["blocks"]!.SetValue(JsonSerializer.Serialize(blockListValue));
ContentService.Save(content);
blockListValue.ContentData[0].Values[0].Value = "#1: The second invariant content value";
blockListValue.ContentData[0].Values[1].Value = "#1: The second content value in English";
blockListValue.ContentData[0].Values[2].Value = "#1: The second content value in Danish";
@@ -165,6 +170,136 @@ public partial class BlockListElementLevelVariationTests
}
}
[TestCase(true)]
[TestCase(false)]
[ConfigureBuilder(ActionName = nameof(ConfigureAllowEditInvariantFromNonDefaultTrue))]
public async Task Can_Handle_Limited_User_Access_To_Languages_With_AllowEditInvariantFromNonDefault_WithoutInitialValues(bool updateWithLimitedUserAccess)
{
await LanguageService.CreateAsync(
new Language("de-DE", "German"), Constants.Security.SuperUserKey);
var userKey = updateWithLimitedUserAccess
? (await CreateLimitedUser()).Key
: Constants.Security.SuperUserKey;
var elementType = CreateElementType(ContentVariation.Culture);
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var content = CreateContent(contentType, elementType, [], false);
content.SetCultureName("Home (de)", "de-DE");
ContentService.Save(content);
var blockListValue = BlockListPropertyValue(
elementType,
[
(
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue> {
new() { Alias = "invariantText", Value = "#1: The second invariant content value" },
new() { Alias = "variantText", Value = "#1: The second content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "#1: The second content value in Danish", Culture = "da-DK" },
new() { Alias = "variantText", Value = "#1: The second content value in German", Culture = "de-DE" }
},
new List<BlockPropertyValue> {
new() { Alias = "invariantText", Value = "#1: The second invariant settings value" },
new() { Alias = "variantText", Value = "#1: The second settings value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "#1: The second settings value in Danish", Culture = "da-DK" },
new() { Alias = "variantText", Value = "#1: The second settings value in German", Culture = "de-DE" }
},
null,
null
)
),
(
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue> {
new() { Alias = "invariantText", Value = "#2: The second invariant content value" },
new() { Alias = "variantText", Value = "#2: The second content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "#2: The second content value in Danish", Culture = "da-DK" },
new() { Alias = "variantText", Value = "#2: The second content value in German", Culture = "de-DE" }
},
new List<BlockPropertyValue> {
new() { Alias = "invariantText", Value = "#2: The second invariant settings value" },
new() { Alias = "variantText", Value = "#2: The second settings value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "#2: The second settings value in Danish", Culture = "da-DK" },
new() { Alias = "variantText", Value = "#2: The second settings value in German", Culture = "de-DE" }
},
null,
null
)
)
]
);
var updateModel = new ContentUpdateModel
{
InvariantProperties = new[]
{
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
},
Variants = new[]
{
new VariantModel { Name = content.GetCultureName("en-US")!, Culture = "en-US", Properties = [] },
new VariantModel { Name = content.GetCultureName("da-DK")!, Culture = "da-DK", Properties = [] },
new VariantModel { Name = content.GetCultureName("de-DE")!, Culture = "de-DE", Properties = [] }
}
};
var result = await ContentEditingService.UpdateAsync(content.Key, updateModel, userKey);
Assert.IsTrue(result.Success);
content = ContentService.GetById(content.Key);
var savedBlocksValue = content?.Properties["blocks"]?.GetValue()?.ToString();
Assert.NotNull(savedBlocksValue);
blockListValue = JsonSerializer.Deserialize<BlockListValue>(savedBlocksValue);
// the Danish and invariant values should be updated regardless of the executing user
Assert.Multiple(() =>
{
Assert.AreEqual("#1: The second invariant content value", blockListValue.ContentData[0].Values.Single(v => v.Culture == null).Value);
Assert.AreEqual("#1: The second content value in Danish", blockListValue.ContentData[0].Values.Single(v => v.Culture == "da-DK").Value);
Assert.AreEqual("#1: The second invariant settings value", blockListValue.SettingsData[0].Values.Single(v => v.Culture == null).Value);
Assert.AreEqual("#1: The second settings value in Danish", blockListValue.SettingsData[0].Values.Single(v => v.Culture == "da-DK").Value);
Assert.AreEqual("#2: The second invariant content value", blockListValue.ContentData[1].Values.Single(v => v.Culture == null).Value);
Assert.AreEqual("#2: The second content value in Danish", blockListValue.ContentData[1].Values.Single(v => v.Culture == "da-DK").Value);
Assert.AreEqual("#2: The second invariant settings value", blockListValue.SettingsData[1].Values.Single(v => v.Culture == null).Value);
Assert.AreEqual("#2: The second settings value in Danish", blockListValue.SettingsData[1].Values.Single(v => v.Culture == "da-DK").Value);
});
// limited user access means English and German should not have been updated - changes should be rolled back to the initial block values
if (updateWithLimitedUserAccess)
{
Assert.Multiple(() =>
{
Assert.AreEqual(2, blockListValue.ContentData[0].Values.Count);
Assert.AreEqual(2, blockListValue.ContentData[1].Values.Count);
});
}
else
{
Assert.Multiple(() =>
{
Assert.AreEqual(4, blockListValue.ContentData[0].Values.Count);
Assert.AreEqual(4, blockListValue.ContentData[1].Values.Count);
Assert.AreEqual("#1: The second content value in English", blockListValue.ContentData[0].Values[1].Value);
Assert.AreEqual("#1: The second settings value in English", blockListValue.SettingsData[0].Values[1].Value);
Assert.AreEqual("#1: The second content value in German", blockListValue.ContentData[0].Values[3].Value);
Assert.AreEqual("#1: The second settings value in German", blockListValue.SettingsData[0].Values[3].Value);
Assert.AreEqual("#2: The second content value in English", blockListValue.ContentData[1].Values[1].Value);
Assert.AreEqual("#2: The second settings value in English", blockListValue.SettingsData[1].Values[1].Value);
Assert.AreEqual("#2: The second content value in German", blockListValue.ContentData[1].Values[3].Value);
Assert.AreEqual("#2: The second settings value in German", blockListValue.SettingsData[1].Values[3].Value);
});
}
}
[TestCase(true)]
[TestCase(false)]
public async Task Can_Handle_Limited_User_Access_To_Languages_Without_AllowEditInvariantFromNonDefault(bool updateWithLimitedUserAccess)
@@ -325,6 +460,135 @@ public partial class BlockListElementLevelVariationTests
[TestCase(true)]
[TestCase(false)]
public async Task Can_Handle_Limited_User_Access_To_Languages_Without_AllowEditInvariantFromNonDefault_WithoutInitialValue(bool updateWithLimitedUserAccess)
{
await LanguageService.CreateAsync(
new Language("de-DE", "German"), Constants.Security.SuperUserKey);
var userKey = updateWithLimitedUserAccess
? (await CreateLimitedUser()).Key
: Constants.Security.SuperUserKey;
var elementType = CreateElementType(ContentVariation.Culture);
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType);
var content = CreateContent(contentType, elementType, [], false);
content.SetCultureName("Home (de)", "de-DE");
ContentService.Save(content);
var blockListValue = BlockListPropertyValue(
elementType,
[
(
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue> {
new() { Alias = "invariantText", Value = "#1: The second invariant content value" },
new() { Alias = "variantText", Value = "#1: The second content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "#1: The second content value in Danish", Culture = "da-DK" },
new() { Alias = "variantText", Value = "#1: The second content value in German", Culture = "de-DE" }
},
new List<BlockPropertyValue> {
new() { Alias = "invariantText", Value = "#1: The second invariant settings value" },
new() { Alias = "variantText", Value = "#1: The second settings value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "#1: The second settings value in Danish", Culture = "da-DK" },
new() { Alias = "variantText", Value = "#1: The second settings value in German", Culture = "de-DE" }
},
null,
null
)
),
(
Guid.NewGuid(),
Guid.NewGuid(),
new BlockProperty(
new List<BlockPropertyValue> {
new() { Alias = "invariantText", Value = "#2: The second invariant content value" },
new() { Alias = "variantText", Value = "#2: The second content value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "#2: The second content value in Danish", Culture = "da-DK" },
new() { Alias = "variantText", Value = "#2: The second content value in German", Culture = "de-DE" }
},
new List<BlockPropertyValue> {
new() { Alias = "invariantText", Value = "#2: The second invariant settings value" },
new() { Alias = "variantText", Value = "#2: The second settings value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = "#2: The second settings value in Danish", Culture = "da-DK" },
new() { Alias = "variantText", Value = "#2: The second settings value in German", Culture = "de-DE" }
},
null,
null
)
)
]
);
var updateModel = new ContentUpdateModel
{
InvariantProperties = new[]
{
new PropertyValueModel { Alias = "blocks", Value = JsonSerializer.Serialize(blockListValue) }
},
Variants = new[]
{
new VariantModel { Name = content.GetCultureName("en-US")!, Culture = "en-US", Properties = [] },
new VariantModel { Name = content.GetCultureName("da-DK")!, Culture = "da-DK", Properties = [] },
new VariantModel { Name = content.GetCultureName("de-DE")!, Culture = "de-DE", Properties = [] }
}
};
var result = await ContentEditingService.UpdateAsync(content.Key, updateModel, userKey);
Assert.IsTrue(result.Success);
content = ContentService.GetById(content.Key);
var savedBlocksValue = content?.Properties["blocks"]?.GetValue()?.ToString();
Assert.NotNull(savedBlocksValue);
blockListValue = JsonSerializer.Deserialize<BlockListValue>(savedBlocksValue);
// the Danish values should be updated regardless of the executing user
Assert.Multiple(() =>
{
Assert.AreEqual("#1: The second content value in Danish", blockListValue.ContentData[0].Values.Single(v => v.Culture == "da-DK").Value);
Assert.AreEqual("#1: The second settings value in Danish", blockListValue.SettingsData[0].Values.Single(v => v.Culture == "da-DK").Value);
Assert.AreEqual("#2: The second content value in Danish", blockListValue.ContentData[1].Values.Single(v => v.Culture == "da-DK").Value);
Assert.AreEqual("#2: The second settings value in Danish", blockListValue.SettingsData[1].Values.Single(v => v.Culture == "da-DK").Value);
});
// limited user access means invariant, English and German should not have been updated - changes should be rolled back to the initial block values
if (updateWithLimitedUserAccess)
{
Assert.Multiple(() =>
{
Assert.AreEqual(1, blockListValue.ContentData[0].Values.Count);
Assert.AreEqual(1, blockListValue.ContentData[1].Values.Count);
});
}
else
{
Assert.Multiple(() =>
{
Assert.AreEqual(4, blockListValue.ContentData[0].Values.Count);
Assert.AreEqual(4, blockListValue.ContentData[1].Values.Count);
Assert.AreEqual("#1: The second invariant content value", blockListValue.ContentData[0].Values[0].Value);
Assert.AreEqual("#1: The second invariant settings value", blockListValue.SettingsData[0].Values[0].Value);
Assert.AreEqual("#1: The second content value in English", blockListValue.ContentData[0].Values[1].Value);
Assert.AreEqual("#1: The second settings value in English", blockListValue.SettingsData[0].Values[1].Value);
Assert.AreEqual("#1: The second content value in German", blockListValue.ContentData[0].Values[3].Value);
Assert.AreEqual("#1: The second settings value in German", blockListValue.SettingsData[0].Values[3].Value);
Assert.AreEqual("#2: The second invariant content value", blockListValue.ContentData[1].Values[0].Value);
Assert.AreEqual("#2: The second invariant settings value", blockListValue.SettingsData[1].Values[0].Value);
Assert.AreEqual("#2: The second content value in English", blockListValue.ContentData[1].Values[1].Value);
Assert.AreEqual("#2: The second settings value in English", blockListValue.SettingsData[1].Values[1].Value);
Assert.AreEqual("#2: The second content value in German", blockListValue.ContentData[1].Values[3].Value);
Assert.AreEqual("#2: The second settings value in German", blockListValue.SettingsData[1].Values[3].Value);
});
}
}
[TestCase(true)]
[TestCase(false)]
[ConfigureBuilder(ActionName = nameof(ConfigureAllowEditInvariantFromNonDefaultTrue))]
public async Task Can_Handle_Limited_User_Access_To_Languages_In_Nested_Blocks_Without_Access_With_AllowEditInvariantFromNonDefault(bool updateWithLimitedUserAccess)
{
await LanguageService.CreateAsync(

View File

@@ -7,7 +7,7 @@ using Umbraco.Cms.Tests.Common.Builders.Extensions;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
public partial class BlockListElementLevelVariationTests
internal partial class BlockListElementLevelVariationTests
{
[Test]
public async Task Can_Index_Cultures_Independently_Invariant_Blocks()

View File

@@ -5,7 +5,7 @@ using Umbraco.Cms.Core.Models.Blocks;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
public partial class BlockListElementLevelVariationTests
internal partial class BlockListElementLevelVariationTests
{
[TestCase("en-US", "The culture variant content value in English", "The culture variant settings value in English")]
[TestCase("da-DK", "The culture variant content value in Danish", "The culture variant settings value in Danish")]

View File

@@ -6,6 +6,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Integration.Attributes;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
@@ -21,7 +22,7 @@ This means that an invariant element cannot be "turned off" for a single variati
It also means that in a variant setting, the parent property variance has no effect for the variance notation for any nested blocks.
*/
public partial class BlockListElementLevelVariationTests
internal partial class BlockListElementLevelVariationTests
{
[Test]
public async Task Can_Publish_Cultures_Independently_Invariant_Blocks()
@@ -1387,6 +1388,7 @@ public partial class BlockListElementLevelVariationTests
}
[Test]
[ConfigureBuilder(ActionName = nameof(ConfigureAllowEditInvariantFromNonDefaultTrue))]
public async Task Can_Publish_Invariant_Properties_Without_Default_Culture_With_AllowEditInvariantFromNonDefault()
{
var elementType = CreateElementType(ContentVariation.Culture);

View File

@@ -6,7 +6,7 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
public partial class BlockListElementLevelVariationTests
internal partial class BlockListElementLevelVariationTests
{
private IContentValidationService ContentValidationService => GetRequiredService<IContentValidationService>();

View File

@@ -1,5 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Models.PublishedContent;
@@ -10,18 +12,12 @@ using Umbraco.Cms.Tests.Common.Builders.Extensions;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
public partial class BlockListElementLevelVariationTests : BlockEditorElementVariationTestBase
internal partial class BlockListElementLevelVariationTests : BlockEditorElementVariationTestBase
{
[OneTimeSetUp]
public void OneTimeSetUp()
public static new void ConfigureAllowEditInvariantFromNonDefaultTrue(IUmbracoBuilder builder)
{
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Publish_Invariant_Properties_Without_Default_Culture_With_AllowEditInvariantFromNonDefault));
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Handle_Limited_User_Access_To_Languages_With_AllowEditInvariantFromNonDefault));
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Handle_Limited_User_Access_To_Languages_In_Nested_Blocks_Without_Access_With_AllowEditInvariantFromNonDefault));
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Handle_Limited_User_Access_To_Languages_With_AllowEditInvariantFromNonDefault) + "(True)");
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Handle_Limited_User_Access_To_Languages_With_AllowEditInvariantFromNonDefault) + "(False)");
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Handle_Limited_User_Access_To_Languages_In_Nested_Blocks_Without_Access_With_AllowEditInvariantFromNonDefault) + "(True)");
TestsRequiringAllowEditInvariantFromNonDefault.Add(nameof(Can_Handle_Limited_User_Access_To_Languages_In_Nested_Blocks_Without_Access_With_AllowEditInvariantFromNonDefault) + "(False)");
builder.Services.Configure<ContentSettings>(config =>
config.AllowEditInvariantFromNonDefault = true);
}
private IJsonSerializer JsonSerializer => GetRequiredService<IJsonSerializer>();

View File

@@ -14,7 +14,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class BlockListPropertyEditorTests : UmbracoIntegrationTest
internal class BlockListPropertyEditorTests : UmbracoIntegrationTest
{
private IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();

View File

@@ -14,7 +14,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class PropertyIndexValueFactoryTests : UmbracoIntegrationTest
internal class PropertyIndexValueFactoryTests : UmbracoIntegrationTest
{
private IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();

View File

@@ -18,7 +18,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class RichTextEditorPastedImagesTests : UmbracoIntegrationTest
internal class RichTextEditorPastedImagesTests : UmbracoIntegrationTest
{
private static readonly Guid GifFileKey = Guid.Parse("E625C7FA-6CA7-4A01-92CD-FB5C6F89973D");

View File

@@ -14,7 +14,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
// NOTE: These tests are in place to ensure that element level variation works for Rich Text. Element level variation
// is tested more in-depth for Block List (see BlockListElementLevelVariationTests), but since the actual
// implementation is shared between Block List and Rich Text, we won't repeat all those tests here.
public class RichTextElementLevelVariationTests : BlockEditorElementVariationTestBase
internal class RichTextElementLevelVariationTests : BlockEditorElementVariationTestBase
{
private IJsonSerializer JsonSerializer => GetRequiredService<IJsonSerializer>();

View File

@@ -13,7 +13,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class RichTextPropertyEditorTests : UmbracoIntegrationTest
internal class RichTextPropertyEditorTests : UmbracoIntegrationTest
{
private IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();