Handle multiple simultaneous block editor layouts (#16184)

* Handle multiple simultaneous block editor layouts

* Cleanup

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
Kenn Jacobsen
2024-04-30 12:53:26 +02:00
committed by GitHub
parent b833bace1e
commit e3a4d4dae2
17 changed files with 1303 additions and 43 deletions

View File

@@ -40,7 +40,7 @@ public class BlockEditorData<TValue, TLayout>
/// <summary>
/// Returns the layout for this specific property editor
/// </summary>
public IEnumerable<TLayout>? Layout => BlockValue.Layout.TryGetValue(_propertyEditorAlias, out IEnumerable<TLayout>? layout) ? layout : null;
public IEnumerable<TLayout>? Layout => BlockValue.GetLayouts(_propertyEditorAlias);
/// <summary>
/// Returns the reference to the original BlockValue

View File

@@ -15,21 +15,23 @@ public abstract class BlockEditorDataConverter<TValue, TLayout>
where TValue : BlockValue<TLayout>, new()
where TLayout : class, IBlockLayoutItem, new()
{
private readonly string _propertyEditorAlias;
private readonly IJsonSerializer _jsonSerializer;
[Obsolete("Use the constructor that takes IJsonSerializer. Will be removed in V15.")]
[Obsolete("Use the non-obsolete constructor. Will be removed in V15.")]
protected BlockEditorDataConverter(string propertyEditorAlias)
: this(propertyEditorAlias, StaticServiceProvider.Instance.GetRequiredService<IJsonSerializer>())
{
}
[Obsolete("Use the non-obsolete constructor. Will be removed in V15.")]
protected BlockEditorDataConverter(string propertyEditorAlias, IJsonSerializer jsonSerializer)
: this(jsonSerializer)
{
_propertyEditorAlias = propertyEditorAlias;
_jsonSerializer = jsonSerializer;
}
protected BlockEditorDataConverter(IJsonSerializer jsonSerializer)
=> _jsonSerializer = jsonSerializer;
public bool TryDeserialize(string json, [MaybeNullWhen(false)] out BlockEditorData<TValue, TLayout> blockEditorData)
{
try
@@ -60,16 +62,15 @@ public abstract class BlockEditorDataConverter<TValue, TLayout>
public BlockEditorData<TValue, TLayout> Convert(TValue? value)
{
if (value?.Layout == null)
var propertyEditorAlias = new TValue().PropertyEditorAlias;
IEnumerable<TLayout>? layouts = value?.GetLayouts(propertyEditorAlias);
if (layouts is null)
{
return BlockEditorData<TValue, TLayout>.Empty;
}
IEnumerable<ContentAndSettingsReference> references =
value.Layout.TryGetValue(_propertyEditorAlias, out IEnumerable<TLayout>? layout)
? GetBlockReferences(layout)
: Enumerable.Empty<ContentAndSettingsReference>();
IEnumerable<ContentAndSettingsReference> references = GetBlockReferences(layouts);
return new BlockEditorData<TValue, TLayout>(_propertyEditorAlias, references, value);
return new BlockEditorData<TValue, TLayout>(propertyEditorAlias, references, value!);
}
}

View File

@@ -19,7 +19,7 @@ public class BlockGridEditorDataConverter : BlockEditorDataConverter<BlockGridVa
}
public BlockGridEditorDataConverter(IJsonSerializer jsonSerializer)
: base(Constants.PropertyEditors.Aliases.BlockGrid, jsonSerializer)
: base(jsonSerializer)
{
}

View File

@@ -2,4 +2,5 @@
public class BlockGridValue : BlockValue<BlockGridLayoutItem>
{
public override string PropertyEditorAlias => Constants.PropertyEditors.Aliases.BlockGrid;
}

View File

@@ -2,4 +2,5 @@
public class BlockListValue : BlockValue<BlockListLayoutItem>
{
public override string PropertyEditorAlias => Constants.PropertyEditors.Aliases.BlockList;
}

View File

@@ -3,11 +3,22 @@
namespace Umbraco.Cms.Core.Models.Blocks;
public abstract class BlockValue<TLayout> where TLayout : IBlockLayoutItem
public abstract class BlockValue<TLayout> : BlockValue
where TLayout : IBlockLayoutItem
{
public IDictionary<string, IEnumerable<TLayout>> Layout { get; set; } = null!;
public IEnumerable<TLayout>? GetLayouts(string propertyEditorAlias)
=> Layout.TryGetValue(propertyEditorAlias, out IEnumerable<IBlockLayoutItem>? layouts) is true
? layouts.OfType<TLayout>()
: null;
}
public abstract class BlockValue
{
public IDictionary<string, IEnumerable<IBlockLayoutItem>> Layout { get; set; } = new Dictionary<string, IEnumerable<IBlockLayoutItem>>();
public List<BlockItemData> ContentData { get; set; } = new();
public List<BlockItemData> SettingsData { get; set; } = new();
public abstract string PropertyEditorAlias { get; }
}

View File

@@ -2,4 +2,5 @@ namespace Umbraco.Cms.Core.Models.Blocks;
public class RichTextBlockValue : BlockValue<RichTextBlockLayoutItem>
{
public override string PropertyEditorAlias => Constants.PropertyEditors.Aliases.TinyMce;
}

View File

@@ -1,15 +1,23 @@
namespace Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Serialization;
namespace Umbraco.Cms.Core.Models.Blocks;
/// <summary>
/// Data converter for blocks in the richtext property editor
/// </summary>
public sealed class RichTextEditorBlockDataConverter : BlockEditorDataConverter<RichTextBlockValue, RichTextBlockLayoutItem>
{
[Obsolete("Use the constructor that takes IJsonSerializer. Will be removed in V15.")]
public RichTextEditorBlockDataConverter()
: base(Constants.PropertyEditors.Aliases.TinyMce)
{
}
public RichTextEditorBlockDataConverter(IJsonSerializer jsonSerializer)
: base(jsonSerializer)
{
}
protected override IEnumerable<ContentAndSettingsReference> GetBlockReferences(IEnumerable<RichTextBlockLayoutItem> layout)
=> layout.Select(x => new ContentAndSettingsReference(x.ContentUdi, x.SettingsUdi)).ToList();
}

View File

@@ -3,11 +3,9 @@
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Core.PropertyEditors;
@@ -23,16 +21,6 @@ internal sealed class BlockValuePropertyIndexValueFactory :
{
}
[Obsolete("Use constructor that doesn't take IContentTypeService, scheduled for removal in V15")]
public BlockValuePropertyIndexValueFactory(
PropertyEditorCollection propertyEditorCollection,
IContentTypeService contentTypeService,
IJsonSerializer jsonSerializer,
IOptionsMonitor<IndexingSettings> indexingSettings)
: this(propertyEditorCollection, jsonSerializer, indexingSettings)
{
}
protected override IContentType? GetContentTypeOfNestedItem(BlockItemData input, IDictionary<Guid, IContentType> contentTypeDictionary)
=> contentTypeDictionary.TryGetValue(input.ContentTypeKey, out var result) ? result : null;
@@ -41,14 +29,9 @@ internal sealed class BlockValuePropertyIndexValueFactory :
protected override IEnumerable<BlockItemData> GetDataItems(IndexValueFactoryBlockValue input) => input.ContentData;
internal class IndexValueFactoryBlockValue : BlockValue<IndexValueFactoryBlockLayoutItem>
// we only care about the content data when extracting values for indexing - not the layouts nor the settings
internal class IndexValueFactoryBlockValue
{
}
internal class IndexValueFactoryBlockLayoutItem : IBlockLayoutItem
{
public Udi? ContentUdi { get; set; }
public Udi? SettingsUdi { get; set; }
public List<BlockItemData> ContentData { get; set; } = new();
}
}

