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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -130,48 +130,72 @@ internal sealed class ContentEditingService
|
||||
|
||||
ILanguage? defaultLanguage = await _languageService.GetDefaultLanguageAsync();
|
||||
|
||||
foreach (var culture in contentWithPotentialUnallowedChanges.EditedCultures ?? contentWithPotentialUnallowedChanges.PublishedCultures)
|
||||
{
|
||||
if (allowedCultures.Contains(culture))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var disallowedCultures = (contentWithPotentialUnallowedChanges.EditedCultures ??
|
||||
contentWithPotentialUnallowedChanges.PublishedCultures)
|
||||
.Where(culture => allowedCultures.Contains(culture) is false).ToList();
|
||||
|
||||
// else override the updates values with the original values.
|
||||
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 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);
|
||||
variantProperties.Add(property);
|
||||
}
|
||||
else if (_propertyEditorCollection.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor) && dataEditor.CanMergePartialPropertyValues(property.PropertyType))
|
||||
{
|
||||
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 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
|
||||
if (_propertyEditorCollection.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor) && dataEditor.CanMergePartialPropertyValues(property.PropertyType))
|
||||
foreach ((IProperty Property, IDataEditor DataEditor) propertyWithEditor in invariantWithVariantSupportProperties)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
// 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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() =>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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]]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user