Skip to content

Commit a4d893a

Browse files
kjacAndyButland
andauthored
Rich text editor: Treat an "empty" value as a non-value (closes #20454) (#20719)
* Make the RTE treat an "empty" value as a non-value * Additional tests * Add tests for invariant and variant content. --------- Co-authored-by: Andy Butland <[email protected]>
1 parent 313b60a commit a4d893a

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType
7272
// to be cached at the published snapshot level, because we have no idea what the block renderings may depend on actually.
7373
PropertyCacheLevel.Snapshot;
7474

75+
/// <inheritdoc />
76+
public override bool? IsValue(object? value, PropertyValueLevel level)
77+
=> level switch
78+
{
79+
// we cannot determine if an RTE has a value at source level, because some RTEs might
80+
// be saved with an "empty" representation like {"markup":"","blocks":null}.
81+
PropertyValueLevel.Source => null,
82+
// we assume the RTE has a value if the intermediate value has markup beyond an empty paragraph tag.
83+
PropertyValueLevel.Inter => value is IRichTextEditorIntermediateValue { Markup.Length: > 0 } intermediateValue
84+
&& intermediateValue.Markup != "<p></p>",
85+
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null)
86+
};
87+
7588
// to counterweigh the cache level, we're going to do as much of the heavy lifting as we can while converting source to intermediate
7689
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
7790
{

tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditorTests.cs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
using NUnit.Framework;
22
using Umbraco.Cms.Core;
3+
using Umbraco.Cms.Core.Cache;
34
using Umbraco.Cms.Core.Models;
45
using Umbraco.Cms.Core.Models.Blocks;
6+
using Umbraco.Cms.Core.Notifications;
57
using Umbraco.Cms.Core.PropertyEditors;
8+
using Umbraco.Cms.Core.PublishedCache;
69
using Umbraco.Cms.Core.Serialization;
710
using Umbraco.Cms.Core.Services;
11+
using Umbraco.Cms.Core.Sync;
812
using Umbraco.Cms.Tests.Common.Builders;
13+
using Umbraco.Cms.Tests.Common.Builders.Extensions;
914
using Umbraco.Cms.Tests.Common.Testing;
1015
using Umbraco.Cms.Tests.Integration.Testing;
16+
using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
1117

1218
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors;
1319

@@ -23,6 +29,14 @@ internal sealed class RichTextPropertyEditorTests : UmbracoIntegrationTest
2329

2430
private IJsonSerializer JsonSerializer => GetRequiredService<IJsonSerializer>();
2531

32+
private IPublishedContentCache PublishedContentCache => GetRequiredService<IPublishedContentCache>();
33+
34+
protected override void CustomTestSetup(IUmbracoBuilder builder)
35+
{
36+
builder.AddNotificationHandler<ContentTreeChangeNotification, ContentTreeChangeDistributedCacheNotificationHandler>();
37+
builder.Services.AddUnique<IServerMessenger, ContentEventsTests.LocalServerMessenger>();
38+
}
39+
2640
[Test]
2741
public void Can_Use_Markup_String_As_Value()
2842
{
@@ -180,4 +194,103 @@ public void Can_Track_Block_Tags()
180194
Assert.IsNotNull(tags.Single(tag => tag.Text == "Tag Two"));
181195
Assert.IsNotNull(tags.Single(tag => tag.Text == "Tag Three"));
182196
}
197+
198+
[TestCase(null, false)]
199+
[TestCase("", false)]
200+
[TestCase("""{"markup":"","blocks":null}""", false)]
201+
[TestCase("""{"markup":"<p></p>","blocks":null}""", false)]
202+
[TestCase("abc", true)]
203+
[TestCase("""{"markup":"abc","blocks":null}""", true)]
204+
public async Task Can_Handle_Empty_Value_Representations_For_Invariant_Content(string? rteValue, bool expectedHasValue)
205+
{
206+
var contentType = await CreateContentTypeForEmptyValueTests();
207+
208+
var content = new ContentBuilder()
209+
.WithContentType(contentType)
210+
.WithName("Page")
211+
.WithPropertyValues(
212+
new
213+
{
214+
rte = rteValue
215+
})
216+
.Build();
217+
218+
var contentResult = ContentService.Save(content);
219+
Assert.IsTrue(contentResult.Success);
220+
221+
var publishResult = ContentService.Publish(content, []);
222+
Assert.IsTrue(publishResult.Success);
223+
224+
var publishedContent = await PublishedContentCache.GetByIdAsync(content.Key);
225+
Assert.IsNotNull(publishedContent);
226+
227+
var publishedProperty = publishedContent.Properties.First(property => property.Alias == "rte");
228+
Assert.AreEqual(expectedHasValue, publishedProperty.HasValue());
229+
230+
Assert.AreEqual(expectedHasValue, publishedContent.HasValue("rte"));
231+
}
232+
233+
[TestCase(null, false)]
234+
[TestCase("", false)]
235+
[TestCase("""{"markup":"","blocks":null}""", false)]
236+
[TestCase("""{"markup":"<p></p>","blocks":null}""", false)]
237+
[TestCase("abc", true)]
238+
[TestCase("""{"markup":"abc","blocks":null}""", true)]
239+
public async Task Can_Handle_Empty_Value_Representations_For_Variant_Content(string? rteValue, bool expectedHasValue)
240+
{
241+
var contentType = await CreateContentTypeForEmptyValueTests(ContentVariation.Culture);
242+
243+
var content = new ContentBuilder()
244+
.WithContentType(contentType)
245+
.WithName("Page")
246+
.WithCultureName("en-US", "Page")
247+
.WithPropertyValues(
248+
new
249+
{
250+
rte = rteValue
251+
},
252+
"en-US")
253+
.Build();
254+
255+
var contentResult = ContentService.Save(content);
256+
Assert.IsTrue(contentResult.Success);
257+
258+
var publishResult = ContentService.Publish(content, ["en-US"]);
259+
Assert.IsTrue(publishResult.Success);
260+
261+
var publishedContent = await PublishedContentCache.GetByIdAsync(content.Key);
262+
Assert.IsNotNull(publishedContent);
263+
264+
var publishedProperty = publishedContent.Properties.First(property => property.Alias == "rte");
265+
Assert.AreEqual(expectedHasValue, publishedProperty.HasValue("en-US"));
266+
267+
Assert.AreEqual(expectedHasValue, publishedContent.HasValue("rte", "en-US"));
268+
}
269+
270+
private async Task<IContentType> CreateContentTypeForEmptyValueTests(ContentVariation contentVariation = ContentVariation.Nothing)
271+
{
272+
var contentType = new ContentTypeBuilder()
273+
.WithAlias("myPage")
274+
.WithName("My Page")
275+
.WithContentVariation(contentVariation)
276+
.AddPropertyGroup()
277+
.WithAlias("content")
278+
.WithName("Content")
279+
.WithSupportsPublishing(true)
280+
.AddPropertyType()
281+
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.RichText)
282+
.WithDataTypeId(Constants.DataTypes.RichtextEditor)
283+
.WithValueStorageType(ValueStorageType.Ntext)
284+
.WithAlias("rte")
285+
.WithName("RTE")
286+
.WithVariations(contentVariation)
287+
.Done()
288+
.Done()
289+
.Build();
290+
291+
var contentTypeResult = await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey);
292+
Assert.IsTrue(contentTypeResult.Success);
293+
294+
return contentType;
295+
}
183296
}

0 commit comments

Comments
 (0)