View File

@@ -274,6 +274,6 @@ public class RichTextPropertyEditor : DataEditor
}
private BlockEditorValues<RichTextBlockValue, RichTextBlockLayoutItem> CreateBlockEditorValues()
=> new(new RichTextEditorBlockDataConverter(), _contentTypeService, _logger);
=> new(new RichTextEditorBlockDataConverter(_jsonSerializer), _contentTypeService, _logger);
}
}

View File

@@ -2,16 +2,24 @@
// See LICENSE for more details.
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Serialization;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
internal class RichTextBlockPropertyValueCreator : BlockPropertyValueCreatorBase<RichTextBlockModel, RichTextBlockItem, RichTextBlockLayoutItem, RichTextConfiguration.RichTextBlockConfiguration, RichTextBlockValue>
{
private readonly IJsonSerializer _jsonSerializer;
private readonly RichTextBlockPropertyValueConstructorCache _constructorCache;
public RichTextBlockPropertyValueCreator(BlockEditorConverter blockEditorConverter, RichTextBlockPropertyValueConstructorCache constructorCache)
public RichTextBlockPropertyValueCreator(
BlockEditorConverter blockEditorConverter,
IJsonSerializer jsonSerializer,
RichTextBlockPropertyValueConstructorCache constructorCache)
: base(blockEditorConverter)
=> _constructorCache = constructorCache;
{
_jsonSerializer = jsonSerializer;
_constructorCache = constructorCache;
}
public RichTextBlockModel CreateBlockModel(PropertyCacheLevel referenceCacheLevel, RichTextBlockValue blockValue, bool preview, RichTextConfiguration.RichTextBlockConfiguration[] blockConfigurations)
{
@@ -24,7 +32,7 @@ internal class RichTextBlockPropertyValueCreator : BlockPropertyValueCreatorBase
return blockModel;
}
protected override BlockEditorDataConverter<RichTextBlockValue, RichTextBlockLayoutItem> CreateBlockEditorDataConverter() => new RichTextEditorBlockDataConverter();
protected override BlockEditorDataConverter<RichTextBlockValue, RichTextBlockLayoutItem> CreateBlockEditorDataConverter() => new RichTextEditorBlockDataConverter(_jsonSerializer);
protected override BlockItemActivator<RichTextBlockItem> CreateBlockItemActivator() => new RichTextBlockItemActivator(BlockEditorConverter, _constructorCache);

View File

@@ -189,7 +189,7 @@ public class RteBlockRenderingValueConverter : SimpleTinyMceValueConverter, IDel
return null;
}
var creator = new RichTextBlockPropertyValueCreator(_blockEditorConverter, _constructorCache);
var creator = new RichTextBlockPropertyValueCreator(_blockEditorConverter, _jsonSerializer, _constructorCache);
return creator.CreateBlockModel(referenceCacheLevel, blocks, preview, configuration.Blocks);
}

View File

@@ -0,0 +1,189 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Serialization;
/// <summary>
/// JSON converter for block values, because block value layouts are strongly typed but different from implementation to implementation.
/// </summary>
public class JsonBlockValueConverter : JsonConverter<BlockValue>
{
public override bool CanConvert(Type typeToConvert) => typeToConvert.IsAssignableTo(typeof(BlockValue));
/// <inheritdoc />
public override BlockValue Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException("Expected start object");
}
BlockValue? blockValue;
try
{
blockValue = (BlockValue?)Activator.CreateInstance(typeToConvert);
}
catch (Exception ex)
{
throw new JsonException($"Unable to create an instance of {nameof(BlockValue)} from type: {typeToConvert.FullName}. Please make sure the type has an default (parameterless) constructor. See the inner exception for more details.", ex);
}
if (blockValue is null)
{
throw new JsonException($"Could not create an instance of {nameof(BlockValue)} from type: {typeToConvert.FullName}.");
}
while (reader.Read())
{
if (reader.TokenType is JsonTokenType.EndObject)
{
break;
}
if (reader.TokenType is JsonTokenType.PropertyName)
{
var propertyName = reader.GetString();
if (propertyName is null)
{
continue;
}
switch (propertyName.ToFirstUpperInvariant())
{
case nameof(BlockValue.ContentData):
blockValue.ContentData = DeserializeBlockItemData(ref reader, options, typeToConvert, nameof(BlockValue.ContentData));
break;
case nameof(BlockValue.SettingsData):
blockValue.SettingsData = DeserializeBlockItemData(ref reader, options, typeToConvert, nameof(BlockValue.SettingsData));
break;
case nameof(BlockValue.Layout):
DeserializeAndSetLayout(ref reader, options, typeToConvert, blockValue);
break;
}
}
}
return blockValue;
}
public override void Write(Utf8JsonWriter writer, BlockValue value, JsonSerializerOptions options)
{
value.Layout.TryGetValue(value.PropertyEditorAlias, out IEnumerable<IBlockLayoutItem>? blockLayoutItems);
blockLayoutItems ??= Enumerable.Empty<IBlockLayoutItem>();
writer.WriteStartObject();
writer.WritePropertyName(nameof(BlockValue.ContentData).ToFirstLowerInvariant());
JsonSerializer.Serialize(writer, value.ContentData, options);
if (value.SettingsData is not null)
{
writer.WritePropertyName(nameof(BlockValue.SettingsData).ToFirstLowerInvariant());
JsonSerializer.Serialize(writer, value.SettingsData, options);
}
Type layoutItemType = GetLayoutItemType(value.GetType());
writer.WriteStartObject(nameof(BlockValue.Layout));
if (blockLayoutItems.Any())
{
writer.WriteStartArray(value.PropertyEditorAlias);
foreach (IBlockLayoutItem blockLayoutItem in blockLayoutItems)
{
JsonSerializer.Serialize(writer, blockLayoutItem, layoutItemType, options);
}
writer.WriteEndArray();
}
writer.WriteEndObject();
writer.WriteEndObject();
}
private static Type GetLayoutItemType(Type blockValueType)
{
Type? layoutItemType = blockValueType.BaseType?.GenericTypeArguments.FirstOrDefault();
if (layoutItemType is null || layoutItemType.Implements<IBlockLayoutItem>() is false)
{
throw new JsonException($"The {nameof(BlockValue)} implementation should have an {nameof(IBlockLayoutItem)} type as its first generic type argument - found: {layoutItemType?.FullName ?? "none"}.");
}
return layoutItemType;
}
private List<BlockItemData> DeserializeBlockItemData(ref Utf8JsonReader reader, JsonSerializerOptions options, Type typeToConvert, string propertyName)
=> JsonSerializer.Deserialize<List<BlockItemData>>(ref reader, options)
?? throw new JsonException($"Unable to deserialize {propertyName} from type: {typeToConvert.FullName}.");
private void DeserializeAndSetLayout(ref Utf8JsonReader reader, JsonSerializerOptions options, Type typeToConvert, BlockValue blockValue)
{
// the block editor layouts collection can contain layouts from any number of block editors.
// we only want to deserialize the one identified by the concrete block value.
// here's an example of how the layouts collection JSON might look:
// "layout": {
// "Umbraco.BlockGrid": [{
// "contentUdi": "umb://element/1304E1DDAC87439684FE8A399231CB3D",
// "rowSpan": 1,
// "columnSpan": 12,
// "areas": []
// }
// ],
// "Umbraco.BlockList": [{
// "contentUdi": "umb://element/1304E1DDAC87439684FE8A399231CB3D"
// }
// ],
// "Some.Custom.BlockEditor": [{
// "contentUdi": "umb://element/1304E1DDAC87439684FE8A399231CB3D"
// }
// ]
// }
// the concrete block editor layout items type
Type layoutItemType = GetLayoutItemType(typeToConvert);
// the type describing a list of concrete block editor layout items
Type layoutItemsType = typeof(List<>).MakeGenericType(layoutItemType);
while (reader.Read())
{
if (reader.TokenType is JsonTokenType.EndObject)
{
break;
}
if (reader.TokenType is JsonTokenType.PropertyName)
{
// grab the block editor alias (e.g. "Umbraco.BlockGrid")
var blockEditorAlias = reader.GetString()
?? throw new JsonException($"Could bot get the block editor alias from the layout while attempting to deserialize type: {typeToConvert.FullName}.");
// forward the reader to the next JSON token, which *should* be the array of corresponding layout items
reader.Read();
if (reader.TokenType is not JsonTokenType.StartArray)
{
throw new JsonException($"Expected to find the beginning of an array of layout items for block editor alias: {blockEditorAlias}, got: {reader.TokenType}. This happened while attempting to deserialize type: {typeToConvert.FullName}.");
}
// did we encounter the concrete block value?
if (blockEditorAlias == blockValue.PropertyEditorAlias)
{
// yes, deserialize the block layout items as their concrete type (list of layoutItemType)
var layoutItems = JsonSerializer.Deserialize(ref reader, layoutItemsType, options);
blockValue.Layout[blockEditorAlias] = layoutItems as IEnumerable<IBlockLayoutItem>
?? throw new JsonException($"Could not deserialize block editor layout items as type: {layoutItemType.FullName} while attempting to deserialize layout items for block editor alias: {blockEditorAlias} for type: {typeToConvert.FullName}.");
}
else
{
// ignore this layout - forward the reader to the end of the array and look for the next one
while (reader.TokenType is not JsonTokenType.EndArray)
{
reader.Read();
}
}
}
}
}
}

