From 4d80073547d017461bab3385f48edea2e9333fb1 Mon Sep 17 00:00:00 2001 From: Vitor Rodrigues Date: Sat, 26 Aug 2023 17:56:16 +0200 Subject: [PATCH 01/21] Fixed incorrect BlockListPropertyValueConverter delivery api property value type --- .../ValueConverters/BlockListPropertyValueConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs index 334b1f1aa3..52efe3755a 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs @@ -113,7 +113,7 @@ public class BlockListPropertyValueConverter : BlockPropertyValueConverterBase public Type GetDeliveryApiPropertyValueType(IPublishedPropertyType propertyType) - => typeof(IEnumerable); + => typeof(ApiBlockListModel); /// public object? ConvertIntermediateToDeliveryApiObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview, bool expanding) From efe00910f33f8052bb6f3c6c9301dd5920734e06 Mon Sep 17 00:00:00 2001 From: Vitor Rodrigues Date: Sun, 27 Aug 2023 11:11:02 +0200 Subject: [PATCH 02/21] DeliveryApi: Ensure the ContentType property is serialized first This is needed so it can be used as a discriminator field by System.Text.Json Changed both ApiElement and IApiElement to ensure not only ApiContentResponse, but also ApiBlockItem, which uses the interface directly, are adjusted. --- src/Umbraco.Core/Models/DeliveryApi/ApiElement.cs | 5 +++++ src/Umbraco.Core/Models/DeliveryApi/IApiElement.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/Umbraco.Core/Models/DeliveryApi/ApiElement.cs b/src/Umbraco.Core/Models/DeliveryApi/ApiElement.cs index b7d10814fc..d5941a1ed1 100644 --- a/src/Umbraco.Core/Models/DeliveryApi/ApiElement.cs +++ b/src/Umbraco.Core/Models/DeliveryApi/ApiElement.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace Umbraco.Cms.Core.Models.DeliveryApi; public class ApiElement : IApiElement @@ -11,6 +13,9 @@ public class ApiElement : IApiElement public Guid Id { get; } + // Ensure the ContentType property is serialized first + // This is needed so it can be used as a discriminator field by System.Text.Json + [JsonPropertyOrder(-100)] public string ContentType { get; } public IDictionary Properties { get; } diff --git a/src/Umbraco.Core/Models/DeliveryApi/IApiElement.cs b/src/Umbraco.Core/Models/DeliveryApi/IApiElement.cs index 7630225f64..e62525aa34 100644 --- a/src/Umbraco.Core/Models/DeliveryApi/IApiElement.cs +++ b/src/Umbraco.Core/Models/DeliveryApi/IApiElement.cs @@ -1,9 +1,14 @@ +using System.Text.Json.Serialization; + namespace Umbraco.Cms.Core.Models.DeliveryApi; public interface IApiElement { Guid Id { get; } + // Ensure the ContentType property is serialized first + // This is needed so it can be used as a discriminator field by System.Text.Json + [JsonPropertyOrder(-100)] string ContentType { get; } IDictionary Properties { get; } From e5d6372dbdfea41bc84cf3c69097d1f391976d2d Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 1 Sep 2023 16:45:06 +0200 Subject: [PATCH 03/21] Change default UpgradeUnattended value to true (#13281) --- src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs b/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs index 577fb9a2d9..7f757d81c0 100644 --- a/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs @@ -13,7 +13,7 @@ namespace Umbraco.Cms.Core.Configuration.Models; public class UnattendedSettings { private const bool StaticInstallUnattended = false; - private const bool StaticUpgradeUnattended = false; + private const bool StaticUpgradeUnattended = true; /// /// Gets or sets a value indicating whether unattended installs are enabled. From 334dff8645a6b2cbe1dacde866f54bf10f1e30b4 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 4 Sep 2023 10:23:44 +0200 Subject: [PATCH 04/21] bump version number --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index f4c3bd0a8a..7b6fdb03be 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.7.0-rc", + "version": "10.7.0", "assemblyVersion": { "precision": "build" }, From bc68f11d820234bd4234e11ce7e2e0a19668fa6c Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 4 Sep 2023 10:49:19 +0200 Subject: [PATCH 05/21] Revert #13281 and only set `UpgradeUnattended` value to true for new projects (#14764) * Revert "Change default UpgradeUnattended value to true (#13281)" This reverts commit e5d6372dbdfea41bc84cf3c69097d1f391976d2d. * Set UpgradeUnattended value to true for new projects --- src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs | 2 +- templates/UmbracoProject/appsettings.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs b/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs index 7f757d81c0..577fb9a2d9 100644 --- a/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs @@ -13,7 +13,7 @@ namespace Umbraco.Cms.Core.Configuration.Models; public class UnattendedSettings { private const bool StaticInstallUnattended = false; - private const bool StaticUpgradeUnattended = true; + private const bool StaticUpgradeUnattended = false; /// /// Gets or sets a value indicating whether unattended installs are enabled. diff --git a/templates/UmbracoProject/appsettings.json b/templates/UmbracoProject/appsettings.json index f1e53a9e48..03457ad0b7 100644 --- a/templates/UmbracoProject/appsettings.json +++ b/templates/UmbracoProject/appsettings.json @@ -33,6 +33,9 @@ "ContentVersionCleanupPolicy": { "EnableCleanup": true } + }, + "Unattended": { + "UpgradeUnattended": true } } } From fd8f80952456fe9608ffaaed93741783785b9102 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 5 Sep 2023 08:04:16 +0200 Subject: [PATCH 06/21] bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 6761d6acc5..1d5d5613a8 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "11.5.0-rc", + "version": "11.5.0", "assemblyVersion": { "precision": "build" }, From f750bca453c12d4cc4321868eb1197c9916f8243 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Tue, 5 Sep 2023 10:14:06 +0200 Subject: [PATCH 07/21] V10/bugfix/14543 publish descendants (#14763) * WIP: Fix publish descendants and related notifications * Removed related entitities from publish notification * Fixed root not being saved when publishingWithDescendants * Updated integrationtests to reflect the update view on when to save the root when its part of a branch * PR formatting fix Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * PR Cleanup Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Spicing up the codebase with some PR pattern matching Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> --------- Co-authored-by: Sven Geusens Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> --- src/Umbraco.Core/Services/ContentService.cs | 42 ++++++++++++++----- .../ContentServicePublishBranchTests.cs | 12 +++--- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 1d8811ba50..e9d0f8f4fd 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1950,7 +1950,7 @@ public class ContentService : RepositoryService, IContentService cultures = new HashSet(); // empty means 'already published' } - if (edited) + if (isRoot || edited) { cultures.Add(c); // means 'republish this culture' } @@ -2105,11 +2105,13 @@ public class ContentService : RepositoryService, IContentService } // deal with the branch root - if it fails, abort - PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs); - if (result != null) + var rootPublishNotificationState = new Dictionary(); + PublishResult? rootResult = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, + publishedDocuments, eventMessages, userId, allLangs, rootPublishNotificationState); + if (rootResult != null) { - results.Add(result); - if (!result.Success) + results.Add(rootResult); + if (!rootResult.Success) { return results; } @@ -2122,6 +2124,7 @@ public class ContentService : RepositoryService, IContentService int count; var page = 0; const int pageSize = 100; + PublishResult? result = null; do { count = 0; @@ -2140,7 +2143,8 @@ public class ContentService : RepositoryService, IContentService } // no need to check path here, parent has to be published here - result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs); + result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, + publishedDocuments, eventMessages, userId, allLangs,null); if (result != null) { results.Add(result); @@ -2164,7 +2168,12 @@ public class ContentService : RepositoryService, IContentService // (SaveAndPublishBranchOne does *not* do it) scope.Notifications.Publish( new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages)); - scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages)); + if (rootResult?.Success is true) + { + scope.Notifications.Publish( + new ContentPublishedNotification(rootResult!.Content!, eventMessages) + .WithState(rootPublishNotificationState)); + } scope.Complete(); } @@ -2175,6 +2184,9 @@ public class ContentService : RepositoryService, IContentService // shouldPublish: a function determining whether the document has changes that need to be published // note - 'force' is handled by 'editing' // publishValues: a function publishing values (using the appropriate PublishCulture calls) + /// Only set this when processing a the root of the branch + /// Published notification will not be send when this property is set + /// private PublishResult? SaveAndPublishBranchItem( ICoreScope scope, IContent document, @@ -2185,7 +2197,8 @@ public class ContentService : RepositoryService, IContentService ICollection publishedDocuments, EventMessages evtMsgs, int userId, - IReadOnlyCollection allLangs) + IReadOnlyCollection allLangs, + IDictionary? rootPublishingNotificationState) { HashSet? culturesToPublish = shouldPublish(document); @@ -2214,10 +2227,17 @@ public class ContentService : RepositoryService, IContentService return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document); } - PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, savingNotification.State, userId, true, isRoot); - if (result.Success) + var notificationState = rootPublishingNotificationState ?? new Dictionary(); + PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, notificationState, userId, true, isRoot); + if (!result.Success) { - publishedDocuments.Add(document); + return result; + } + + publishedDocuments.Add(document); + if (rootPublishingNotificationState == null) + { + scope.Notifications.Publish(new ContentPublishedNotification(result.Content!, evtMsgs).WithState(notificationState)); } return result; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs index e5ef5789db..f6e6aaf0a7 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs @@ -91,7 +91,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest AssertPublishResults( r, x => x.Result, - PublishResultType.SuccessPublishAlready, + PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublishAlready); @@ -138,7 +138,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest AssertPublishResults( r, x => x.Result, - PublishResultType.SuccessPublishAlready, + PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublish, @@ -183,7 +183,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest var r = ContentService.SaveAndPublishBranch(vRoot, false) .ToArray(); // no culture specified so "*" is used, so all cultures - Assert.AreEqual(PublishResultType.SuccessPublishAlready, r[0].Result); + Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[0].Result); // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[1].Result); } @@ -219,7 +219,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest var saveResult = ContentService.Save(iv1); var r = ContentService.SaveAndPublishBranch(vRoot, false, "de").ToArray(); - Assert.AreEqual(PublishResultType.SuccessPublishAlready, r[0].Result); + Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[0].Result); // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[1].Result); } @@ -379,7 +379,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest AssertPublishResults( r, x => x.Result, - PublishResultType.SuccessPublishAlready, + PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. PublishResultType.SuccessPublish, PublishResultType.SuccessPublishCulture); @@ -405,7 +405,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest AssertPublishResults( r, x => x.Result, - PublishResultType.SuccessPublishAlready, + PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. PublishResultType.SuccessPublish, PublishResultType.SuccessPublishCulture); From 39a102ed8a666e084859c82cbcbe1e414e0bea05 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 5 Sep 2023 10:14:21 +0200 Subject: [PATCH 08/21] rollback version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 3c0aeb9872..7b6fdb03be 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.8.0-rc", + "version": "10.7.0", "assemblyVersion": { "precision": "build" }, From 147b46440fa5ba399838ff2f8aaf115518e7ad41 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Tue, 5 Sep 2023 10:14:06 +0200 Subject: [PATCH 09/21] V10/bugfix/14543 publish descendants (#14763) * WIP: Fix publish descendants and related notifications * Removed related entitities from publish notification * Fixed root not being saved when publishingWithDescendants * Updated integrationtests to reflect the update view on when to save the root when its part of a branch * PR formatting fix Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * PR Cleanup Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Spicing up the codebase with some PR pattern matching Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> --------- Co-authored-by: Sven Geusens Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> --- src/Umbraco.Core/Services/ContentService.cs | 42 ++++++++++++++----- .../ContentServicePublishBranchTests.cs | 12 +++--- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index cee1e5bc01..e9c45c1304 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1950,7 +1950,7 @@ public class ContentService : RepositoryService, IContentService cultures = new HashSet(); // empty means 'already published' } - if (edited) + if (isRoot || edited) { cultures.Add(c); // means 'republish this culture' } @@ -2105,11 +2105,13 @@ public class ContentService : RepositoryService, IContentService } // deal with the branch root - if it fails, abort - PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs); - if (result != null) + var rootPublishNotificationState = new Dictionary(); + PublishResult? rootResult = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, + publishedDocuments, eventMessages, userId, allLangs, rootPublishNotificationState); + if (rootResult != null) { - results.Add(result); - if (!result.Success) + results.Add(rootResult); + if (!rootResult.Success) { return results; } @@ -2122,6 +2124,7 @@ public class ContentService : RepositoryService, IContentService int count; var page = 0; const int pageSize = 100; + PublishResult? result = null; do { count = 0; @@ -2140,7 +2143,8 @@ public class ContentService : RepositoryService, IContentService } // no need to check path here, parent has to be published here - result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs); + result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, + publishedDocuments, eventMessages, userId, allLangs,null); if (result != null) { results.Add(result); @@ -2164,7 +2168,12 @@ public class ContentService : RepositoryService, IContentService // (SaveAndPublishBranchOne does *not* do it) scope.Notifications.Publish( new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages)); - scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages)); + if (rootResult?.Success is true) + { + scope.Notifications.Publish( + new ContentPublishedNotification(rootResult!.Content!, eventMessages) + .WithState(rootPublishNotificationState)); + } scope.Complete(); } @@ -2175,6 +2184,9 @@ public class ContentService : RepositoryService, IContentService // shouldPublish: a function determining whether the document has changes that need to be published // note - 'force' is handled by 'editing' // publishValues: a function publishing values (using the appropriate PublishCulture calls) + /// Only set this when processing a the root of the branch + /// Published notification will not be send when this property is set + /// private PublishResult? SaveAndPublishBranchItem( ICoreScope scope, IContent document, @@ -2185,7 +2197,8 @@ public class ContentService : RepositoryService, IContentService ICollection publishedDocuments, EventMessages evtMsgs, int userId, - IReadOnlyCollection allLangs) + IReadOnlyCollection allLangs, + IDictionary? rootPublishingNotificationState) { HashSet? culturesToPublish = shouldPublish(document); @@ -2214,10 +2227,17 @@ public class ContentService : RepositoryService, IContentService return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document); } - PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, savingNotification.State, userId, true, isRoot); - if (result.Success) + var notificationState = rootPublishingNotificationState ?? new Dictionary(); + PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, notificationState, userId, true, isRoot); + if (!result.Success) { - publishedDocuments.Add(document); + return result; + } + + publishedDocuments.Add(document); + if (rootPublishingNotificationState == null) + { + scope.Notifications.Publish(new ContentPublishedNotification(result.Content!, evtMsgs).WithState(notificationState)); } return result; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs index e5ef5789db..f6e6aaf0a7 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs @@ -91,7 +91,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest AssertPublishResults( r, x => x.Result, - PublishResultType.SuccessPublishAlready, + PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublishAlready); @@ -138,7 +138,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest AssertPublishResults( r, x => x.Result, - PublishResultType.SuccessPublishAlready, + PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublish, @@ -183,7 +183,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest var r = ContentService.SaveAndPublishBranch(vRoot, false) .ToArray(); // no culture specified so "*" is used, so all cultures - Assert.AreEqual(PublishResultType.SuccessPublishAlready, r[0].Result); + Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[0].Result); // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[1].Result); } @@ -219,7 +219,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest var saveResult = ContentService.Save(iv1); var r = ContentService.SaveAndPublishBranch(vRoot, false, "de").ToArray(); - Assert.AreEqual(PublishResultType.SuccessPublishAlready, r[0].Result); + Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[0].Result); // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[1].Result); } @@ -379,7 +379,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest AssertPublishResults( r, x => x.Result, - PublishResultType.SuccessPublishAlready, + PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. PublishResultType.SuccessPublish, PublishResultType.SuccessPublishCulture); @@ -405,7 +405,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest AssertPublishResults( r, x => x.Result, - PublishResultType.SuccessPublishAlready, + PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. PublishResultType.SuccessPublish, PublishResultType.SuccessPublishCulture); From 28d16f69ba38c34b6fa26f72650bfb37c456a0ef Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 6 Sep 2023 08:51:36 +0200 Subject: [PATCH 10/21] bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 4b04b0255b..eeb1511652 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "12.2.0-rc", + "version": "12.3.0-rc", "assemblyVersion": { "precision": "build" }, From 280d0a236b69df175890c4d7b8a6290c6c4c3a60 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 6 Sep 2023 09:49:44 +0200 Subject: [PATCH 11/21] Breaking changes for the Delivery API (#14745) * Use interfaces for API media return values + introduce dedicated models for crops and focal points * Move content and media controllers to their own namespaces --- .../{ => Content}/ByIdContentApiController.cs | 2 +- .../ByIdsContentApiController.cs | 2 +- .../ByRouteContentApiController.cs | 3 +- .../{ => Content}/ContentApiControllerBase.cs | 2 +- .../ContentApiItemControllerBase.cs | 2 +- .../QueryContentApiController.cs | 2 +- .../{ => Media}/ByIdMediaApiController.cs | 4 +-- .../{ => Media}/ByIdsMediaApiController.cs | 6 ++-- .../{ => Media}/ByPathMediaApiController.cs | 4 +-- .../{ => Media}/MediaApiControllerBase.cs | 4 +-- .../{ => Media}/QueryMediaApiController.cs | 6 ++-- .../SwaggerContentDocumentationFilter.cs | 1 + .../SwaggerMediaDocumentationFilter.cs | 1 + .../Models/DeliveryApi/IApiMediaWithCrops.cs | 8 ++++++ .../DeliveryApi/IApiMediaWithCropsResponse.cs | 10 +++++++ .../Models/DeliveryApi/ImageCrop.cs | 20 +++++++++++++ .../DeliveryApi/ImageCropCoordinates.cs | 20 +++++++++++++ .../Models/DeliveryApi/ImageFocalPoint.cs | 14 ++++++++++ .../DeliveryApi/ApiMediaWithCropsBuilder.cs | 11 ++++---- .../ApiMediaWithCropsBuilderBase.cs | 8 +++--- .../ApiMediaWithCropsResponseBuilder.cs | 9 +++--- .../DeliveryApi/IApiMediaWithCropsBuilder.cs | 4 +-- .../IApiMediaWithCropsResponseBuilder.cs | 2 +- .../Extensions/DeliveryApiImageExtensions.cs | 21 ++++++++++++++ .../DeliveryApi/ApiImageCropperValue.cs | 8 ++---- .../Models/DeliveryApi/ApiMediaWithCrops.cs | 12 ++++---- .../DeliveryApi/ApiMediaWithCropsResponse.cs | 10 +++---- .../ImageCropperValueConverter.cs | 7 +++-- .../MediaPickerWithCropsValueConverter.cs | 6 ++-- ...MediaPickerWithCropsValueConverterTests.cs | 28 +++++++++---------- 30 files changed, 162 insertions(+), 75 deletions(-) rename src/Umbraco.Cms.Api.Delivery/Controllers/{ => Content}/ByIdContentApiController.cs (96%) rename src/Umbraco.Cms.Api.Delivery/Controllers/{ => Content}/ByIdsContentApiController.cs (96%) rename src/Umbraco.Cms.Api.Delivery/Controllers/{ => Content}/ByRouteContentApiController.cs (98%) rename src/Umbraco.Cms.Api.Delivery/Controllers/{ => Content}/ContentApiControllerBase.cs (97%) rename src/Umbraco.Cms.Api.Delivery/Controllers/{ => Content}/ContentApiItemControllerBase.cs (94%) rename src/Umbraco.Cms.Api.Delivery/Controllers/{ => Content}/QueryContentApiController.cs (97%) rename src/Umbraco.Cms.Api.Delivery/Controllers/{ => Media}/ByIdMediaApiController.cs (89%) rename src/Umbraco.Cms.Api.Delivery/Controllers/{ => Media}/ByIdsMediaApiController.cs (85%) rename src/Umbraco.Cms.Api.Delivery/Controllers/{ => Media}/ByPathMediaApiController.cs (91%) rename src/Umbraco.Cms.Api.Delivery/Controllers/{ => Media}/MediaApiControllerBase.cs (94%) rename src/Umbraco.Cms.Api.Delivery/Controllers/{ => Media}/QueryMediaApiController.cs (91%) create mode 100644 src/Umbraco.Core/Models/DeliveryApi/IApiMediaWithCrops.cs create mode 100644 src/Umbraco.Core/Models/DeliveryApi/IApiMediaWithCropsResponse.cs create mode 100644 src/Umbraco.Core/Models/DeliveryApi/ImageCrop.cs create mode 100644 src/Umbraco.Core/Models/DeliveryApi/ImageCropCoordinates.cs create mode 100644 src/Umbraco.Core/Models/DeliveryApi/ImageFocalPoint.cs create mode 100644 src/Umbraco.Infrastructure/Extensions/DeliveryApiImageExtensions.cs diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/ByIdContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdContentApiController.cs similarity index 96% rename from src/Umbraco.Cms.Api.Delivery/Controllers/ByIdContentApiController.cs rename to src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdContentApiController.cs index fc2e91e1ba..d16afc4d6d 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/ByIdContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdContentApiController.cs @@ -6,7 +6,7 @@ using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Api.Delivery.Controllers; +namespace Umbraco.Cms.Api.Delivery.Controllers.Content; [ApiVersion("1.0")] public class ByIdContentApiController : ContentApiItemControllerBase diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/ByIdsContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdsContentApiController.cs similarity index 96% rename from src/Umbraco.Cms.Api.Delivery/Controllers/ByIdsContentApiController.cs rename to src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdsContentApiController.cs index ca2deb360d..5d415fffe6 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/ByIdsContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByIdsContentApiController.cs @@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Api.Delivery.Controllers; +namespace Umbraco.Cms.Api.Delivery.Controllers.Content; [ApiVersion("1.0")] public class ByIdsContentApiController : ContentApiItemControllerBase diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/ByRouteContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs similarity index 98% rename from src/Umbraco.Cms.Api.Delivery/Controllers/ByRouteContentApiController.cs rename to src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs index 04900f52d2..2d22887637 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/ByRouteContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ByRouteContentApiController.cs @@ -1,4 +1,3 @@ -using System.Net; using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -9,7 +8,7 @@ using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Api.Delivery.Controllers; +namespace Umbraco.Cms.Api.Delivery.Controllers.Content; [ApiVersion("1.0")] public class ByRouteContentApiController : ContentApiItemControllerBase diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/ContentApiControllerBase.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiControllerBase.cs similarity index 97% rename from src/Umbraco.Cms.Api.Delivery/Controllers/ContentApiControllerBase.cs rename to src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiControllerBase.cs index 07439505e0..ebfa32c479 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/ContentApiControllerBase.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiControllerBase.cs @@ -5,7 +5,7 @@ using Umbraco.Cms.Api.Delivery.Routing; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Services.OperationStatus; -namespace Umbraco.Cms.Api.Delivery.Controllers; +namespace Umbraco.Cms.Api.Delivery.Controllers.Content; [DeliveryApiAccess] [VersionedDeliveryApiRoute("content")] diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/ContentApiItemControllerBase.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiItemControllerBase.cs similarity index 94% rename from src/Umbraco.Cms.Api.Delivery/Controllers/ContentApiItemControllerBase.cs rename to src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiItemControllerBase.cs index 8c8e2b5fd7..dad11de009 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/ContentApiItemControllerBase.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/ContentApiItemControllerBase.cs @@ -2,7 +2,7 @@ using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Api.Delivery.Controllers; +namespace Umbraco.Cms.Api.Delivery.Controllers.Content; public abstract class ContentApiItemControllerBase : ContentApiControllerBase { diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/QueryContentApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/QueryContentApiController.cs similarity index 97% rename from src/Umbraco.Cms.Api.Delivery/Controllers/QueryContentApiController.cs rename to src/Umbraco.Cms.Api.Delivery/Controllers/Content/QueryContentApiController.cs index e46204f85e..d4db82d9be 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/QueryContentApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Content/QueryContentApiController.cs @@ -10,7 +10,7 @@ using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Extensions; -namespace Umbraco.Cms.Api.Delivery.Controllers; +namespace Umbraco.Cms.Api.Delivery.Controllers.Content; [ApiVersion("1.0")] public class QueryContentApiController : ContentApiControllerBase diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/ByIdMediaApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdMediaApiController.cs similarity index 89% rename from src/Umbraco.Cms.Api.Delivery/Controllers/ByIdMediaApiController.cs rename to src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdMediaApiController.cs index 423d70fd5b..b0242bea5d 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/ByIdMediaApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdMediaApiController.cs @@ -6,7 +6,7 @@ using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Infrastructure.DeliveryApi; -namespace Umbraco.Cms.Api.Delivery.Controllers; +namespace Umbraco.Cms.Api.Delivery.Controllers.Media; [ApiVersion("1.0")] public class ByIdMediaApiController : MediaApiControllerBase @@ -23,7 +23,7 @@ public class ByIdMediaApiController : MediaApiControllerBase /// The media item or not found result. [HttpGet("item/{id:guid}")] [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(ApiMediaWithCropsResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(IApiMediaWithCropsResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task ById(Guid id) { diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/ByIdsMediaApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdsMediaApiController.cs similarity index 85% rename from src/Umbraco.Cms.Api.Delivery/Controllers/ByIdsMediaApiController.cs rename to src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdsMediaApiController.cs index b8327e95c3..a9421eaa0c 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/ByIdsMediaApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdsMediaApiController.cs @@ -7,7 +7,7 @@ using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Infrastructure.DeliveryApi; using Umbraco.Extensions; -namespace Umbraco.Cms.Api.Delivery.Controllers; +namespace Umbraco.Cms.Api.Delivery.Controllers.Media; [ApiVersion("1.0")] public class ByIdsMediaApiController : MediaApiControllerBase @@ -24,7 +24,7 @@ public class ByIdsMediaApiController : MediaApiControllerBase /// The media items. [HttpGet("item")] [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task Item([FromQuery(Name = "id")] HashSet ids) { IPublishedContent[] mediaItems = ids @@ -32,7 +32,7 @@ public class ByIdsMediaApiController : MediaApiControllerBase .WhereNotNull() .ToArray(); - ApiMediaWithCropsResponse[] apiMediaItems = mediaItems + IApiMediaWithCropsResponse[] apiMediaItems = mediaItems .Select(BuildApiMediaWithCrops) .ToArray(); diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/ByPathMediaApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByPathMediaApiController.cs similarity index 91% rename from src/Umbraco.Cms.Api.Delivery/Controllers/ByPathMediaApiController.cs rename to src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByPathMediaApiController.cs index 947dd820a1..1d725ac5ab 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/ByPathMediaApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByPathMediaApiController.cs @@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Infrastructure.DeliveryApi; -namespace Umbraco.Cms.Api.Delivery.Controllers; +namespace Umbraco.Cms.Api.Delivery.Controllers.Media; [ApiVersion("1.0")] public class ByPathMediaApiController : MediaApiControllerBase @@ -28,7 +28,7 @@ public class ByPathMediaApiController : MediaApiControllerBase /// The media item or not found result. [HttpGet("item/{*path}")] [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(ApiMediaWithCropsResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(IApiMediaWithCropsResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task ByPath(string path) { diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/MediaApiControllerBase.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/MediaApiControllerBase.cs similarity index 94% rename from src/Umbraco.Cms.Api.Delivery/Controllers/MediaApiControllerBase.cs rename to src/Umbraco.Cms.Api.Delivery/Controllers/Media/MediaApiControllerBase.cs index dc279cf703..73a385fd2e 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/MediaApiControllerBase.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/MediaApiControllerBase.cs @@ -9,7 +9,7 @@ using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Infrastructure.DeliveryApi; using Umbraco.Extensions; -namespace Umbraco.Cms.Api.Delivery.Controllers; +namespace Umbraco.Cms.Api.Delivery.Controllers.Media; [DeliveryApiMediaAccess] [VersionedDeliveryApiRoute("media")] @@ -30,7 +30,7 @@ public abstract class MediaApiControllerBase : DeliveryApiControllerBase ??= _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Media ?? throw new InvalidOperationException("Could not obtain the published media cache"); - protected ApiMediaWithCropsResponse BuildApiMediaWithCrops(IPublishedContent media) + protected IApiMediaWithCropsResponse BuildApiMediaWithCrops(IPublishedContent media) => _apiMediaWithCropsResponseBuilder.Build(media); protected IActionResult ApiMediaQueryOperationStatusResult(ApiMediaQueryOperationStatus status) => diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/QueryMediaApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/QueryMediaApiController.cs similarity index 91% rename from src/Umbraco.Cms.Api.Delivery/Controllers/QueryMediaApiController.cs rename to src/Umbraco.Cms.Api.Delivery/Controllers/Media/QueryMediaApiController.cs index 98110e9589..5d962ea4bf 100644 --- a/src/Umbraco.Cms.Api.Delivery/Controllers/QueryMediaApiController.cs +++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/QueryMediaApiController.cs @@ -12,7 +12,7 @@ using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Infrastructure.DeliveryApi; using Umbraco.Extensions; -namespace Umbraco.Cms.Api.Delivery.Controllers; +namespace Umbraco.Cms.Api.Delivery.Controllers.Media; [ApiVersion("1.0")] public class QueryMediaApiController : MediaApiControllerBase @@ -37,7 +37,7 @@ public class QueryMediaApiController : MediaApiControllerBase /// The paged result of the media item(s). [HttpGet] [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] public async Task Query( string? fetch, @@ -56,7 +56,7 @@ public class QueryMediaApiController : MediaApiControllerBase PagedModel pagedResult = queryAttempt.Result; IPublishedContent[] mediaItems = pagedResult.Items.Select(PublishedMediaCache.GetById).WhereNotNull().ToArray(); - var model = new PagedViewModel + var model = new PagedViewModel { Total = pagedResult.Total, Items = mediaItems.Select(BuildApiMediaWithCrops) diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs index 8b3c946873..7c42a3d0aa 100644 --- a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs +++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs @@ -3,6 +3,7 @@ using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Api.Delivery.Configuration; using Umbraco.Cms.Api.Delivery.Controllers; +using Umbraco.Cms.Api.Delivery.Controllers.Content; namespace Umbraco.Cms.Api.Delivery.Filters; diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerMediaDocumentationFilter.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerMediaDocumentationFilter.cs index af22914f78..8529178888 100644 --- a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerMediaDocumentationFilter.cs +++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerMediaDocumentationFilter.cs @@ -3,6 +3,7 @@ using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Api.Delivery.Configuration; using Umbraco.Cms.Api.Delivery.Controllers; +using Umbraco.Cms.Api.Delivery.Controllers.Media; namespace Umbraco.Cms.Api.Delivery.Filters; diff --git a/src/Umbraco.Core/Models/DeliveryApi/IApiMediaWithCrops.cs b/src/Umbraco.Core/Models/DeliveryApi/IApiMediaWithCrops.cs new file mode 100644 index 0000000000..073ea89ca3 --- /dev/null +++ b/src/Umbraco.Core/Models/DeliveryApi/IApiMediaWithCrops.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Core.Models.DeliveryApi; + +public interface IApiMediaWithCrops : IApiMedia +{ + public ImageFocalPoint? FocalPoint { get; } + + public IEnumerable? Crops { get; } +} diff --git a/src/Umbraco.Core/Models/DeliveryApi/IApiMediaWithCropsResponse.cs b/src/Umbraco.Core/Models/DeliveryApi/IApiMediaWithCropsResponse.cs new file mode 100644 index 0000000000..34912bbec7 --- /dev/null +++ b/src/Umbraco.Core/Models/DeliveryApi/IApiMediaWithCropsResponse.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Cms.Core.Models.DeliveryApi; + +public interface IApiMediaWithCropsResponse : IApiMediaWithCrops +{ + public string Path { get; } + + public DateTime CreateDate { get; } + + public DateTime UpdateDate { get; } +} diff --git a/src/Umbraco.Core/Models/DeliveryApi/ImageCrop.cs b/src/Umbraco.Core/Models/DeliveryApi/ImageCrop.cs new file mode 100644 index 0000000000..eda02d8fa7 --- /dev/null +++ b/src/Umbraco.Core/Models/DeliveryApi/ImageCrop.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Cms.Core.Models.DeliveryApi; + +public class ImageCrop +{ + public ImageCrop(string? alias, int width, int height, ImageCropCoordinates? coordinates) + { + Alias = alias; + Width = width; + Height = height; + Coordinates = coordinates; + } + + public string? Alias { get; } + + public int Width { get; } + + public int Height { get; } + + public ImageCropCoordinates? Coordinates { get; } +} diff --git a/src/Umbraco.Core/Models/DeliveryApi/ImageCropCoordinates.cs b/src/Umbraco.Core/Models/DeliveryApi/ImageCropCoordinates.cs new file mode 100644 index 0000000000..48ad0b6201 --- /dev/null +++ b/src/Umbraco.Core/Models/DeliveryApi/ImageCropCoordinates.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Cms.Core.Models.DeliveryApi; + +public class ImageCropCoordinates +{ + public ImageCropCoordinates(decimal x1, decimal y1, decimal x2, decimal y2) + { + X1 = x1; + Y1 = y1; + X2 = x2; + Y2 = y2; + } + + public decimal X1 { get; } + + public decimal Y1 { get; } + + public decimal X2 { get; } + + public decimal Y2 { get; } +} diff --git a/src/Umbraco.Core/Models/DeliveryApi/ImageFocalPoint.cs b/src/Umbraco.Core/Models/DeliveryApi/ImageFocalPoint.cs new file mode 100644 index 0000000000..d3ae5e65cd --- /dev/null +++ b/src/Umbraco.Core/Models/DeliveryApi/ImageFocalPoint.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Cms.Core.Models.DeliveryApi; + +public class ImageFocalPoint +{ + public ImageFocalPoint(decimal left, decimal top) + { + Left = left; + Top = top; + } + + public decimal Left { get; } + + public decimal Top { get; } +} diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiMediaWithCropsBuilder.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiMediaWithCropsBuilder.cs index ab9d7943b4..d3490dcb13 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiMediaWithCropsBuilder.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiMediaWithCropsBuilder.cs @@ -1,21 +1,20 @@ using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors.ValueConverters; namespace Umbraco.Cms.Infrastructure.DeliveryApi; -internal sealed class ApiMediaWithCropsBuilder : ApiMediaWithCropsBuilderBase, IApiMediaWithCropsBuilder +internal sealed class ApiMediaWithCropsBuilder : ApiMediaWithCropsBuilderBase, IApiMediaWithCropsBuilder { public ApiMediaWithCropsBuilder(IApiMediaBuilder apiMediaBuilder, IPublishedValueFallback publishedValueFallback) : base(apiMediaBuilder, publishedValueFallback) { } - protected override ApiMediaWithCrops Create( + protected override IApiMediaWithCrops Create( IPublishedContent media, IApiMedia inner, - ImageCropperValue.ImageCropperFocalPoint? focalPoint, - IEnumerable? crops) => - new ApiMediaWithCrops(inner, focalPoint, crops); + ImageFocalPoint? focalPoint, + IEnumerable? crops) + => new ApiMediaWithCrops(inner, focalPoint, crops); } diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiMediaWithCropsBuilderBase.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiMediaWithCropsBuilderBase.cs index 8754eea976..d8ee9c9289 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiMediaWithCropsBuilderBase.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiMediaWithCropsBuilderBase.cs @@ -9,7 +9,7 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.DeliveryApi; internal abstract class ApiMediaWithCropsBuilderBase - where T : IApiMedia + where T : IApiMediaWithCrops { private readonly IApiMediaBuilder _apiMediaBuilder; private readonly IPublishedValueFallback _publishedValueFallback; @@ -23,8 +23,8 @@ internal abstract class ApiMediaWithCropsBuilderBase protected abstract T Create( IPublishedContent media, IApiMedia inner, - ImageCropperValue.ImageCropperFocalPoint? focalPoint, - IEnumerable? crops); + ImageFocalPoint? focalPoint, + IEnumerable? crops); public T Build(MediaWithCrops media) { @@ -38,7 +38,7 @@ internal abstract class ApiMediaWithCropsBuilderBase localCrops = localCrops.Merge(mediaCrops); } - return Create(media.Content, inner, localCrops.FocalPoint, localCrops.Crops); + return Create(media.Content, inner, localCrops.GetImageFocalPoint(), localCrops.GetImageCrops()); } public T Build(IPublishedContent media) diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiMediaWithCropsResponseBuilder.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiMediaWithCropsResponseBuilder.cs index 68c73304da..3fd3ca34ef 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiMediaWithCropsResponseBuilder.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiMediaWithCropsResponseBuilder.cs @@ -1,22 +1,21 @@ using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors.ValueConverters; namespace Umbraco.Cms.Infrastructure.DeliveryApi; -internal sealed class ApiMediaWithCropsResponseBuilder : ApiMediaWithCropsBuilderBase, IApiMediaWithCropsResponseBuilder +internal sealed class ApiMediaWithCropsResponseBuilder : ApiMediaWithCropsBuilderBase, IApiMediaWithCropsResponseBuilder { public ApiMediaWithCropsResponseBuilder(IApiMediaBuilder apiMediaBuilder, IPublishedValueFallback publishedValueFallback) : base(apiMediaBuilder, publishedValueFallback) { } - protected override ApiMediaWithCropsResponse Create( + protected override IApiMediaWithCropsResponse Create( IPublishedContent media, IApiMedia inner, - ImageCropperValue.ImageCropperFocalPoint? focalPoint, - IEnumerable? crops) + ImageFocalPoint? focalPoint, + IEnumerable? crops) { var path = $"/{string.Join("/", PathSegments(media).Reverse())}/"; return new ApiMediaWithCropsResponse(inner, focalPoint, crops, path, media.CreateDate, media.UpdateDate); diff --git a/src/Umbraco.Infrastructure/DeliveryApi/IApiMediaWithCropsBuilder.cs b/src/Umbraco.Infrastructure/DeliveryApi/IApiMediaWithCropsBuilder.cs index 63c2a0d218..5d56a7c6f1 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/IApiMediaWithCropsBuilder.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/IApiMediaWithCropsBuilder.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Infrastructure.DeliveryApi; public interface IApiMediaWithCropsBuilder { - ApiMediaWithCrops Build(MediaWithCrops media); + IApiMediaWithCrops Build(MediaWithCrops media); - ApiMediaWithCrops Build(IPublishedContent media); + IApiMediaWithCrops Build(IPublishedContent media); } diff --git a/src/Umbraco.Infrastructure/DeliveryApi/IApiMediaWithCropsResponseBuilder.cs b/src/Umbraco.Infrastructure/DeliveryApi/IApiMediaWithCropsResponseBuilder.cs index 62e2cc7156..9c6377b4ea 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/IApiMediaWithCropsResponseBuilder.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/IApiMediaWithCropsResponseBuilder.cs @@ -5,5 +5,5 @@ namespace Umbraco.Cms.Infrastructure.DeliveryApi; public interface IApiMediaWithCropsResponseBuilder { - ApiMediaWithCropsResponse Build(IPublishedContent media); + IApiMediaWithCropsResponse Build(IPublishedContent media); } diff --git a/src/Umbraco.Infrastructure/Extensions/DeliveryApiImageExtensions.cs b/src/Umbraco.Infrastructure/Extensions/DeliveryApiImageExtensions.cs new file mode 100644 index 0000000000..ba23ed36de --- /dev/null +++ b/src/Umbraco.Infrastructure/Extensions/DeliveryApiImageExtensions.cs @@ -0,0 +1,21 @@ +using Umbraco.Cms.Core.Models.DeliveryApi; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +namespace Umbraco.Extensions; + +public static class DeliveryApiImageExtensions +{ + public static ImageFocalPoint? GetImageFocalPoint(this ImageCropperValue imageCropperValue) + => imageCropperValue.FocalPoint is not null + ? new ImageFocalPoint(imageCropperValue.FocalPoint.Left, imageCropperValue.FocalPoint.Top) + : null; + + public static IEnumerable? GetImageCrops(this ImageCropperValue imageCropperValue) + => imageCropperValue.Crops?.Select(crop => new ImageCrop( + crop.Alias, + crop.Width, + crop.Height, + crop.Coordinates is not null + ? new ImageCropCoordinates(crop.Coordinates.X1, crop.Coordinates.Y1, crop.Coordinates.X2, crop.Coordinates.Y2) + : null)); +} diff --git a/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiImageCropperValue.cs b/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiImageCropperValue.cs index 5cb6ee535e..275c88c619 100644 --- a/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiImageCropperValue.cs +++ b/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiImageCropperValue.cs @@ -1,10 +1,8 @@ -using Umbraco.Cms.Core.PropertyEditors.ValueConverters; - namespace Umbraco.Cms.Core.Models.DeliveryApi; internal sealed class ApiImageCropperValue { - public ApiImageCropperValue(string url, ImageCropperValue.ImageCropperFocalPoint? focalPoint, IEnumerable? crops) + public ApiImageCropperValue(string url, ImageFocalPoint? focalPoint, IEnumerable? crops) { Url = url; FocalPoint = focalPoint; @@ -13,7 +11,7 @@ internal sealed class ApiImageCropperValue public string Url { get; } - public ImageCropperValue.ImageCropperFocalPoint? FocalPoint { get; } + public ImageFocalPoint? FocalPoint { get; } - public IEnumerable? Crops { get; } + public IEnumerable? Crops { get; } } diff --git a/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiMediaWithCrops.cs b/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiMediaWithCrops.cs index d51a34e27d..9b7f60d2ba 100644 --- a/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiMediaWithCrops.cs +++ b/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiMediaWithCrops.cs @@ -1,15 +1,13 @@ -using Umbraco.Cms.Core.PropertyEditors.ValueConverters; - namespace Umbraco.Cms.Core.Models.DeliveryApi; -public class ApiMediaWithCrops : IApiMedia +internal class ApiMediaWithCrops : IApiMediaWithCrops { private readonly IApiMedia _inner; public ApiMediaWithCrops( IApiMedia inner, - ImageCropperValue.ImageCropperFocalPoint? focalPoint, - IEnumerable? crops) + ImageFocalPoint? focalPoint, + IEnumerable? crops) { _inner = inner; FocalPoint = focalPoint; @@ -34,7 +32,7 @@ public class ApiMediaWithCrops : IApiMedia public IDictionary Properties => _inner.Properties; - public ImageCropperValue.ImageCropperFocalPoint? FocalPoint { get; } + public ImageFocalPoint? FocalPoint { get; } - public IEnumerable? Crops { get; } + public IEnumerable? Crops { get; } } diff --git a/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiMediaWithCropsResponse.cs b/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiMediaWithCropsResponse.cs index e1c1f09344..b7c056a395 100644 --- a/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiMediaWithCropsResponse.cs +++ b/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiMediaWithCropsResponse.cs @@ -1,13 +1,11 @@ -using Umbraco.Cms.Core.PropertyEditors.ValueConverters; +namespace Umbraco.Cms.Core.Models.DeliveryApi; -namespace Umbraco.Cms.Core.Models.DeliveryApi; - -public sealed class ApiMediaWithCropsResponse : ApiMediaWithCrops +internal sealed class ApiMediaWithCropsResponse : ApiMediaWithCrops, IApiMediaWithCropsResponse { public ApiMediaWithCropsResponse( IApiMedia inner, - ImageCropperValue.ImageCropperFocalPoint? focalPoint, - IEnumerable? crops, + ImageFocalPoint? focalPoint, + IEnumerable? crops, string path, DateTime createDate, DateTime updateDate) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs index 37a4b406fc..1f6dfc9578 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs @@ -73,7 +73,10 @@ public class ImageCropperValueConverter : PropertyValueConverterBase, IDeliveryA public Type GetDeliveryApiPropertyValueType(IPublishedPropertyType propertyType) => typeof(ApiImageCropperValue); public object? ConvertIntermediateToDeliveryApiObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview, bool expanding) - => inter is ImageCropperValue {Src: { }} imageCropperValue - ? new ApiImageCropperValue(imageCropperValue.Src, imageCropperValue.FocalPoint, imageCropperValue.Crops) + => inter is ImageCropperValue { Src: { } } imageCropperValue + ? new ApiImageCropperValue( + imageCropperValue.Src, + imageCropperValue.GetImageFocalPoint(), + imageCropperValue.GetImageCrops()) : null; } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MediaPickerWithCropsValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MediaPickerWithCropsValueConverter.cs index 7e0829a399..817d48687a 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MediaPickerWithCropsValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MediaPickerWithCropsValueConverter.cs @@ -151,13 +151,13 @@ public class MediaPickerWithCropsValueConverter : PropertyValueConverterBase, ID public PropertyCacheLevel GetDeliveryApiPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Elements; - public Type GetDeliveryApiPropertyValueType(IPublishedPropertyType propertyType) => typeof(IEnumerable); + public Type GetDeliveryApiPropertyValueType(IPublishedPropertyType propertyType) => typeof(IEnumerable); public object? ConvertIntermediateToDeliveryApiObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview, bool expanding) { var isMultiple = IsMultipleDataType(propertyType.DataType); - ApiMediaWithCrops ToApiMedia(MediaWithCrops media) => _apiMediaWithCropsBuilder.Build(media); + IApiMediaWithCrops ToApiMedia(MediaWithCrops media) => _apiMediaWithCropsBuilder.Build(media); // NOTE: eventually we might implement this explicitly instead of piggybacking on the default object conversion. however, this only happens once per cache rebuild, // and the performance gain from an explicit implementation is negligible, so... at least for the time being this will do just fine. @@ -171,7 +171,7 @@ public class MediaPickerWithCropsValueConverter : PropertyValueConverterBase, ID return new [] { ToApiMedia(mediaWithCrops) }; } - return Array.Empty(); + return Array.Empty(); } private bool IsMultipleDataType(PublishedDataType dataType) => diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaPickerWithCropsValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaPickerWithCropsValueConverterTests.cs index 9e0284ffa8..365e48a325 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaPickerWithCropsValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaPickerWithCropsValueConverterTests.cs @@ -43,7 +43,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes var serializer = new JsonNetSerializer(); var valueConverter = MediaPickerWithCropsValueConverter(); - Assert.AreEqual(typeof(IEnumerable), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType)); + Assert.AreEqual(typeof(IEnumerable), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType)); var inter = serializer.Serialize(new[] { @@ -63,7 +63,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes } }); - var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType, PropertyCacheLevel.Element, inter, false, false) as IEnumerable; + var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType, PropertyCacheLevel.Element, inter, false, false) as IEnumerable; Assert.NotNull(result); Assert.AreEqual(1, result.Count()); var first = result.Single(); @@ -87,7 +87,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes var serializer = new JsonNetSerializer(); var valueConverter = MediaPickerWithCropsValueConverter(); - Assert.AreEqual(typeof(IEnumerable), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType)); + Assert.AreEqual(typeof(IEnumerable), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType)); var inter = serializer.Serialize(new[] { @@ -121,7 +121,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes } }); - var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType, PropertyCacheLevel.Element, inter, false, false) as IEnumerable; + var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType, PropertyCacheLevel.Element, inter, false, false) as IEnumerable; Assert.NotNull(result); Assert.AreEqual(2, result.Count()); var first = result.First(); @@ -169,7 +169,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes var serializer = new JsonNetSerializer(); var valueConverter = MediaPickerWithCropsValueConverter(); - Assert.AreEqual(typeof(IEnumerable), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType)); + Assert.AreEqual(typeof(IEnumerable), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType)); var inter = serializer.Serialize(new[] { @@ -188,7 +188,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes } }); - var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType, PropertyCacheLevel.Element, inter, false, false) as IEnumerable; + var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType, PropertyCacheLevel.Element, inter, false, false) as IEnumerable; Assert.NotNull(result); Assert.AreEqual(1, result.Count()); var mediaWithCrops = result.Single(); @@ -206,7 +206,6 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes ValidateCrop(mediaWithCrops.Crops.Last(), "mediaOne", 111, 222, 2m, 4m, 20m, 40m); } - [Test] public void MediaPickerWithCropsValueConverter_LocalCropsAndFocalPointTakesPrecedenceOverMediaCropsAndFocalPoint() { @@ -230,7 +229,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes var serializer = new JsonNetSerializer(); var valueConverter = MediaPickerWithCropsValueConverter(); - Assert.AreEqual(typeof(IEnumerable), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType)); + Assert.AreEqual(typeof(IEnumerable), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType)); var inter = serializer.Serialize(new[] { @@ -250,7 +249,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes } }); - var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType, PropertyCacheLevel.Element, inter, false, false) as IEnumerable; + var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType, PropertyCacheLevel.Element, inter, false, false) as IEnumerable; Assert.NotNull(result); Assert.AreEqual(1, result.Count()); var mediaWithCrops = result.Single(); @@ -277,7 +276,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes var valueConverter = MediaPickerWithCropsValueConverter(); - var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType, PropertyCacheLevel.Element, inter, false, false) as IEnumerable; + var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType, PropertyCacheLevel.Element, inter, false, false) as IEnumerable; Assert.NotNull(result); Assert.IsEmpty(result); } @@ -292,12 +291,11 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes var valueConverter = MediaPickerWithCropsValueConverter(); - var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType, PropertyCacheLevel.Element, inter, false, false) as IEnumerable; + var result = valueConverter.ConvertIntermediateToDeliveryApiObject(Mock.Of(), publishedPropertyType, PropertyCacheLevel.Element, inter, false, false) as IEnumerable; Assert.NotNull(result); Assert.IsEmpty(result); } - private IPublishedPropertyType SetupMediaPropertyType(bool multiSelect) { var publishedDataType = new PublishedDataType(123, "test", new Lazy(() => new MediaPicker3Configuration @@ -361,7 +359,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes } private void ValidateMedia( - ApiMediaWithCrops actual, + IApiMediaWithCrops actual, string expectedName, string expectedUrl, string expectedExtension, @@ -378,7 +376,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes } - private void ValidateFocalPoint(ImageCropperValue.ImageCropperFocalPoint? actual, decimal expectedLeft, decimal expectedTop) + private void ValidateFocalPoint(ImageFocalPoint? actual, decimal expectedLeft, decimal expectedTop) { Assert.NotNull(actual); Assert.AreEqual(expectedLeft, actual.Left); @@ -386,7 +384,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes } private void ValidateCrop( - ImageCropperValue.ImageCropperCrop actual, + ImageCrop actual, string expectedAlias, int expectedWidth, int expectedHeight, From eef0da096eed79d424fde85b593dfe9aac061970 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Wed, 6 Sep 2023 10:18:04 +0200 Subject: [PATCH 12/21] Being able to specify a start item in preview through Start-Item header (#14757) --- .../Services/RequestStartItemProvider.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RequestStartItemProvider.cs b/src/Umbraco.Cms.Api.Delivery/Services/RequestStartItemProvider.cs index 2ff748dcdc..dd72d930bd 100644 --- a/src/Umbraco.Cms.Api.Delivery/Services/RequestStartItemProvider.cs +++ b/src/Umbraco.Cms.Api.Delivery/Services/RequestStartItemProvider.cs @@ -11,6 +11,7 @@ internal sealed class RequestStartItemProvider : RequestHeaderHandler, IRequestS { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IVariationContextAccessor _variationContextAccessor; + private readonly IRequestPreviewService _requestPreviewService; // this provider lifetime is Scope, so we can cache this as a field private IPublishedContent? _requestedStartContent; @@ -18,11 +19,13 @@ internal sealed class RequestStartItemProvider : RequestHeaderHandler, IRequestS public RequestStartItemProvider( IHttpContextAccessor httpContextAccessor, IPublishedSnapshotAccessor publishedSnapshotAccessor, - IVariationContextAccessor variationContextAccessor) + IVariationContextAccessor variationContextAccessor, + IRequestPreviewService requestPreviewService) : base(httpContextAccessor) { _publishedSnapshotAccessor = publishedSnapshotAccessor; _variationContextAccessor = variationContextAccessor; + _requestPreviewService = requestPreviewService; } /// @@ -45,7 +48,7 @@ internal sealed class RequestStartItemProvider : RequestHeaderHandler, IRequestS return null; } - IEnumerable rootContent = publishedSnapshot.Content.GetAtRoot(); + IEnumerable rootContent = publishedSnapshot.Content.GetAtRoot(_requestPreviewService.IsPreview()); _requestedStartContent = Guid.TryParse(headerValue, out Guid key) ? rootContent.FirstOrDefault(c => c.Key == key) From 81b0886021e0e556c46561d997e2a300206f9b0d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 7 Sep 2023 08:55:27 +0200 Subject: [PATCH 13/21] Fix issue with too long audit log messages --- src/Umbraco.Core/Services/ContentService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 0340a07735..f7c399b13e 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1098,7 +1098,7 @@ public class ContentService : RepositoryService, IContentService new ContentTreeChangeNotification(contentsA, TreeChangeTypes.RefreshNode, eventMessages)); string contentIds = string.Join(", ", contentsA.Select(x => x.Id)); - Audit(AuditType.Save, userId, Constants.System.Root, $"Saved multiple content items ({contentIds})"); + Audit(AuditType.Save, userId, Constants.System.Root, $"Saved multiple content items (#{contentIds.Length})"); scope.Complete(); } From 56486b1ace877bb0dc11b807dce713c1a1534a84 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Thu, 7 Sep 2023 16:28:11 +0200 Subject: [PATCH 14/21] V10 Cherrypicked issuu RTE embed (#14777) * Added a UserAgent to header * Added the use of iframe. This allows us to see the issuu embed in the RTE --- src/Umbraco.Core/Media/EmbedProviders/Issuu.cs | 3 +++ src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs b/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs index 7da51b51ad..62e77b4f2d 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs @@ -19,6 +19,9 @@ public class Issuu : EmbedProviderBase public override Dictionary RequestParams => new() { + // ApiUrl/?iframe=true + { "iframe", "true" }, + // ApiUrl/?format=xml { "format", "xml" }, }; diff --git a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs index b09baba0db..9385dcf6c9 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs @@ -55,11 +55,12 @@ public abstract class OEmbedProviderBase : IEmbedProvider if (_httpClient == null) { _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("Umbraco-CMS"); } using (var request = new HttpRequestMessage(HttpMethod.Get, url)) { - HttpResponseMessage response = _httpClient.SendAsync(request).Result; + HttpResponseMessage response = _httpClient.SendAsync(request).GetAwaiter().GetResult(); return response.Content.ReadAsStringAsync().Result; } } From 2fc29e638cd19e441deee56c9c24fa34e2ef7fc6 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Thu, 7 Sep 2023 17:18:57 +0200 Subject: [PATCH 15/21] Limbo models builder changes the content to element when using Compositions (#14776) --- .../ModelsBuilder/PublishedModelUtility.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/PublishedModelUtility.cs b/src/Umbraco.Infrastructure/ModelsBuilder/PublishedModelUtility.cs index cfcbd82229..bac3dcf0d8 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/PublishedModelUtility.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/PublishedModelUtility.cs @@ -37,6 +37,7 @@ public static class PublishedModelUtility switch (itemType) { case PublishedItemType.Content: + case PublishedItemType.Element: return publishedSnapshot.Content?.GetContentType(alias); case PublishedItemType.Media: return publishedSnapshot.Media?.GetContentType(alias); From f85a0d1715880a4ebfbbcf31ca7b3a8d5b9a456b Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 6 Jul 2023 07:52:11 +0000 Subject: [PATCH 16/21] Render all tabs in the background to ensure they register their initial state on any $scope.model in the stack. (#14493) Sometimes a tab, e.g. a "block list settings model" might register some default values, but if the user never clicks on the tab then they are not registered due to how AngularJS renders views with "ng-if". --- .../src/views/components/editor/umb-editor-sub-views.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html index 56c7a9cf48..be6f21ed96 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html @@ -5,7 +5,7 @@ ng-repeat="subView in subViews track by subView.alias" ng-class="'sub-view-' + subView.name" val-sub-view="subView" - ng-if="subView.active" + ng-show="subView.active" >
From ca8ac21f4b133bb8a7b899fae7807b0712557b7a Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 10 Jul 2023 10:38:43 +0200 Subject: [PATCH 17/21] Added test for checking if settings model contains default value (#14513) * Added test for checking if a settings model contains default value. Fixed tests * Bumped version * Moved createSliderWithDefaultValue function to our testHelpers. Removed the use of count in test since there was only one --- .../blockListEditorContent.spec.ts | 248 ++++++++++++------ 1 file changed, 172 insertions(+), 76 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts index 8dcadef04d..0c99d46ab5 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts @@ -6,24 +6,26 @@ import {BlockListDataTypeBuilder} from "@umbraco/json-models-builders/dist/lib/b test.describe('BlockListEditorContent', () => { const documentName = 'DocumentTestName'; const blockListName = 'BlockListTest'; - const elementName = 'TestElement'; + const contentElementName = 'ContentElement'; + const settingElementName = 'SettingsElement'; const documentAlias = AliasHelper.toAlias(documentName); const blockListAlias = AliasHelper.toAlias(blockListName); - // Won't work if I use the to alias for the elementAlias - const elementAlias = 'testElement'; - + // Won't work if we use the to alias for the elementAliases + const contentElementAlias = 'contentElement'; + const settingsElementAlias = 'settingsElement'; + test.beforeEach(async ({page, umbracoApi, umbracoUi}, testInfo) => { await umbracoApi.report.report(testInfo); await umbracoApi.login(); await umbracoApi.documentTypes.ensureNameNotExists(documentName); - await umbracoApi.documentTypes.ensureNameNotExists(elementName); + await umbracoApi.documentTypes.ensureNameNotExists(contentElementName); await umbracoApi.dataTypes.ensureNameNotExists(blockListName); }); - + test.afterEach(async ({page, umbracoApi, umbracoUi}) => { await umbracoApi.documentTypes.ensureNameNotExists(documentName); - await umbracoApi.documentTypes.ensureNameNotExists(elementName); + await umbracoApi.documentTypes.ensureNameNotExists(contentElementName); await umbracoApi.dataTypes.ensureNameNotExists(blockListName); }); @@ -31,23 +33,22 @@ test.describe('BlockListEditorContent', () => { const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .addBlock() - .withContentElementTypeKey(element['key']) - .withSettingsElementTypeKey(element['key']) + .withContentElementTypeKey(element['key']) + .withSettingsElementTypeKey(element['key']) .done() .build(); return await umbracoApi.dataTypes.save(dataTypeBlockList); } - - async function createDocumentWithOneBlockListEditor(umbracoApi, element, dataType){ - + + async function createDocumentWithOneBlockListEditor(umbracoApi, element, dataType, elementName?, elementAlias?){ if(element == null) { element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); } - + if(dataType == null) { dataType = await createDefaultBlockList(umbracoApi, blockListName, element); } - + const docType = new DocumentTypeBuilder() .withName(documentName) .withAlias(documentAlias) @@ -61,16 +62,15 @@ test.describe('BlockListEditorContent', () => { .done() .build(); await umbracoApi.documentTypes.save(docType); - + return element; } - - async function createContentWithOneBlockListEditor(umbracoApi, element) { - + + async function createContentWithOneBlockListEditor(umbracoApi, element, datatype?, elementName?, elementAlias?) { if(element == null) { - element = await createDocumentWithOneBlockListEditor(umbracoApi, null, null); + element = await createDocumentWithOneBlockListEditor(umbracoApi, null, datatype, elementName, elementAlias); } - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -89,13 +89,35 @@ test.describe('BlockListEditorContent', () => { .done() .build(); await umbracoApi.content.save(rootContentNode); - + return element; } + async function createElementWithSlider(umbracoApi, name, alias, sliderId, sliderName?, sliderDefaultValue?) { + if (sliderId == null) { + const sliderData = await umbracoApi.dataTypes.createSliderWithDefaultValue(sliderName, sliderDefaultValue); + sliderId = sliderData.id; + } + + const contentElement = new DocumentTypeBuilder() + .withName(name) + .withAlias(alias) + .AsElementType() + .addGroup() + .withName('Content') + .withAlias('content') + .addCustomProperty(sliderId) + .withLabel('Slider') + .withAlias('slider') + .done() + .done() + .build(); + return await umbracoApi.documentTypes.save(contentElement); + } + test('can create content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - await createDocumentWithOneBlockListEditor(umbracoApi, null, null); - + await createDocumentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -113,7 +135,7 @@ test.describe('BlockListEditorContent', () => { await umbracoUi.clickDataElementByElementName('tree-item-' + blockListName); // Adds TestElement - await page.locator('[key="blockEditor_addThis"]', {hasText: elementName}).click(); + await page.locator('[key="blockEditor_addThis"]', {hasText: contentElementName}).click(); await page.locator('[id="sub-view-0"]').locator('[id="title"]').fill('Testing...'); await page.locator('[label="Create"]').click(); @@ -121,17 +143,17 @@ test.describe('BlockListEditorContent', () => { // Assert await umbracoUi.isSuccessNotificationVisible(); - + // Checks if the content was created await expect(page.locator('.umb-block-list__block--view')).toHaveCount(1); - await expect(page.locator('.umb-block-list__block--view').nth(0)).toHaveText(elementName); + await expect(page.locator('.umb-block-list__block--view')).toHaveText(contentElementName); // Checks if the content contains the correct value - await page.locator('.umb-block-list__block--view').nth(0).click(); + await page.locator('.umb-block-list__block--view', {hasText: contentElementName}).click(); await expect(page.locator('[id="sub-view-0"]').locator('[id="title"]')).toHaveValue('Testing...'); }); test('can update content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - await createContentWithOneBlockListEditor(umbracoApi, null); + await createContentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -154,7 +176,7 @@ test.describe('BlockListEditorContent', () => { }); test('can delete a block list editor in content', async ({page, umbracoApi, umbracoUi}) => { - await createContentWithOneBlockListEditor(umbracoApi, null); + await createContentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -172,13 +194,13 @@ test.describe('BlockListEditorContent', () => { // Assert await umbracoUi.isSuccessNotificationVisible(); - + // Checks if the content is actually deleted await expect(page.locator('[ui-sortable="vm.sortableOptions"]').nth(0)).not.toBeVisible(); }); test('can copy block list content and paste it', async ({page, umbracoApi, umbracoUi}) => { - await createContentWithOneBlockListEditor(umbracoApi, null); + await createContentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -194,7 +216,7 @@ test.describe('BlockListEditorContent', () => { await expect(page.locator('.alert-success', {hasText: 'Copied to clipboard'})).toBeVisible(); // Pastes block list content await page.locator('[title="Clipboard"]').click(); - await page.locator('umb-block-card', {hasText: elementName}).click(); + await page.locator('umb-block-card', {hasText: contentElementName}).click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); // Assert @@ -204,9 +226,9 @@ test.describe('BlockListEditorContent', () => { }); test('can copy block list content and paste it into another group with the same block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); - const dataType = await createDefaultBlockList(umbracoApi, blockListName, element); + const dataType = await createDefaultBlockList(umbracoApi, blockListName, contentElement); const docType = new DocumentTypeBuilder() .withName(documentName) @@ -227,7 +249,7 @@ test.describe('BlockListEditorContent', () => { .build(); await umbracoApi.documentTypes.save(docType); - await createContentWithOneBlockListEditor(umbracoApi, element); + await createContentWithOneBlockListEditor(umbracoApi, contentElement); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -245,7 +267,7 @@ test.describe('BlockListEditorContent', () => { await expect(page.locator('.alert-success', {hasText: 'Copied to clipboard'})).toBeVisible(); // Pastes into the second group await page.locator('[title="Clipboard"]').nth(1).click(); - await page.locator('umb-block-card', {hasText: elementName}).click(); + await page.locator('umb-block-card', {hasText: contentElementName}).click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); await expect(page.locator('.alert-success', {hasText: 'Content Published'})).toBeVisible(); @@ -257,20 +279,20 @@ test.describe('BlockListEditorContent', () => { }); test('can set a minimum of required blocks in content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .withMin(2) .addBlock() - .withContentElementTypeKey(element['key']) + .withContentElementTypeKey(contentElement['key']) .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockList); - await createDocumentWithOneBlockListEditor(umbracoApi, element, dataType); - - await createContentWithOneBlockListEditor(umbracoApi, element); + await createDocumentWithOneBlockListEditor(umbracoApi, contentElement, dataType); + + await createContentWithOneBlockListEditor(umbracoApi, contentElement); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -294,18 +316,18 @@ test.describe('BlockListEditorContent', () => { }); test('can set a maximum of required blocks in content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .withMax(2) .addBlock() - .withContentElementTypeKey(element['key']) + .withContentElementTypeKey(contentElement['key']) .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockList); - await createDocumentWithOneBlockListEditor(umbracoApi, element, dataType); + await createDocumentWithOneBlockListEditor(umbracoApi, contentElement, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -317,16 +339,16 @@ test.describe('BlockListEditorContent', () => { .withAlias(blockListAlias) .addBlockListValue() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "aliasTest") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "aliasTest") .done() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "aliasTests") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "aliasTests") .done() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "aliasTester") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "aliasTester") .done() .done() .done() @@ -357,20 +379,20 @@ test.describe('BlockListEditorContent', () => { }); test('can use inline editing mode in content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .withUseInlineEditingAsDefault(true) .addBlock() - .withContentElementTypeKey(element['key']) + .withContentElementTypeKey(contentElement['key']) .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockList); - await createDocumentWithOneBlockListEditor(umbracoApi, element, dataType); - - await createContentWithOneBlockListEditor(umbracoApi, element); + await createDocumentWithOneBlockListEditor(umbracoApi, contentElement, dataType); + + await createContentWithOneBlockListEditor(umbracoApi, contentElement); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -387,11 +409,11 @@ test.describe('BlockListEditorContent', () => { test('can see rendered content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { await umbracoApi.templates.ensureNameNotExists(documentName); - await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',elementAlias + '.cshtml'); + await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',contentElementAlias + '.cshtml'); - const element = new DocumentTypeBuilder() - .withName(elementName) - .withAlias(elementAlias) + const contentElement = new DocumentTypeBuilder() + .withName(contentElementName) + .withAlias(contentElementAlias) .AsElementType() .addGroup() .withName("TestString") @@ -406,9 +428,9 @@ test.describe('BlockListEditorContent', () => { .done() .done() .build(); - await umbracoApi.documentTypes.save(element); + await umbracoApi.documentTypes.save(contentElement); - const dataType = await createDefaultBlockList(umbracoApi, blockListName, element); + const dataType = await createDefaultBlockList(umbracoApi, blockListName, contentElement); const docType = new DocumentTypeBuilder() .withName(documentName) @@ -418,7 +440,7 @@ test.describe('BlockListEditorContent', () => { .addGroup() .withName('BlockListGroup') .addCustomProperty(dataType['id']) - .withAlias(elementAlias) + .withAlias(contentElementAlias) .done() .done() .build(); @@ -431,15 +453,15 @@ test.describe('BlockListEditorContent', () => { '\n Layout = null;' + '\n}' + '\n' + - '@Html.GetBlockListHtml(Model.' + elementName + ')'); + '@Html.GetBlockListHtml(Model.' + contentElementName + ')'); const partialView = new PartialViewBuilder() - .withName(elementAlias) + .withName(contentElementAlias) .withContent("@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage;\n" + "@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;\n" + "@{\n" + - "var content = (ContentModels." + elementName + ")Model.Content;\n" + - "var settings = (ContentModels." + elementName + ")Model.Settings;\n" + + "var content = (ContentModels." + contentElementName + ")Model.Content;\n" + + "var settings = (ContentModels." + contentElementName + ")Model.Settings;\n" + "}\n" + "\n" + "

@content.Title

" + @@ -459,15 +481,15 @@ test.describe('BlockListEditorContent', () => { .withSave(true) .withPublish(true) .addProperty() - .withAlias(elementAlias) + .withAlias(contentElementAlias) .addBlockListValue() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "ContentTest") - .appendContentProperties(element.groups[0].properties[1].alias, "RTEContent") - .withSettingsTypeKey(element['key']) - .appendSettingsProperties(element.groups[0].properties[0].alias, "SettingTest") - .appendSettingsProperties(element.groups[0].properties[1].alias, "RTESetting") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "ContentTest") + .appendContentProperties(contentElement.groups[0].properties[1].alias, "RTEContent") + .withSettingsTypeKey(contentElement['key']) + .appendSettingsProperties(contentElement.groups[0].properties[0].alias, "SettingTest") + .appendSettingsProperties(contentElement.groups[0].properties[1].alias, "RTESetting") .done() .done() .done() @@ -479,9 +501,83 @@ test.describe('BlockListEditorContent', () => { // Ensure that the view gets rendered correctly const expected = `

ContentTest

RTEContent

SettingTest

RTESetting

`; await expect(await umbracoApi.content.verifyRenderedContent('/', expected, true)).toBeTruthy(); - + // Clean await umbracoApi.templates.ensureNameNotExists(documentName); - await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',elementAlias + '.cshtml'); + await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',contentElementAlias + '.cshtml'); }); -}); \ No newline at end of file + + test('Checks if the settings content model contains a default value when created', async ({page, umbracoApi, umbracoUi}) => { + const contentSliderName = 'ContentSlider'; + const contentSliderDefaultValue = 5; + const settingsSliderName = 'SettingsSlider'; + const settingsSliderDefaultValue = 9; + + await umbracoApi.documentTypes.ensureNameNotExists(settingElementName); + await umbracoApi.dataTypes.ensureNameNotExists(contentSliderName); + await umbracoApi.dataTypes.ensureNameNotExists(settingsSliderName); + + const contentElement = await createElementWithSlider(umbracoApi, contentElementName, contentElementAlias, null, contentSliderName, contentSliderDefaultValue); + + const settingsElement = await createElementWithSlider(umbracoApi, settingElementName, settingsElementAlias, null, settingsSliderName, settingsSliderDefaultValue); + + const blockList = new BlockListDataTypeBuilder() + .withName(blockListName) + .addBlock() + .withContentElementTypeKey(contentElement['key']) + .withSettingsElementTypeKey(settingsElement['key']) + .done() + .build(); + const blockListDataType = await umbracoApi.dataTypes.save(blockList); + + const document = new DocumentTypeBuilder() + .withName(documentName) + .withAlias(documentAlias) + .withAllowAsRoot(true) + .addGroup() + .withName('Content') + .withAlias('content') + .addCustomProperty(blockListDataType['id']) + .withLabel('BlockListTest') + .withAlias('blockListTest') + .done() + .done() + .build(); + await umbracoApi.documentTypes.save(document); + + const content = new ContentBuilder() + .withContentTypeAlias(documentAlias) + .withAction(ConstantHelper.actions.publish) + .addVariant() + .withName(blockListName) + .withSave(true) + .withPublish(true) + .done() + .build(); + const contentNode = await umbracoApi.content.save(content); + + await umbracoUi.goToSection(ConstantHelper.sections.content); + await umbracoUi.refreshContentTree(); + await umbracoUi.clickDataElementByElementName('tree-item-' + blockListName); + await page.locator('[key="blockEditor_addThis"]', {hasText: contentElementName}).click(); + await page.locator('[label="Create"]').click(); + await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); + + // Assert + // Checks if the content contains the BlockListEditor block + await umbracoUi.clickDataElementByElementName("tree-root"); + await umbracoUi.refreshContentTree(); + await umbracoUi.clickDataElementByElementName('tree-item-' + blockListName); + await expect(page.locator('.umb-block-list__block--view')).toHaveCount(1); + + // Checks if both the content and settings have been assigned their DefaultValues + const contentData = await umbracoApi.content.getContent(contentNode.id); + await expect(contentData.variants[0].tabs[0].properties[0].value.contentData[0].slider).toEqual(contentSliderDefaultValue.toString()); + await expect(contentData.variants[0].tabs[0].properties[0].value.settingsData[0].slider).toEqual(settingsSliderDefaultValue.toString()); + + // Clean + await umbracoApi.documentTypes.ensureNameNotExists(settingElementName); + await umbracoApi.dataTypes.ensureNameNotExists(contentSliderName); + await umbracoApi.dataTypes.ensureNameNotExists(settingsSliderName); + }); +}); From cee485c6db8f08492b93644d25951237ec56f92f Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 6 Jul 2023 07:52:11 +0000 Subject: [PATCH 18/21] Render all tabs in the background to ensure they register their initial state on any $scope.model in the stack. (#14493) Sometimes a tab, e.g. a "block list settings model" might register some default values, but if the user never clicks on the tab then they are not registered due to how AngularJS renders views with "ng-if". --- .../src/views/components/editor/umb-editor-sub-views.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html index 56c7a9cf48..be6f21ed96 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html @@ -5,7 +5,7 @@ ng-repeat="subView in subViews track by subView.alias" ng-class="'sub-view-' + subView.name" val-sub-view="subView" - ng-if="subView.active" + ng-show="subView.active" >
From c42ca63b8d3f3e0605242f70958b41d11cd4e739 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 10 Jul 2023 10:38:43 +0200 Subject: [PATCH 19/21] Added test for checking if settings model contains default value (#14513) * Added test for checking if a settings model contains default value. Fixed tests * Bumped version * Moved createSliderWithDefaultValue function to our testHelpers. Removed the use of count in test since there was only one --- .../blockListEditorContent.spec.ts | 248 ++++++++++++------ 1 file changed, 172 insertions(+), 76 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts index 8dcadef04d..0c99d46ab5 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts @@ -6,24 +6,26 @@ import {BlockListDataTypeBuilder} from "@umbraco/json-models-builders/dist/lib/b test.describe('BlockListEditorContent', () => { const documentName = 'DocumentTestName'; const blockListName = 'BlockListTest'; - const elementName = 'TestElement'; + const contentElementName = 'ContentElement'; + const settingElementName = 'SettingsElement'; const documentAlias = AliasHelper.toAlias(documentName); const blockListAlias = AliasHelper.toAlias(blockListName); - // Won't work if I use the to alias for the elementAlias - const elementAlias = 'testElement'; - + // Won't work if we use the to alias for the elementAliases + const contentElementAlias = 'contentElement'; + const settingsElementAlias = 'settingsElement'; + test.beforeEach(async ({page, umbracoApi, umbracoUi}, testInfo) => { await umbracoApi.report.report(testInfo); await umbracoApi.login(); await umbracoApi.documentTypes.ensureNameNotExists(documentName); - await umbracoApi.documentTypes.ensureNameNotExists(elementName); + await umbracoApi.documentTypes.ensureNameNotExists(contentElementName); await umbracoApi.dataTypes.ensureNameNotExists(blockListName); }); - + test.afterEach(async ({page, umbracoApi, umbracoUi}) => { await umbracoApi.documentTypes.ensureNameNotExists(documentName); - await umbracoApi.documentTypes.ensureNameNotExists(elementName); + await umbracoApi.documentTypes.ensureNameNotExists(contentElementName); await umbracoApi.dataTypes.ensureNameNotExists(blockListName); }); @@ -31,23 +33,22 @@ test.describe('BlockListEditorContent', () => { const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .addBlock() - .withContentElementTypeKey(element['key']) - .withSettingsElementTypeKey(element['key']) + .withContentElementTypeKey(element['key']) + .withSettingsElementTypeKey(element['key']) .done() .build(); return await umbracoApi.dataTypes.save(dataTypeBlockList); } - - async function createDocumentWithOneBlockListEditor(umbracoApi, element, dataType){ - + + async function createDocumentWithOneBlockListEditor(umbracoApi, element, dataType, elementName?, elementAlias?){ if(element == null) { element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); } - + if(dataType == null) { dataType = await createDefaultBlockList(umbracoApi, blockListName, element); } - + const docType = new DocumentTypeBuilder() .withName(documentName) .withAlias(documentAlias) @@ -61,16 +62,15 @@ test.describe('BlockListEditorContent', () => { .done() .build(); await umbracoApi.documentTypes.save(docType); - + return element; } - - async function createContentWithOneBlockListEditor(umbracoApi, element) { - + + async function createContentWithOneBlockListEditor(umbracoApi, element, datatype?, elementName?, elementAlias?) { if(element == null) { - element = await createDocumentWithOneBlockListEditor(umbracoApi, null, null); + element = await createDocumentWithOneBlockListEditor(umbracoApi, null, datatype, elementName, elementAlias); } - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -89,13 +89,35 @@ test.describe('BlockListEditorContent', () => { .done() .build(); await umbracoApi.content.save(rootContentNode); - + return element; } + async function createElementWithSlider(umbracoApi, name, alias, sliderId, sliderName?, sliderDefaultValue?) { + if (sliderId == null) { + const sliderData = await umbracoApi.dataTypes.createSliderWithDefaultValue(sliderName, sliderDefaultValue); + sliderId = sliderData.id; + } + + const contentElement = new DocumentTypeBuilder() + .withName(name) + .withAlias(alias) + .AsElementType() + .addGroup() + .withName('Content') + .withAlias('content') + .addCustomProperty(sliderId) + .withLabel('Slider') + .withAlias('slider') + .done() + .done() + .build(); + return await umbracoApi.documentTypes.save(contentElement); + } + test('can create content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - await createDocumentWithOneBlockListEditor(umbracoApi, null, null); - + await createDocumentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -113,7 +135,7 @@ test.describe('BlockListEditorContent', () => { await umbracoUi.clickDataElementByElementName('tree-item-' + blockListName); // Adds TestElement - await page.locator('[key="blockEditor_addThis"]', {hasText: elementName}).click(); + await page.locator('[key="blockEditor_addThis"]', {hasText: contentElementName}).click(); await page.locator('[id="sub-view-0"]').locator('[id="title"]').fill('Testing...'); await page.locator('[label="Create"]').click(); @@ -121,17 +143,17 @@ test.describe('BlockListEditorContent', () => { // Assert await umbracoUi.isSuccessNotificationVisible(); - + // Checks if the content was created await expect(page.locator('.umb-block-list__block--view')).toHaveCount(1); - await expect(page.locator('.umb-block-list__block--view').nth(0)).toHaveText(elementName); + await expect(page.locator('.umb-block-list__block--view')).toHaveText(contentElementName); // Checks if the content contains the correct value - await page.locator('.umb-block-list__block--view').nth(0).click(); + await page.locator('.umb-block-list__block--view', {hasText: contentElementName}).click(); await expect(page.locator('[id="sub-view-0"]').locator('[id="title"]')).toHaveValue('Testing...'); }); test('can update content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - await createContentWithOneBlockListEditor(umbracoApi, null); + await createContentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -154,7 +176,7 @@ test.describe('BlockListEditorContent', () => { }); test('can delete a block list editor in content', async ({page, umbracoApi, umbracoUi}) => { - await createContentWithOneBlockListEditor(umbracoApi, null); + await createContentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -172,13 +194,13 @@ test.describe('BlockListEditorContent', () => { // Assert await umbracoUi.isSuccessNotificationVisible(); - + // Checks if the content is actually deleted await expect(page.locator('[ui-sortable="vm.sortableOptions"]').nth(0)).not.toBeVisible(); }); test('can copy block list content and paste it', async ({page, umbracoApi, umbracoUi}) => { - await createContentWithOneBlockListEditor(umbracoApi, null); + await createContentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -194,7 +216,7 @@ test.describe('BlockListEditorContent', () => { await expect(page.locator('.alert-success', {hasText: 'Copied to clipboard'})).toBeVisible(); // Pastes block list content await page.locator('[title="Clipboard"]').click(); - await page.locator('umb-block-card', {hasText: elementName}).click(); + await page.locator('umb-block-card', {hasText: contentElementName}).click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); // Assert @@ -204,9 +226,9 @@ test.describe('BlockListEditorContent', () => { }); test('can copy block list content and paste it into another group with the same block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); - const dataType = await createDefaultBlockList(umbracoApi, blockListName, element); + const dataType = await createDefaultBlockList(umbracoApi, blockListName, contentElement); const docType = new DocumentTypeBuilder() .withName(documentName) @@ -227,7 +249,7 @@ test.describe('BlockListEditorContent', () => { .build(); await umbracoApi.documentTypes.save(docType); - await createContentWithOneBlockListEditor(umbracoApi, element); + await createContentWithOneBlockListEditor(umbracoApi, contentElement); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -245,7 +267,7 @@ test.describe('BlockListEditorContent', () => { await expect(page.locator('.alert-success', {hasText: 'Copied to clipboard'})).toBeVisible(); // Pastes into the second group await page.locator('[title="Clipboard"]').nth(1).click(); - await page.locator('umb-block-card', {hasText: elementName}).click(); + await page.locator('umb-block-card', {hasText: contentElementName}).click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); await expect(page.locator('.alert-success', {hasText: 'Content Published'})).toBeVisible(); @@ -257,20 +279,20 @@ test.describe('BlockListEditorContent', () => { }); test('can set a minimum of required blocks in content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .withMin(2) .addBlock() - .withContentElementTypeKey(element['key']) + .withContentElementTypeKey(contentElement['key']) .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockList); - await createDocumentWithOneBlockListEditor(umbracoApi, element, dataType); - - await createContentWithOneBlockListEditor(umbracoApi, element); + await createDocumentWithOneBlockListEditor(umbracoApi, contentElement, dataType); + + await createContentWithOneBlockListEditor(umbracoApi, contentElement); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -294,18 +316,18 @@ test.describe('BlockListEditorContent', () => { }); test('can set a maximum of required blocks in content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .withMax(2) .addBlock() - .withContentElementTypeKey(element['key']) + .withContentElementTypeKey(contentElement['key']) .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockList); - await createDocumentWithOneBlockListEditor(umbracoApi, element, dataType); + await createDocumentWithOneBlockListEditor(umbracoApi, contentElement, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -317,16 +339,16 @@ test.describe('BlockListEditorContent', () => { .withAlias(blockListAlias) .addBlockListValue() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "aliasTest") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "aliasTest") .done() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "aliasTests") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "aliasTests") .done() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "aliasTester") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "aliasTester") .done() .done() .done() @@ -357,20 +379,20 @@ test.describe('BlockListEditorContent', () => { }); test('can use inline editing mode in content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .withUseInlineEditingAsDefault(true) .addBlock() - .withContentElementTypeKey(element['key']) + .withContentElementTypeKey(contentElement['key']) .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockList); - await createDocumentWithOneBlockListEditor(umbracoApi, element, dataType); - - await createContentWithOneBlockListEditor(umbracoApi, element); + await createDocumentWithOneBlockListEditor(umbracoApi, contentElement, dataType); + + await createContentWithOneBlockListEditor(umbracoApi, contentElement); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -387,11 +409,11 @@ test.describe('BlockListEditorContent', () => { test('can see rendered content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { await umbracoApi.templates.ensureNameNotExists(documentName); - await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',elementAlias + '.cshtml'); + await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',contentElementAlias + '.cshtml'); - const element = new DocumentTypeBuilder() - .withName(elementName) - .withAlias(elementAlias) + const contentElement = new DocumentTypeBuilder() + .withName(contentElementName) + .withAlias(contentElementAlias) .AsElementType() .addGroup() .withName("TestString") @@ -406,9 +428,9 @@ test.describe('BlockListEditorContent', () => { .done() .done() .build(); - await umbracoApi.documentTypes.save(element); + await umbracoApi.documentTypes.save(contentElement); - const dataType = await createDefaultBlockList(umbracoApi, blockListName, element); + const dataType = await createDefaultBlockList(umbracoApi, blockListName, contentElement); const docType = new DocumentTypeBuilder() .withName(documentName) @@ -418,7 +440,7 @@ test.describe('BlockListEditorContent', () => { .addGroup() .withName('BlockListGroup') .addCustomProperty(dataType['id']) - .withAlias(elementAlias) + .withAlias(contentElementAlias) .done() .done() .build(); @@ -431,15 +453,15 @@ test.describe('BlockListEditorContent', () => { '\n Layout = null;' + '\n}' + '\n' + - '@Html.GetBlockListHtml(Model.' + elementName + ')'); + '@Html.GetBlockListHtml(Model.' + contentElementName + ')'); const partialView = new PartialViewBuilder() - .withName(elementAlias) + .withName(contentElementAlias) .withContent("@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage;\n" + "@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;\n" + "@{\n" + - "var content = (ContentModels." + elementName + ")Model.Content;\n" + - "var settings = (ContentModels." + elementName + ")Model.Settings;\n" + + "var content = (ContentModels." + contentElementName + ")Model.Content;\n" + + "var settings = (ContentModels." + contentElementName + ")Model.Settings;\n" + "}\n" + "\n" + "

@content.Title

" + @@ -459,15 +481,15 @@ test.describe('BlockListEditorContent', () => { .withSave(true) .withPublish(true) .addProperty() - .withAlias(elementAlias) + .withAlias(contentElementAlias) .addBlockListValue() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "ContentTest") - .appendContentProperties(element.groups[0].properties[1].alias, "RTEContent") - .withSettingsTypeKey(element['key']) - .appendSettingsProperties(element.groups[0].properties[0].alias, "SettingTest") - .appendSettingsProperties(element.groups[0].properties[1].alias, "RTESetting") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "ContentTest") + .appendContentProperties(contentElement.groups[0].properties[1].alias, "RTEContent") + .withSettingsTypeKey(contentElement['key']) + .appendSettingsProperties(contentElement.groups[0].properties[0].alias, "SettingTest") + .appendSettingsProperties(contentElement.groups[0].properties[1].alias, "RTESetting") .done() .done() .done() @@ -479,9 +501,83 @@ test.describe('BlockListEditorContent', () => { // Ensure that the view gets rendered correctly const expected = `

ContentTest

RTEContent

SettingTest

RTESetting

`; await expect(await umbracoApi.content.verifyRenderedContent('/', expected, true)).toBeTruthy(); - + // Clean await umbracoApi.templates.ensureNameNotExists(documentName); - await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',elementAlias + '.cshtml'); + await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',contentElementAlias + '.cshtml'); }); -}); \ No newline at end of file + + test('Checks if the settings content model contains a default value when created', async ({page, umbracoApi, umbracoUi}) => { + const contentSliderName = 'ContentSlider'; + const contentSliderDefaultValue = 5; + const settingsSliderName = 'SettingsSlider'; + const settingsSliderDefaultValue = 9; + + await umbracoApi.documentTypes.ensureNameNotExists(settingElementName); + await umbracoApi.dataTypes.ensureNameNotExists(contentSliderName); + await umbracoApi.dataTypes.ensureNameNotExists(settingsSliderName); + + const contentElement = await createElementWithSlider(umbracoApi, contentElementName, contentElementAlias, null, contentSliderName, contentSliderDefaultValue); + + const settingsElement = await createElementWithSlider(umbracoApi, settingElementName, settingsElementAlias, null, settingsSliderName, settingsSliderDefaultValue); + + const blockList = new BlockListDataTypeBuilder() + .withName(blockListName) + .addBlock() + .withContentElementTypeKey(contentElement['key']) + .withSettingsElementTypeKey(settingsElement['key']) + .done() + .build(); + const blockListDataType = await umbracoApi.dataTypes.save(blockList); + + const document = new DocumentTypeBuilder() + .withName(documentName) + .withAlias(documentAlias) + .withAllowAsRoot(true) + .addGroup() + .withName('Content') + .withAlias('content') + .addCustomProperty(blockListDataType['id']) + .withLabel('BlockListTest') + .withAlias('blockListTest') + .done() + .done() + .build(); + await umbracoApi.documentTypes.save(document); + + const content = new ContentBuilder() + .withContentTypeAlias(documentAlias) + .withAction(ConstantHelper.actions.publish) + .addVariant() + .withName(blockListName) + .withSave(true) + .withPublish(true) + .done() + .build(); + const contentNode = await umbracoApi.content.save(content); + + await umbracoUi.goToSection(ConstantHelper.sections.content); + await umbracoUi.refreshContentTree(); + await umbracoUi.clickDataElementByElementName('tree-item-' + blockListName); + await page.locator('[key="blockEditor_addThis"]', {hasText: contentElementName}).click(); + await page.locator('[label="Create"]').click(); + await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); + + // Assert + // Checks if the content contains the BlockListEditor block + await umbracoUi.clickDataElementByElementName("tree-root"); + await umbracoUi.refreshContentTree(); + await umbracoUi.clickDataElementByElementName('tree-item-' + blockListName); + await expect(page.locator('.umb-block-list__block--view')).toHaveCount(1); + + // Checks if both the content and settings have been assigned their DefaultValues + const contentData = await umbracoApi.content.getContent(contentNode.id); + await expect(contentData.variants[0].tabs[0].properties[0].value.contentData[0].slider).toEqual(contentSliderDefaultValue.toString()); + await expect(contentData.variants[0].tabs[0].properties[0].value.settingsData[0].slider).toEqual(settingsSliderDefaultValue.toString()); + + // Clean + await umbracoApi.documentTypes.ensureNameNotExists(settingElementName); + await umbracoApi.dataTypes.ensureNameNotExists(contentSliderName); + await umbracoApi.dataTypes.ensureNameNotExists(settingsSliderName); + }); +}); From ca8c260b6c2e6afb8d4a453e3b837fa2d8020ce5 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 12 Sep 2023 09:46:07 +0200 Subject: [PATCH 20/21] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 1d5d5613a8..840819c76b 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "11.5.0", + "version": "11.6.0-rc", "assemblyVersion": { "precision": "build" }, From 2d76e447bb6c0e9f965f65f6ccfa74669c789ff3 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 12 Sep 2023 09:53:10 +0200 Subject: [PATCH 21/21] Post merge build fix --- src/Umbraco.Core/Services/ContentService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index ae7cdbc4e5..160804c85b 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2172,7 +2172,7 @@ public class ContentService : RepositoryService, IContentService if (rootResult?.Success is true) { scope.Notifications.Publish( - new ContentPublishedNotification(rootResult!.Content!, eventMessages, true) + new ContentPublishedNotification(rootResult!.Content!.Yield(), eventMessages, true) .WithState(rootPublishNotificationState)); }