using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; 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; using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors; [TestFixture] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] internal sealed class RichTextPropertyEditorTests : UmbracoIntegrationTest { private IContentTypeService ContentTypeService => GetRequiredService(); private IContentService ContentService => GetRequiredService(); private IDataTypeService DataTypeService => GetRequiredService(); private IJsonSerializer JsonSerializer => GetRequiredService(); private IPublishedContentCache PublishedContentCache => GetRequiredService(); protected override void CustomTestSetup(IUmbracoBuilder builder) { builder.AddNotificationHandler(); builder.Services.AddUnique(); } [Test] public void Can_Use_Markup_String_As_Value() { var contentType = ContentTypeBuilder.CreateTextPageContentType("myContentType"); contentType.AllowedTemplates = Enumerable.Empty(); ContentTypeService.Save(contentType); var dataType = DataTypeService.GetDataType(contentType.PropertyTypes.First(propertyType => propertyType.Alias == "bodyText").DataTypeId)!; var editor = dataType.Editor!; var valueEditor = editor.GetValueEditor(); const string markup = "

This is some markup

"; var content = ContentBuilder.CreateTextpageContent(contentType, "My Content", -1); content.Properties["bodyText"]!.SetValue(markup); ContentService.Save(content); var toEditor = valueEditor.ToEditor(content.Properties["bodyText"]); var richTextEditorValue = toEditor as RichTextEditorValue; Assert.IsNotNull(richTextEditorValue); Assert.AreEqual(markup, richTextEditorValue.Markup); } [Test] public void Can_Use_RichTextEditorValue_As_Value() { var contentType = ContentTypeBuilder.CreateTextPageContentType("myContentType"); contentType.AllowedTemplates = Enumerable.Empty(); ContentTypeService.Save(contentType); var dataType = DataTypeService.GetDataType(contentType.PropertyTypes.First(propertyType => propertyType.Alias == "bodyText").DataTypeId)!; var editor = dataType.Editor!; var valueEditor = editor.GetValueEditor(); const string markup = "

This is some markup

"; var propertyValue = RichTextPropertyEditorHelper.SerializeRichTextEditorValue(new RichTextEditorValue { Markup = markup, Blocks = null }, JsonSerializer); var content = ContentBuilder.CreateTextpageContent(contentType, "My Content", -1); content.Properties["bodyText"]!.SetValue(propertyValue); ContentService.Save(content); var toEditor = valueEditor.ToEditor(content.Properties["bodyText"]); var richTextEditorValue = toEditor as RichTextEditorValue; Assert.IsNotNull(richTextEditorValue); Assert.AreEqual(markup, richTextEditorValue.Markup); } [Test] public void Can_Track_Block_References() { var elementType = ContentTypeBuilder.CreateAllTypesContentType("myElementType", "My Element Type"); elementType.IsElement = true; ContentTypeService.Save(elementType); var contentType = ContentTypeBuilder.CreateTextPageContentType("myContentType"); contentType.AllowedTemplates = Enumerable.Empty(); ContentTypeService.Save(contentType); var pickedContent = ContentBuilder.CreateTextpageContent(contentType, "My Content", -1); ContentService.Save(pickedContent); var dataType = DataTypeService.GetDataType(contentType.PropertyTypes.First(propertyType => propertyType.Alias == "bodyText").DataTypeId)!; var editor = dataType.Editor!; var valueEditor = (BlockValuePropertyValueEditorBase)editor.GetValueEditor(); var elementId = Guid.NewGuid(); var propertyValue = RichTextPropertyEditorHelper.SerializeRichTextEditorValue( new RichTextEditorValue { Markup = @$"

This is some markup

", Blocks = JsonSerializer.Deserialize($$""" { "layout": { "{{Constants.PropertyEditors.Aliases.RichText}}": [{ "contentKey": "{{elementId:D}}" } ] }, "contentData": [{ "contentTypeKey": "{{elementType.Key:D}}", "key": "{{elementId:D}}", "values": [ { "alias": "contentPicker", "value": "umb://document/{{pickedContent.Key:N}}" } ] } ], "settingsData": [] } """) }, JsonSerializer); var content = ContentBuilder.CreateTextpageContent(contentType, "My Content", -1); content.Properties["bodyText"]!.SetValue(propertyValue); ContentService.Save(content); var references = valueEditor.GetReferences(content.GetValue("bodyText")).ToArray(); Assert.AreEqual(1, references.Length); var reference = references.First(); Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedDocumentAlias, reference.RelationTypeAlias); Assert.AreEqual(pickedContent.GetUdi(), reference.Udi); } [Test] public void Can_Track_Block_Tags() { var elementType = ContentTypeBuilder.CreateAllTypesContentType("myElementType", "My Element Type"); elementType.IsElement = true; ContentTypeService.Save(elementType); var contentType = ContentTypeBuilder.CreateTextPageContentType("myContentType"); contentType.AllowedTemplates = Enumerable.Empty(); ContentTypeService.Save(contentType); var dataType = DataTypeService.GetDataType(contentType.PropertyTypes.First(propertyType => propertyType.Alias == "bodyText").DataTypeId)!; var editor = dataType.Editor!; var valueEditor = (BlockValuePropertyValueEditorBase)editor.GetValueEditor(); var elementId = Guid.NewGuid(); var propertyValue = RichTextPropertyEditorHelper.SerializeRichTextEditorValue( new RichTextEditorValue { Markup = @$"

This is some markup

", Blocks = JsonSerializer.Deserialize($$""" { "layout": { "{{Constants.PropertyEditors.Aliases.RichText}}": [{ "contentKey": "{{elementId:D}}" } ] }, "contentData": [{ "contentTypeKey": "{{elementType.Key:D}}", "key": "{{elementId:D}}", "values": [ { "alias": "tags", "value": "[\"Tag One\", \"Tag Two\", \"Tag Three\"]" } ] } ], "settingsData": [] } """) }, JsonSerializer); var content = ContentBuilder.CreateTextpageContent(contentType, "My Content", -1); content.Properties["bodyText"]!.SetValue(propertyValue); ContentService.Save(content); var tags = valueEditor.GetTags(content.GetValue("bodyText"), null, null).ToArray(); Assert.AreEqual(3, tags.Length); Assert.IsNotNull(tags.Single(tag => tag.Text == "Tag One")); Assert.IsNotNull(tags.Single(tag => tag.Text == "Tag Two")); Assert.IsNotNull(tags.Single(tag => tag.Text == "Tag Three")); } [TestCase(null, false)] [TestCase("", false)] [TestCase("""{"markup":"","blocks":null}""", false)] [TestCase("""{"markup":"

","blocks":null}""", false)] [TestCase("abc", true)] [TestCase("""{"markup":"abc","blocks":null}""", true)] public async Task Can_Handle_Empty_Value_Representations_For_Invariant_Content(string? rteValue, bool expectedHasValue) { var contentType = await CreateContentTypeForEmptyValueTests(); var content = new ContentBuilder() .WithContentType(contentType) .WithName("Page") .WithPropertyValues( new { rte = rteValue }) .Build(); var contentResult = ContentService.Save(content); Assert.IsTrue(contentResult.Success); var publishResult = ContentService.Publish(content, []); Assert.IsTrue(publishResult.Success); var publishedContent = await PublishedContentCache.GetByIdAsync(content.Key); Assert.IsNotNull(publishedContent); var publishedProperty = publishedContent.Properties.First(property => property.Alias == "rte"); Assert.AreEqual(expectedHasValue, publishedProperty.HasValue()); Assert.AreEqual(expectedHasValue, publishedContent.HasValue("rte")); } [TestCase(null, false)] [TestCase("", false)] [TestCase("""{"markup":"","blocks":null}""", false)] [TestCase("""{"markup":"

","blocks":null}""", false)] [TestCase("abc", true)] [TestCase("""{"markup":"abc","blocks":null}""", true)] public async Task Can_Handle_Empty_Value_Representations_For_Variant_Content(string? rteValue, bool expectedHasValue) { var contentType = await CreateContentTypeForEmptyValueTests(ContentVariation.Culture); var content = new ContentBuilder() .WithContentType(contentType) .WithName("Page") .WithCultureName("en-US", "Page") .WithPropertyValues( new { rte = rteValue }, "en-US") .Build(); var contentResult = ContentService.Save(content); Assert.IsTrue(contentResult.Success); var publishResult = ContentService.Publish(content, ["en-US"]); Assert.IsTrue(publishResult.Success); var publishedContent = await PublishedContentCache.GetByIdAsync(content.Key); Assert.IsNotNull(publishedContent); var publishedProperty = publishedContent.Properties.First(property => property.Alias == "rte"); Assert.AreEqual(expectedHasValue, publishedProperty.HasValue("en-US")); Assert.AreEqual(expectedHasValue, publishedContent.HasValue("rte", "en-US")); } private async Task CreateContentTypeForEmptyValueTests(ContentVariation contentVariation = ContentVariation.Nothing) { var contentType = new ContentTypeBuilder() .WithAlias("myPage") .WithName("My Page") .WithContentVariation(contentVariation) .AddPropertyGroup() .WithAlias("content") .WithName("Content") .WithSupportsPublishing(true) .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.RichText) .WithDataTypeId(Constants.DataTypes.RichtextEditor) .WithValueStorageType(ValueStorageType.Ntext) .WithAlias("rte") .WithName("RTE") .WithVariations(contentVariation) .Done() .Done() .Build(); var contentTypeResult = await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey); Assert.IsTrue(contentTypeResult.Success); return contentType; } }