View File

@@ -20,7 +20,8 @@ public sealed class SystemTextJsonSerializer : SystemTextJsonSerializerBase
new JsonStringEnumConverter(),
new JsonUdiConverter(),
new JsonUdiRangeConverter(),
new JsonObjectConverter() // Required for block editor values
new JsonObjectConverter(), // Required for block editor values
new JsonBlockValueConverter()
}
};

View File

@@ -0,0 +1,381 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class PropertyIndexValueFactoryTests : UmbracoIntegrationTest
{
private IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();
private IContentService ContentService => GetRequiredService<IContentService>();
private IDataTypeService DataTypeService => GetRequiredService<IDataTypeService>();
private IJsonSerializer JsonSerializer => GetRequiredService<IJsonSerializer>();
private PropertyEditorCollection PropertyEditorCollection => GetRequiredService<PropertyEditorCollection>();
private IConfigurationEditorJsonSerializer ConfigurationEditorJsonSerializer => GetRequiredService<IConfigurationEditorJsonSerializer>();
[Test]
public void Can_Get_Index_Values_From_RichText_With_Blocks()
{
var elementType = ContentTypeBuilder.CreateAllTypesContentType("myElementType", "My Element Type");
elementType.IsElement = true;
ContentTypeService.Save(elementType);
var contentType = ContentTypeBuilder.CreateTextPageContentType("myContentType");
contentType.AllowedTemplates = Enumerable.Empty<ITemplate>();
ContentTypeService.Save(contentType);
var dataType = DataTypeService.GetDataType(contentType.PropertyTypes.First(propertyType => propertyType.Alias == "bodyText").DataTypeId)!;
var editor = dataType.Editor!;
var elementId = Guid.NewGuid();
var propertyValue = RichTextPropertyEditorHelper.SerializeRichTextEditorValue(
new RichTextEditorValue
{
Markup = @$"<p>This is some markup</p><umb-rte-block data-content-udi=""umb://element/{elementId:N}""><!--Umbraco-Block--></umb-rte-block>",
Blocks = JsonSerializer.Deserialize<RichTextBlockValue>($$"""
{
"layout": {
"Umbraco.TinyMCE": [{
"contentUdi": "umb://element/{{elementId:N}}"
}
]
},
"contentData": [{
"contentTypeKey": "{{elementType.Key:D}}",
"udi": "umb://element/{{elementId:N}}",
"singleLineText": "The single line of text in the block",
"bodyText": "<p>The body text in the block</p>"
}
],
"settingsData": []
}
""")
},
JsonSerializer);
var content = ContentBuilder.CreateTextpageContent(contentType, "My Content", -1);
content.Properties["bodyText"]!.SetValue(propertyValue);
ContentService.Save(content);
var indexValues = editor.PropertyIndexValueFactory.GetIndexValues(
content.Properties["bodyText"]!,
culture: null,
segment: null,
published: false,
availableCultures: Enumerable.Empty<string>(),
contentTypeDictionary: new Dictionary<Guid, IContentType>
{
{ elementType.Key, elementType }, { contentType.Key, contentType }
}).ToDictionary();
Assert.IsTrue(indexValues.TryGetValue("bodyText", out var bodyTextIndexValues));
Assert.AreEqual(1, bodyTextIndexValues.Count());
var bodyTextIndexValue = bodyTextIndexValues.First() as string;
Assert.IsNotNull(bodyTextIndexValue);
Assert.Multiple(() =>
{
Assert.IsTrue(bodyTextIndexValue.Contains("This is some markup"));
Assert.IsTrue(bodyTextIndexValue.Contains("The single line of text in the block"));
Assert.IsTrue(bodyTextIndexValue.Contains("The body text in the block"));
});
}
[Test]
public void Can_Get_Index_Values_From_RichText_Without_Blocks()
{
var contentType = ContentTypeBuilder.CreateTextPageContentType("myContentType");
contentType.AllowedTemplates = Enumerable.Empty<ITemplate>();
ContentTypeService.Save(contentType);
var dataType = DataTypeService.GetDataType(contentType.PropertyTypes.First(propertyType => propertyType.Alias == "bodyText").DataTypeId)!;
var editor = dataType.Editor!;
var content = ContentBuilder.CreateTextpageContent(contentType, "My Content", -1);
content.Properties["bodyText"]!.SetValue("<p>This is some markup</p>");
ContentService.Save(content);
var indexValues = editor.PropertyIndexValueFactory.GetIndexValues(
content.Properties["bodyText"]!,
culture: null,
segment: null,
published: false,
availableCultures: Enumerable.Empty<string>(),
contentTypeDictionary: new Dictionary<Guid, IContentType>
{
{ contentType.Key, contentType }
}).ToDictionary();
Assert.IsTrue(indexValues.TryGetValue("bodyText", out var bodyTextIndexValues));
Assert.AreEqual(1, bodyTextIndexValues.Count());
var bodyTextIndexValue = bodyTextIndexValues.First() as string;
Assert.IsNotNull(bodyTextIndexValue);
Assert.IsTrue(bodyTextIndexValue.Contains("This is some markup"));
}
[Test]
public async Task Can_Get_Index_Values_From_BlockList()
{
var elementType = ContentTypeBuilder.CreateAllTypesContentType("myElementType", "My Element Type");
elementType.IsElement = true;
ContentTypeService.Save(elementType);
var dataType = new DataType(PropertyEditorCollection[Constants.PropertyEditors.Aliases.BlockList], ConfigurationEditorJsonSerializer)
{
ConfigurationData = new Dictionary<string, object>
{
{
"blocks",
ConfigurationEditorJsonSerializer.Serialize(new BlockListConfiguration.BlockConfiguration[]
{
new() { ContentElementTypeKey = elementType.Key }
})
}
},
Name = "My Block List",
DatabaseType = ValueStorageType.Ntext,
ParentId = Constants.System.Root,
CreateDate = DateTime.UtcNow
};
await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey);
var builder = new ContentTypeBuilder();
var contentType = builder
.WithAlias("myPage")
.WithName("My Page")
.AddPropertyType()
.WithAlias("blocks")
.WithName("Blocks")
.WithDataTypeId(dataType.Id)
.Done()
.Build();
ContentTypeService.Save(contentType);
var editor = dataType.Editor!;
var contentElementUdi = new GuidUdi(Constants.UdiEntityType.Element, Guid.NewGuid());
var blockListValue = new BlockListValue
{
Layout = new Dictionary<string, IEnumerable<IBlockLayoutItem>>
{
{
Constants.PropertyEditors.Aliases.BlockList,
new IBlockLayoutItem[]
{
new BlockListLayoutItem()
{
ContentUdi = contentElementUdi
}
}
}
},
ContentData =
[
new()
{
Udi = contentElementUdi,
ContentTypeAlias = elementType.Alias,
ContentTypeKey = elementType.Key,
RawPropertyValues = new Dictionary<string, object?>
{
{"singleLineText", "The single line of text in the block"},
{"bodyText", "<p>The body text in the block</p>"}
}
}
],
SettingsData = []
};
var propertyValue = JsonSerializer.Serialize(blockListValue);
var content = ContentBuilder.CreateBasicContent(contentType);
content.Properties["blocks"]!.SetValue(propertyValue);
ContentService.Save(content);
var indexValues = editor.PropertyIndexValueFactory.GetIndexValues(
content.Properties["blocks"]!,
culture: null,
segment: null,
published: false,
availableCultures: Enumerable.Empty<string>(),
contentTypeDictionary: new Dictionary<Guid, IContentType>
{
{ elementType.Key, elementType }, { contentType.Key, contentType }
}).ToDictionary();
Assert.IsTrue(indexValues.TryGetValue("blocks", out var blocksIndexValues));
Assert.AreEqual(1, blocksIndexValues.Count());
var blockIndexValue = blocksIndexValues.First() as string;
Assert.IsNotNull(blockIndexValue);
Assert.Multiple(() =>
{
Assert.IsTrue(blockIndexValue.Contains("The single line of text in the block"));
Assert.IsTrue(blockIndexValue.Contains("The body text in the block"));
});
}
[Test]
public async Task Can_Get_Index_Values_From_BlockGrid()
{
var elementType = ContentTypeBuilder.CreateAllTypesContentType("myElementType", "My Element Type");
elementType.IsElement = true;
ContentTypeService.Save(elementType);
var dataType = new DataType(PropertyEditorCollection[Constants.PropertyEditors.Aliases.BlockGrid], ConfigurationEditorJsonSerializer)
{
ConfigurationData = new Dictionary<string, object>
{
{
"blocks",
ConfigurationEditorJsonSerializer.Serialize(new BlockGridConfiguration.BlockGridBlockConfiguration[]
{
new()
{
ContentElementTypeKey = elementType.Key,
Areas = new BlockGridConfiguration.BlockGridAreaConfiguration[]
{
new()
{
Key = Guid.NewGuid(),
Alias = "one",
ColumnSpan = 12,
RowSpan = 1
}
}
}
})
}
},
Name = "My Block Grid",
DatabaseType = ValueStorageType.Ntext,
ParentId = Constants.System.Root,
CreateDate = DateTime.UtcNow
};
await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey);
var builder = new ContentTypeBuilder();
var contentType = builder
.WithAlias("myPage")
.WithName("My Page")
.AddPropertyType()
.WithAlias("blocks")
.WithName("Blocks")
.WithDataTypeId(dataType.Id)
.Done()
.Build();
ContentTypeService.Save(contentType);
var editor = dataType.Editor!;
var contentElementUdi = new GuidUdi(Constants.UdiEntityType.Element, Guid.NewGuid());
var contentAreaElementUdi = new GuidUdi(Constants.UdiEntityType.Element, Guid.NewGuid());
var blockGridValue = new BlockGridValue
{
Layout = new Dictionary<string, IEnumerable<IBlockLayoutItem>>
{
{
Constants.PropertyEditors.Aliases.BlockGrid,
new IBlockLayoutItem[]
{
new BlockGridLayoutItem
{
ColumnSpan = 12,
RowSpan = 1,
ContentUdi = contentElementUdi,
Areas = new []
{
new BlockGridLayoutAreaItem
{
Key = Guid.NewGuid(),
Items = new []
{
new BlockGridLayoutItem
{
ContentUdi = contentAreaElementUdi,
ColumnSpan = 12,
RowSpan = 1
}
}
}
}
}
}
}
},
ContentData =
[
new()
{
Udi = contentElementUdi,
ContentTypeAlias = elementType.Alias,
ContentTypeKey = elementType.Key,
RawPropertyValues = new Dictionary<string, object?>
{
{"singleLineText", "The single line of text in the grid root"},
{"bodyText", "<p>The body text in the grid root</p>"}
}
},
new()
{
Udi = contentAreaElementUdi,
ContentTypeAlias = elementType.Alias,
ContentTypeKey = elementType.Key,
RawPropertyValues = new Dictionary<string, object?>
{
{"singleLineText", "The single line of text in the grid area"},
{"bodyText", "<p>The body text in the grid area</p>"}
}
}
],
SettingsData = []
};
var propertyValue = JsonSerializer.Serialize(blockGridValue);
var content = ContentBuilder.CreateBasicContent(contentType);
content.Properties["blocks"]!.SetValue(propertyValue);
ContentService.Save(content);
var indexValues = editor.PropertyIndexValueFactory.GetIndexValues(
content.Properties["blocks"]!,
culture: null,
segment: null,
published: false,
availableCultures: Enumerable.Empty<string>(),
contentTypeDictionary: new Dictionary<Guid, IContentType>
{
{ elementType.Key, elementType }, { contentType.Key, contentType }
}).ToDictionary();
Assert.IsTrue(indexValues.TryGetValue("blocks", out var blocksIndexValues));
Assert.AreEqual(1, blocksIndexValues.Count());
var blockIndexValue = blocksIndexValues.First() as string;
Assert.IsNotNull(blockIndexValue);
Assert.Multiple(() =>
{
Assert.IsTrue(blockIndexValue.Contains("The single line of text in the grid root"));
Assert.IsTrue(blockIndexValue.Contains("The body text in the grid root"));
Assert.IsTrue(blockIndexValue.Contains("The single line of text in the grid area"));
Assert.IsTrue(blockIndexValue.Contains("The body text in the grid area"));
});
}
}

