From 81e63e7fa9e4172e137eaf82fb686d8308c51504 Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Tue, 18 Mar 2025 04:22:20 +0000 Subject: [PATCH 1/7] Tiptap RTE: Added `data-mark` attributes (#18689) Tiptap: adds `data-mark` attributes to RTE, toolbar and toolbar items. --- .../components/input-tiptap/input-tiptap.element.ts | 2 +- .../components/input-tiptap/tiptap-toolbar.element.ts | 9 ++++++++- ...rty-editor-ui-tiptap-toolbar-configuration.element.ts | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts index 4541209e21..459e01e2ab 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts @@ -179,7 +179,7 @@ export class UmbInputTiptapElement extends UmbFormControlMixin `, )} -
+
`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts index 6c66cc56bf..8338b7bf43 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts @@ -30,6 +30,8 @@ export class UmbTiptapToolbarElement extends UmbLitElement { override connectedCallback(): void { super.connectedCallback(); + this.setAttribute('data-mark', 'tiptap-toolbar'); + this.#attached = true; this.#observeExtensions(); } @@ -51,7 +53,12 @@ export class UmbTiptapToolbarElement extends UmbLitElement { [], (manifest) => this.toolbar.flat(2).includes(manifest.alias), (extensionControllers) => { - this._lookup = new Map(extensionControllers.map((ext) => [ext.alias, ext.component])); + this._lookup = new Map( + extensionControllers.map((ext) => { + (ext.component as HTMLElement)?.setAttribute('data-mark', `action:tiptap-toolbar:${ext.alias}`); + return [ext.alias, ext.component]; + }), + ); }, undefined, undefined, diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts index 46525299dc..159a040f77 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts @@ -165,6 +165,7 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement Date: Tue, 18 Mar 2025 06:33:24 +0100 Subject: [PATCH 2/7] Restrict valid API user client IDs to 100 characters. (#18688) --- .../ClientCredentialsUserControllerBase.cs | 4 ++-- src/Umbraco.Core/Services/UserService.cs | 2 +- .../Services/UserServiceTests.cs | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs index deeb8d8f0d..9a93a6814e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Security.OperationStatus; @@ -20,7 +20,7 @@ public abstract class ClientCredentialsUserControllerBase : UserControllerBase .Build()), BackOfficeUserClientCredentialsOperationStatus.InvalidClientId => BadRequest(problemDetailsBuilder .WithTitle("Invalid client ID") - .WithDetail("The specified client ID is invalid. A valid client ID can only contain [a-z], [A-Z], [0-9], and [-._~]. Furthermore, including the prefix it cannot be longer than 255 characters.") + .WithDetail("The specified client ID is invalid. A valid client ID can only contain [a-z], [A-Z], [0-9], and [-._~]. Furthermore, including the prefix it cannot be longer than 100 characters.") .Build()), _ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder .WithTitle("Unknown client credentials operation status.") diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index e92a5b0cd0..659a98ea07 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -2680,7 +2680,7 @@ internal partial class UserService : RepositoryService, IUserService } } - [GeneratedRegex(@"^[\w\d\-\._~]{1,255}$")] + [GeneratedRegex(@"^[\w\d\-\._~]{1,100}$")] private static partial Regex ValidClientId(); #endregion diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs index 92829fdf37..6896b396df 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs @@ -1004,7 +1004,6 @@ public class UserServiceTests : UmbracoIntegrationTest [TestCase("@", UserClientCredentialsOperationStatus.InvalidClientId)] [TestCase("[", UserClientCredentialsOperationStatus.InvalidClientId)] [TestCase("]", UserClientCredentialsOperationStatus.InvalidClientId)] - [TestCase("More_Than_255_characters_012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", UserClientCredentialsOperationStatus.InvalidClientId)] public async Task Can_Use_Only_Unreserved_Characters_For_ClientId(string clientId, UserClientCredentialsOperationStatus expectedResult) { // Arrange @@ -1017,6 +1016,20 @@ public class UserServiceTests : UmbracoIntegrationTest Assert.AreEqual(expectedResult, result); } + [TestCase("Less_Than_100_characters_0123456789012345678901234567890123456789012345678901234567890123456789", UserClientCredentialsOperationStatus.Success)] + [TestCase("More_Than_100_characters_01234567890123456789012345678901234567890123456789012345678901234567890123456789", UserClientCredentialsOperationStatus.InvalidClientId)] + public async Task Cannot_Create_Too_Long_ClientId(string clientId, UserClientCredentialsOperationStatus expectedResult) + { + // Arrange + var user = await CreateTestUser(UserKind.Api); + + // Act + var result = await UserService.AddClientIdAsync(user.Key, clientId); + + // Assert + Assert.AreEqual(expectedResult, result); + } + private Content[] BuildContentItems(int numberToCreate) { var template = TemplateBuilder.CreateTextPageTemplate(); From 88657c1bb01ce2869f1767a2f0cd5bd9f9d0d1f7 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Tue, 18 Mar 2025 10:35:28 +0100 Subject: [PATCH 3/7] V15 QA added acceptance tests for block grid variants (#18658) * Added block grid variant tests * Bumped version * Updated command to run tests on pipeline * Removed test command * Fixed comment --- .../package-lock.json | 9 +- .../Umbraco.Tests.AcceptanceTest/package.json | 2 +- .../BlockGrid/VariantBlockGrid.spec.ts | 198 ++++++++++++++++++ .../DocumentTypeDesignTab.spec.ts | 2 +- 4 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/VariantBlockGrid.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 0518f7a7f5..18dbd68c44 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -8,7 +8,7 @@ "hasInstallScript": true, "dependencies": { "@umbraco/json-models-builders": "^2.0.29", - "@umbraco/playwright-testhelpers": "^15.0.34", + "@umbraco/playwright-testhelpers": "^15.0.35", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -67,9 +67,10 @@ } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "15.0.34", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.34.tgz", - "integrity": "sha512-dEWjiUCWdxBpvDnCoShqRZ5xEfNEo02BBgNIKqDAUDEBht/lAM/pDaUgzu2sUbb7D8AbkrrIxPiNn69XakoNSg==", + "version": "15.0.35", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.35.tgz", + "integrity": "sha512-zYZROoEkT2250y0SjmFAA9QbbpHTBXwJFXRd1/e07ScuDE0aHIB0KY1YhNN8fBo0xf3CF4N8SET3KBRYjOXL5g==", + "license": "MIT", "dependencies": { "@umbraco/json-models-builders": "2.0.30", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index da652bf8fc..50c7c5ae4c 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@umbraco/json-models-builders": "^2.0.29", - "@umbraco/playwright-testhelpers": "^15.0.34", + "@umbraco/playwright-testhelpers": "^15.0.35", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/VariantBlockGrid.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/VariantBlockGrid.spec.ts new file mode 100644 index 0000000000..69e8561187 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockGrid/VariantBlockGrid.spec.ts @@ -0,0 +1,198 @@ +import {ConstantHelper, NotificationConstantHelper, test} from "@umbraco/playwright-testhelpers"; + +// Document Type +const documentTypeName = 'DocumentTypeName'; +let documentTypeId = null; +const documentTypeGroupName = 'DocumentGroup'; + +// Block Grid +const blockGridName = 'BlockGridName'; +let blockGridId = null; + +// Element Type +const blockName = 'BlockName'; +let elementTypeId = null; +const elementGroupName = 'ElementGroup'; + +// Text String +const textStringName = 'TextStringName'; +let textStringDataTypeId = null; +const textStringDataTypeName = 'Textstring'; +const textStringText = 'ThisIsATextString'; + +// Content Name +const contentName = 'ContentName'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.language.ensureIsoCodeNotExists('da'); + const textStringDataType = await umbracoApi.dataType.getByName(textStringDataTypeName); + textStringDataTypeId = textStringDataType.id; + await umbracoApi.language.createDanishLanguage(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.language.ensureIsoCodeNotExists('da'); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.documentType.ensureNameNotExists(blockName); + await umbracoApi.dataType.ensureNameNotExists(blockGridName); +}); + +test('invariant document type with invariant block grid with invariant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementType(blockName, elementGroupName, textStringName, textStringDataTypeId); + blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('invariant document type with invariant block grid with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false); + blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +// Remove fixme when this test works. Currently, the textstring value is not saved when saving / publishing the document +test('invariant document type with invariant block grid with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName) + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with variant block grid with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, documentTypeGroupName, true); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with invariant block grid with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false); + blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, documentTypeGroupName, true, false); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with invariant block grid with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, documentTypeGroupName, true, false); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton() + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts index e04ed42622..f0ce32091b 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts @@ -397,7 +397,7 @@ test('can enable validation for a property in a document type', async ({umbracoA test('can allow vary by culture for a property in a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { // Arrange const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName, false); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName, true); await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings); // Act From 42cb9b5c1e75af09801e8742dd61468a9d63a4b1 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 18 Mar 2025 10:52:32 +0100 Subject: [PATCH 4/7] Removes one of the two duplicate ContentPublishingNotification publishings. (#18702) --- src/Umbraco.Core/Services/ContentService.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 581159ab64..c76e89381a 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1267,7 +1267,7 @@ public class ContentService : RepositoryService, IContentService // we need to guard against unsaved changes before proceeding; the content will be saved, but we're not firing any saved notifications if (HasUnsavedChanges(content)) { - return new PublishResult(PublishResultType.FailedPublishUnsavedChanges, EventMessagesFactory.Get(), content); + return new PublishResult(PublishResultType.FailedPublishUnsavedChanges, evtMsgs, content); } if (content.Name != null && content.Name.Length > 255) @@ -1324,13 +1324,8 @@ public class ContentService : RepositoryService, IContentService // Change state to publishing content.PublishedState = PublishedState.Publishing; - var publishingNotification = new ContentPublishingNotification(content, evtMsgs); - if (scope.Notifications.PublishCancelable(publishingNotification)) - { - return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); - } - PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, publishingNotification.State, userId); + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, new Dictionary(), userId); scope.Complete(); return result; } From b07a24ba55607c201aace6c29ba0c86e504bbe02 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 18 Mar 2025 11:08:30 +0100 Subject: [PATCH 5/7] Added clarifying comments to the logic for granular permissions. (#18705) --- .../Services/UserServiceTests.cs | 91 ++++++++++++++----- 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs index 6896b396df..755b17d1c3 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; using System.Security.Cryptography; using System.Text; using NUnit.Framework; @@ -17,7 +15,6 @@ using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; -using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; @@ -302,57 +299,109 @@ public class UserServiceTests : UmbracoIntegrationTest [Test] public void Calculate_Permissions_For_User_For_Path() { - // see: http://issues.umbraco.org/issue/U4-10075#comment=67-40085 - // for an overview of what this is testing + // Details of what this is testing were originally at http://issues.umbraco.org/issue/U4-10075#comment=67-40085 (and available + // now at https://web.archive.org/web/20211021021851/http://issues.umbraco.org/issue/U4-10075). + // Here is the same information copied and reformatted: + + // Here's a mockup of how user group permissions will work with inheritance. + // User (UserX), belongs to these groups which have the following default permission characters applied: + + // | Groups | Defaults | + // |--------|----------| + // | G1 | sdf | + // | G2 | sdgk | + // | G3 | fg | + + // Thus the aggregate defaults for UserX is "sdfgk" + + // As an example node structure, here is the node tree and where any explicit permissions are assigned. + // It shows you the resulting permissions for each node that the user has: + + // | Nodes | G1 | G2 | G3 | Result | + // |-------------|------|------|------|-------------------------------------------| + // | - A | | | | sdfgk (Defaults) | + // | - - B | | fr | | sdfgr (Defaults + Explicit) | + // | - - - C | | | qz | sdfrqz (Defaults + Explicit + Inherited) | + // | - - - - D | | | | sdfrqz (Inherited) | + // | - - - - - E | | | | sdfrqz (Inherited) | + + // Notice that 'k' is no longer in the result for node B and below. + // Notice that 'g' is no longer in the result for node C and below. + const string path = "-1,1,2,3,4"; var pathIds = path.GetIdsFromPathReversed(); + // Setup: 3 groups with different default permissions. + // - Group A has permissions S, D, F. + // - Group B has permissions S, D, G, K. + // - Group C has permissions F, G. const int groupA = 7; const int groupB = 8; const int groupC = 9; - var userGroups = new Dictionary> { - {groupA, new[] {"S", "D", "F"}.ToHashSet()}, {groupB, new[] {"S", "D", "G", "K"}.ToHashSet()}, {groupC, new[] {"F", "G"}.ToHashSet()} + { groupA, new[] {"S", "D", "F"}.ToHashSet() }, + { groupB, new[] {"S", "D", "G", "K"}.ToHashSet() }, + { groupC, new[] {"F", "G"}.ToHashSet() } }; + // Setup: combination of default and specific permissions for each group and document. EntityPermission[] permissions = { - new(groupA, 1, userGroups[groupA], true), new(groupA, 2, userGroups[groupA], true), - new(groupA, 3, userGroups[groupA], true), new(groupA, 4, userGroups[groupA], true), - new(groupB, 1, userGroups[groupB], true), new(groupB, 2, new[] {"F", "R"}.ToHashSet(), false), - new(groupB, 3, userGroups[groupB], true), new(groupB, 4, userGroups[groupB], true), - new(groupC, 1, userGroups[groupC], true), new(groupC, 2, userGroups[groupC], true), - new(groupC, 3, new[] {"Q", "Z"}.ToHashSet(), false), new(groupC, 4, userGroups[groupC], true) + new(groupA, 1, userGroups[groupA], true), // Comes from the default permission on group A. + new(groupA, 2, userGroups[groupA], true), // Comes from the default permission on group A. + new(groupA, 3, userGroups[groupA], true), // Comes from the default permission on group A. + new(groupA, 4, userGroups[groupA], true), // Comes from the default permission on group A. + new(groupB, 1, userGroups[groupB], true), // Comes from the default permission on group B. + new(groupB, 2, new[] {"F", "R"}.ToHashSet(), false), // Group B has a specific permission on document 2 of of F, R. + new(groupB, 3, userGroups[groupB], true), // Comes from the default permission on group B. + new(groupB, 4, userGroups[groupB], true), // Comes from the default permission on group B. + new(groupC, 1, userGroups[groupC], true), // Comes from the default permission on group C. + new(groupC, 2, userGroups[groupC], true), // Comes from the default permission on group C. + new(groupC, 3, new[] {"Q", "Z"}.ToHashSet(), false), // Group C has a specific permission on document 3 of of Q, Z. + new(groupC, 4, userGroups[groupC], true) // Comes from the default permission on group C. }; - // Permissions for Id 4 + // Permissions for document 4 var result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds); Assert.AreEqual(4, result.EntityId); var allPermissions = result.GetAllPermissions().ToArray(); Assert.AreEqual(6, allPermissions.Length, string.Join(",", allPermissions)); - Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "R", "Q", "Z" })); - // Permissions for Id 3 + // - has S, D, F from group A, R from group B (specific permission on document 2), Q, Z from group C (specific permission on document 3). + // - doesn't have K or G due to specific permissions set on group B, document 2. + Assert.IsTrue(allPermissions.ContainsAll(["S", "D", "F", "R", "Q", "Z"])); + + // Permissions for document 3 result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(1).ToArray()); Assert.AreEqual(3, result.EntityId); allPermissions = result.GetAllPermissions().ToArray(); Assert.AreEqual(6, allPermissions.Length, string.Join(",", allPermissions)); - Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "R", "Q", "Z" })); - // Permissions for Id 2 + // - has S, D, F from group A, R from group B (specific permission on document 2), Q, Z from group C (specific permission on document 3). + // - doesn't have K or G due to specific permissions set on group B, document 2. + Assert.IsTrue(allPermissions.ContainsAll(["S", "D", "F", "R", "Q", "Z"])); + + // Permissions for document 2 result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(2).ToArray()); Assert.AreEqual(2, result.EntityId); allPermissions = result.GetAllPermissions().ToArray(); Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions)); - Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "R" })); - // Permissions for Id 1 + // - has S, D, F from group A, G from group C, R from group B (specific permission on document 2). + // - doesn't have K due to specific permissions set on group B, document 2. + // - this would remove G too, but it's there as it's also defined in group C that doesn't have a specific permission override. + // - doesn't have Q, Z as these specific permissions are added to a document further down the path. + Assert.IsTrue(allPermissions.ContainsAll(["S", "D", "F", "G", "R"])); + + // Permissions for document 1 result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(3).ToArray()); Assert.AreEqual(1, result.EntityId); allPermissions = result.GetAllPermissions().ToArray(); Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions)); - Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "K" })); + + // - has S, D, F from group A, G, K from group B (also G from group C) + Assert.IsTrue(allPermissions.ContainsAll(["S", "D", "F", "G", "K"])); } [Test] From b6fa93edac6587f4b5b3b0b0244f27044a8bfd01 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Tue, 18 Mar 2025 11:37:11 +0100 Subject: [PATCH 6/7] V15 QA added Tip Tap block variant acceptance tests (#18668) * Moved tests * Added variant tests for tiptap * Updated file name * Added test command * Bumped package lock file * removed npm command * Bumped test helpers --- .../package-lock.json | 8 +- .../Umbraco.Tests.AcceptanceTest/package.json | 2 +- .../ContentWithTiptap.spec.ts | 0 .../VariantTipTapBlocks.spec.ts | 199 ++++++++++++++++++ 4 files changed, 204 insertions(+), 5 deletions(-) rename tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/{ => RichTextEditor}/ContentWithTiptap.spec.ts (100%) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/VariantTipTapBlocks.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 18dbd68c44..e0a21cb1d2 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -8,7 +8,7 @@ "hasInstallScript": true, "dependencies": { "@umbraco/json-models-builders": "^2.0.29", - "@umbraco/playwright-testhelpers": "^15.0.35", + "@umbraco/playwright-testhelpers": "^15.0.36", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -67,9 +67,9 @@ } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "15.0.35", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.35.tgz", - "integrity": "sha512-zYZROoEkT2250y0SjmFAA9QbbpHTBXwJFXRd1/e07ScuDE0aHIB0KY1YhNN8fBo0xf3CF4N8SET3KBRYjOXL5g==", + "version": "15.0.36", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.36.tgz", + "integrity": "sha512-cu3iRE+NAKbGqwoiHULMLp99VBLVe8AbjDPLmjMMWXD6lXQE4fKsDXyIJiihRwxR1Qoz0L/DgNrysQ8mnyoCjg==", "license": "MIT", "dependencies": { "@umbraco/json-models-builders": "2.0.30", diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 50c7c5ae4c..d478531a4f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@umbraco/json-models-builders": "^2.0.29", - "@umbraco/playwright-testhelpers": "^15.0.35", + "@umbraco/playwright-testhelpers": "^15.0.36", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithTiptap.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/ContentWithTiptap.spec.ts similarity index 100% rename from tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithTiptap.spec.ts rename to tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/ContentWithTiptap.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/VariantTipTapBlocks.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/VariantTipTapBlocks.spec.ts new file mode 100644 index 0000000000..21f8a99295 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/VariantTipTapBlocks.spec.ts @@ -0,0 +1,199 @@ +import {ConstantHelper, NotificationConstantHelper, test} from "@umbraco/playwright-testhelpers"; + +// Document Type +const documentTypeName = 'DocumentTypeName'; +let documentTypeId = null; +const documentTypeGroupName = 'DocumentGroup'; + +// Tip Tap +const tipTapName = 'TipTapTest'; +let tipTapId = null; + +// Element Type +const blockName = 'BlockName'; +let elementTypeId = null; +const elementGroupName = 'ElementGroup'; + +// Text String +const textStringName = 'TextStringName'; +let textStringDataTypeId = null; +const textStringDataTypeName = 'Textstring'; +const textStringText = 'ThisIsATextString'; + +// Content Name +const contentName = 'ContentName'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.language.ensureIsoCodeNotExists('da'); + const textStringDataType = await umbracoApi.dataType.getByName(textStringDataTypeName); + textStringDataTypeId = textStringDataType.id; + await umbracoApi.language.createDanishLanguage(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.language.ensureIsoCodeNotExists('da'); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.documentType.ensureNameNotExists(blockName); + await umbracoApi.dataType.ensureNameNotExists(tipTapName); +}); + +test('invariant document type with invariant tiptap RTE with invariant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementType(blockName, elementGroupName, textStringName, textStringDataTypeId); + tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickInsertBlockButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('invariant document type with invariant tiptap RTE with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false); + tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickInsertBlockButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +// Remove fixme when this test works. Currently the textstring value is is not saved when saving / publishing the document +test.fixme('invariant document type with invariant tiptap RTE with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickInsertBlockButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with variant tiptap RTE with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, documentTypeGroupName, true); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickInsertBlockButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with invariant tiptap RTE with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false); + tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, documentTypeGroupName, true, false); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickInsertBlockButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with invariant tiptap RTE with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, documentTypeGroupName, true, false); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickInsertBlockButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); From f3f7fcc0512af8bb40bdefe89472e41c23acdc8e Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Tue, 18 Mar 2025 12:17:07 +0100 Subject: [PATCH 7/7] V15 QA added acceptance tests for block list variants (#18654) * Moved tests * Added variant tests * Updated usage of helper * Added tests * Cleaned up * Bumped version and added test command --- .../ContentWithBlockList.spec.ts | 0 .../BlockList/VariantBlockList.spec.ts | 198 ++++++++++++++++++ .../ContentWithAllowVaryByCulture.spec.ts | 2 +- .../Permissions/UserGroup/Languages.spec.ts | 2 +- 4 files changed, 200 insertions(+), 2 deletions(-) rename tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/{ => BlockList}/ContentWithBlockList.spec.ts (100%) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/VariantBlockList.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithBlockList.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/ContentWithBlockList.spec.ts similarity index 100% rename from tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithBlockList.spec.ts rename to tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/ContentWithBlockList.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/VariantBlockList.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/VariantBlockList.spec.ts new file mode 100644 index 0000000000..1ad6625c86 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/VariantBlockList.spec.ts @@ -0,0 +1,198 @@ +import {ConstantHelper, NotificationConstantHelper, test} from "@umbraco/playwright-testhelpers"; + +// Document Type +const documentTypeName = 'DocumentTypeName'; +let documentTypeId = null; +const documentTypeGroupName = 'DocumentGroup'; + +// Block List +const blockListName = 'BlockListName'; +let blockListId = null; + +// Element Type +const blockName = 'BlockName'; +let elementTypeId = null; +const elementGroupName = 'ElementGroup'; + +// Text String +const textStringName = 'TextStringName'; +let textStringDataTypeId = null; +const textStringDataTypeName = 'Textstring'; +const textStringText = 'ThisIsATextString'; + +// Content Name +const contentName = 'ContentName'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.language.ensureIsoCodeNotExists('da'); + const textStringDataType = await umbracoApi.dataType.getByName(textStringDataTypeName); + textStringDataTypeId = textStringDataType.id; + await umbracoApi.language.createDanishLanguage(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.language.ensureIsoCodeNotExists('da'); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.documentType.ensureNameNotExists(blockName); + await umbracoApi.dataType.ensureNameNotExists(blockListName); +}); + +test('invariant document type with invariant block list with invariant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementType(blockName, elementGroupName, textStringName, textStringDataTypeId); + blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('invariant document type with invariant block list with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false); + blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +// Remove fixme when this test works. Currently the textstring value is is not saved when saving / publishing the document +test.fixme('invariant document type with invariant block list with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName) + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with variant block list with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, documentTypeGroupName, true); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with invariant block list with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false); + blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, documentTypeGroupName, true, false); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with invariant block list with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, documentTypeGroupName, true, false); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton() + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts index 3c567fd688..a81b98e84f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts @@ -101,7 +101,7 @@ test('can create content with names and content that vary by culture', async ({u const danishTextContent = 'Dette er testtekst'; const dataTypeName = 'Textstring'; const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, 'Test Group', true); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, 'Test Group', true, true); await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, textContent, dataTypeName, true); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts index 34966f2466..2242683de9 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts @@ -49,7 +49,7 @@ test.beforeEach(async ({umbracoApi}) => { await umbracoApi.language.createVietnameseLanguage(); const dataType = await umbracoApi.dataType.getByName(dataTypeName); dataTypeId = dataType.id; - documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeId, 'TestGroup', true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeId, 'TestGroup', true, true); await umbracoApi.document.createDocumentWithMultipleVariants(documentName, documentTypeId, AliasHelper.toAlias(dataTypeName), cultureVariants); });