View File

@@ -29,6 +29,144 @@ public class BlockGridPropertyValueConverterTests : BlockPropertyValueConverterT
Assert.AreEqual(typeof(BlockGridModel), valueType);
}
[Test]
public void Convert_Valid_Json()
{
var editor = CreateConverter();
var config = ConfigForSingle(SettingKey1);
var propertyType = GetPropertyType(config);
var publishedElement = Mock.Of<IPublishedElement>();
var json = @"
{
""layout"": {
""" + Constants.PropertyEditors.Aliases.BlockGrid + @""": [
{
""contentUdi"": ""umb://element/1304E1DDAC87439684FE8A399231CB3D"",
""settingsUdi"": ""umb://element/2D3529EDB47B4B109F6D4B802DD5DFE2"",
""rowSpan"": 1,
""columnSpan"": 12,
""areas"": []
}
]
},
""contentData"": [
{
""contentTypeKey"": """ + ContentKey1 + @""",
""udi"": ""umb://element/1304E1DDAC87439684FE8A399231CB3D""
}
],
""settingsData"": [
{
""contentTypeKey"": """ + SettingKey1 + @""",
""udi"": ""umb://element/2D3529EDB47B4B109F6D4B802DD5DFE2""
}
]
}";
var converted =
editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as
BlockGridModel;
Assert.IsNotNull(converted);
Assert.AreEqual(1, converted.Count);
Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), converted[0].Content.Key);
Assert.AreEqual(UdiParser.Parse("umb://element/1304E1DDAC87439684FE8A399231CB3D"), converted[0].ContentUdi);
Assert.AreEqual(ContentAlias1, converted[0].Content.ContentType.Alias);
Assert.AreEqual(Guid.Parse("2D3529ED-B47B-4B10-9F6D-4B802DD5DFE2"), converted[0].Settings.Key);
Assert.AreEqual(UdiParser.Parse("umb://element/2D3529EDB47B4B109F6D4B802DD5DFE2"), converted[0].SettingsUdi);
Assert.AreEqual(SettingAlias1, converted[0].Settings.ContentType.Alias);
}
[Test]
public void Can_Convert_Without_Settings()
{
var editor = CreateConverter();
var config = ConfigForSingle();
var propertyType = GetPropertyType(config);
var publishedElement = Mock.Of<IPublishedElement>();
var json = @"
{
""layout"": {
""" + Constants.PropertyEditors.Aliases.BlockGrid + @""": [
{
""contentUdi"": ""umb://element/1304E1DDAC87439684FE8A399231CB3D"",
""rowSpan"": 1,
""columnSpan"": 12,
""areas"": []
}
]
},
""contentData"": [
{
""contentTypeKey"": """ + ContentKey1 + @""",
""udi"": ""umb://element/1304E1DDAC87439684FE8A399231CB3D""
}
]
}";
var converted =
editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as
BlockGridModel;
Assert.IsNotNull(converted);
Assert.AreEqual(1, converted.Count);
var item0 = converted[0].Content;
Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), item0.Key);
Assert.AreEqual(UdiParser.Parse("umb://element/1304E1DDAC87439684FE8A399231CB3D"), converted[0].ContentUdi);
Assert.AreEqual("Test1", item0.ContentType.Alias);
Assert.IsNull(converted[0].Settings);
}
[Test]
public void Ignores_Other_Layouts()
{
var editor = CreateConverter();
var config = ConfigForSingle();
var propertyType = GetPropertyType(config);
var publishedElement = Mock.Of<IPublishedElement>();
var json = @"
{
""layout"": {
""" + Constants.PropertyEditors.Aliases.BlockGrid + @""": [
{
""contentUdi"": ""umb://element/1304E1DDAC87439684FE8A399231CB3D"",
""rowSpan"": 1,
""columnSpan"": 12,
""areas"": []
}
],
""" + Constants.PropertyEditors.Aliases.BlockList + @""": [
{
""contentUdi"": ""umb://element/1304E1DDAC87439684FE8A399231CB3D""
}
],
""Some.Custom.BlockEditor"": [
{
""contentUdi"": ""umb://element/1304E1DDAC87439684FE8A399231CB3D""
}
]
},
""contentData"": [
{
""contentTypeKey"": """ + ContentKey1 + @""",
""udi"": ""umb://element/1304E1DDAC87439684FE8A399231CB3D""
}
]
}";
var converted =
editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as
BlockGridModel;
Assert.IsNotNull(converted);
Assert.AreEqual(1, converted.Count);
var item0 = converted[0].Content;
Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), item0.Key);
Assert.AreEqual(UdiParser.Parse("umb://element/1304E1DDAC87439684FE8A399231CB3D"), converted[0].ContentUdi);
Assert.AreEqual("Test1", item0.ContentType.Alias);
Assert.IsNull(converted[0].Settings);
}
private BlockGridPropertyValueConverter CreateConverter()
{
var publishedSnapshotAccessor = GetPublishedSnapshotAccessor();
@@ -42,8 +180,8 @@ public class BlockGridPropertyValueConverterTests : BlockPropertyValueConverterT
return editor;
}
private BlockGridConfiguration ConfigForSingle() => new()
private BlockGridConfiguration ConfigForSingle(Guid? settingsElementTypeKey = null) => new()
{
Blocks = new[] { new BlockGridConfiguration.BlockGridBlockConfiguration { ContentElementTypeKey = ContentKey1 } },
Blocks = new[] { new BlockGridConfiguration.BlockGridBlockConfiguration { ContentElementTypeKey = ContentKey1, SettingsElementTypeKey = settingsElementTypeKey} },
};
}

View File

@@ -0,0 +1,537 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Infrastructure.Serialization;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Serialization;
[TestFixture]
public class JsonBlockValueConverterTests
{
[Test]
public void Can_Serialize_BlockGrid_With_Blocks()
{
var contentElementUdi1 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var settingsElementUdi1 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var contentElementUdi2 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var settingsElementUdi2 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var contentElementUdi3 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var settingsElementUdi3 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var contentElementUdi4 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var settingsElementUdi4 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var elementType1Key = Guid.NewGuid();
var elementType2Key = Guid.NewGuid();
var elementType3Key = Guid.NewGuid();
var elementType4Key = Guid.NewGuid();
var blockGridValue = new BlockGridValue
{
Layout = new Dictionary<string, IEnumerable<IBlockLayoutItem>>
{
{
Constants.PropertyEditors.Aliases.BlockGrid,
new IBlockLayoutItem[]
{
new BlockGridLayoutItem
{
ColumnSpan = 123,
RowSpan = 456,
ContentUdi = contentElementUdi1,
SettingsUdi = settingsElementUdi1,
Areas = new []
{
new BlockGridLayoutAreaItem
{
Key = Guid.NewGuid(),
Items = new []
{
new BlockGridLayoutItem
{
ColumnSpan = 12,
RowSpan = 34,
ContentUdi = contentElementUdi3,
SettingsUdi = settingsElementUdi3,
Areas = new []
{
new BlockGridLayoutAreaItem
{
Key = Guid.NewGuid(),
Items = new []
{
new BlockGridLayoutItem
{
ColumnSpan = 56,
RowSpan = 78,
ContentUdi = contentElementUdi4,
SettingsUdi = settingsElementUdi4
}
}
}
}
}
}
}
}
},
new BlockGridLayoutItem
{
ColumnSpan = 789,
RowSpan = 123,
ContentUdi = contentElementUdi2,
SettingsUdi = settingsElementUdi2
}
}
}
},
ContentData =
[
new() { Udi = contentElementUdi1, ContentTypeAlias = "elementType1", ContentTypeKey = elementType1Key },
new() { Udi = contentElementUdi2, ContentTypeAlias = "elementType2", ContentTypeKey = elementType2Key },
new() { Udi = contentElementUdi3, ContentTypeAlias = "elementType3", ContentTypeKey = elementType3Key },
new() { Udi = contentElementUdi4, ContentTypeAlias = "elementType¤", ContentTypeKey = elementType4Key },
],
SettingsData =
[
new() { Udi = settingsElementUdi1, ContentTypeAlias = "elementType3", ContentTypeKey = elementType3Key },
new() { Udi = settingsElementUdi2, ContentTypeAlias = "elementType4", ContentTypeKey = elementType4Key },
new() { Udi = settingsElementUdi3, ContentTypeAlias = "elementType1", ContentTypeKey = elementType1Key },
new() { Udi = settingsElementUdi4, ContentTypeAlias = "elementType2", ContentTypeKey = elementType2Key }
]
};
var serializer = new SystemTextJsonSerializer();
var serialized = serializer.Serialize(blockGridValue);
var deserialized = serializer.Deserialize<BlockGridValue>(serialized);
Assert.IsNotNull(deserialized);
Assert.AreEqual(1, deserialized.Layout.Count);
Assert.IsTrue(deserialized.Layout.ContainsKey(Constants.PropertyEditors.Aliases.BlockGrid));
var layoutItems = deserialized.Layout[Constants.PropertyEditors.Aliases.BlockGrid].OfType<BlockGridLayoutItem>().ToArray();
Assert.AreEqual(2, layoutItems.Count());
Assert.Multiple(() =>
{
Assert.AreEqual(123, layoutItems[0].ColumnSpan);
Assert.AreEqual(456, layoutItems[0].RowSpan);
Assert.AreEqual(contentElementUdi1, layoutItems[0].ContentUdi);
Assert.AreEqual(settingsElementUdi1, layoutItems[0].SettingsUdi);
Assert.AreEqual(789, layoutItems[1].ColumnSpan);
Assert.AreEqual(123, layoutItems[1].RowSpan);
Assert.AreEqual(contentElementUdi2, layoutItems[1].ContentUdi);
Assert.AreEqual(settingsElementUdi2, layoutItems[1].SettingsUdi);
});
Assert.AreEqual(1, layoutItems[0].Areas.Length);
Assert.AreEqual(1, layoutItems[0].Areas[0].Items.Length);
Assert.Multiple(() =>
{
Assert.AreEqual(12, layoutItems[0].Areas[0].Items[0].ColumnSpan);
Assert.AreEqual(34, layoutItems[0].Areas[0].Items[0].RowSpan);
Assert.AreEqual(contentElementUdi3, layoutItems[0].Areas[0].Items[0].ContentUdi);
Assert.AreEqual(settingsElementUdi3, layoutItems[0].Areas[0].Items[0].SettingsUdi);
});
Assert.AreEqual(1, layoutItems[0].Areas[0].Items[0].Areas.Length);
Assert.AreEqual(1, layoutItems[0].Areas[0].Items[0].Areas[0].Items.Length);
Assert.Multiple(() =>
{
Assert.AreEqual(56, layoutItems[0].Areas[0].Items[0].Areas[0].Items[0].ColumnSpan);
Assert.AreEqual(78, layoutItems[0].Areas[0].Items[0].Areas[0].Items[0].RowSpan);
Assert.AreEqual(contentElementUdi4, layoutItems[0].Areas[0].Items[0].Areas[0].Items[0].ContentUdi);
Assert.AreEqual(settingsElementUdi4, layoutItems[0].Areas[0].Items[0].Areas[0].Items[0].SettingsUdi);
});
Assert.AreEqual(4, deserialized.ContentData.Count);
Assert.Multiple(() =>
{
Assert.AreEqual(contentElementUdi1, deserialized.ContentData[0].Udi);
Assert.AreEqual(elementType1Key, deserialized.ContentData[0].ContentTypeKey);
Assert.AreEqual(string.Empty, deserialized.ContentData[0].ContentTypeAlias); // explicitly annotated to be ignored by the serializer
Assert.AreEqual(contentElementUdi2, deserialized.ContentData[1].Udi);
Assert.AreEqual(elementType2Key, deserialized.ContentData[1].ContentTypeKey);
Assert.AreEqual(string.Empty, deserialized.ContentData[1].ContentTypeAlias);
Assert.AreEqual(contentElementUdi3, deserialized.ContentData[2].Udi);
Assert.AreEqual(elementType3Key, deserialized.ContentData[2].ContentTypeKey);
Assert.AreEqual(string.Empty, deserialized.ContentData[2].ContentTypeAlias);
Assert.AreEqual(contentElementUdi3, deserialized.ContentData[2].Udi);
Assert.AreEqual(elementType3Key, deserialized.ContentData[2].ContentTypeKey);
Assert.AreEqual(string.Empty, deserialized.ContentData[2].ContentTypeAlias);
});
Assert.AreEqual(4, deserialized.SettingsData.Count);
Assert.Multiple(() =>
{
Assert.AreEqual(settingsElementUdi1, deserialized.SettingsData[0].Udi);
Assert.AreEqual(elementType3Key, deserialized.SettingsData[0].ContentTypeKey);
Assert.AreEqual(string.Empty, deserialized.SettingsData[0].ContentTypeAlias);
Assert.AreEqual(settingsElementUdi2, deserialized.SettingsData[1].Udi);
Assert.AreEqual(elementType4Key, deserialized.SettingsData[1].ContentTypeKey);
Assert.AreEqual(string.Empty, deserialized.SettingsData[1].ContentTypeAlias);
Assert.AreEqual(settingsElementUdi3, deserialized.SettingsData[2].Udi);
Assert.AreEqual(elementType1Key, deserialized.SettingsData[2].ContentTypeKey);
Assert.AreEqual(string.Empty, deserialized.SettingsData[2].ContentTypeAlias);
Assert.AreEqual(settingsElementUdi4, deserialized.SettingsData[3].Udi);
Assert.AreEqual(elementType2Key, deserialized.SettingsData[3].ContentTypeKey);
Assert.AreEqual(string.Empty, deserialized.SettingsData[3].ContentTypeAlias);
});
}
[Test]
public void Can_Serialize_BlockGrid_Without_Blocks()
{
var blockGridValue = new BlockGridValue
{
Layout = new Dictionary<string, IEnumerable<IBlockLayoutItem>>(),
ContentData = [],
SettingsData = []
};
var serializer = new SystemTextJsonSerializer();
var serialized = serializer.Serialize(blockGridValue);
var deserialized = serializer.Deserialize<BlockGridValue>(serialized);
Assert.IsNotNull(deserialized);
Assert.Multiple(() =>
{
Assert.IsEmpty(deserialized.Layout);
Assert.IsEmpty(deserialized.ContentData);
Assert.IsEmpty(deserialized.SettingsData);
});
}
[Test]
public void Can_Serialize_BlockList_With_Blocks()
{
var contentElementUdi1 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var settingsElementUdi1 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var contentElementUdi2 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var settingsElementUdi2 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var elementType1Key = Guid.NewGuid();
var elementType2Key = Guid.NewGuid();
var elementType3Key = Guid.NewGuid();
var elementType4Key = Guid.NewGuid();
var blockListValue = new BlockListValue
{
Layout = new Dictionary<string, IEnumerable<IBlockLayoutItem>>
{
{
Constants.PropertyEditors.Aliases.BlockList,
new IBlockLayoutItem[]
{
new BlockListLayoutItem()
{
ContentUdi = contentElementUdi1,
SettingsUdi = settingsElementUdi1
},
new BlockListLayoutItem
{
ContentUdi = contentElementUdi2,
SettingsUdi = settingsElementUdi2
}
}
}
},
ContentData =
[
new() { Udi = contentElementUdi1, ContentTypeAlias = "elementType1", ContentTypeKey = elementType1Key },
new() { Udi = contentElementUdi2, ContentTypeAlias = "elementType2", ContentTypeKey = elementType2Key }
],
SettingsData =
[
new() { Udi = settingsElementUdi1, ContentTypeAlias = "elementType3", ContentTypeKey = elementType3Key },
new() { Udi = settingsElementUdi2, ContentTypeAlias = "elementType4", ContentTypeKey = elementType4Key }
]
};
var serializer = new SystemTextJsonSerializer();
var serialized = serializer.Serialize(blockListValue);
var deserialized = serializer.Deserialize<BlockListValue>(serialized);
Assert.IsNotNull(deserialized);
Assert.AreEqual(1, deserialized.Layout.Count);
Assert.IsTrue(deserialized.Layout.ContainsKey(Constants.PropertyEditors.Aliases.BlockList));
var layoutItems = deserialized.Layout[Constants.PropertyEditors.Aliases.BlockList].OfType<BlockListLayoutItem>().ToArray();
Assert.AreEqual(2, layoutItems.Count());
Assert.Multiple(() =>
{
Assert.AreEqual(contentElementUdi1, layoutItems.First().ContentUdi);
Assert.AreEqual(settingsElementUdi1, layoutItems.First().SettingsUdi);
Assert.AreEqual(contentElementUdi2, layoutItems.Last().ContentUdi);
Assert.AreEqual(settingsElementUdi2, layoutItems.Last().SettingsUdi);
});
Assert.AreEqual(2, deserialized.ContentData.Count);
Assert.Multiple(() =>
{
Assert.AreEqual(contentElementUdi1, deserialized.ContentData.First().Udi);
Assert.AreEqual(elementType1Key, deserialized.ContentData.First().ContentTypeKey);
Assert.AreEqual(string.Empty, deserialized.ContentData.First().ContentTypeAlias); // explicitly annotated to be ignored by the serializer
Assert.AreEqual(contentElementUdi2, deserialized.ContentData.Last().Udi);
Assert.AreEqual(elementType2Key, deserialized.ContentData.Last().ContentTypeKey);
Assert.AreEqual(string.Empty, deserialized.ContentData.Last().ContentTypeAlias);
});
Assert.AreEqual(2, deserialized.SettingsData.Count);
Assert.Multiple(() =>
{
Assert.AreEqual(settingsElementUdi1, deserialized.SettingsData.First().Udi);
Assert.AreEqual(elementType3Key, deserialized.SettingsData.First().ContentTypeKey);
Assert.AreEqual(string.Empty, deserialized.SettingsData.First().ContentTypeAlias);
Assert.AreEqual(settingsElementUdi2, deserialized.SettingsData.Last().Udi);
Assert.AreEqual(elementType4Key, deserialized.SettingsData.Last().ContentTypeKey);
Assert.AreEqual(string.Empty, deserialized.SettingsData.Last().ContentTypeAlias);
});
}
[Test]
public void Can_Serialize_BlockList_Without_Blocks()
{
var blockListValue = new BlockListValue
{
Layout = new Dictionary<string, IEnumerable<IBlockLayoutItem>>(),
ContentData = [],
SettingsData = []
};
var serializer = new SystemTextJsonSerializer();
var serialized = serializer.Serialize(blockListValue);
var deserialized = serializer.Deserialize<BlockListValue>(serialized);
Assert.IsNotNull(deserialized);
Assert.Multiple(() =>
{
Assert.IsEmpty(deserialized.Layout);
Assert.IsEmpty(deserialized.ContentData);
Assert.IsEmpty(deserialized.SettingsData);
});
}
[Test]
public void Can_Serialize_Richtext_With_Blocks()
{
var contentElementUdi1 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var settingsElementUdi1 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var contentElementUdi2 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var settingsElementUdi2 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var elementType1Key = Guid.NewGuid();
var elementType2Key = Guid.NewGuid();
var elementType3Key = Guid.NewGuid();
var elementType4Key = Guid.NewGuid();
var richTextBlockValue = new RichTextBlockValue
{
Layout = new Dictionary<string, IEnumerable<IBlockLayoutItem>>
{
{
Constants.PropertyEditors.Aliases.TinyMce,
new IBlockLayoutItem[]
{
new RichTextBlockLayoutItem
{
ContentUdi = contentElementUdi1,
SettingsUdi = settingsElementUdi1
},
new RichTextBlockLayoutItem
{
ContentUdi = contentElementUdi2,
SettingsUdi = settingsElementUdi2
}
}
}
},
ContentData =
[
new() { Udi = contentElementUdi1, ContentTypeAlias = "elementType1", ContentTypeKey = elementType1Key },
new() { Udi = contentElementUdi2, ContentTypeAlias = "elementType2", ContentTypeKey = elementType2Key }
],
SettingsData =
[
new() { Udi = settingsElementUdi1, ContentTypeAlias = "elementType3", ContentTypeKey = elementType3Key },
new() { Udi = settingsElementUdi2, ContentTypeAlias = "elementType4", ContentTypeKey = elementType4Key }
]
};
var richTextEditorValue = new RichTextEditorValue
{
Blocks = richTextBlockValue,
Markup = "<p>This is some markup</p>"
};
var serializer = new SystemTextJsonSerializer();
var serialized = serializer.Serialize(richTextEditorValue);
var deserialized = serializer.Deserialize<RichTextEditorValue>(serialized);
Assert.IsNotNull(deserialized);
Assert.AreEqual("<p>This is some markup</p>", deserialized.Markup);
var deserializedBlocks = deserialized.Blocks;
Assert.IsNotNull(deserializedBlocks);
Assert.AreEqual(1, deserializedBlocks.Layout.Count);
Assert.IsTrue(deserializedBlocks.Layout.ContainsKey(Constants.PropertyEditors.Aliases.TinyMce));
var layoutItems = deserializedBlocks.Layout[Constants.PropertyEditors.Aliases.TinyMce].OfType<RichTextBlockLayoutItem>().ToArray();
Assert.AreEqual(2, layoutItems.Count());
Assert.Multiple(() =>
{
Assert.AreEqual(contentElementUdi1, layoutItems.First().ContentUdi);
Assert.AreEqual(settingsElementUdi1, layoutItems.First().SettingsUdi);
Assert.AreEqual(contentElementUdi2, layoutItems.Last().ContentUdi);
Assert.AreEqual(settingsElementUdi2, layoutItems.Last().SettingsUdi);
});
Assert.AreEqual(2, deserializedBlocks.ContentData.Count);
Assert.Multiple(() =>
{
Assert.AreEqual(contentElementUdi1, deserializedBlocks.ContentData.First().Udi);
Assert.AreEqual(elementType1Key, deserializedBlocks.ContentData.First().ContentTypeKey);
Assert.AreEqual(string.Empty, deserializedBlocks.ContentData.First().ContentTypeAlias); // explicitly annotated to be ignored by the serializer
Assert.AreEqual(contentElementUdi2, deserializedBlocks.ContentData.Last().Udi);
Assert.AreEqual(elementType2Key, deserializedBlocks.ContentData.Last().ContentTypeKey);
Assert.AreEqual(string.Empty, deserializedBlocks.ContentData.Last().ContentTypeAlias);
});
Assert.AreEqual(2, deserializedBlocks.SettingsData.Count);
Assert.Multiple(() =>
{
Assert.AreEqual(settingsElementUdi1, deserializedBlocks.SettingsData.First().Udi);
Assert.AreEqual(elementType3Key, deserializedBlocks.SettingsData.First().ContentTypeKey);
Assert.AreEqual(string.Empty, deserializedBlocks.SettingsData.First().ContentTypeAlias);
Assert.AreEqual(settingsElementUdi2, deserializedBlocks.SettingsData.Last().Udi);
Assert.AreEqual(elementType4Key, deserializedBlocks.SettingsData.Last().ContentTypeKey);
Assert.AreEqual(string.Empty, deserializedBlocks.SettingsData.Last().ContentTypeAlias);
});
}
[Test]
public void Can_Serialize_Richtext_Without_Blocks()
{
var richTextEditorValue = new RichTextEditorValue
{
Blocks = new RichTextBlockValue
{
Layout = new Dictionary<string, IEnumerable<IBlockLayoutItem>>(),
ContentData = new List<BlockItemData>(),
SettingsData = new List<BlockItemData>()
},
Markup = "<p>This is some markup</p>"
};
var serializer = new SystemTextJsonSerializer();
var serialized = serializer.Serialize(richTextEditorValue);
var deserialized = serializer.Deserialize<RichTextEditorValue>(serialized);
Assert.IsNotNull(deserialized);
Assert.AreEqual("<p>This is some markup</p>", deserialized.Markup);
Assert.IsNotNull(deserialized.Blocks);
Assert.Multiple(() =>
{
Assert.IsEmpty(deserialized.Blocks.Layout);
Assert.IsEmpty(deserialized.Blocks.ContentData);
Assert.IsEmpty(deserialized.Blocks.SettingsData);
});
}
[Test]
public void Ignores_Other_Layouts()
{
var contentElementUdi1 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var settingsElementUdi1 = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid());
var elementType1Key = Guid.NewGuid();
var elementType2Key = Guid.NewGuid();
var blockListValue = new BlockListValue
{
Layout = new Dictionary<string, IEnumerable<IBlockLayoutItem>>
{
{
Constants.PropertyEditors.Aliases.TinyMce,
new IBlockLayoutItem[]
{
new RichTextBlockLayoutItem
{
ContentUdi = contentElementUdi1,
SettingsUdi = settingsElementUdi1
}
}
},
{
Constants.PropertyEditors.Aliases.BlockList,
new IBlockLayoutItem[]
{
new BlockListLayoutItem
{
ContentUdi = contentElementUdi1,
SettingsUdi = settingsElementUdi1
}
}
},
{
Constants.PropertyEditors.Aliases.BlockGrid,
new IBlockLayoutItem[]
{
new BlockGridLayoutItem
{
ContentUdi = contentElementUdi1,
SettingsUdi = settingsElementUdi1
}
}
},
{
"Some.Custom.Block.Editor",
new IBlockLayoutItem[]
{
new BlockListLayoutItem
{
ContentUdi = contentElementUdi1,
SettingsUdi = settingsElementUdi1
}
}
}
},
ContentData =
[
new() { Udi = contentElementUdi1, ContentTypeAlias = "elementType1", ContentTypeKey = elementType1Key },
],
SettingsData =
[
new() { Udi = settingsElementUdi1, ContentTypeAlias = "elementType2", ContentTypeKey = elementType2Key },
]
};
var serializer = new SystemTextJsonSerializer();
var serialized = serializer.Serialize(blockListValue);
var deserialized = serializer.Deserialize<BlockListValue>(serialized);
Assert.IsNotNull(deserialized);
Assert.AreEqual(1, deserialized.Layout.Count);
Assert.IsTrue(deserialized.Layout.ContainsKey(Constants.PropertyEditors.Aliases.BlockList));
var layoutItems = deserialized.Layout[Constants.PropertyEditors.Aliases.BlockList].OfType<BlockListLayoutItem>().ToArray();
Assert.AreEqual(1, layoutItems.Count());
Assert.Multiple(() =>
{
Assert.AreEqual(contentElementUdi1, layoutItems.First().ContentUdi);
Assert.AreEqual(settingsElementUdi1, layoutItems.First().SettingsUdi);
});
}
}