From 609e9f43e1b55f0987ecf982ed9dadc0c73ec607 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 26 Mar 2025 07:37:04 +0100 Subject: [PATCH 01/23] Fixed issue where siblings of type at route are omitted from the result. (#18796) --- .../Extensions/PublishedContentExtensions.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs index 6b9b0359d7..f8ef83c350 100644 --- a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs @@ -3850,9 +3850,8 @@ public static class PublishedContentExtensions if (parentKey.HasValue) { - IEnumerable childrenKeys; var foundChildrenKeys = contentTypeAlias is null - ? navigationQueryService.TryGetChildrenKeys(parentKey.Value, out childrenKeys) + ? navigationQueryService.TryGetChildrenKeys(parentKey.Value, out IEnumerable childrenKeys) : navigationQueryService.TryGetChildrenKeysOfType(parentKey.Value, contentTypeAlias, out childrenKeys); return foundChildrenKeys @@ -3860,19 +3859,12 @@ public static class PublishedContentExtensions : []; } - IEnumerable rootKeys; var foundRootKeys = contentTypeAlias is null - ? navigationQueryService.TryGetRootKeys(out rootKeys) + ? navigationQueryService.TryGetRootKeys(out IEnumerable rootKeys) : navigationQueryService.TryGetRootKeysOfType(contentTypeAlias, out rootKeys); - if (foundRootKeys) - { - IEnumerable rootKeysArray = rootKeys as Guid[] ?? rootKeys.ToArray(); - return rootKeysArray.Contains(content.Key) - ? publishedStatusFilteringService.FilterAvailable(rootKeysArray, culture) - : []; - } - - return []; + return foundRootKeys + ? publishedStatusFilteringService.FilterAvailable(rootKeys, culture) + : []; } } From 619e025e7aa0b185f79364569a7612a39fd46478 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 26 Mar 2025 07:37:04 +0100 Subject: [PATCH 02/23] Fixed issue where siblings of type at route are omitted from the result. (#18796) --- .../Extensions/PublishedContentExtensions.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs index 6b9b0359d7..f8ef83c350 100644 --- a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs @@ -3850,9 +3850,8 @@ public static class PublishedContentExtensions if (parentKey.HasValue) { - IEnumerable childrenKeys; var foundChildrenKeys = contentTypeAlias is null - ? navigationQueryService.TryGetChildrenKeys(parentKey.Value, out childrenKeys) + ? navigationQueryService.TryGetChildrenKeys(parentKey.Value, out IEnumerable childrenKeys) : navigationQueryService.TryGetChildrenKeysOfType(parentKey.Value, contentTypeAlias, out childrenKeys); return foundChildrenKeys @@ -3860,19 +3859,12 @@ public static class PublishedContentExtensions : []; } - IEnumerable rootKeys; var foundRootKeys = contentTypeAlias is null - ? navigationQueryService.TryGetRootKeys(out rootKeys) + ? navigationQueryService.TryGetRootKeys(out IEnumerable rootKeys) : navigationQueryService.TryGetRootKeysOfType(contentTypeAlias, out rootKeys); - if (foundRootKeys) - { - IEnumerable rootKeysArray = rootKeys as Guid[] ?? rootKeys.ToArray(); - return rootKeysArray.Contains(content.Key) - ? publishedStatusFilteringService.FilterAvailable(rootKeysArray, culture) - : []; - } - - return []; + return foundRootKeys + ? publishedStatusFilteringService.FilterAvailable(rootKeys, culture) + : []; } } From 54601d42e25b771993d5d7b0d32d41bda646946f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Mar 2025 15:26:37 +0000 Subject: [PATCH 03/23] Bump vite from 6.2.2 to 6.2.3 in /src/Umbraco.Web.UI.Login Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.2 to 6.2.3. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v6.2.3/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v6.2.3/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- src/Umbraco.Web.UI.Login/package-lock.json | 8 ++++---- src/Umbraco.Web.UI.Login/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/package-lock.json b/src/Umbraco.Web.UI.Login/package-lock.json index d2685028b3..847fd8258f 100644 --- a/src/Umbraco.Web.UI.Login/package-lock.json +++ b/src/Umbraco.Web.UI.Login/package-lock.json @@ -9,7 +9,7 @@ "@umbraco-cms/backoffice": "15.2.1", "msw": "^2.7.0", "typescript": "^5.7.3", - "vite": "^6.2.2", + "vite": "^6.2.3", "vite-tsconfig-paths": "^5.1.4" }, "engines": { @@ -3772,9 +3772,9 @@ } }, "node_modules/vite": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.2.tgz", - "integrity": "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.3.tgz", + "integrity": "sha512-IzwM54g4y9JA/xAeBPNaDXiBF8Jsgl3VBQ2YQ/wOY6fyW3xMdSoltIV3Bo59DErdqdE6RxUfv8W69DvUorE4Eg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/src/Umbraco.Web.UI.Login/package.json b/src/Umbraco.Web.UI.Login/package.json index 449fa53bfe..02dc6a9ee9 100644 --- a/src/Umbraco.Web.UI.Login/package.json +++ b/src/Umbraco.Web.UI.Login/package.json @@ -16,7 +16,7 @@ "@umbraco-cms/backoffice": "15.2.1", "msw": "^2.7.0", "typescript": "^5.7.3", - "vite": "^6.2.2", + "vite": "^6.2.3", "vite-tsconfig-paths": "^5.1.4" }, "msw": { From 76dd141d866151b91aa9a74631bdf1ff8dfc2a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 26 Mar 2025 10:12:25 +0100 Subject: [PATCH 04/23] implement validation context (#18808) --- .../src/packages/core/property-type/index.ts | 1 + .../src/packages/core/property-type/utils/index.ts | 11 +++++++++++ .../workspace/property-type-workspace.context.ts | 14 ++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-type/utils/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/index.ts index 7a20184c1a..4d8ddde711 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/index.ts @@ -1,3 +1,4 @@ export * from './constants.js'; +export * from './utils/index.js'; export * from './workspace/index.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/utils/index.ts new file mode 100644 index 0000000000..05b3611367 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/utils/index.ts @@ -0,0 +1,11 @@ +import type { UmbPropertyTypeData } from '../types.js'; + +/** + * Validation Data Path Query generator for Property Type. + * write a JSON-Path filter similar to `?(@.id == '1234-1224-1244')` + * @param {UmbPropertyTypeData} value - the object holding Property Type. + * @returns {string} - a JSON-path query + */ +export function UmbDataPathPropertyTypeQuery(value: Pick): string { + return `?(@.id == '${value.id}')`; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts index 01224eb0f5..7c759ca1d8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts @@ -19,6 +19,8 @@ import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type' import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content-type'; import { UmbId } from '@umbraco-cms/backoffice/id'; import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; +import { UmbValidationContext } from '@umbraco-cms/backoffice/validation'; +import { UmbDataPathPropertyTypeQuery } from '../utils/index.js'; export class UmbPropertyTypeWorkspaceContext extends UmbSubmittableWorkspaceContextBase @@ -32,6 +34,8 @@ export class UmbPropertyTypeWorkspaceContext(undefined); @@ -53,6 +57,15 @@ export class UmbPropertyTypeWorkspaceContext { + if (unique) { + this.validationgContext.setDataPath(UmbDataPathPropertyTypeQuery({ id: unique })); + } + }); + this.#init = this.consumeContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT, (context) => { this.#contentTypeContext = context; }) @@ -217,6 +230,7 @@ export class UmbPropertyTypeWorkspaceContext Date: Wed, 26 Mar 2025 10:24:09 +0100 Subject: [PATCH 05/23] Obsolete a few unused things. (#18814) --- src/Umbraco.Core/Actions/IAction.cs | 2 ++ src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs | 1 + src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs | 1 + 3 files changed, 4 insertions(+) diff --git a/src/Umbraco.Core/Actions/IAction.cs b/src/Umbraco.Core/Actions/IAction.cs index d63e912fa8..59a3e871d2 100644 --- a/src/Umbraco.Core/Actions/IAction.cs +++ b/src/Umbraco.Core/Actions/IAction.cs @@ -37,6 +37,7 @@ public interface IAction : IDiscoverable /// /// Gets the icon to display for this action /// + [Obsolete("No longer used. Scheduled for removal in V17.")] string Icon { get; } /// @@ -51,5 +52,6 @@ public interface IAction : IDiscoverable /// /// Used in the UI when assigning permissions /// + [Obsolete("No longer used. Scheduled for removal in V17.")] string? Category { get; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs b/src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs index 61845214a5..4ab5f10c58 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs @@ -1,5 +1,6 @@ namespace Umbraco.Cms.Core.Models.TemplateQuery; +[Obsolete("No longer used. Scheduled for removal in V17.")] public class QueryResultModel { public string? QueryExpression { get; set; } diff --git a/src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs b/src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs index 4e56beb635..c5e9b5df96 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs @@ -1,5 +1,6 @@ namespace Umbraco.Cms.Core.Models.TemplateQuery; +[Obsolete("No longer used. Scheduled for removal in V17.")] public class TemplateQueryResult { public string? Icon { get; set; } From 056434fa277239fe1910d2e4615858b2e9f81bb2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 26 Mar 2025 10:32:57 +0100 Subject: [PATCH 06/23] fix preset logic (#18815) --- .../input-document-granular-user-permission.element.ts | 9 +++++---- .../entity-user-permission-settings-modal.element.ts | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/input-document-granular-user-permission/input-document-granular-user-permission.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/input-document-granular-user-permission/input-document-granular-user-permission.element.ts index e66458cd5c..f2d34b04a3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/input-document-granular-user-permission/input-document-granular-user-permission.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/input-document-granular-user-permission/input-document-granular-user-permission.element.ts @@ -126,6 +126,7 @@ export class UmbInputDocumentGranularUserPermissionElement extends UUIFormContro const name = item.variants[0]?.name; const headline = name ? `Permissions for ${name}` : 'Permissions'; const fallbackVerbs = this.#getFallbackPermissionVerbsForEntityType(item.entityType); + const value = allowedVerbs.length > 0 ? { allowedVerbs } : undefined; this.#entityUserPermissionModalContext = this.#modalManagerContext?.open(this, UMB_ENTITY_USER_PERMISSION_MODAL, { data: { unique: item.unique, @@ -135,16 +136,16 @@ export class UmbInputDocumentGranularUserPermissionElement extends UUIFormContro allowedVerbs: fallbackVerbs, }, }, - value: { - allowedVerbs: allowedVerbs, - }, + value, }); try { + // When the modal is submitted we return the new value from the modal const value = await this.#entityUserPermissionModalContext?.onSubmit(); return value?.allowedVerbs; } catch { - throw new Error(); + // When the modal is rejected we return the current value + return allowedVerbs; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/modals/settings/entity-user-permission-settings-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/modals/settings/entity-user-permission-settings-modal.element.ts index a01df5aab1..f1c60d67af 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/modals/settings/entity-user-permission-settings-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/modals/settings/entity-user-permission-settings-modal.element.ts @@ -31,8 +31,8 @@ export class UmbEntityUserPermissionSettingsModalElement extends UmbModalBaseEle override connectedCallback(): void { super.connectedCallback(); - if (this._preset?.allowedVerbs) { - this.updateValue({ allowedVerbs: this._preset?.allowedVerbs }); + if (this._preset?.allowedVerbs && !this.value?.allowedVerbs) { + this.updateValue({ allowedVerbs: this._preset.allowedVerbs }); } } From f8c35bb7e9e743e86e47c510815ad6457e9e8a92 Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Wed, 26 Mar 2025 16:55:22 +0700 Subject: [PATCH 07/23] V15 QA Added acceptance tests for user group default configuration (#18800) * Updated userGroup tests due to test helper changes * Added tests for user group default configuration * Bumped version * Fixed due to test helper changes * Reverted npm command --- .../package-lock.json | 11 +- .../Umbraco.Tests.AcceptanceTest/package.json | 4 +- .../DefaultConfig/Users/UserGroups.spec.ts | 8 +- .../UserGroupsDefaultConfiguration.spec.ts | 224 ++++++++++++++++++ 4 files changed, 235 insertions(+), 12 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroupsDefaultConfiguration.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index cc69130fd9..bf03842d46 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -7,8 +7,8 @@ "name": "acceptancetest", "hasInstallScript": true, "dependencies": { - "@umbraco/json-models-builders": "^2.0.29", - "@umbraco/playwright-testhelpers": "^15.0.38", + "@umbraco/json-models-builders": "^2.0.31", + "@umbraco/playwright-testhelpers": "^15.0.39", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -67,10 +67,9 @@ } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "15.0.38", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.38.tgz", - "integrity": "sha512-eWJdK2qkdcJJ7st4eAMCJAdYprX7pgDOQoTVTEt62uFCkILuBkGd/MpCYUhFu7IMbFMjNEhWW/+sGSBjS5G14g==", - "license": "MIT", + "version": "15.0.39", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.39.tgz", + "integrity": "sha512-dNl+P5LOW4CZrlzt7TnXOKUDeHI3juN/BfG9b0P/selpiFPrseH1HrB0myJVmPfuq4KCa+490VTu46qXHGwGLw==", "dependencies": { "@umbraco/json-models-builders": "2.0.31", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 99c4008a8d..82bbc68831 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -20,8 +20,8 @@ "typescript": "^4.8.3" }, "dependencies": { - "@umbraco/json-models-builders": "^2.0.29", - "@umbraco/playwright-testhelpers": "^15.0.38", + "@umbraco/json-models-builders": "^2.0.31", + "@umbraco/playwright-testhelpers": "^15.0.39", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts index c56fa1675c..c52f5ed27b 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts @@ -136,7 +136,7 @@ test('can add a section to a user group', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); await umbracoUi.userGroup.clickUserGroupsButton(); - await umbracoUi.userGroup.doesUserGroupHaveSection(userGroupName, 'Content'); + await umbracoUi.userGroup.doesUserGroupTableHaveSection(userGroupName, 'Content'); }) test('can add multiple sections to a user group', async ({umbracoApi, umbracoUi}) => { @@ -152,8 +152,8 @@ test('can add multiple sections to a user group', async ({umbracoApi, umbracoUi} // Assert await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); await umbracoUi.userGroup.clickUserGroupsButton(); - await umbracoUi.userGroup.doesUserGroupHaveSection(userGroupName, 'Content'); - await umbracoUi.userGroup.doesUserGroupHaveSection(userGroupName, 'Media'); + await umbracoUi.userGroup.doesUserGroupTableHaveSection(userGroupName, 'Content'); + await umbracoUi.userGroup.doesUserGroupTableHaveSection(userGroupName, 'Media'); }); test('can remove a section from a user group', async ({umbracoApi, umbracoUi}) => { @@ -170,7 +170,7 @@ test('can remove a section from a user group', async ({umbracoApi, umbracoUi}) = // Assert await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); await umbracoUi.userGroup.clickUserGroupsButton(); - await umbracoUi.userGroup.doesUserGroupHaveSection(userGroupName, 'Content', false); + await umbracoUi.userGroup.doesUserGroupTableHaveSection(userGroupName, 'Content', false); const userGroupData = await umbracoApi.userGroup.getByName(userGroupName); expect(userGroupData.sections).toEqual([]); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroupsDefaultConfiguration.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroupsDefaultConfiguration.spec.ts new file mode 100644 index 0000000000..d164753ea8 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroupsDefaultConfiguration.spec.ts @@ -0,0 +1,224 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +test.beforeEach(async ({umbracoUi}) => { + await umbracoUi.goToBackOffice(); + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.users); + await umbracoUi.userGroup.clickUserGroupsButton(); +}); + +test('the default configuration of Administrators is correct', async ({umbracoApi, umbracoUi}) => { + // Arrange + const userGroupName = 'Administrators'; + const sections = [ + "Umb.Section.Content", + "Umb.Section.Media", + "Umb.Section.Members", + "Umb.Section.Packages", + "Umb.Section.Settings", + "Umb.Section.Translation", + "Umb.Section.Users" + ]; + const fallbackPermissions = [ + "Umb.Document.Create", + "Umb.Document.Update", + "Umb.Document.Delete", + "Umb.Document.Move", + "Umb.Document.Duplicate", + "Umb.Document.Sort", + "Umb.Document.Rollback", + "Umb.Document.PublicAccess", + "Umb.Document.CultureAndHostnames", + "Umb.Document.Publish", + "Umb.Document.Permissions", + "Umb.Document.Unpublish", + "Umb.Document.Read", + "Umb.Document.CreateBlueprint", + "Umb.Document.Notifications", + ]; + const granularPermissions = []; + const hasAccessToAllLanguages = true; + const documentRootAccess = true; + const mediaRootAccess = true; + const uiPermissions = await umbracoApi.userGroup.convertApiPermissionsToUiPermissions(fallbackPermissions); + const uiSections = await umbracoApi.userGroup.convertApiSectionsToUiSections(sections); + + // Act + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.doesSettingHaveValue('Assign access', ConstantHelper.userGroupAssignAccessSettings); + await umbracoUi.userGroup.doesSettingHaveValue('Default permissions', ConstantHelper.userGroupDefaultPermissionsSettings); + await umbracoUi.userGroup.doesSettingHaveValue('Granular permissions', ConstantHelper.userGroupGranularPermissionsSettings); + await umbracoUi.userGroup.doesPermissionsSettingsHaveValue(ConstantHelper.userGroupPermissionsSettings); + await umbracoUi.userGroup.doesUserGroupHavePermissionEnabled(uiPermissions); + await umbracoUi.userGroup.doesUserGroupHaveSections(uiSections); + await umbracoUi.userGroup.doesUserGroupSectionsHaveCount(uiSections.length); + // Fixme - Uncomment this when the front-end is ready. Currently the sections includes "Umb.Section.Forms" which should be removed. + //expect(await umbracoApi.userGroup.doesUserGroupHaveSections(userGroupName, sections)).toBeTruthy(); + // Fixme - Uncomment this when the front-end is ready. Currently the fallbackPermissions includes some unnecessary values such as ":", "5", "T" + //expect(await umbracoApi.userGroup.doesUserGroupHaveFallbackPermissions(userGroupName, fallbackPermissions)).toBeTruthy(); + const userGroupData = await umbracoApi.userGroup.getByName(userGroupName); + expect(userGroupData.hasAccessToAllLanguages).toEqual(hasAccessToAllLanguages); + expect(userGroupData.documentRootAccess).toEqual(documentRootAccess); + expect(userGroupData.mediaRootAccess).toEqual(mediaRootAccess); + expect(userGroupData.permissions).toEqual(granularPermissions); +}); + +test('the default configuration of Editors is correct', async ({umbracoApi, umbracoUi}) => { + // Arrange + const userGroupName = 'Editors'; + const sections = [ + "Umb.Section.Content", + "Umb.Section.Media" + ]; + const fallbackPermissions = [ + "Umb.Document.Create", + "Umb.Document.Update", + "Umb.Document.Delete", + "Umb.Document.Move", + "Umb.Document.Duplicate", + "Umb.Document.Sort", + "Umb.Document.Rollback", + "Umb.Document.PublicAccess", + "Umb.Document.Publish", + "Umb.Document.Unpublish", + "Umb.Document.Read", + "Umb.Document.CreateBlueprint", + "Umb.Document.Notifications", + ]; + const granularPermissions = []; + const hasAccessToAllLanguages = true; + const documentRootAccess = true; + const mediaRootAccess = true; + const uiPermissions = await umbracoApi.userGroup.convertApiPermissionsToUiPermissions(fallbackPermissions); + const uiSections = await umbracoApi.userGroup.convertApiSectionsToUiSections(sections); + + // Act + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.doesSettingHaveValue('Assign access', ConstantHelper.userGroupAssignAccessSettings); + await umbracoUi.userGroup.doesSettingHaveValue('Default permissions', ConstantHelper.userGroupDefaultPermissionsSettings); + await umbracoUi.userGroup.doesSettingHaveValue('Granular permissions', ConstantHelper.userGroupGranularPermissionsSettings); + await umbracoUi.userGroup.doesPermissionsSettingsHaveValue(ConstantHelper.userGroupPermissionsSettings); + await umbracoUi.userGroup.doesUserGroupHavePermissionEnabled(uiPermissions); + await umbracoUi.userGroup.doesUserGroupHaveSections(uiSections); + await umbracoUi.userGroup.doesUserGroupSectionsHaveCount(uiSections.length); + // Fixme - Uncomment this when the front-end is ready. Currently the sections includes "Umb.Section.Forms" which should be removed. + //expect(await umbracoApi.userGroup.doesUserGroupHaveSections(userGroupName, sections)).toBeTruthy(); + // Fixme - Uncomment this when the front-end is ready. Currently the fallbackPermissions includes some unnecessary values such as ":", "5", "T" + //expect(await umbracoApi.userGroup.doesUserGroupHaveFallbackPermissions(userGroupName, fallbackPermissions)).toBeTruthy(); + const userGroupData = await umbracoApi.userGroup.getByName(userGroupName); + expect(userGroupData.hasAccessToAllLanguages).toEqual(hasAccessToAllLanguages); + expect(userGroupData.documentRootAccess).toEqual(documentRootAccess); + expect(userGroupData.mediaRootAccess).toEqual(mediaRootAccess); + expect(userGroupData.permissions).toEqual(granularPermissions); +}); + +test('the default configuration of Sensitive data is correct', async ({umbracoApi, umbracoUi}) => { + // Arrange + const userGroupName = 'Sensitive data'; + const sections = []; + const fallbackPermissions = []; + const granularPermissions = []; + const hasAccessToAllLanguages = false; + const documentRootAccess = false; + const mediaRootAccess = false; + const uiPermissions = await umbracoApi.userGroup.convertApiPermissionsToUiPermissions(fallbackPermissions); + const uiSections = await umbracoApi.userGroup.convertApiSectionsToUiSections(sections); + + // Act + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.doesSettingHaveValue('Assign access', ConstantHelper.userGroupAssignAccessSettings); + await umbracoUi.userGroup.doesSettingHaveValue('Default permissions', ConstantHelper.userGroupDefaultPermissionsSettings); + await umbracoUi.userGroup.doesSettingHaveValue('Granular permissions', ConstantHelper.userGroupGranularPermissionsSettings); + await umbracoUi.userGroup.doesPermissionsSettingsHaveValue(ConstantHelper.userGroupPermissionsSettings); + await umbracoUi.userGroup.doesUserGroupHavePermissionEnabled(uiPermissions); + await umbracoUi.userGroup.doesUserGroupHaveSections(uiSections); + await umbracoUi.userGroup.doesUserGroupSectionsHaveCount(uiSections.length); + // Fixme - Uncomment this when the front-end is ready. Currently the sections includes "Umb.Section.Forms" which should be removed. + //expect(await umbracoApi.userGroup.doesUserGroupHaveSections(userGroupName, sections)).toBeTruthy(); + // Fixme - Uncomment this when the front-end is ready. Currently the fallbackPermissions includes some unnecessary values such as ":", "5", "T" + //expect(await umbracoApi.userGroup.doesUserGroupHaveFallbackPermissions(userGroupName, fallbackPermissions)).toBeTruthy(); + const userGroupData = await umbracoApi.userGroup.getByName(userGroupName); + expect(userGroupData.hasAccessToAllLanguages).toEqual(hasAccessToAllLanguages); + expect(userGroupData.documentRootAccess).toEqual(documentRootAccess); + expect(userGroupData.mediaRootAccess).toEqual(mediaRootAccess); + expect(userGroupData.permissions).toEqual(granularPermissions); +}); + +test('the default configuration of Translators data is correct', async ({umbracoApi, umbracoUi}) => { + // Arrange + const userGroupName = 'Translators'; + const sections = ["Umb.Section.Translation"]; + const fallbackPermissions = [ + "Umb.Document.Update", + "Umb.Document.Read" + ]; + const granularPermissions = []; + const hasAccessToAllLanguages = true; + const documentRootAccess = true; + const mediaRootAccess = true; + const uiPermissions = await umbracoApi.userGroup.convertApiPermissionsToUiPermissions(fallbackPermissions); + const uiSections = await umbracoApi.userGroup.convertApiSectionsToUiSections(sections); + + // Act + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.doesSettingHaveValue('Assign access', ConstantHelper.userGroupAssignAccessSettings); + await umbracoUi.userGroup.doesSettingHaveValue('Default permissions', ConstantHelper.userGroupDefaultPermissionsSettings); + await umbracoUi.userGroup.doesSettingHaveValue('Granular permissions', ConstantHelper.userGroupGranularPermissionsSettings); + await umbracoUi.userGroup.doesPermissionsSettingsHaveValue(ConstantHelper.userGroupPermissionsSettings); + await umbracoUi.userGroup.doesUserGroupHavePermissionEnabled(uiPermissions); + await umbracoUi.userGroup.doesUserGroupHaveSections(uiSections); + await umbracoUi.userGroup.doesUserGroupSectionsHaveCount(uiSections.length); + expect(await umbracoApi.userGroup.doesUserGroupHaveSections(userGroupName, sections)).toBeTruthy(); + expect(await umbracoApi.userGroup.doesUserGroupHaveFallbackPermissions(userGroupName, fallbackPermissions)).toBeTruthy(); + const userGroupData = await umbracoApi.userGroup.getByName(userGroupName); + expect(userGroupData.hasAccessToAllLanguages).toEqual(hasAccessToAllLanguages); + expect(userGroupData.documentRootAccess).toEqual(documentRootAccess); + expect(userGroupData.mediaRootAccess).toEqual(mediaRootAccess); + expect(userGroupData.permissions).toEqual(granularPermissions); +}); + +test('the default configuration of Writers data is correct', async ({umbracoApi, umbracoUi}) => { + // Arrange + const userGroupName = 'Writers'; + const sections = ["Umb.Section.Content"]; + const fallbackPermissions = [ + "Umb.Document.Create", + "Umb.Document.Update", + "Umb.Document.Read", + "Umb.Document.Notifications" + ]; + const granularPermissions = []; + const hasAccessToAllLanguages = true; + const documentRootAccess = true; + const mediaRootAccess = true; + const uiPermissions = await umbracoApi.userGroup.convertApiPermissionsToUiPermissions(fallbackPermissions); + const uiSections = await umbracoApi.userGroup.convertApiSectionsToUiSections(sections); + + // Act + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.doesSettingHaveValue('Assign access', ConstantHelper.userGroupAssignAccessSettings); + await umbracoUi.userGroup.doesSettingHaveValue('Default permissions', ConstantHelper.userGroupDefaultPermissionsSettings); + await umbracoUi.userGroup.doesSettingHaveValue('Granular permissions', ConstantHelper.userGroupGranularPermissionsSettings); + await umbracoUi.userGroup.doesPermissionsSettingsHaveValue(ConstantHelper.userGroupPermissionsSettings); + await umbracoUi.userGroup.doesUserGroupHavePermissionEnabled(uiPermissions); + await umbracoUi.userGroup.doesUserGroupHaveSections(uiSections); + await umbracoUi.userGroup.doesUserGroupSectionsHaveCount(uiSections.length); + expect(await umbracoApi.userGroup.doesUserGroupHaveSections(userGroupName, sections)).toBeTruthy(); + // Fixme - Uncomment this when the front-end is ready. Currently the fallbackPermissions includes some unnecessary values such as ":", "5", "T" + //expect(await umbracoApi.userGroup.doesUserGroupHaveFallbackPermissions(userGroupName, fallbackPermissions)).toBeTruthy(); + const userGroupData = await umbracoApi.userGroup.getByName(userGroupName); + expect(userGroupData.hasAccessToAllLanguages).toEqual(hasAccessToAllLanguages); + expect(userGroupData.documentRootAccess).toEqual(documentRootAccess); + expect(userGroupData.mediaRootAccess).toEqual(mediaRootAccess); + expect(userGroupData.permissions).toEqual(granularPermissions); +}); \ No newline at end of file From 37035e6e7fb25177182d13b7c5dcec55a65b22c8 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 26 Mar 2025 11:27:23 +0100 Subject: [PATCH 08/23] Clean up leftover block item data when changing element variance (#18804) --- .../BlockValuePropertyValueEditorBase.cs | 13 +- .../BlockEditorVarianceHandler.cs | 54 ++++- ...kListElementLevelVariationTests.Editing.cs | 184 +++++++++++++++++- 3 files changed, 232 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs index 00bc627d60..91d3ddf2ad 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs @@ -184,6 +184,12 @@ public abstract class BlockValuePropertyValueEditorBase : DataV foreach (BlockItemData item in items) { + // if changes were made to the element type variations, we need those changes reflected in the block property values. + // for regular content this happens when a content type is saved (copies of property values are created in the DB), + // but for local block level properties we don't have that kind of handling, so we to do it manually. + // to be friendly we'll map "formerly invariant properties" to the default language ISO code instead of performing a + // hard reset of the property values (which would likely be the most correct thing to do from a data point of view). + item.Values = _blockEditorVarianceHandler.AlignPropertyVarianceAsync(item.Values, culture).GetAwaiter().GetResult(); foreach (BlockPropertyValue blockPropertyValue in item.Values) { IPropertyType? propertyType = blockPropertyValue.PropertyType; @@ -199,13 +205,6 @@ public abstract class BlockValuePropertyValueEditorBase : DataV continue; } - // if changes were made to the element type variation, we need those changes reflected in the block property values. - // for regular content this happens when a content type is saved (copies of property values are created in the DB), - // but for local block level properties we don't have that kind of handling, so we to do it manually. - // to be friendly we'll map "formerly invariant properties" to the default language ISO code instead of performing a - // hard reset of the property values (which would likely be the most correct thing to do from a data point of view). - _blockEditorVarianceHandler.AlignPropertyVarianceAsync(blockPropertyValue, propertyType, culture).GetAwaiter().GetResult(); - if (!valueEditorsByKey.TryGetValue(propertyType.DataTypeKey, out IDataValueEditor? valueEditor)) { var configuration = _dataTypeConfigurationCache.GetConfiguration(propertyType.DataTypeKey); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs index 815bc0f1b0..042d57ff04 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorVarianceHandler.cs @@ -25,15 +25,7 @@ public sealed class BlockEditorVarianceHandler _contentTypeService = contentTypeService; } - /// - /// Aligns a block property value for variance changes. - /// - /// The block property value to align. - /// The underlying property type. - /// The culture being handled (null if invariant). - /// - /// Used for aligning variance changes when editing content. - /// + [Obsolete("Please use the method that allows alignment for a collection of values. Scheduled for removal in V17.")] public async Task AlignPropertyVarianceAsync(BlockPropertyValue blockPropertyValue, IPropertyType propertyType, string? culture) { culture ??= await _languageService.GetDefaultIsoCodeAsync(); @@ -45,6 +37,48 @@ public sealed class BlockEditorVarianceHandler } } + /// + /// Aligns a collection of block property values for variance changes. + /// + /// The block property values to align. + /// The culture being handled (null if invariant). + /// + /// Used for aligning variance changes when editing content. + /// + public async Task> AlignPropertyVarianceAsync(IList blockPropertyValues, string? culture) + { + var defaultIsoCodeAsync = await _languageService.GetDefaultIsoCodeAsync(); + culture ??= defaultIsoCodeAsync; + + var valuesToRemove = new List(); + foreach (BlockPropertyValue blockPropertyValue in blockPropertyValues) + { + IPropertyType? propertyType = blockPropertyValue.PropertyType; + if (propertyType is null) + { + throw new ArgumentException("One or more block properties did not have a resolved property type. Block editor values must be resolved before attempting to map them to editor.", nameof(blockPropertyValues)); + } + + if (propertyType.VariesByCulture() == VariesByCulture(blockPropertyValue)) + { + continue; + } + + if (propertyType.VariesByCulture() is false && blockPropertyValue.Culture.InvariantEquals(defaultIsoCodeAsync) is false) + { + valuesToRemove.Add(blockPropertyValue); + } + else + { + blockPropertyValue.Culture = propertyType.VariesByCulture() + ? culture + : null; + } + } + + return blockPropertyValues.Except(valuesToRemove).ToList(); + } + /// /// Aligns a block property value for variance changes. /// @@ -199,6 +233,8 @@ public sealed class BlockEditorVarianceHandler blockValue.Expose.Add(new BlockItemVariation(contentData.Key, value.Culture, value.Segment)); } } + + blockValue.Expose = blockValue.Expose.DistinctBy(e => $"{e.ContentKey}.{e.Culture}.{e.Segment}").ToList(); } private static bool VariesByCulture(BlockPropertyValue blockPropertyValue) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Editing.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Editing.cs index 295b134e22..8af4cf4987 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Editing.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Editing.cs @@ -1,11 +1,10 @@ -using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; +using NUnit.Framework; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; @@ -742,6 +741,185 @@ internal partial class BlockListElementLevelVariationTests } } + [Test] + public async Task Can_Align_Culture_Variance_For_Variant_Element_Types() + { + var elementType = CreateElementType(ContentVariation.Culture); + var blockListDataType = await CreateBlockListDataType(elementType); + var contentType = CreateContentType(ContentVariation.Nothing, blockListDataType); + + var content = CreateContent( + contentType, + elementType, + new List + { + new() { Alias = "invariantText", Value = "The invariant content value" }, + new() { Alias = "variantText", Value = "Another invariant content value" } + }, + new List + { + new() { Alias = "invariantText", Value = "The invariant settings value" }, + new() { Alias = "variantText", Value = "Another invariant settings value" } + }, + false); + + contentType.Variations = ContentVariation.Culture; + ContentTypeService.Save(contentType); + + // re-fetch content + content = ContentService.GetById(content.Key); + + var valueEditor = (BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor)blockListDataType.Editor!.GetValueEditor(); + + var blockListValue = valueEditor.ToEditor(content!.Properties["blocks"]!) as BlockListValue; + Assert.IsNotNull(blockListValue); + Assert.Multiple(() => + { + Assert.AreEqual(1, blockListValue.ContentData.Count); + Assert.AreEqual(2, blockListValue.ContentData.First().Values.Count); + var invariantValue = blockListValue.ContentData.First().Values.First(value => value.Alias == "invariantText"); + var variantValue = blockListValue.ContentData.First().Values.First(value => value.Alias == "variantText"); + Assert.IsNull(invariantValue.Culture); + Assert.AreEqual("en-US", variantValue.Culture); + }); + Assert.Multiple(() => + { + Assert.AreEqual(1, blockListValue.SettingsData.Count); + Assert.AreEqual(2, blockListValue.SettingsData.First().Values.Count); + var invariantValue = blockListValue.SettingsData.First().Values.First(value => value.Alias == "invariantText"); + var variantValue = blockListValue.SettingsData.First().Values.First(value => value.Alias == "variantText"); + Assert.IsNull(invariantValue.Culture); + Assert.AreEqual("en-US", variantValue.Culture); + }); + Assert.Multiple(() => + { + Assert.AreEqual(1, blockListValue.Expose.Count); + Assert.AreEqual("en-US", blockListValue.Expose.First().Culture); + }); + } + + [TestCase(ContentVariation.Culture)] + [TestCase(ContentVariation.Nothing)] + public async Task Can_Turn_Invariant_Element_Variant(ContentVariation contentTypeVariation) + { + var elementType = CreateElementType(ContentVariation.Nothing); + var blockListDataType = await CreateBlockListDataType(elementType); + var contentType = CreateContentType(contentTypeVariation, blockListDataType); + + var content = CreateContent( + contentType, + elementType, + new List + { + new() { Alias = "invariantText", Value = "The invariant content value" }, + new() { Alias = "variantText", Value = "Another invariant content value" } + }, + new List + { + new() { Alias = "invariantText", Value = "The invariant settings value" }, + new() { Alias = "variantText", Value = "Another invariant settings value" } + }, + false); + + elementType.Variations = ContentVariation.Culture; + elementType.PropertyTypes.First(p => p.Alias == "variantText").Variations = ContentVariation.Culture; + ContentTypeService.Save(elementType); + + // re-fetch content + content = ContentService.GetById(content.Key); + + var valueEditor = (BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor)blockListDataType.Editor!.GetValueEditor(); + + var blockListValue = valueEditor.ToEditor(content!.Properties["blocks"]!) as BlockListValue; + Assert.IsNotNull(blockListValue); + Assert.Multiple(() => + { + Assert.AreEqual(1, blockListValue.ContentData.Count); + Assert.AreEqual(2, blockListValue.ContentData.First().Values.Count); + var invariantValue = blockListValue.ContentData.First().Values.First(value => value.Alias == "invariantText"); + var variantValue = blockListValue.ContentData.First().Values.First(value => value.Alias == "variantText"); + Assert.IsNull(invariantValue.Culture); + Assert.AreEqual("en-US", variantValue.Culture); + }); + Assert.Multiple(() => + { + Assert.AreEqual(1, blockListValue.SettingsData.Count); + Assert.AreEqual(2, blockListValue.SettingsData.First().Values.Count); + var invariantValue = blockListValue.SettingsData.First().Values.First(value => value.Alias == "invariantText"); + var variantValue = blockListValue.SettingsData.First().Values.First(value => value.Alias == "variantText"); + Assert.IsNull(invariantValue.Culture); + Assert.AreEqual("en-US", variantValue.Culture); + }); + Assert.Multiple(() => + { + Assert.AreEqual(1, blockListValue.Expose.Count); + Assert.AreEqual("en-US", blockListValue.Expose.First().Culture); + }); + } + + [TestCase(ContentVariation.Nothing)] + [TestCase(ContentVariation.Culture)] + public async Task Can_Turn_Variant_Element_Invariant(ContentVariation contentTypeVariation) + { + var elementType = CreateElementType(ContentVariation.Culture); + var blockListDataType = await CreateBlockListDataType(elementType); + var contentType = CreateContentType(contentTypeVariation, blockListDataType); + + var content = CreateContent( + contentType, + elementType, + new List + { + new() { Alias = "invariantText", Value = "The invariant content value" }, + new() { Alias = "variantText", Value = "Variant content in English", Culture = "en-US" }, + new() { Alias = "variantText", Value = "Variant content in Danish", Culture = "da-DK" } + }, + new List + { + new() { Alias = "invariantText", Value = "The invariant settings value" }, + new() { Alias = "variantText", Value = "Variant settings in English", Culture = "en-US" }, + new() { Alias = "variantText", Value = "Variant settings in Danish", Culture = "da-DK" } + }, + false); + + elementType.Variations = ContentVariation.Nothing; + elementType.PropertyTypes.First(p => p.Alias == "variantText").Variations = ContentVariation.Nothing; + ContentTypeService.Save(elementType); + + // re-fetch content + content = ContentService.GetById(content.Key); + + var valueEditor = (BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor)blockListDataType.Editor!.GetValueEditor(); + + var blockListValue = valueEditor.ToEditor(content!.Properties["blocks"]!) as BlockListValue; + Assert.IsNotNull(blockListValue); + Assert.Multiple(() => + { + Assert.AreEqual(1, blockListValue.ContentData.Count); + Assert.AreEqual(2, blockListValue.ContentData.First().Values.Count); + var invariantValue = blockListValue.ContentData.First().Values.First(value => value.Alias == "invariantText"); + var variantValue = blockListValue.ContentData.First().Values.First(value => value.Alias == "variantText"); + Assert.IsNull(invariantValue.Culture); + Assert.IsNull(variantValue.Culture); + Assert.AreEqual("Variant content in English", variantValue.Value); + }); + Assert.Multiple(() => + { + Assert.AreEqual(1, blockListValue.SettingsData.Count); + Assert.AreEqual(2, blockListValue.SettingsData.First().Values.Count); + var invariantValue = blockListValue.SettingsData.First().Values.First(value => value.Alias == "invariantText"); + var variantValue = blockListValue.SettingsData.First().Values.First(value => value.Alias == "variantText"); + Assert.IsNull(invariantValue.Culture); + Assert.IsNull(variantValue.Culture); + Assert.AreEqual("Variant settings in English", variantValue.Value); + }); + Assert.Multiple(() => + { + Assert.AreEqual(1, blockListValue.Expose.Count); + Assert.IsNull(blockListValue.Expose.First().Culture); + }); + } + private async Task CreateLimitedUser() { var userGroupService = GetRequiredService(); From 028da4545ebe23df44ed03f2cbc7df21d41775c3 Mon Sep 17 00:00:00 2001 From: Henrik Date: Wed, 26 Mar 2025 16:01:53 +0100 Subject: [PATCH 09/23] Reduce CPU time when initiating RepositoryCacheKeys (#18267) * Avoid an unneeded lookups in the Keys dictionary when initiating key cache * Add further comments and unit tests around updated code. --------- Co-authored-by: Andy Butland --- .../Repositories/RepositoryCacheKeys.cs | 25 ++++++++++++++++-- .../Repositories/RepositoryCacheKeysTests.cs | 26 +++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeysTests.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs index a6b6c16aa5..aca68e9762 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs @@ -1,3 +1,5 @@ +using System.Runtime.InteropServices; + namespace Umbraco.Cms.Core.Persistence.Repositories; /// @@ -5,15 +7,34 @@ namespace Umbraco.Cms.Core.Persistence.Repositories; /// public static class RepositoryCacheKeys { - // used to cache keys so we don't keep allocating strings + /// + /// A cache for the keys we don't keep allocating strings. + /// private static readonly Dictionary Keys = new(); + /// + /// Gets the repository cache key for the provided type. + /// public static string GetKey() { Type type = typeof(T); - return Keys.TryGetValue(type, out var key) ? key : Keys[type] = "uRepo_" + type.Name + "_"; + + // The following code is a micro-optimization to avoid an unnecessary lookup in the Keys dictionary, when writing the newly created key. + // Previously, the code was: + // return Keys.TryGetValue(type, out var key) + // ? key + // : Keys[type] = "uRepo_" + type.Name + "_"; + + // Look up the existing value or get a reference to the newly created default value. + ref string? key = ref CollectionsMarshal.GetValueRefOrAddDefault(Keys, type, out _); + + // As we have the reference, we can just assign it if null, without the expensive write back to the dictionary. + return key ??= "uRepo_" + type.Name + "_"; } + /// + /// Gets the repository cache key for the provided type and Id. + /// public static string GetKey(TId? id) { if (EqualityComparer.Default.Equals(id, default)) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeysTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeysTests.cs new file mode 100644 index 0000000000..fef83541b9 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeysTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; + +[TestFixture] +public class RepositoryCacheKeysTests +{ + [Test] + public void GetKey_Returns_Expected_Key_For_Type() + { + var key = RepositoryCacheKeys.GetKey(); + Assert.AreEqual("uRepo_IContent_", key); + } + + [Test] + public void GetKey_Returns_Expected_Key_For_Type_And_Id() + { + var key = RepositoryCacheKeys.GetKey(1000); + Assert.AreEqual("uRepo_IContent_1000", key); + } +} From b25bcdc436c35fb40002036c6f1b491fc8417bfa Mon Sep 17 00:00:00 2001 From: NguyenThuyLan <116753400+NguyenThuyLan@users.noreply.github.com> Date: Wed, 26 Mar 2025 16:04:19 +0100 Subject: [PATCH 10/23] Fix bug uploading an image via the Media Picker is no longer automatically selected (#18667) * Fix bug uploading an image via the Media Picker is no longer automatically selected * Update src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts Co-authored-by: Bjarne Fyrstenborg * Update src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> * remove submit modal * fix: avoids overriding the dropzone manager as that does not work with inheritance * fix: set disabling of folders correctly --------- Co-authored-by: Lan Nguyen Thuy Co-authored-by: Bjarne Fyrstenborg Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> --- .../input-dropzone/input-dropzone.element.ts | 22 +++++++------------ .../media/dropzone/dropzone-manager.class.ts | 10 ++++++++- .../dropzone/dropzone-media-manager.class.ts | 21 ------------------ .../media/dropzone/dropzone-media.element.ts | 16 +++++--------- .../packages/media/media/dropzone/index.ts | 1 - .../media-picker-modal.element.ts | 17 ++++++++++++-- 6 files changed, 38 insertions(+), 49 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media-manager.class.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.element.ts index a4049b995a..8515f2f673 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.element.ts @@ -44,14 +44,8 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin = []; - #manager = new UmbDropzoneManager(this); + protected _manager = new UmbDropzoneManager(this); constructor() { super(); this.observe( - this.#manager.progress, + this._manager.progress, (progress) => this.dispatchEvent(new ProgressEvent('progress', { loaded: progress.completed, total: progress.total })), '_observeProgress', ); this.observe( - this.#manager.progressItems, + this._manager.progressItems, (progressItems) => { this._progressItems = [...progressItems]; const waiting = this._progressItems.find((item) => item.status === UmbFileDropzoneItemStatus.WAITING); @@ -106,7 +100,7 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin} - The items about to be uploaded. diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media-manager.class.ts deleted file mode 100644 index b49dca7188..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media-manager.class.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - UmbDropzoneManager, - type UmbFileDropzoneDroppedItems, - type UmbUploadableItem, -} from '@umbraco-cms/backoffice/dropzone'; - -export class UmbDropzoneMediaManager extends UmbDropzoneManager { - /** - * Uploads files and folders to the server and creates the media items with corresponding media type.\ - * Allows the user to pick a media type option if multiple types are allowed. - * @param {UmbFileDropzoneDroppedItems} items - The files and folders to upload. - * @param {string | null} parentUnique - Where the items should be uploaded. - * @returns {Array} - The items about to be uploaded. - */ - public override createMediaItems( - items: UmbFileDropzoneDroppedItems, - parentUnique: string | null, - ): Array { - return super.createMediaItems(items, parentUnique); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media.element.ts index 912a51780e..75adf08d70 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-media.element.ts @@ -1,4 +1,3 @@ -import { UmbDropzoneMediaManager } from './dropzone-media-manager.class.js'; import { UmbInputDropzoneElement, UmbFileDropzoneItemStatus, @@ -29,8 +28,6 @@ export class UmbDropzoneMediaElement extends UmbInputDropzoneElement { @property({ type: Boolean, attribute: 'create-as-temporary' }) createAsTemporary: boolean = false; - #dropzoneManager = new UmbDropzoneMediaManager(this); - /** * @deprecated Please use `getItems()` instead; this method will be removed in Umbraco 17. * @returns {Array} An array of uploadable items. @@ -45,8 +42,8 @@ export class UmbDropzoneMediaElement extends UmbInputDropzoneElement { return this._progressItems; } - public progressItems = () => this.#dropzoneManager.progressItems; - public progress = () => this.#dropzoneManager.progress; + public progressItems = () => this._manager.progressItems; + public progress = () => this._manager.progress; constructor() { super(); @@ -56,20 +53,19 @@ export class UmbDropzoneMediaElement extends UmbInputDropzoneElement { document.addEventListener('drop', this.#handleDrop.bind(this)); this.observe( - this.#dropzoneManager.progressItems, + this._manager.progressItems, (progressItems: Array) => { const waiting = progressItems.find((item) => item.status === UmbFileDropzoneItemStatus.WAITING); if (progressItems.length && !waiting) { this.dispatchEvent(new CustomEvent('complete', { detail: progressItems })); } }, - '_observeProgressItems', + '_observeProgressItemsComplete', ); } override disconnectedCallback(): void { super.disconnectedCallback(); - this.#dropzoneManager.destroy(); document.removeEventListener('dragenter', this.#handleDragEnter.bind(this)); document.removeEventListener('dragleave', this.#handleDragLeave.bind(this)); document.removeEventListener('drop', this.#handleDrop.bind(this)); @@ -80,10 +76,10 @@ export class UmbDropzoneMediaElement extends UmbInputDropzoneElement { if (!event.detail.files.length && !event.detail.folders.length) return; if (this.createAsTemporary) { - const uploadable = this.#dropzoneManager.createTemporaryFiles(event.detail.files); + const uploadable = this._manager.createTemporaryFiles(event.detail.files); this.dispatchEvent(new UmbDropzoneSubmittedEvent(await uploadable)); } else { - const uploadable = this.#dropzoneManager.createMediaItems(event.detail, this.parentUnique); + const uploadable = this._manager.createMediaItems(event.detail, this.parentUnique); this.dispatchEvent(new UmbDropzoneSubmittedEvent(uploadable)); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/index.ts index a48fad8436..587d8c7b20 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/index.ts @@ -1,3 +1,2 @@ -export * from './dropzone-media-manager.class.js'; export * from './dropzone-media.element.js'; export * from './dropzone.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts index 5f709abe61..525c21d667 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts @@ -6,6 +6,7 @@ import { UmbMediaSearchProvider } from '../../search/index.js'; import type { UmbMediaPathModel } from './types.js'; import type { UmbMediaPickerFolderPathElement } from './components/media-picker-folder-path.element.js'; import type { UmbMediaPickerModalData, UmbMediaPickerModalValue } from './media-picker-modal.token.js'; +import type { UmbDropzoneChangeEvent, UmbUploadableItem } from '@umbraco-cms/backoffice/dropzone'; import type { UmbDropzoneMediaElement } from '@umbraco-cms/backoffice/media'; import { css, @@ -116,7 +117,7 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement) { const key = this._currentMediaEntity.entityType + this._currentMediaEntity.unique; let paginationManager = this.#pagingMap.get(key); @@ -143,6 +144,18 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement x.unique == selectedItems[0].unique); + if (selectedItem) { + this.#onSelected(selectedItem); + } + } + } + + #onDropzoneChange(evt: UmbDropzoneChangeEvent) { + const target = evt.target as UmbDropzoneMediaElement; + this.#loadChildrenOfCurrentMediaItem(target.value); } #onOpen(item: UmbMediaTreeItemModel | UmbMediaSearchItemModel) { @@ -290,7 +303,7 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement ${this._searchQuery ? this.#renderSearchResult() : this.#renderCurrentChildren()} `; } From 95e89f84816dd0ca9738f5cd82930656a58e7f68 Mon Sep 17 00:00:00 2001 From: Henrik Date: Wed, 26 Mar 2025 16:55:03 +0100 Subject: [PATCH 11/23] Avoid a hash key generation and lookup when inserting in the LockingMechanism (#18243) * Avoid a hash key generation and lookup when inserting in the LockingMechanism * Added comments for CollectionsMarshal.GetValueRefOrAddDefault * Added further comments and tests. --------- Co-authored-by: Andy Butland --- src/Umbraco.Core/Scoping/LockingMechanism.cs | 42 ++++++++++++----- .../Builders/ContentBuilder.cs | 5 +-- .../Scoping/LockingMechanismTests.cs | 45 +++++++++++++++++++ 3 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/LockingMechanismTests.cs diff --git a/src/Umbraco.Core/Scoping/LockingMechanism.cs b/src/Umbraco.Core/Scoping/LockingMechanism.cs index 0cee4293f6..e078b047e6 100644 --- a/src/Umbraco.Core/Scoping/LockingMechanism.cs +++ b/src/Umbraco.Core/Scoping/LockingMechanism.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using System.Text; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Collections; @@ -7,7 +8,7 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Scoping; /// -/// Mechanism for handling read and write locks +/// Mechanism for handling read and write locks. /// public class LockingMechanism : ILockingMechanism { @@ -189,24 +190,43 @@ public class LockingMechanism : ILockingMechanism /// Lock ID to increment. /// Instance ID of the scope requesting the lock. /// Reference to the dictionary to increment on - private void IncrementLock(int lockId, Guid instanceId, ref Dictionary>? locks) + /// Internal for tests. + internal static void IncrementLock(int lockId, Guid instanceId, ref Dictionary>? locks) { // Since we've already checked that we're the parent in the WriteLockInner method, we don't need to check again. - // If it's the very first time a lock has been requested the WriteLocks dict hasn't been instantiated yet. - locks ??= new Dictionary>(); + // If it's the very first time a lock has been requested the WriteLocks dictionary hasn't been instantiated yet. + locks ??= []; - // Try and get the dict associated with the scope id. - var locksDictFound = locks.TryGetValue(instanceId, out Dictionary? locksDict); + // Try and get the dictionary associated with the scope id. + + // The following code is a micro-optimization. + // GetValueRefOrAddDefault does lookup or creation with only one hash key generation, internal bucket lookup and value lookup in the bucket. + // This compares to doing it twice when initializing, one for the lookup and one for the insertion of the initial value, we had with the + // previous code: + // var locksDictFound = locks.TryGetValue(instanceId, out Dictionary? locksDict); + // if (locksDictFound) + // { + // locksDict!.TryGetValue(lockId, out var value); + // locksDict[lockId] = value + 1; + // } + // else + // { + // // The scope hasn't requested a lock yet, so we have to create a dict for it. + // locks.Add(instanceId, new Dictionary()); + // locks[instanceId][lockId] = 1; + // } + + ref Dictionary? locksDict = ref CollectionsMarshal.GetValueRefOrAddDefault(locks, instanceId, out bool locksDictFound); if (locksDictFound) { - locksDict!.TryGetValue(lockId, out var value); - locksDict[lockId] = value + 1; + // By getting a reference to any existing or default 0 value, we can increment it without the expensive write back into the dictionary. + ref int value = ref CollectionsMarshal.GetValueRefOrAddDefault(locksDict!, lockId, out _); + value++; } else { - // The scope hasn't requested a lock yet, so we have to create a dict for it. - locks.Add(instanceId, new Dictionary()); - locks[instanceId][lockId] = 1; + // The scope hasn't requested a lock yet, so we have to create a dictionary for it. + locksDict = new Dictionary { { lockId, 1 } }; } } diff --git a/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs b/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs index 53c2f50f10..1fd66da312 100644 --- a/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs +++ b/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs @@ -185,10 +185,7 @@ public class ContentBuilder { if (string.IsNullOrWhiteSpace(name)) { - if (_cultureNames.TryGetValue(culture, out _)) - { - _cultureNames.Remove(culture); - } + _cultureNames.Remove(culture); } else { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/LockingMechanismTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/LockingMechanismTests.cs new file mode 100644 index 0000000000..eb2d6abdfb --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/LockingMechanismTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; +using Umbraco.Cms.Core.Scoping; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Scoping; + +[TestFixture] +internal class LockingMechanismTests +{ + private const int LockId = 1000; + private const int LockId2 = 1001; + private static readonly Guid _scopeInstanceId = Guid.NewGuid(); + + [Test] + public void IncrementLock_WithoutLocksDictionary_CreatesLock() + { + var locks = new Dictionary>(); + LockingMechanism.IncrementLock(LockId, _scopeInstanceId, ref locks); + Assert.AreEqual(1, locks.Count); + Assert.AreEqual(1, locks[_scopeInstanceId][LockId]); + } + + [Test] + public void IncrementLock_WithExistingLocksDictionary_CreatesLock() + { + var locks = new Dictionary>() + { + { + _scopeInstanceId, + new Dictionary() + { + { LockId, 100 }, + { LockId2, 200 } + } + } + }; + LockingMechanism.IncrementLock(LockId, _scopeInstanceId, ref locks); + Assert.AreEqual(1, locks.Count); + Assert.AreEqual(2, locks[_scopeInstanceId].Count); + Assert.AreEqual(101, locks[_scopeInstanceId][LockId]); + Assert.AreEqual(200, locks[_scopeInstanceId][LockId2]); + } +} From 9c8832668147f42fe14b6195f29e3a19277fd4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 26 Mar 2025 19:36:17 +0100 Subject: [PATCH 12/23] lint corrections (#18833) --- .../shared/layout/installer-layout.element.ts | 3 ++- src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts | 3 ++- src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts | 3 ++- .../block-grid-manager.context.ts | 3 +++ ...tor-ui-block-grid-group-configuration.element.ts | 6 +++--- ...ditor-ui-block-grid-layout-stylesheet.element.ts | 6 +++--- ...-grid-validation-property-path-translator.api.ts | 2 +- .../context/block-list-manager.context.ts | 3 +++ .../property-editor-ui-block-list.element.ts | 2 +- ...itor-ui-block-list-type-configuration.element.ts | 6 +++--- ...-list-validation-property-path-translator.api.ts | 2 +- .../block-rte/context/block-rte-manager.context.ts | 3 +++ .../edit/block-workspace-view-edit-tab.element.ts | 10 +++++----- .../content-type-structure-data-source.interface.ts | 5 ++++- .../content-type-structure-repository-base.ts | 1 + .../content-type-structure-repository.interface.ts | 5 ++++- ...ontent-type-structure-server-data-source-base.ts | 11 +++++++++-- .../core/content/manager/element-data-manager.ts | 5 +++++ .../content-detail-validation-path-translator.ts | 2 +- .../workspace/content-detail-workspace-base.ts | 2 +- .../core/entity-action/entity-action.event.ts | 1 - .../controllers/peek-error/peek-error.controller.ts | 2 +- .../extractUmbNotificationColor.function.ts | 2 +- .../workspace/property-type-workspace.context.ts | 2 +- .../packages/core/resources/resource.controller.ts | 3 ++- .../src/packages/core/sorter/sorter.controller.ts | 2 +- .../temporary-file/temporary-file.repository.ts | 1 + .../validation-path-translation.controller.ts | 4 ++-- ...lidation-property-path-translation.controller.ts | 5 ++++- .../validation/controllers/validation.controller.ts | 8 +++++--- ...-values-validation-path-translator.controller.ts | 2 +- ...ariants-validation-path-translator.controller.ts | 2 +- .../utils/query-mapper-json-paths.function.ts | 4 ++++ .../core/variant/variant-object-compare.function.ts | 5 +++++ .../conditions/workspace-entity-is-new.condition.ts | 2 +- ...operty-editor-ui-document-type-picker.element.ts | 2 +- .../document-type-structure.server.data-source.ts | 7 +++++-- .../property-editor-ui-document-picker.element.ts | 2 +- ...cument-publish-with-descendants-modal.element.ts | 1 - .../repository/document-publishing.repository.ts | 6 +----- .../schedule-publish/workspace-action/manifests.ts | 2 +- .../document-publishing.workspace-context.ts | 1 + .../log-viewer-log-types-chart.element.ts | 3 ++- .../markdown-editor-property-value-preset.ts | 2 +- .../media/dropzone/dropzone-manager.class.ts | 1 + .../property-editor-ui-media-type-picker.element.ts | 2 +- .../media-type-structure.server.data-source.ts | 5 ++++- .../property-editor-ui-image-cropper.element.ts | 6 +++--- ...roperty-editor-ui-media-entity-picker.element.ts | 2 +- .../property-editor-ui-upload-field.element.ts | 6 +++--- .../member/repository/member-repository-base.ts | 4 ++-- .../models-builder-dashboard.element.ts | 4 ++-- .../color-picker/Umbraco.ColorPicker.ts | 3 +-- ...perty-editor-ui-content-picker-source.element.ts | 6 +++--- .../property-editor-ui-overlay-size.element.ts | 6 +++--- .../slider/property-editor-ui-slider.element.ts | 2 +- .../text-box/property-editor-ui-text-box.element.ts | 6 +++--- .../toggle/property-editor-ui-toggle.element.ts | 2 +- .../property-editor-ui-value-type.element.ts | 6 +++--- .../dashboard-published-status.element.ts | 1 - .../rte-validation-property-path-translator.api.ts | 2 +- ...property-editor-ui-static-file-picker.element.ts | 6 +++--- .../tags/property-editor-ui-tags.element.ts | 6 +++--- .../query-builder/query-builder-modal.element.ts | 13 +++++++++---- .../src/packages/tiptap/components/manifests.ts | 5 +---- .../packages/webhook/webhook/collection/types.ts | 2 +- 66 files changed, 150 insertions(+), 100 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/shared/layout/installer-layout.element.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/shared/layout/installer-layout.element.ts index b4b81b3770..a32ad52bce 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/installer/shared/layout/installer-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/installer/shared/layout/installer-layout.element.ts @@ -31,7 +31,8 @@ export class UmbInstallerLayoutElement extends LitElement { height: 100%; background-color: hsla(240, 68%, 11%, 1); - background-image: radial-gradient(at 99% 2%, hsla(212, 40%, 12%, 1) 0px, transparent 50%), + background-image: + radial-gradient(at 99% 2%, hsla(212, 40%, 12%, 1) 0px, transparent 50%), radial-gradient(at 98% 95%, hsla(255, 40%, 12%, 1) 0px, transparent 50%), radial-gradient(at 1% 2%, hsla(249, 40%, 12%, 1) 0px, transparent 50%), radial-gradient(at 2% 97%, hsla(228, 40%, 12%, 1) 0px, transparent 50%), diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts index 16ffe924be..473292c787 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts @@ -1221,7 +1221,8 @@ export default { colorsTitle: 'Farver', colorsDescription: 'Tilføj, fjern eller sorter farver', showLabelTitle: 'Inkluder label?', - showLabelDescription: 'Gemmer farver som et Json-objekt, der både indeholder farvens hex streng og label, i stedet for kun at gemme hex strengen.', + showLabelDescription: + 'Gemmer farver som et Json-objekt, der både indeholder farvens hex streng og label, i stedet for kun at gemme hex strengen.', }, contentPicker: { allowedItemTypes: 'Du kan kun vælge følgende type(r) dokumenter: %0%', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts index d864c5ce7f..bf7d91a548 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts @@ -1247,7 +1247,8 @@ export default { colorsTitle: 'Colors', colorsDescription: 'Add, remove or sort colors', showLabelTitle: 'Include labels?', - showLabelDescription: 'Stores colors as a JSON object containing both the color hex string and label, rather than just the hex string.', + showLabelDescription: + 'Stores colors as a JSON object containing both the color hex string and label, rather than just the hex string.', }, contentPicker: { allowedItemTypes: 'You can only select items of type(s): %0%', diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts index 6b6a74b924..602b3186c5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts @@ -92,6 +92,9 @@ export class UmbBlockGridManagerContext< }); } /** + * @param contentElementTypeKey + * @param partialLayoutEntry + * @param _originData * @deprecated Use createWithPresets instead. Will be removed in v.17. */ create( diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.element.ts index bbb80c1071..ab1ad0f462 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.element.ts @@ -1,7 +1,7 @@ import { html, customElement, property, css } from '@umbraco-cms/backoffice/external/lit'; -import { - type UmbPropertyEditorUiElement, - type UmbPropertyEditorConfigCollection, +import type { + UmbPropertyEditorUiElement, + UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.element.ts index 28fe0f0434..d3f3e63c01 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.element.ts @@ -4,9 +4,9 @@ import type { UmbInputStaticFileElement } from '@umbraco-cms/backoffice/static-f import '@umbraco-cms/backoffice/static-file'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; -import { - type UmbPropertyEditorUiElement, - type UmbPropertyEditorConfigCollection, +import type { + UmbPropertyEditorUiElement, + UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/validation/block-grid-validation-property-path-translator.api.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/validation/block-grid-validation-property-path-translator.api.ts index 6f6ceebc03..02747996af 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/validation/block-grid-validation-property-path-translator.api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/validation/block-grid-validation-property-path-translator.api.ts @@ -1,5 +1,5 @@ -import type { UmbPropertyValueDataPotentiallyWithEditorAlias } from '@umbraco-cms/backoffice/property'; import type { UmbBlockGridValueModel } from '../types.js'; +import type { UmbPropertyValueDataPotentiallyWithEditorAlias } from '@umbraco-cms/backoffice/property'; import { UmbBlockEditorValidationPropertyPathTranslatorBase } from '@umbraco-cms/backoffice/block'; export class UmbBlockGridValidationPropertyPathTranslator extends UmbBlockEditorValidationPropertyPathTranslatorBase { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts index 6dd0267cb3..c8c66a1a3a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts @@ -22,6 +22,9 @@ export class UmbBlockListManagerContext< } /** + * @param contentElementTypeKey + * @param partialLayoutEntry + * @param _originData * @deprecated Use createWithPresets instead. Will be removed in v.17. */ create( diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts index e916852946..02c405e426 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts @@ -14,7 +14,7 @@ import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; -import { type UmbBlockLayoutBaseModel } from '@umbraco-cms/backoffice/block'; +import type { UmbBlockLayoutBaseModel } from '@umbraco-cms/backoffice/block'; import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type'; import '../../components/block-list-entry/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts index 482b509553..db98ad1626 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts @@ -1,9 +1,9 @@ import '../../../block-type/components/input-block-type/index.js'; import { UMB_BLOCK_LIST_TYPE } from '../../constants.js'; import type { UmbBlockTypeBaseModel, UmbInputBlockTypeElement } from '@umbraco-cms/backoffice/block-type'; -import { - type UmbPropertyEditorUiElement, - type UmbPropertyEditorConfigCollection, +import type { + UmbPropertyEditorUiElement, + UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/validation/block-list-validation-property-path-translator.api.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/validation/block-list-validation-property-path-translator.api.ts index a5f396b7c6..0211ae346b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/validation/block-list-validation-property-path-translator.api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/validation/block-list-validation-property-path-translator.api.ts @@ -1,5 +1,5 @@ -import type { UmbPropertyValueDataPotentiallyWithEditorAlias } from '@umbraco-cms/backoffice/property'; import type { UmbBlockListValueModel } from '../types.js'; +import type { UmbPropertyValueDataPotentiallyWithEditorAlias } from '@umbraco-cms/backoffice/property'; import { UmbBlockEditorValidationPropertyPathTranslatorBase } from '@umbraco-cms/backoffice/block'; export class UmbBlockListValidationPropertyPathTranslator extends UmbBlockEditorValidationPropertyPathTranslatorBase { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts index 30458896c1..79cfb81b40 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts @@ -19,6 +19,9 @@ export class UmbBlockRteManagerContext< } /** + * @param contentElementTypeKey + * @param partialLayoutEntry + * @param _originData * @deprecated Use createWithPresets instead. Will be removed in v.17. */ create( diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-tab.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-tab.element.ts index 9b84ceb4da..fbfc307945 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-tab.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-tab.element.ts @@ -79,11 +79,11 @@ export class UmbBlockWorkspaceViewEditTabElement extends UmbLitElement { return html` ${this._hasProperties ? html` - - ` + + ` : ''} ${this.hideSingleGroup && this._groups.length === 1 ? this.renderGroup(this._groups[0]) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-data-source.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-data-source.interface.ts index 49d31fe5be..9ac4e7d0ba 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-data-source.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-data-source.interface.ts @@ -6,5 +6,8 @@ export interface UmbContentTypeStructureDataSourceConstructor { } export interface UmbContentTypeStructureDataSource { - getAllowedChildrenOf(unique: string | null, parentContentUnique: string | null): Promise>>; + getAllowedChildrenOf( + unique: string | null, + parentContentUnique: string | null, + ): Promise>>; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository-base.ts index 4d51925ed8..08205b9851 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository-base.ts @@ -20,6 +20,7 @@ export abstract class UmbContentTypeStructureRepositoryBase /** * Returns a promise with the allowed children of a content type * @param {string} unique + * @param parentContentUnique * @returns {*} * @memberof UmbContentTypeStructureRepositoryBase */ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository.interface.ts index 1f74549895..99f1d994ad 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-repository.interface.ts @@ -1,5 +1,8 @@ import type { UmbDataSourceResponse, UmbPagedModel } from '@umbraco-cms/backoffice/repository'; export interface UmbContentTypeStructureRepository { - requestAllowedChildrenOf(unique: string, parentContentUnique: string | null): Promise>>; + requestAllowedChildrenOf( + unique: string, + parentContentUnique: string | null, + ): Promise>>; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-server-data-source-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-server-data-source-base.ts index 3ce3d6ae67..4991c103b3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-server-data-source-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-server-data-source-base.ts @@ -16,7 +16,10 @@ export interface UmbContentTypeStructureServerDataSourceBaseArgs< ServerItemType extends AllowedContentTypeBaseModel, ClientItemType extends UmbEntityModel, > { - getAllowedChildrenOf: (unique: string | null, parentContentUnique: string | null) => Promise>; + getAllowedChildrenOf: ( + unique: string | null, + parentContentUnique: string | null, + ) => Promise>; mapper: (item: ServerItemType) => ClientItemType; } @@ -47,11 +50,15 @@ export abstract class UmbContentTypeStructureServerDataSourceBase< /** * Returns a promise with the allowed content types for the given unique * @param {string} unique + * @param parentContentUnique * @returns {*} * @memberof UmbContentTypeStructureServerDataSourceBase */ async getAllowedChildrenOf(unique: string | null, parentContentUnique: string | null) { - const { data, error } = await tryExecuteAndNotify(this.#host, this.#getAllowedChildrenOf(unique, parentContentUnique)); + const { data, error } = await tryExecuteAndNotify( + this.#host, + this.#getAllowedChildrenOf(unique, parentContentUnique), + ); if (data) { const items = data.items.map((item) => this.#mapper(item)); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/element-data-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/element-data-manager.ts index 623f99a2c4..e64def667f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/element-data-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/element-data-manager.ts @@ -3,6 +3,11 @@ import type { UmbElementDetailModel } from '../types.js'; import { UmbVariantId, umbVariantObjectCompare } from '@umbraco-cms/backoffice/variant'; import { UmbEntityWorkspaceDataManager, type UmbWorkspaceDataManager } from '@umbraco-cms/backoffice/workspace'; +/** + * + * @param a + * @param b + */ function valueObjectCompare(a: any, b: any) { return a.alias === b.alias && umbVariantObjectCompare(a, b); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-validation-path-translator.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-validation-path-translator.ts index 3012b1f37d..3b8d0d5a77 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-validation-path-translator.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-validation-path-translator.ts @@ -1,3 +1,4 @@ +import type { UmbContentDetailModel } from '../types.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { UmbDataPathPropertyValueQuery, @@ -7,7 +8,6 @@ import { umbScopeMapperForJsonPaths, type UmbValidationPathTranslator, } from '@umbraco-cms/backoffice/validation'; -import type { UmbContentDetailModel } from '../types.js'; export class UmbContentDetailValidationPathTranslator extends UmbControllerBase diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts index 28760926dd..da8ccb7b14 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts @@ -5,6 +5,7 @@ import type { UmbContentVariantPickerData, UmbContentVariantPickerValue } from ' import type { UmbContentPropertyDatasetContext } from '../property-dataset-context/index.js'; import type { UmbContentValidationRepository } from '../repository/content-validation-repository.interface.js'; import type { UmbContentWorkspaceContext } from './content-workspace-context.interface.js'; +import { UmbContentDetailValidationPathTranslator } from './content-detail-validation-path-translator.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbDetailRepository, UmbDetailRepositoryConstructor } from '@umbraco-cms/backoffice/repository'; import { @@ -51,7 +52,6 @@ import { type UmbPropertyTypePresetModel, type UmbPropertyTypePresetModelTypeModel, } from '@umbraco-cms/backoffice/property'; -import { UmbContentDetailValidationPathTranslator } from './content-detail-validation-path-translator.js'; export interface UmbContentDetailWorkspaceContextArgs< DetailModelType extends UmbContentDetailModel, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/entity-action.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/entity-action.event.ts index ff91281d89..c9d598d10b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/entity-action.event.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/entity-action.event.ts @@ -1,7 +1,6 @@ import { UmbControllerEvent } from '@umbraco-cms/backoffice/controller-api'; import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; -// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbEntityActionEventArgs extends UmbEntityModel { eventUnique?: string; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/notification/controllers/peek-error/peek-error.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/notification/controllers/peek-error/peek-error.controller.ts index 80dc9638a1..2d8e4ea0bf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/notification/controllers/peek-error/peek-error.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/notification/controllers/peek-error/peek-error.controller.ts @@ -1,7 +1,7 @@ import { UMB_NOTIFICATION_CONTEXT } from '../../notification.context.js'; +import type { UmbPeekErrorArgs } from '../../types.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbPeekErrorArgs } from '../../types.js'; import './peek-error-notification.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/notification/extractUmbNotificationColor.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/notification/extractUmbNotificationColor.function.ts index cf9b9e89d8..492f7c76b7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/notification/extractUmbNotificationColor.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/notification/extractUmbNotificationColor.function.ts @@ -1,5 +1,5 @@ -import { EventMessageTypeModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbNotificationColor } from './types.js'; +import { EventMessageTypeModel } from '@umbraco-cms/backoffice/external/backend-api'; /** * diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts index 7c759ca1d8..1192f50788 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts @@ -1,3 +1,4 @@ +import { UmbDataPathPropertyTypeQuery } from '../utils/index.js'; import { UmbPropertyTypeWorkspaceEditorElement } from './property-type-workspace-editor.element.js'; import type { UmbPropertyTypeWorkspaceData } from './property-type-workspace.modal-token.js'; import type { UmbPropertyDatasetContext, UmbPropertyValueData } from '@umbraco-cms/backoffice/property'; @@ -20,7 +21,6 @@ import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/cont import { UmbId } from '@umbraco-cms/backoffice/id'; import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; import { UmbValidationContext } from '@umbraco-cms/backoffice/validation'; -import { UmbDataPathPropertyTypeQuery } from '../utils/index.js'; export class UmbPropertyTypeWorkspaceContext extends UmbSubmittableWorkspaceContextBase diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/resource.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/resource.controller.ts index f37ce13fdf..57d27808f8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/resource.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/resource.controller.ts @@ -50,8 +50,9 @@ export class UmbResourceController extends UmbControllerBase { * Wrap the {tryExecute} function in a try/catch block and return the result. * If the executor function throws an error, then show the details in a notification. * @param _options + * @param options */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars + async tryExecuteAndNotify(options?: UmbNotificationOptions): Promise> { const { data, error } = await UmbResourceController.tryExecute(this.#promise); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index 62d164d18b..0de3dde5ea 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -230,7 +230,7 @@ export type UmbSorterConfig = Partial, 'ignorerSelector' | 'containerSelector' | 'identifier'>>; /** - + * @class UmbSorterController * @implements {UmbControllerInterface} * @description This controller can make user able to sort items. diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file.repository.ts index 772ec5c182..154de953c6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file.repository.ts @@ -25,6 +25,7 @@ export class UmbTemporaryFileRepository extends UmbRepositoryBase { * @param {string} id * @param {File} file * @param onProgress + * @param abortSignal * @returns {*} * @memberof UmbTemporaryFileRepository */ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation-path-translation/validation-path-translation.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation-path-translation/validation-path-translation.controller.ts index 1cf11eedf3..6f1ffa4bdc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation-path-translation/validation-path-translation.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation-path-translation/validation-path-translation.controller.ts @@ -1,7 +1,7 @@ -import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import { type ClassConstructor } from '@umbraco-cms/backoffice/extension-api'; import type { UmbValidationMessage } from '../../context/validation-messages.manager.js'; import type { UmbValidationPathTranslator } from './types.js'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { ClassConstructor } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export type UmbValidationTranslationControllerArgs = { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation-path-translation/validation-property-path-translation.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation-path-translation/validation-property-path-translation.controller.ts index f1e5aa9f5d..e3fd51c0f3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation-path-translation/validation-property-path-translation.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation-path-translation/validation-property-path-translation.controller.ts @@ -1,14 +1,17 @@ +import { umbQueryMapperForJsonPaths } from '../../utils/query-mapper-json-paths.function.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbPropertyValueDataPotentiallyWithEditorAlias } from '@umbraco-cms/backoffice/property'; -import { umbQueryMapperForJsonPaths } from '../../utils/query-mapper-json-paths.function.js'; // Write interface that can be handed to the API for the Host, so each path Translator can communicate back to the host here. For translating inner values. export class UmbValidationPropertyPathTranslationController extends UmbControllerBase { /** * translates the property data. * @param {UmbPropertyValueDataPotentiallyWithEditorAlias} property - The property data. + * @param paths + * @param data + * @param queryConstructor * @returns {Promise} - A promise that resolves to the cloned property data. */ async translateProperties( diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts index e9e4d3721f..6e497a22e7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts @@ -3,13 +3,13 @@ import type { UmbValidationMessageTranslator } from '../translators/index.js'; import { GetValueByJsonPath } from '../utils/json-path.function.js'; import { UMB_VALIDATION_CONTEXT } from '../context/validation.context-token.js'; import { type UmbValidationMessage, UmbValidationMessagesManager } from '../context/validation-messages.manager.js'; +import { ReplaceStartOfPath } from '../utils/replace-start-of-path.function.js'; +import type { UmbVariantId } from '../../variant/variant-id.class.js'; +import { UmbDeprecation } from '../../utils/deprecation/deprecation.js'; import type { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api'; import { type UmbClassInterface, UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; -import { ReplaceStartOfPath } from '../utils/replace-start-of-path.function.js'; -import type { UmbVariantId } from '../../variant/variant-id.class.js'; -import { UmbDeprecation } from '../../utils/deprecation/deprecation.js'; const Regex = /@\.culture == ('[^']*'|null) *&& *@\.segment == ('[^']*'|null)/g; @@ -31,12 +31,14 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal // Local version of the data send to the server, only use-case is for translation. #translationData = new UmbObjectState(undefined); /** + * @param path * @deprecated Use extension type 'propertyValidationPathTranslator' instead. Will be removed in v.17 */ translationDataOf(path: string): any { return this.#translationData.asObservablePart((data) => GetValueByJsonPath(data, path)); } /** + * @param data * @deprecated Use extension type 'propertyValidationPathTranslator' instead. Will be removed in v.17 */ setTranslationData(data: any): void { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/translators/variant-values-validation-path-translator.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/translators/variant-values-validation-path-translator.controller.ts index 529fa97a6c..ab4322d932 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/translators/variant-values-validation-path-translator.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/translators/variant-values-validation-path-translator.controller.ts @@ -1,6 +1,6 @@ -import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; import { UmbDataPathPropertyValueQuery } from '../utils/data-path-property-value-query.function.js'; import { UmbAbstractArrayValidationPathTranslator } from './abstract-array-path-translator.controller.js'; +import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export class UmbVariantValuesValidationPathTranslator extends UmbAbstractArrayValidationPathTranslator { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/translators/variants-validation-path-translator.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/translators/variants-validation-path-translator.controller.ts index f61b616187..4c3dfcae8a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/translators/variants-validation-path-translator.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/translators/variants-validation-path-translator.controller.ts @@ -1,6 +1,6 @@ -import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; import { UmbDataPathVariantQuery } from '../utils/data-path-variant-query.function.js'; import { UmbAbstractArrayValidationPathTranslator } from './abstract-array-path-translator.controller.js'; +import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export class UmbVariantsValidationPathTranslator extends UmbAbstractArrayValidationPathTranslator { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/query-mapper-json-paths.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/query-mapper-json-paths.function.ts index 16d75e6ebd..3b444a1d3c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/query-mapper-json-paths.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/query-mapper-json-paths.function.ts @@ -6,6 +6,10 @@ import { umbScopeMapperForJsonPaths } from './scope-mapper-json-paths.function.j * @param {Array} paths - the JSON paths to map. * @param {string} scopePath - the JSON path to scope the mapping to. * @param {(Array)} scopedMapper - Map function which receives the paths in the scope and returns the resolved paths. + * @param scopePaths + * @param scopeData + * @param queryConstructor + * @param mapper * @returns {string} - the paths, including the once mapped by the scoped mapper. Notice the order is kept. */ export async function umbQueryMapperForJsonPaths( diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-object-compare.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-object-compare.function.ts index 546667db9d..aeeefb96a4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-object-compare.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-object-compare.function.ts @@ -1,5 +1,10 @@ import type { UmbObjectWithVariantProperties } from './types.js'; +/** + * + * @param a + * @param b + */ export function umbVariantObjectCompare(a: UmbObjectWithVariantProperties, b: UmbObjectWithVariantProperties): boolean { return a.culture === b.culture && a.segment === b.segment; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new.condition.ts index 771020c5c3..3519d57fef 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new.condition.ts @@ -34,4 +34,4 @@ export const manifest: UmbExtensionManifest = { name: 'Workspace Entity Is New Condition', alias: UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, api: UmbWorkspaceEntityIsNewCondition, -}; \ No newline at end of file +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/property-editors/document-type-picker/property-editor-ui-document-type-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/property-editors/document-type-picker/property-editor-ui-document-type-picker.element.ts index 4b1ada0c6e..52a044deec 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/property-editors/document-type-picker/property-editor-ui-document-type-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/property-editors/document-type-picker/property-editor-ui-document-type-picker.element.ts @@ -1,5 +1,5 @@ -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputDocumentTypeElement } from '../../components/input-document-type/input-document-type.element.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/document-type-structure.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/document-type-structure.server.data-source.ts index 7fe5488195..c00b8faf0b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/document-type-structure.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/document-type-structure.server.data-source.ts @@ -7,7 +7,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; /** * - + * @class UmbDocumentTypeStructureServerDataSource * @augments {UmbContentTypeStructureServerDataSourceBase} */ @@ -23,7 +23,10 @@ export class UmbDocumentTypeStructureServerDataSource extends UmbContentTypeStru const getAllowedChildrenOf = (unique: string | null, parentContentUnique: string | null) => { if (unique) { // eslint-disable-next-line local-rules/no-direct-api-import - return DocumentTypeService.getDocumentTypeByIdAllowedChildren({ id: unique, parentContentKey: parentContentUnique ?? undefined }); + return DocumentTypeService.getDocumentTypeByIdAllowedChildren({ + id: unique, + parentContentKey: parentContentUnique ?? undefined, + }); } else { // eslint-disable-next-line local-rules/no-direct-api-import return DocumentTypeService.getDocumentTypeAllowedAtRoot({}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts index 5a20decc39..567d59739c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts @@ -1,6 +1,6 @@ -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputDocumentElement } from '../../components/input-document/input-document.element.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts index f5f84f3290..6fb46c3812 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.element.ts @@ -84,7 +84,6 @@ export class UmbDocumentPublishWithDescendantsModalElement extends UmbModalBaseE } async #submit() { - this.value = { selection: this.#selectionManager.getSelection(), includeUnpublishedDescendants: this.#includeUnpublishedDescendants, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.repository.ts index 7599d8a763..f0df4391b9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.repository.ts @@ -73,11 +73,7 @@ export class UmbDocumentPublishingRepository extends UmbRepositoryBase { * @param includeUnpublishedDescendants * @memberof UmbDocumentPublishingRepository */ - async publishWithDescendants( - id: string, - variantIds: Array, - includeUnpublishedDescendants: boolean, - ) { + async publishWithDescendants(id: string, variantIds: Array, includeUnpublishedDescendants: boolean) { if (!id) throw new Error('id is missing'); if (!variantIds) throw new Error('variant IDs are missing'); await this.#init; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/workspace-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/workspace-action/manifests.ts index 99fb381eaf..dec984b1c6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/workspace-action/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/workspace-action/manifests.ts @@ -1,9 +1,9 @@ -import { UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS, UMB_USER_PERMISSION_DOCUMENT_PUBLISH, UMB_USER_PERMISSION_DOCUMENT_UPDATE, } from '../../../user-permissions/constants.js'; +import { UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; export const manifests: Array = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts index 70ee93541f..5d67883062 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/workspace-context/document-publishing.workspace-context.ts @@ -172,6 +172,7 @@ export class UmbDocumentPublishingWorkspaceContext extends UmbContextBase { async processValue(value: undefined | UmbMarkdownPropertyEditorUiValue, config: UmbPropertyEditorConfig) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts index 2036e6da36..52d4636371 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts @@ -70,6 +70,7 @@ export class UmbDropzoneManager extends UmbControllerBase { } /** + * @param isAllowed * @deprecated Not used anymore; this method will be removed in Umbraco 17. */ public setIsFoldersAllowed(isAllowed: boolean) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/property-editors/media-type-picker/property-editor-ui-media-type-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/property-editors/media-type-picker/property-editor-ui-media-type-picker.element.ts index 63b9d3a847..1b8808a1b3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/property-editors/media-type-picker/property-editor-ui-media-type-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/property-editors/media-type-picker/property-editor-ui-media-type-picker.element.ts @@ -1,5 +1,5 @@ -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputMediaTypeElement } from '../../components/index.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts index d2384cdde3..71758ace60 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts @@ -29,7 +29,10 @@ export class UmbMediaTypeStructureServerDataSource extends UmbContentTypeStructu const getAllowedChildrenOf = (unique: string | null, parentContentUnique: string | null) => { if (unique) { // eslint-disable-next-line local-rules/no-direct-api-import - return MediaTypeService.getMediaTypeByIdAllowedChildren({ id: unique, parentContentKey: parentContentUnique ?? undefined }); + return MediaTypeService.getMediaTypeByIdAllowedChildren({ + id: unique, + parentContentKey: parentContentUnique ?? undefined, + }); } else { // eslint-disable-next-line local-rules/no-direct-api-import return MediaTypeService.getMediaTypeAllowedAtRoot({}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts index e4a213b940..e5a5f5692a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts @@ -1,8 +1,8 @@ import type { UmbImageCropperPropertyEditorValue, UmbInputImageCropperElement } from '../../components/index.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; -import { - type UmbPropertyEditorUiElement, - type UmbPropertyEditorConfigCollection, +import type { + UmbPropertyEditorUiElement, + UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts index 6b052f1293..d69c9262a7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts @@ -1,5 +1,5 @@ -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputMediaElement } from '../../components/index.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.element.ts index a506793085..d19c729f58 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.element.ts @@ -1,9 +1,9 @@ import type { UmbInputUploadFieldElement } from '../../components/input-upload-field/input-upload-field.element.js'; import type { MediaValueType } from './types.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; -import { - type UmbPropertyEditorUiElement, - type UmbPropertyEditorConfigCollection, +import type { + UmbPropertyEditorUiElement, + UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/member-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/member-repository-base.ts index 74257955e1..5f02ba07dd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/member-repository-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/member-repository-base.ts @@ -1,7 +1,7 @@ -import type { UmbMemberDetailStore } from './detail/member-detail.store.js'; -import { UMB_MEMBER_DETAIL_STORE_CONTEXT } from './detail/member-detail.store.context-token.js'; import type { UmbMemberItemStore } from '../item/repository/member-item.store.js'; import { UMB_MEMBER_ITEM_STORE_CONTEXT } from '../item/repository/member-item.store.context-token.js'; +import type { UmbMemberDetailStore } from './detail/member-detail.store.js'; +import { UMB_MEMBER_DETAIL_STORE_CONTEXT } from './detail/member-detail.store.context-token.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbNotificationContext } from '@umbraco-cms/backoffice/notification'; import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/models-builder/models-builder-dashboard.element.ts b/src/Umbraco.Web.UI.Client/src/packages/models-builder/models-builder-dashboard.element.ts index 89fe8d2de9..0a4ebd0dc8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/models-builder/models-builder-dashboard.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/models-builder/models-builder-dashboard.element.ts @@ -63,8 +63,8 @@ export class UmbModelsBuilderDashboardElement extends UmbLitElement { look="secondary" label="Reload" @click="${this._onDashboardReload}"> - Reload - + Reload +

Version: ${this._modelsBuilder?.version}

ModelsBuilder is enabled with the following configuration:

diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/Umbraco.ColorPicker.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/Umbraco.ColorPicker.ts index f4d632bbe1..2bde8c1c33 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/Umbraco.ColorPicker.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/Umbraco.ColorPicker.ts @@ -11,8 +11,7 @@ export const manifest: ManifestPropertyEditorSchema = { { alias: 'useLabel', label: '#colorPickerConfigurations_showLabelTitle', - description: - '{umbLocalize: colorPickerConfigurations_showLabelDescription}', + description: '{umbLocalize: colorPickerConfigurations_showLabelDescription}', propertyEditorUiAlias: 'Umb.PropertyEditorUi.Toggle', }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.element.ts index e5f347edfa..862779ef07 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.element.ts @@ -1,8 +1,8 @@ import type { UmbContentPickerSource } from '../../types.js'; import type { UmbInputContentPickerSourceElement } from './input-content-picker-source.element.js'; -import { - type UmbPropertyEditorUiElement, - type UmbPropertyEditorConfigCollection, +import type { + UmbPropertyEditorUiElement, + UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.element.ts index 7063e24206..555b1767c7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/overlay-size/property-editor-ui-overlay-size.element.ts @@ -1,7 +1,7 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; -import { - type UmbPropertyEditorUiElement, - type UmbPropertyEditorConfigCollection, +import type { + UmbPropertyEditorUiElement, + UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UUIModalSidebarSize, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/slider/property-editor-ui-slider.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/slider/property-editor-ui-slider.element.ts index 1179bbae9d..83c6e64606 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/slider/property-editor-ui-slider.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/slider/property-editor-ui-slider.element.ts @@ -1,5 +1,5 @@ -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbSliderPropertyEditorUiValue } from './types.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputSliderElement } from '@umbraco-cms/backoffice/components'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.element.ts index 191669cc55..ae0ab74bcf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/text-box/property-editor-ui-text-box.element.ts @@ -1,8 +1,8 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, state, ifDefined, property } from '@umbraco-cms/backoffice/external/lit'; -import { - type UmbPropertyEditorUiElement, - type UmbPropertyEditorConfigCollection, +import type { + UmbPropertyEditorUiElement, + UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UUIInputElement } from '@umbraco-cms/backoffice/external/uui'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/toggle/property-editor-ui-toggle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/toggle/property-editor-ui-toggle.element.ts index 1206c1f92f..3d18b7dd79 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/toggle/property-editor-ui-toggle.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/toggle/property-editor-ui-toggle.element.ts @@ -1,5 +1,5 @@ -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbTogglePropertyEditorUiValue } from './types.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputToggleElement } from '@umbraco-cms/backoffice/components'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/value-type/property-editor-ui-value-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/value-type/property-editor-ui-value-type.element.ts index 4886542e13..49dd5b2455 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/value-type/property-editor-ui-value-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/value-type/property-editor-ui-value-type.element.ts @@ -1,8 +1,8 @@ import { html, customElement, property, state, query } from '@umbraco-cms/backoffice/external/lit'; import type { UUISelectElement, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; -import { - type UmbPropertyEditorUiElement, - type UmbPropertyEditorConfigCollection, +import type { + UmbPropertyEditorUiElement, + UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/publish-cache/dashboard-published-status.element.ts b/src/Umbraco.Web.UI.Client/src/packages/publish-cache/dashboard-published-status.element.ts index f05a6bf488..c1e77a4a08 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/publish-cache/dashboard-published-status.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/publish-cache/dashboard-published-status.element.ts @@ -39,7 +39,6 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement { // Rebuild private async _rebuildDatabaseCache() { - this._buttonStateRebuild = 'waiting'; const { error } = await tryExecuteAndNotify(this, PublishedCacheService.postPublishedCacheRebuild()); if (error) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/validation/rte-validation-property-path-translator.api.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/validation/rte-validation-property-path-translator.api.ts index f8f608422a..639e68b5f4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/validation/rte-validation-property-path-translator.api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/validation/rte-validation-property-path-translator.api.ts @@ -1,5 +1,5 @@ -import type { UmbPropertyValueDataPotentiallyWithEditorAlias } from '@umbraco-cms/backoffice/property'; import type { UmbPropertyEditorRteValueType } from '../types.js'; +import type { UmbPropertyValueDataPotentiallyWithEditorAlias } from '@umbraco-cms/backoffice/property'; import { UmbBlockEditorValidationPropertyPathTranslatorBase } from '@umbraco-cms/backoffice/block'; export class UmbRteValidationPropertyPathTranslator extends UmbBlockEditorValidationPropertyPathTranslatorBase { diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.element.ts index ab402df21d..81cfbda18d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.element.ts @@ -1,7 +1,7 @@ import type { UmbInputStaticFileElement } from '../../components/index.js'; -import { - type UmbPropertyEditorUiElement, - type UmbPropertyEditorConfigCollection, +import type { + UmbPropertyEditorUiElement, + UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.element.ts index 2deede67d5..b0d1ef864f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tags/property-editors/tags/property-editor-ui-tags.element.ts @@ -1,9 +1,9 @@ import type { UmbTagsInputElement } from '../../components/tags-input/tags-input.element.js'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { - type UmbPropertyEditorUiElement, - type UmbPropertyEditorConfigCollection, +import type { + UmbPropertyEditorUiElement, + UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/modals/query-builder/query-builder-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/modals/query-builder/query-builder-modal.element.ts index 2ecf70b819..e1b5f53152 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/modals/query-builder/query-builder-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/modals/query-builder/query-builder-modal.element.ts @@ -135,21 +135,26 @@ export default class UmbTemplateQueryBuilderModalElement extends UmbModalBaseEle #setSortProperty(event: Event) { const target = event.target as UUIComboboxListElement; - this.#setSort(target.value as string, this._queryRequest.sort?.direction as SortOrder ?? this._defaultSortDirection); + this.#setSort( + target.value as string, + (this._queryRequest.sort?.direction as SortOrder) ?? this._defaultSortDirection, + ); } #setSortDirection() { const direction = this._queryRequest.sort?.direction - ? this._queryRequest.sort.direction === SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending + ? this._queryRequest.sort.direction === SortOrder.Ascending + ? SortOrder.Descending + : SortOrder.Ascending : this._defaultSortDirection; - this.#setSort(this._queryRequest.sort?.propertyAlias ?? "", direction); + this.#setSort(this._queryRequest.sort?.propertyAlias ?? '', direction); } #setSort(propertyAlias: string, direction: SortOrder) { this.#updateQueryRequest({ sort: { propertyAlias, - direction + direction, }, }); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/manifests.ts index a81acb72c3..51a61448ef 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/manifests.ts @@ -1,7 +1,4 @@ import { manifests as anchorModal } from './anchor-modal/manifests.js'; import { manifests as characterMap } from './character-map/manifests.js'; -export const manifests: Array = [ - ...anchorModal, - ...characterMap, -]; +export const manifests: Array = [...anchorModal, ...characterMap]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/types.ts index 4482deba59..fc7ef64ca7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/collection/types.ts @@ -1,4 +1,4 @@ export interface UmbWebhookCollectionFilterModel { skip?: number; take?: number; -} \ No newline at end of file +} From 94f0add4d90cdb5a76db8702b64c572179c09e11 Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Thu, 27 Mar 2025 10:42:13 +0700 Subject: [PATCH 13/23] V15 Added acceptance tests for data type default configuration (#18740) * Added tests for Approved Color default configuration * Updated tests for Approved Color configuration * Added tests for ChecklboxList configuration * Added tests for Data type default configuration - part 1 * Added tests for data type configuration and updated tests due to test helper changes * Added more steps to verify the default configuration * Added tests for the default configuration and refactoring code * Added steps to verify the TinyMCE default configuration * Bumped version * Fixed tests due to test helper changes * Make all Data Type tests run in the pipeline * Updated assertion steps * Fixed format * Bumped version * Bumped version * Comment failing tests * Reverted npm command --- .../package-lock.json | 8 +- .../Umbraco.Tests.AcceptanceTest/package.json | 2 +- .../Content/ContentWithApprovedColor.spec.ts | 2 +- .../DataType/ApprovedColor.spec.ts | 98 ++--- .../DataType/CheckboxList.spec.ts | 86 ++-- .../DataType/ContentPicker.spec.ts | 75 ++-- .../DefaultConfig/DataType/DataType.spec.ts | 9 +- .../DefaultConfig/DataType/DatePicker.spec.ts | 114 ++--- .../DefaultConfig/DataType/Dropdown.spec.ts | 85 ++-- .../DataType/ImageCropper.spec.ts | 120 ++--- .../DefaultConfig/DataType/Label.spec.ts | 75 ++-- .../DefaultConfig/DataType/ListView.spec.ts | 414 +++++++----------- .../DataType/MediaPicker.spec.ts | 394 ++++++++--------- .../DataType/MemberPicker.spec.ts | 22 + .../DataType/MultiUrlPicker.spec.ts | 88 ++-- .../DefaultConfig/DataType/Numeric.spec.ts | 77 ++-- .../DefaultConfig/DataType/Radiobox.spec.ts | 67 +-- .../DataType/RichTextEditor.spec.ts | 70 ++- .../tests/DefaultConfig/DataType/Tags.spec.ts | 50 ++- .../DefaultConfig/DataType/Textarea.spec.ts | 53 ++- .../DefaultConfig/DataType/Textstring.spec.ts | 41 +- .../DefaultConfig/DataType/TinyMCE.spec.ts | 102 ++--- .../DefaultConfig/DataType/Tiptap.spec.ts | 51 +-- .../DefaultConfig/DataType/TrueFalse.spec.ts | 81 ++-- .../DefaultConfig/DataType/Upload.spec.ts | 76 ---- .../DataType/UploadField.spec.ts | 73 +++ .../RenderingContentWithApprovedColor.spec.ts | 2 +- 27 files changed, 1082 insertions(+), 1253 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MemberPicker.spec.ts delete mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Upload.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/UploadField.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index bf03842d46..3454d8ffe7 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.31", - "@umbraco/playwright-testhelpers": "^15.0.39", + "@umbraco/playwright-testhelpers": "^15.0.40", "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.39", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.39.tgz", - "integrity": "sha512-dNl+P5LOW4CZrlzt7TnXOKUDeHI3juN/BfG9b0P/selpiFPrseH1HrB0myJVmPfuq4KCa+490VTu46qXHGwGLw==", + "version": "15.0.40", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.40.tgz", + "integrity": "sha512-dxXCCYeUH0rlASdHHNu8gQQrhK52gxGcwb/K1BlXFsr7Z7dz1U5eYMPUiVjDVg6LNCbqmQ/tmZqoAZLU5zDzIw==", "dependencies": { "@umbraco/json-models-builders": "2.0.31", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 82bbc68831..63aafd3813 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.31", - "@umbraco/playwright-testhelpers": "^15.0.39", + "@umbraco/playwright-testhelpers": "^15.0.40", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithApprovedColor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithApprovedColor.spec.ts index cfe7aaeb15..a50279788f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithApprovedColor.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithApprovedColor.spec.ts @@ -63,7 +63,7 @@ test('can create content with the custom approved color data type', async ({umbr const customDataTypeName = 'CustomApprovedColor'; const colorValue = 'd73737'; const colorLabel = 'Test Label'; - const customDataTypeId = await umbracoApi.dataType.createApprovedColorDataTypeWithOneItem(customDataTypeName, colorLabel, colorValue); + const customDataTypeId = await umbracoApi.dataType.createDefaultApprovedColorDataTypeWithOneItem(customDataTypeName, colorLabel, colorValue); const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); await umbracoUi.content.goToSection(ConstantHelper.sections.content); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ApprovedColor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ApprovedColor.spec.ts index e853b2afa8..efff81e2f7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ApprovedColor.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ApprovedColor.spec.ts @@ -1,102 +1,78 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'Approved Color'; -let dataTypeDefaultData = null; -let dataTypeData = null; -const colorValue = 'ffffff'; -const colorLabel = ''; +const customDataTypeName = 'Custom Approved Color'; +const editorAlias = 'Umbraco.ColorPicker'; +const editorUiAlias = 'Umb.PropertyEditorUi.ColorPicker'; +const colorValue = '9c2121'; +const colorLabel = 'red'; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can include label', async ({umbracoApi, umbracoUi}) => { // Arrange - const expectedDataTypeValues = [ - { - "alias": "useLabel", - "value": true - } - ]; - // Remove all existing values - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = []; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + await umbracoApi.dataType.createDefaultApprovedColorDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.clickIncludeLabelsToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'useLabel', true)).toBeTruthy(); }); test('can add color', async ({umbracoApi, umbracoUi}) => { // Arrange - const expectedDataTypeValues = [ - { - "alias": "items", - "value": [ - { - "value": colorValue, - "label": colorLabel - } - ] - } - ]; - // Remove all existing values - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = []; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + await umbracoApi.dataType.createDefaultApprovedColorDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.addColor(colorValue); await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesApprovedColorHaveColor(customDataTypeName, colorValue)).toBeTruthy(); }); test('can remove color', async ({umbracoApi, umbracoUi}) => { // Arrange - const removedDataTypeValues = [ - { - "alias": "items", - "value": [ - { - "value": colorValue, - "label": colorLabel - } - ] - } - ]; - // Remove all existing values and add a color to remove - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = removedDataTypeValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + await umbracoApi.dataType.createApprovedColorDataTypeWithOneItem(customDataTypeName, colorLabel, colorValue); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.removeColorByValue(colorValue); await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual([]); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesApprovedColorHaveColor(customDataTypeName, colorValue)).toBeFalsy();; }); + +test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(dataTypeName); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.approvedColorSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.approvedColorSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(dataTypeDefaultData.values).toEqual([]); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'useLabel')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'items')).toBeFalsy(); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/CheckboxList.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/CheckboxList.spec.ts index 99ad3e1cf2..0e9cd8ed77 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/CheckboxList.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/CheckboxList.spec.ts @@ -1,36 +1,26 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'Checkbox list'; -let dataTypeDefaultData = null; -let dataTypeData = null; +const customDataTypeName = 'Custom Checkbox List'; +const editorAlias = 'Umbraco.CheckBoxList'; +const editorUiAlias = 'Umb.PropertyEditorUi.CheckBoxList'; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can add option', async ({umbracoApi, umbracoUi}) => { // Arrange const optionName = 'Test option'; - const expectedDataTypeValues = [ - { - "alias": "items", - "value": [optionName] - } - ]; - // Remove all existing options - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = []; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + await umbracoApi.dataType.createCheckboxListDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.clickAddOptionButton(); @@ -38,61 +28,53 @@ test('can add option', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'items', [optionName])).toBeTruthy(); }); test('can remove option', async ({umbracoApi, umbracoUi}) => { // Arrange const removedOptionName = 'Removed Option'; - const removedOptionValues = [ - { - "alias": "items", - "value": [removedOptionName] - } - ]; - // Remove all existing options and add an option to remove - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = removedOptionValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + await umbracoApi.dataType.createCheckboxListDataType(customDataTypeName, [removedOptionName]); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.removeOptionByName(removedOptionName); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual([]); + const customDataTypeData = await umbracoApi.dataType.getByName(customDataTypeName); + expect(customDataTypeData.values).toEqual([]); }); test('can update option', async ({umbracoApi, umbracoUi}) => { // Arrange const optionName = 'Test option'; const updatedOptionName = 'Updated option'; - const optionValues = [ - { - "alias": "items", - "value": [optionName] - } - ]; - const expectedOptionValues = [ - { - "alias": "items", - "value": [updatedOptionName] - } - ]; - // Remove all existing options and add an option to update - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = optionValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + await umbracoApi.dataType.createCheckboxListDataType(customDataTypeName, [optionName]); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterOptionName(updatedOptionName); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual(expectedOptionValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'items', [updatedOptionName])).toBeTruthy(); }); + +test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(dataTypeName); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.checkboxListSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.checkboxListSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName) + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(dataTypeDefaultData.values).toEqual([]); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'items')).toBeFalsy(); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ContentPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ContentPicker.spec.ts index 92819d3c37..4f34c9cf2c 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ContentPicker.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ContentPicker.spec.ts @@ -1,37 +1,33 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'Content Picker'; -let dataTypeDefaultData = null; -let dataTypeData = null; +const customDataTypeName = 'Custom Content Picker'; +const editorAlias = 'Umbraco.ContentPicker'; +const editorUiAlias = 'Umb.PropertyEditorUi.DocumentPicker'; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can ignore user start nodes', async ({umbracoApi, umbracoUi}) => { // Arrange - const expectedDataTypeValues = { - "alias": "ignoreUserStartNodes", - "value": true - }; - await umbracoUi.dataType.goToDataType(dataTypeName); + await umbracoApi.dataType.createDefaultContentPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.clickIgnoreUserStartNodesToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'ignoreUserStartNodes', true)).toBeTruthy(); }); test('can add start node', async ({umbracoApi, umbracoUi}) => { @@ -39,17 +35,12 @@ test('can add start node', async ({umbracoApi, umbracoUi}) => { // Create content const documentTypeName = 'TestDocumentType'; const contentName = 'TestStartNode'; - await umbracoApi.documentType.ensureNameNotExists(documentTypeName); const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); - await umbracoApi.document.ensureNameNotExists(contentName); const contentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); expect(await umbracoApi.document.doesExist(contentId)).toBeTruthy(); - - const expectedDataTypeValues = { - "alias": "startNodeId", - "value": contentId - }; - await umbracoUi.dataType.goToDataType(dataTypeName); + // Create data type + await umbracoApi.dataType.createDefaultContentPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.clickChooseButton(); @@ -57,8 +48,8 @@ test('can add start node', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'startNodeId', contentId)).toBeTruthy(); // Clean await umbracoApi.document.ensureNameNotExists(contentName); @@ -70,32 +61,38 @@ test('can remove start node', async ({umbracoApi, umbracoUi}) => { // Create content const documentTypeName = 'TestDocumentType'; const contentName = 'TestStartNode'; - await umbracoApi.documentType.ensureNameNotExists(documentTypeName); const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); - await umbracoApi.document.ensureNameNotExists(contentName); const contentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); expect(await umbracoApi.document.doesExist(contentId)).toBeTruthy(); - - const removedDataTypeValues = [{ - "alias": "startNodeId", - "value": contentId - }]; - - // Remove all existing values and add a start node to remove - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = removedDataTypeValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + // Create data type + await umbracoApi.dataType.createContentPickerDataTypeWithStartNode(customDataTypeName, contentId); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.removeContentStartNode(contentName); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual([]); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + const customDataTypeData = await umbracoApi.dataType.getByName(customDataTypeName); + expect(customDataTypeData.values).toEqual([]); // Clean await umbracoApi.document.ensureNameNotExists(contentName); await umbracoApi.documentType.ensureNameNotExists(documentTypeName); }); + +test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(dataTypeName); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.contentPickerSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.contentPickerSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(dataTypeDefaultData.values).toEqual([]); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts index f32dd850bf..c4d3d3fda1 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts @@ -103,10 +103,6 @@ test('cannot create a data type without selecting the property editor', {tag: '@ test('can change settings', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { // Arrange const maxCharsValue = 126; - const expectedDataTypeValues = { - "alias": "maxChars", - "value": maxCharsValue - }; await umbracoApi.dataType.createTextstringDataType(dataTypeName); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy(); @@ -117,6 +113,5 @@ test('can change settings', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); -}); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'maxChars', maxCharsValue)).toBeTruthy(); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DatePicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DatePicker.spec.ts index 3e71aa59c9..06052af6b6 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DatePicker.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DatePicker.spec.ts @@ -1,75 +1,49 @@ -import { test } from "@umbraco/playwright-testhelpers"; +import { ConstantHelper, NotificationConstantHelper, test } from "@umbraco/playwright-testhelpers"; import { expect } from "@playwright/test"; -const datePickerTypes = ['Date Picker', 'Date Picker with time']; +const editorAlias = 'Umbraco.DateTime'; +const editorUiAlias = 'Umb.PropertyEditorUi.DatePicker'; +const datePickerTypes = [ + {type: 'Date Picker', format: 'YYYY-MM-DD'}, + {type: 'Date Picker with time', format: 'YYYY-MM-DD HH:mm:ss'} +]; +const customDataTypeName = 'Custom DateTime'; + +test.beforeEach(async ({umbracoUi, umbracoApi}) => { + await umbracoUi.goToBackOffice(); + await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); + +test('can update date format', async ({umbracoApi, umbracoUi}) => { + // Arrange + const dateFormatValue = 'DD-MM-YYYY hh:mm:ss'; + await umbracoApi.dataType.createDefaultDateTimeDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); + + // Act + await umbracoUi.dataType.enterDateFormatValue(dateFormatValue); + await umbracoUi.dataType.clickSaveButton(); + + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'format', dateFormatValue)).toBeTruthy(); +}); + for (const datePickerType of datePickerTypes) { - test.describe(`${datePickerType} tests`, () => { - let dataTypeDefaultData = null; - let dataTypeData = null; + test(`the default configuration of ${datePickerType.type} is correct`, async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(datePickerType.type); - test.beforeEach(async ({ umbracoUi, umbracoApi }) => { - await umbracoUi.goToBackOffice(); - await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - dataTypeDefaultData = await umbracoApi.dataType.getByName(datePickerType); - await umbracoUi.dataType.goToDataType(datePickerType); - }); - - test.afterEach(async ({ umbracoApi }) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id,dataTypeDefaultData); - } - }); - - // This test is out-of-date since currently it is impossible to update offset time in front-end - test.skip(`can update offset time`, async ({ umbracoApi, umbracoUi }) => { - // Arrange - const expectedDataTypeValues = - datePickerType === 'Date Picker' - ? [ - { - "alias": "format", - "value": "YYYY-MM-DD", - }, - { - "alias": "offsetTime", - "value": true, - }, - ] - : [ - { - "alias": "format", - "value": "YYYY-MM-DD HH:mm:ss", - }, - { - "alias": "offsetTime", - "value": true, - } - ]; - - // Act - await umbracoUi.dataType.clickOffsetTimeToggle(); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - dataTypeData = await umbracoApi.dataType.getByName(datePickerType); - expect(dataTypeData.values).toEqual(expectedDataTypeValues); - }); - - test('can update date format', async ({umbracoApi, umbracoUi}) => { - // Arrange - const dateFormatValue = - datePickerType === "Date Picker" ? "DD-MM-YYYY" : "DD-MM-YYYY hh:mm:ss"; - const expectedDataTypeValues = { - "alias": "format", - "value": dateFormatValue - }; - // Act - await umbracoUi.dataType.enterDateFormatValue(dateFormatValue); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - dataTypeData = await umbracoApi.dataType.getByName(datePickerType); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.datePickerSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.datePickerSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(datePickerType.type, 'format', datePickerType.format)).toBeTruthy(); }); -} +} \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Dropdown.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Dropdown.spec.ts index 5e7f3586f7..e97bb5db1b 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Dropdown.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Dropdown.spec.ts @@ -1,57 +1,43 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; -const dataTypeName = 'Dropdown'; -let dataTypeDefaultData = null; -let dataTypeData = null; +const customDataTypeName = 'Custom Dropdown'; +const editorAlias = 'Umbraco.DropDown.Flexible'; +const editorUiAlias = 'Umb.PropertyEditorUi.Dropdown'; +const dropdowns = [ + {type: 'Dropdown', multipleChoice: false}, + {type: 'Dropdown multiple', multipleChoice: true} +]; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can enable multiple choice', async ({umbracoApi, umbracoUi}) => { // Arrange - const expectedDataTypeValues = [{ - "alias": "multiple", - "value": true - }]; - // Remove all existing options - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = []; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + await umbracoApi.dataType.createDefaultDropdownDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.clickEnableMultipleChoiceToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'multiple', true)).toBeTruthy(); }); test('can add option', async ({umbracoApi, umbracoUi}) => { // Arrange const optionName = 'Test option'; - const expectedDataTypeValues = [ - { - "alias": "items", - "value": [optionName] - } - ]; - // Remove all existing options - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = []; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + await umbracoApi.dataType.createDefaultDropdownDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.clickAddOptionButton(); @@ -59,30 +45,39 @@ test('can add option', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'items', [optionName])).toBeTruthy(); }); test('can remove option', async ({umbracoApi, umbracoUi}) => { // Arrange const removedOptionName = 'Removed Option'; - const removedOptionValues = [ - { - "alias": "items", - "value": [removedOptionName] - } - ]; - // Remove all existing options and add an option to remove - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = removedOptionValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + await umbracoApi.dataType.createDropdownDataType(customDataTypeName, false, [removedOptionName]); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.removeOptionByName(removedOptionName); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual([]); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'items', [removedOptionName])).toBeFalsy(); }); + +for (const dropdown of dropdowns) { + test(`the default configuration of ${dropdown.type} is correct`, async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(dropdown.type); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.dropdownSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.dropdownSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dropdown.type); + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dropdown.type, 'multiple', dropdown.multipleChoice)).toBeTruthy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dropdown.type, 'items')).toBeFalsy(); + }); +} \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ImageCropper.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ImageCropper.spec.ts index 75c7c47946..6721c974f7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ImageCropper.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ImageCropper.spec.ts @@ -1,41 +1,26 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'Image Cropper'; -let dataTypeDefaultData = null; -let dataTypeData = null; +const editorAlias = 'Umbraco.ImageCropper'; +const editorUiAlias = 'Umb.PropertyEditorUi.ImageCropper'; +const customDataTypeName = 'Custom Image Cropper'; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can add crop', async ({umbracoApi, umbracoUi}) => { // Arrange const cropData = ['Test Label', 'Test Alias', 100, 50]; - const expectedDataTypeValues = [{ - "alias": "crops", - "value": [ - { - "label": cropData[0], - "alias": cropData[1], - "width": cropData[2], - "height": cropData[3] - } - ] - }]; - // Remove all existing crops - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = []; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + await umbracoApi.dataType.createDefaultImageCropperDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterCropValues( @@ -48,79 +33,56 @@ test('can add crop', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveCrops(customDataTypeName, cropData[0], cropData[1], cropData[2], cropData[3])).toBeTruthy(); }); test('can edit crop', async ({umbracoApi, umbracoUi}) => { // Arrange - const wrongCropData = ['Wrong Alias', 50, 100]; - const wrongDataTypeValues = [{ - "alias": "crops", - "value": [ - { - "alias": wrongCropData[0], - "width": wrongCropData[1], - "height": wrongCropData[2] - } - ] - }]; - const updatedCropData = ['Updated Label', 'Updated Test Alias', 100, 50]; - const expectedDataTypeValues = [{ - "alias": "crops", - "value": [ - { - "label": updatedCropData[0], - "alias": updatedCropData[1], - "width": updatedCropData[2], - "height": updatedCropData[3] - } - ] - }]; - // Remove all existing crops and add a crop to edit - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = wrongDataTypeValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + const cropData = ['Test Label', AliasHelper.toAlias('Test Label'), 100, 50]; + const updatedCropData = ['Updated Label', AliasHelper.toAlias('Updated Label'), 80, 30]; + await umbracoApi.dataType.createImageCropperDataTypeWithOneCrop(customDataTypeName, cropData[0], cropData[2], cropData[3]); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act - await umbracoUi.dataType.editCropByAlias(wrongCropData[0].toString()); - await umbracoUi.dataType.enterCropValues(updatedCropData[0].toString(), updatedCropData[1].toString(), updatedCropData[2].toString(), updatedCropData[3].toString()); + await umbracoUi.dataType.editCropByAlias(cropData[0]); + await umbracoUi.dataType.enterCropValues(updatedCropData[0], updatedCropData[1], updatedCropData[2].toString(), updatedCropData[3].toString()); await umbracoUi.dataType.clickSaveCropButton(); await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveCrops(customDataTypeName, updatedCropData[0], updatedCropData[1], updatedCropData[2], updatedCropData[3])).toBeTruthy(); + expect(await umbracoApi.dataType.doesDataTypeHaveCrops(customDataTypeName, cropData[0], cropData[1], cropData[2], cropData[3])).toBeFalsy(); }); test('can delete crop', async ({umbracoApi, umbracoUi}) => { // Arrange - const wrongCropData = ['Wrong Alias', 50, 100]; - const wrongDataTypeValues = [{ - "alias": "crops", - "value": [ - { - "alias": wrongCropData[0], - "width": wrongCropData[1], - "height": wrongCropData[2] - } - ] - }]; - // Remove all existing crops and add a crop to remove - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = wrongDataTypeValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + const cropData = ['Deleted Alias', AliasHelper.toAlias('Deleted Alias'), 50, 100]; + await umbracoApi.dataType.createImageCropperDataTypeWithOneCrop(customDataTypeName, cropData[0], cropData[2], cropData[3]); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act - await umbracoUi.dataType.removeCropByAlias(wrongCropData[0].toString()); + await umbracoUi.dataType.removeCropByAlias(cropData[0].toString()); await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual([]); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveCrops(customDataTypeName, cropData[0], cropData[1], cropData[2], cropData[3])).toBeFalsy(); }); + +test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(dataTypeName); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.imageCropperSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.imageCropperSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(dataTypeDefaultData.values).toEqual([]); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'crops')).toBeFalsy(); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Label.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Label.spec.ts index 8d352af482..1a4f431321 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Label.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Label.spec.ts @@ -1,41 +1,52 @@ -import { test } from "@umbraco/playwright-testhelpers"; +import { ConstantHelper, NotificationConstantHelper, test } from "@umbraco/playwright-testhelpers"; import { expect } from "@playwright/test"; -const labelTypes = ['Label (bigint)', 'Label (datetime)', 'Label (decimal)', 'Label (integer)', 'Label (string)', 'Label (time)']; -for (const labelType of labelTypes) { - test.describe(`${labelType} tests`, () => { - let dataTypeDefaultData = null; - let dataTypeData = null; +const labelTypes = [ + {type: 'Label (bigint)', dataValueType: 'BIGINT'}, + {type: 'Label (datetime)', dataValueType: 'DATETIME'}, + {type: 'Label (decimal)', dataValueType: 'DECIMAL'}, + {type: 'Label (integer)', dataValueType: 'INT'}, + {type: 'Label (string)', dataValueType: 'STRING'}, + {type: 'Label (time)', dataValueType: 'TIME'} +]; +const editorAlias = 'Umbraco.Label'; +const editorUiAlias = 'Umb.PropertyEditorUi.Label'; +const customDataTypeName = 'Custom Label'; - test.beforeEach(async ({ umbracoUi, umbracoApi }) => { - await umbracoUi.goToBackOffice(); - await umbracoUi.dataType.goToSettingsTreeItem("Data Types"); - dataTypeDefaultData = await umbracoApi.dataType.getByName(labelType); - await umbracoUi.dataType.goToDataType(labelType); - }); +test.beforeEach(async ({umbracoUi, umbracoApi}) => { + await umbracoUi.goToBackOffice(); + await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); - test.afterEach(async ({ umbracoApi }) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id,dataTypeDefaultData); - } - }); +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); - test('can change value type', async ({ umbracoApi, umbracoUi }) => { - // Arrange - const expectedDataTypeValues = [ - { - "alias": "umbracoDataValueType", - "value": "TEXT", - } - ]; +test('can change value type', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.dataType.createDefaultLabelDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Act - await umbracoUi.dataType.changeValueType("Long String"); - await umbracoUi.dataType.clickSaveButton(); + // Act + await umbracoUi.dataType.changeValueType("Long String"); + await umbracoUi.dataType.clickSaveButton(); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(labelType); - expect(dataTypeData.values).toEqual(expectedDataTypeValues); - }); + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'umbracoDataValueType', 'TEXT')).toBeTruthy(); +}); + +for (const label of labelTypes) { + test(`the default configuration of ${label.type} is correct`, async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(label.type); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.labelSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.labelSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(label.type, 'umbracoDataValueType', label.dataValueType)).toBeTruthy(); }); } diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts index 41d7af774e..d87b674623 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts @@ -1,292 +1,198 @@ -import { test } from "@umbraco/playwright-testhelpers"; +import { ConstantHelper, NotificationConstantHelper, test } from "@umbraco/playwright-testhelpers"; import { expect } from "@playwright/test"; -const listViewTypes = ['List View - Content', 'List View - Media']; -for (const listViewType of listViewTypes) { - test.describe(`${listViewType} tests`, () => { - let dataTypeDefaultData = null; - let dataTypeData = null; +const listViewTypes = [ + {type: 'List View - Content', collectionViewGird: 'Umb.CollectionView.Document.Grid', collectionViewList: 'Umb.CollectionView.Document.Table'}, + {type: 'List View - Media', collectionViewGird: 'Umb.CollectionView.Media.Grid', collectionViewList: 'Umb.CollectionView.Media.Table'} +]; +const editorAlias = 'Umbraco.ListView'; +const editorUiAlias = 'Umb.PropertyEditorUi.Collection'; +const customDataTypeName = 'Custom List View'; - test.beforeEach(async ({ umbracoUi, umbracoApi }) => { - await umbracoUi.goToBackOffice(); - await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - dataTypeDefaultData = await umbracoApi.dataType.getByName(listViewType); - }); +test.beforeEach(async ({umbracoUi, umbracoApi}) => { + await umbracoUi.goToBackOffice(); + await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); - test.afterEach(async ({ umbracoApi }) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id,dataTypeDefaultData); - } - }); +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); - test('can update page size', async ({umbracoApi, umbracoUi}) => { - // Arrange - const pageSizeValue = 5; - const expectedDataTypeValues = { - "alias": "pageSize", - "value": pageSizeValue - }; +test('can update page size', async ({umbracoApi, umbracoUi}) => { + // Arrange + const pageSizeValue = 5; + await umbracoApi.dataType.createListViewContentDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Act - await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.enterPageSizeValue(pageSizeValue.toString()); - await umbracoUi.dataType.clickSaveButton(); + // Act + await umbracoUi.dataType.enterPageSizeValue(pageSizeValue.toString()); + await umbracoUi.dataType.clickSaveButton(); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'pageSize', pageSizeValue)).toBeTruthy(); +}); - test('can update order direction', async ({umbracoApi, umbracoUi}) => { - // Arrange - const isAscending = listViewType == 'List View - Members' ? false : true; - const orderDirectionValue = isAscending ? 'asc' : 'desc'; - const expectedDataTypeValues = { - "alias": "orderDirection", - "value": orderDirectionValue - }; +test('can update order direction', async ({umbracoApi, umbracoUi}) => { + // Arrange + const orderDirectionValue = 'asc'; + await umbracoApi.dataType.createListViewContentDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Act - await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.chooseOrderDirection(isAscending); - await umbracoUi.dataType.clickSaveButton(); + // Act + await umbracoUi.dataType.chooseOrderDirection(true); + await umbracoUi.dataType.clickSaveButton(); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'orderDirection', orderDirectionValue)).toBeTruthy(); +}); - test('can add column displayed', async ({umbracoApi, umbracoUi}) => { - // Arrange - let columnData: string[]; - if (listViewType === 'List View - Media') { - columnData = ['Document Type', 'TestDocumentType', 'sortOrder', 'Sort']; - await umbracoApi.documentType.ensureNameNotExists(columnData[1]); - await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(columnData[1]); - } else { - columnData = ['Media Type', 'Audio', 'sortOrder', 'Sort']; - } +test('can add column displayed', async ({umbracoApi, umbracoUi}) => { + // Arrange + const columnData = ['Document Type', 'TestDocumentType', 'sortOrder', 'Sort']; + await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(columnData[1]); + await umbracoApi.dataType.createListViewContentDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - const expectedIncludePropertiesValues = { - "alias": columnData[2], - "header": columnData[3], - "isSystem": 1 - }; + // Act + await umbracoUi.dataType.addColumnDisplayed(columnData[0], columnData[1], columnData[2]); + await umbracoUi.dataType.clickSaveButton(); - // Act - await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.addColumnDisplayed(columnData[0], columnData[1], columnData[2]); - await umbracoUi.dataType.clickSaveButton(); + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesListViewHaveProperty(customDataTypeName, columnData[3], columnData[2], 1)).toBeTruthy(); +}); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - const includePropertiesData = dataTypeData.values.find(value => value.alias === "includeProperties"); - expect(includePropertiesData.value).toContainEqual(expectedIncludePropertiesValues); - }); +test('can remove column displayed', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.dataType.createListViewContentDataType(customDataTypeName); + expect(await umbracoApi.dataType.doesListViewHaveProperty(customDataTypeName, 'Last edited', 'updateDate')).toBeTruthy(); + await umbracoUi.dataType.goToDataType(customDataTypeName); - test('can remove column displayed', async ({umbracoApi, umbracoUi}) => { - // Arrange - let columnData: string[]; - if (listViewType === 'List View - Media') { - columnData = ['Document Type', 'TestDocumentType', 'owner', 'Created by']; - await umbracoApi.documentType.ensureNameNotExists(columnData[1]); - await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(columnData[1]); - } else { - columnData = ['Media Type', 'Audio', 'owner', 'Created by']; - } + // Act + await umbracoUi.dataType.removeColumnDisplayed('updateDate'); + await umbracoUi.dataType.clickSaveButton(); - const removedDataTypeValues = [{ - "alias": "includeProperties", - "value": [{ - "alias": columnData[2], - "header": columnData[3], - "isSystem": 1, - }] - }]; + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesListViewHaveProperty(customDataTypeName, 'Last edited', 'updateDate')).toBeFalsy(); +}); - // Remove all existing values and add a column displayed to remove - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - dataTypeData.values = removedDataTypeValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); +test('can add layouts', async ({umbracoApi, umbracoUi}) => { + // Arrange + const layoutName = 'Extension Table Collection View'; + const layoutCollectionView = 'Umb.CollectionView.Extension.Table'; + await umbracoApi.dataType.createListViewContentDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Act - await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.removeColumnDisplayed(columnData[2]); - await umbracoUi.dataType.clickSaveButton(); + // Act + await umbracoUi.dataType.addLayouts(layoutName); + await umbracoUi.dataType.clickSaveButton(); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - expect(dataTypeData.values).toEqual([]); - }); + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesListViewHaveLayout(customDataTypeName, layoutName, 'icon-list', layoutCollectionView)).toBeTruthy(); +}); - test('can add layouts', async ({umbracoApi, umbracoUi}) => { - // Arrange - const layoutName = 'Extension Table Collection View'; - const layoutCollectionView = 'Umb.CollectionView.Extension.Table'; - const expectedIncludePropertiesValues = { - "icon": "icon-list", - "name": layoutName, - "collectionView": layoutCollectionView, - }; +test('can remove layouts', async ({umbracoApi, umbracoUi}) => { + // Arrange + const layoutName = 'List'; + const layoutCollectionView = 'Umb.CollectionView.Document.Table'; + await umbracoApi.dataType.createListViewContentDataType(customDataTypeName); + expect(await umbracoApi.dataType.doesListViewHaveLayout(customDataTypeName, layoutName, 'icon-list', layoutCollectionView)).toBeTruthy(); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Act - await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.addLayouts(layoutName); - await umbracoUi.dataType.clickSaveButton(); + // Act + await umbracoUi.dataType.removeLayouts(layoutCollectionView); + await umbracoUi.dataType.clickSaveButton(); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - const includePropertiesData = dataTypeData.values.find(value => value.alias === "layouts"); - expect(includePropertiesData.value).toContainEqual(expectedIncludePropertiesValues); - }); + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesListViewHaveLayout(customDataTypeName, layoutName, 'icon-list', layoutCollectionView)).toBeFalsy(); +}); - test('can remove layouts', async ({umbracoApi, umbracoUi}) => { - // Arrange - let layoutsData = 'Document Grid Collection View'; - if (listViewType === 'List View - Media') { - layoutsData = 'Media Grid Collection View'; - } +test('can update order by', async ({umbracoApi, umbracoUi}) => { + // Arrange + const orderByValue = 'Last edited'; + await umbracoApi.dataType.createListViewContentDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - const removedDataTypeValues = [{ - "alias": "layouts", - "value": [{ - "icon": "icon-thumbnails-small", - "collectionView": layoutsData, - "isSystem": true, - "name": "Grid", - "selected": true - }] - }]; + // Act + await umbracoUi.dataType.chooseOrderByValue(orderByValue); + await umbracoUi.dataType.clickSaveButton(); - // Remove all existing values and add a layout to remove - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - dataTypeData.values = removedDataTypeValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'orderBy', 'updateDate')).toBeTruthy(); +}); - // Act - await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.removeLayouts(layoutsData); - await umbracoUi.dataType.clickSaveButton(); +test('can update workspace view icon', async ({umbracoApi, umbracoUi}) => { + // Arrange + const iconValue = 'icon-activity'; + await umbracoApi.dataType.createListViewContentDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - expect(dataTypeData.values).toEqual([]); - }); + // Act + await umbracoUi.dataType.clickSelectIconButton(); + await umbracoUi.dataType.chooseWorkspaceViewIconByValue(iconValue); + await umbracoUi.dataType.clickSaveButton(); - test('can update order by', async ({umbracoApi, umbracoUi}) => { - // Arrange - const orderByValue = 'Last edited'; - const expectedDataTypeValues = { - "alias": "orderBy", - "value": "updateDate" - }; + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'icon', iconValue)).toBeTruthy(); +}); - // Act - await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.chooseOrderByValue(orderByValue); - await umbracoUi.dataType.clickSaveButton(); +test('can update workspace view name', async ({umbracoApi, umbracoUi}) => { + // Arrange + const workspaceViewName = 'Test Content Name'; + await umbracoApi.dataType.createListViewContentDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); + // Act + await umbracoUi.dataType.enterWorkspaceViewName(workspaceViewName); + await umbracoUi.dataType.clickSaveButton(); - // Skip this test as currently there is no setting for bulk action permission - test.skip('can update bulk action permission', async ({umbracoApi, umbracoUi}) => { - // Arrange - const bulkActionPermissionValue = 'Allow bulk trash'; - const expectedDataTypeValues = { - "alias": "bulkActionPermissions", - "value": { - "allowBulkCopy": false, - "allowBulkDelete": true, - "allowBulkMove": false, - "allowBulkPublish": false, - "allowBulkUnpublish": false - } - }; + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'tabName', workspaceViewName)).toBeTruthy(); +}); - // Act - await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.clickBulkActionPermissionsToggleByValue(bulkActionPermissionValue); - await umbracoUi.dataType.clickSaveButton(); +test('can enable show content workspace view first', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.dataType.createListViewContentDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); + // Act + await umbracoUi.dataType.clickShowContentWorkspaceViewFirstToggle(); + await umbracoUi.dataType.clickSaveButton(); - test('can update workspace view icon', async ({umbracoApi, umbracoUi}) => { - // Arrange - const iconValue = 'icon-activity'; - const expectedDataTypeValues = { - "alias": "icon", - "value": iconValue - }; + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'showContentFirst', true)).toBeTruthy(); +}); - // Act - await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.clickSelectIconButton(); - await umbracoUi.dataType.chooseWorkspaceViewIconByValue(iconValue); - await umbracoUi.dataType.clickSaveButton(); +for (const listView of listViewTypes) { + test(`the default configuration of ${listView.type} is correct`, async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(listView.type); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); - - test('can update workspace view name', async ({umbracoApi, umbracoUi}) => { - // Arrange - const WorkspaceViewName = 'Test Content Name'; - const expectedDataTypeValues = { - "alias": "tabName", - "value": WorkspaceViewName - }; - - // Act - await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.enterWorkspaceViewName(WorkspaceViewName); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); - - test('can enable show content workspace view first', async ({umbracoApi, umbracoUi}) => { - // Arrange - const expectedDataTypeValues = { - "alias": "showContentFirst", - "value": true - }; - - // Act - await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.clickShowContentWorkspaceViewFirstToggle(); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); - - // Skip this test as there are no setting for infinite editor - test.skip('can enable edit in infinite editor', async ({umbracoApi, umbracoUi}) => { - // Arrange - const expectedDataTypeValues = { - "alias": "useInfiniteEditor", - "value": true - }; - - // Act - await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.clickEditInInfiniteEditorToggle(); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - dataTypeData = await umbracoApi.dataType.getByName(listViewType); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.listViewSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.listViewSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(listView.type, 'pageSize', 100)).toBeTruthy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(listView.type, 'orderBy', 'updateDate')).toBeTruthy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(listView.type, 'orderDirection', 'desc')).toBeTruthy(); + expect(await umbracoApi.dataType.doesListViewHaveLayout(listView.type, 'Grid', 'icon-thumbnails-small', listView.collectionViewGird)).toBeTruthy(); + expect(await umbracoApi.dataType.doesListViewHaveLayout(listView.type, 'List', 'icon-list', listView.collectionViewList)).toBeTruthy(); + expect(await umbracoApi.dataType.doesListViewHaveProperty(listView.type, 'Last edited', 'updateDate')).toBeTruthy(); + expect(await umbracoApi.dataType.doesListViewHaveProperty(listView.type, 'Updated by', 'creator')).toBeTruthy(); + // TODO: Uncomment this when the front-end is ready. Currently there is no default icon for workspace view icon + // expect(await umbracoApi.dataType.doesDataTypeHaveValue(listView.type, 'icon', 'icon-list')).toBeTruthy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(listView.type, 'tabName')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(listView.type, 'showContentFirst')).toBeFalsy(); }); } diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts index d4945b1cb1..cc15b8be68 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts @@ -1,237 +1,203 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; -const dataTypes = ['Media Picker', 'Multiple Media Picker', 'Image Media Picker', 'Multiple Image Media Picker']; -for (const dataTypeName of dataTypes) { - test.describe(`${dataTypeName} tests`, () => { - let dataTypeDefaultData = null; - let dataTypeData = null; +const mediaPickerTypes = [ + {type: 'Media Picker', isMultiple: false}, + {type: 'Multiple Media Picker', isMultiple: true}, + {type: 'Image Media Picker', isMultiple: false}, + {type: 'Multiple Image Media Picker', isMultiple: true}, +]; +const editorAlias = 'Umbraco.MediaPicker3'; +const editorUiAlias = 'Umb.PropertyEditorUi.MediaPicker'; +const customDataTypeName = 'Custom Media Picker'; - test.beforeEach(async ({umbracoUi, umbracoApi}) => { - await umbracoUi.goToBackOffice(); - await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); - }); +test.beforeEach(async ({umbracoUi, umbracoApi}) => { + await umbracoUi.goToBackOffice(); + await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); - test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } - }); +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); - test('can update pick multiple items', async ({umbracoApi, umbracoUi}) => { - // Arrange - const expectedDataTypeValues = { - "alias": "multiple", - "value": dataTypeName === 'Media Picker' || dataTypeName === 'Image Media Picker' ? true : false, - }; +test('can update pick multiple items', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Act - await umbracoUi.dataType.goToDataType(dataTypeName); - await umbracoUi.dataType.clickPickMultipleItemsToggle(); - await umbracoUi.dataType.clickSaveButton(); + // Act + await umbracoUi.dataType.clickPickMultipleItemsToggle(); + await umbracoUi.dataType.clickSaveButton(); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'multiple', true)).toBeTruthy(); +}); - test('can update amount', async ({umbracoApi, umbracoUi}) => { - // Arrange - const lowValue = 5; - const highValue = 1000; - const expectedDataTypeValues = { - "alias": "validationLimit", - "value": { - "min": lowValue, - "max": highValue - } - }; +test('can update amount', async ({umbracoApi, umbracoUi}) => { + // Arrange + const minValue = 5; + const maxValue = 1000; + await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Act - await umbracoUi.dataType.goToDataType(dataTypeName); - await umbracoUi.dataType.enterAmountValue(lowValue.toString(), highValue.toString()); - await umbracoUi.dataType.clickSaveButton(); + // Act + await umbracoUi.dataType.enterAmountValue(minValue.toString(), maxValue.toString()); + await umbracoUi.dataType.clickSaveButton(); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesMediaPickerHaveMinAndMaxAmount(customDataTypeName, minValue, maxValue)).toBeTruthy(); +}); - test('can update enable focal point', async ({umbracoApi, umbracoUi}) => { - // Arrange - const expectedDataTypeValues = { - "alias": "enableLocalFocalPoint", - "value": true - }; +test('can update enable focal point', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Act - await umbracoUi.dataType.goToDataType(dataTypeName); - await umbracoUi.dataType.clickEnableFocalPointToggle(); - await umbracoUi.dataType.clickSaveButton(); + // Act + await umbracoUi.dataType.clickEnableFocalPointToggle(); + await umbracoUi.dataType.clickSaveButton(); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'enableLocalFocalPoint', true)).toBeTruthy(); +}); - test('can add image crop', async ({umbracoApi, umbracoUi}) => { - // Arrange - const cropData = ['Test Label', 'Test Alias', 100, 50]; - const expectedDataTypeValues = { - "alias": "crops", - "value": [ - { - "label": cropData[0], - "alias": cropData[1], - "width": cropData[2], - "height": cropData[3] - } - ] - }; +test('can add image crop', async ({umbracoApi, umbracoUi}) => { + // Arrange + const cropData = ['Test Label', 'testAlias', 100, 50]; + await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Act - await umbracoUi.dataType.goToDataType(dataTypeName); - await umbracoUi.waitForTimeout(500); - await umbracoUi.dataType.enterCropValues( - cropData[0].toString(), - cropData[1].toString(), - cropData[2].toString(), - cropData[3].toString() - ); - await umbracoUi.waitForTimeout(500); - await umbracoUi.dataType.clickAddCropButton(); - await umbracoUi.dataType.clickSaveButton(); - await umbracoUi.waitForTimeout(500); + // Act + await umbracoUi.dataType.enterCropValues( + cropData[0].toString(), + cropData[1].toString(), + cropData[2].toString(), + cropData[3].toString() + ); + await umbracoUi.dataType.clickAddCropButton(); + await umbracoUi.dataType.clickSaveButton(); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveCrops(customDataTypeName, cropData[0], cropData[1], cropData[2], cropData[3])).toBeTruthy(); +}); - test('can update ignore user start nodes', async ({umbracoApi, umbracoUi}) => { - // Arrange - const expectedDataTypeValues = { - "alias": "ignoreUserStartNodes", - "value": true - }; +test('can update ignore user start nodes', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); - // Act - await umbracoUi.dataType.goToDataType(dataTypeName); - await umbracoUi.dataType.clickIgnoreUserStartNodesToggle(); - await umbracoUi.dataType.clickSaveButton(); + // Act + await umbracoUi.dataType.clickIgnoreUserStartNodesToggle(); + await umbracoUi.dataType.clickSaveButton(); - // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'ignoreUserStartNodes', true)).toBeTruthy(); +}); - test('can add accepted types', async ({umbracoApi, umbracoUi}) => { - // Arrange - const mediaTypeName = 'Audio'; - const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName); +test('can add accepted types', async ({umbracoApi, umbracoUi}) => { + // Arrange + const mediaTypeName = 'Audio'; + const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName); + await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); + + // Act + await umbracoUi.dataType.addAcceptedType(mediaTypeName); + await umbracoUi.dataType.clickSaveButton(); + + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'filter', mediaTypeData.id)).toBeTruthy(); +}); + +test('can remove accepted types', async ({umbracoApi, umbracoUi}) => { + // Arrange + const mediaTypeName = 'Image'; + const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName); + await umbracoApi.dataType.createImageMediaPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); + + // Act + await umbracoUi.dataType.removeAcceptedType(mediaTypeName); + await umbracoUi.dataType.clickSaveButton(); + + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'filter', mediaTypeData.id)).toBeFalsy(); +}); + +test('can add start node', async ({umbracoApi, umbracoUi}) => { + // Arrange + // Create media + const mediaName = 'TestStartNode'; + const mediaId = await umbracoApi.media.createDefaultMediaWithArticle(mediaName); + expect(await umbracoApi.media.doesNameExist(mediaName)).toBeTruthy(); + await umbracoApi.dataType.createDefaultMediaPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); + + // Act + await umbracoUi.dataType.clickChooseStartNodeButton(); + await umbracoUi.dataType.addMediaStartNode(mediaName); + await umbracoUi.dataType.clickSaveButton(); + + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'startNodeId', mediaId)).toBeTruthy(); + + // Clean + await umbracoApi.media.ensureNameNotExists(mediaName); +}); + +test('can remove start node', async ({umbracoApi, umbracoUi}) => { + // Arrange + // Create media + const mediaName = 'TestStartNode'; + await umbracoApi.media.ensureNameNotExists(mediaName); + const mediaId = await umbracoApi.media.createDefaultMediaWithArticle(mediaName); + expect(await umbracoApi.media.doesNameExist(mediaName)).toBeTruthy(); + await umbracoApi.dataType.createImageMediaPickerDataTypeWithStartNodeId(customDataTypeName, mediaId); + await umbracoUi.dataType.goToDataType(customDataTypeName); + + // Act + await umbracoUi.dataType.removeMediaStartNode(mediaName); + await umbracoUi.dataType.clickSaveButton(); + + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'startNodeId', mediaId)).toBeFalsy(); + + // Clean + await umbracoApi.media.ensureNameNotExists(mediaName); +}); + +for (const mediaPicker of mediaPickerTypes) { + test(`the default configuration of ${mediaPicker.type} is correct`, async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(mediaPicker.type); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.mediaPickerSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.mediaPickerSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(mediaPicker.type, 'multiple', mediaPicker.isMultiple)).toBeTruthy(); + if (mediaPicker.type.includes('Image')) { const imageTypeData = await umbracoApi.mediaType.getByName('Image'); - const expectedFilterValue = - dataTypeName === "Image Media Picker" || - dataTypeName === "Multiple Image Media Picker" - ? imageTypeData.id + "," + mediaTypeData.id - : mediaTypeData.id; - const expectedDataTypeValues = { - "alias": "filter", - "value": expectedFilterValue - }; - - // Act - await umbracoUi.dataType.goToDataType(dataTypeName); - await umbracoUi.dataType.addAcceptedType(mediaTypeName); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - }); - - test('can remove accepted types', async ({umbracoApi, umbracoUi}) => { - // Arrange - const mediaTypeName = 'Audio'; - const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName); - const removedDataTypeValues = [{ - "alias": "filter", - "value": mediaTypeData.id - }]; - const expectedDataTypeValues = []; - - // Remove all existing options and add an option to remove - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = removedDataTypeValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - - // Act - await umbracoUi.dataType.goToDataType(dataTypeName); - await umbracoUi.dataType.removeAcceptedType(mediaTypeName); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual(expectedDataTypeValues); - }); - - test('can add start node', async ({umbracoApi, umbracoUi}) => { - // Arrange - // Create media - const mediaName = 'TestStartNode'; - await umbracoApi.media.ensureNameNotExists(mediaName); - const mediaId = await umbracoApi.media.createDefaultMediaWithArticle(mediaName); - expect(await umbracoApi.media.doesNameExist(mediaName)).toBeTruthy(); - - const expectedDataTypeValues = { - "alias": "startNodeId", - "value": mediaId - }; - - // Act - await umbracoUi.dataType.goToDataType(dataTypeName); - await umbracoUi.dataType.clickChooseStartNodeButton(); - await umbracoUi.dataType.addMediaStartNode(mediaName); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); - - // Clean - await umbracoApi.media.ensureNameNotExists(mediaName); - }); - - test('can remove start node', async ({umbracoApi, umbracoUi}) => { - // Arrange - // Create media - const mediaName = 'TestStartNode'; - await umbracoApi.media.ensureNameNotExists(mediaName); - const mediaId = await umbracoApi.media.createDefaultMediaWithArticle(mediaName); - expect(await umbracoApi.media.doesNameExist(mediaName)).toBeTruthy(); - - const removedDataTypeValues = [{ - "alias": "startNodeId", - "value": mediaId - }]; - - // Remove all existing values and add a start node to remove - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = removedDataTypeValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - - // Act - await umbracoUi.dataType.goToDataType(dataTypeName); - await umbracoUi.dataType.removeMediaStartNode(mediaName); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual([]); - - // Clean - await umbracoApi.media.ensureNameNotExists(mediaName); - }); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(mediaPicker.type, 'filter', imageTypeData.id)).toBeTruthy(); + } + if (!mediaPicker.type.includes('Multiple')) { + expect(await umbracoApi.dataType.doesMediaPickerHaveMinAndMaxAmount(mediaPicker.type, 0, 1)).toBeTruthy(); + } + expect(await umbracoApi.dataType.doesDataTypeHaveValue(mediaPicker.type, 'startNodeId')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(mediaPicker.type, 'ignoreUserStartNodes')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(mediaPicker.type, 'crops')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(mediaPicker.type, 'enableLocalFocalPoint')).toBeFalsy(); }); } diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MemberPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MemberPicker.spec.ts new file mode 100644 index 0000000000..4e42cea6a8 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MemberPicker.spec.ts @@ -0,0 +1,22 @@ +import {test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const dataTypeName = 'Member Picker'; +const editorAlias = 'Umbraco.MemberPicker'; +const editorUiAlias = 'Umb.PropertyEditorUi.MemberPicker'; + +test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.goToBackOffice(); + await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); + await umbracoUi.dataType.goToDataType(dataTypeName); + + // Assert + await umbracoUi.dataType.doesSettingsContainText('There is no configuration for this property editor.'); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(dataTypeDefaultData.values).toEqual([]); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MultiUrlPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MultiUrlPicker.spec.ts index 1cb557aba1..6900c55fa5 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MultiUrlPicker.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MultiUrlPicker.spec.ts @@ -1,111 +1,101 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'Multi URL Picker'; -let dataTypeDefaultData = null; -let dataTypeData = null; +const editorAlias = 'Umbraco.MultiUrlPicker'; +const editorUiAlias = 'Umb.PropertyEditorUi.MultiUrlPicker'; +const customDataTypeName = 'Custom Multi URL Picker'; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - await umbracoUi.dataType.goToDataType(dataTypeName); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can update minimum number of items value', async ({umbracoApi, umbracoUi}) => { // Arrange const minimumValue = 2; - const expectedDataTypeValues = { - "alias": "minNumber", - "value": minimumValue - }; + await umbracoApi.dataType.createDefaultMultiUrlPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterMinimumNumberOfItemsValue(minimumValue.toString()); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'minNumber', minimumValue)).toBeTruthy(); }); test('can update maximum number of items value', async ({umbracoApi, umbracoUi}) => { // Arrange const maximumValue = 2; - const expectedDataTypeValues = { - "alias": "maxNumber", - "value": maximumValue - }; + await umbracoApi.dataType.createDefaultMultiUrlPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterMaximumNumberOfItemsValue(maximumValue.toString()); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'maxNumber', maximumValue)).toBeTruthy(); }); test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => { // Arrange - const expectedDataTypeValues = { - "alias": "ignoreUserStartNodes", - "value": true - }; + await umbracoApi.dataType.createDefaultMultiUrlPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.clickIgnoreUserStartNodesToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'ignoreUserStartNodes', true)).toBeTruthy(); }); test('can update overlay size', async ({umbracoApi, umbracoUi}) => { // Arrange const overlaySizeValue = 'large'; - const expectedDataTypeValues = { - "alias": "overlaySize", - "value": overlaySizeValue - }; + await umbracoApi.dataType.createDefaultMultiUrlPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.chooseOverlaySizeByValue(overlaySizeValue); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'overlaySize', overlaySizeValue)).toBeTruthy(); }); test('can update hide anchor/query string input', async ({umbracoApi, umbracoUi}) => { // Arrange - const expectedDataTypeValues = { - "alias": "hideAnchor", - "value": true - }; + await umbracoApi.dataType.createDefaultMultiUrlPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.clickHideAnchorQueryStringInputToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'hideAnchor', true)).toBeTruthy(); }); // TODO: Remove skip when the front-end is ready. Currently you still can update the minimum greater than the maximum. -test.skip('cannot update the minimum number of items greater than the maximum', async ({umbracoUi}) => { +test.skip('cannot update the minimum number of items greater than the maximum', async ({umbracoApi, umbracoUi}) => { // Arrange const minimumValue = 5; const maximumValue = 2; + await umbracoApi.dataType.createDefaultMultiUrlPickerDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterMinimumNumberOfItemsValue(minimumValue.toString()); @@ -115,3 +105,23 @@ test.skip('cannot update the minimum number of items greater than the maximum', // Assert await umbracoUi.dataType.isErrorNotificationVisible(); }); + +test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(dataTypeName); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.multiURLPickerSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.multiURLPickerSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(dataTypeDefaultData.values).toEqual([]); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'minNumber')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'maxNumber')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'ignoreUserStartNodes')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'overlaySize')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'hideAnchor')).toBeFalsy(); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Numeric.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Numeric.spec.ts index 2bd0da1404..d1b1de699a 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Numeric.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Numeric.spec.ts @@ -1,95 +1,88 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'Numeric'; -let dataTypeDefaultData = null; -let dataTypeData = null; +const editorAlias = 'Umbraco.Integer'; +const editorUiAlias = 'Umb.PropertyEditorUi.Integer'; +const customDataTypeName = 'Custom Numeric'; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - await umbracoUi.dataType.goToDataType(dataTypeName); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can update minimum value', async ({umbracoApi, umbracoUi}) => { // Arrange const minimumValue = -5; - const expectedDataTypeValues = { - "alias": "min", - "value": minimumValue - }; + await umbracoApi.dataType.createDefaultNumericDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterMinimumValue(minimumValue.toString()); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'min', minimumValue)).toBeTruthy(); }); test('can update Maximum value', async ({umbracoApi, umbracoUi}) => { // Arrange const maximumValue = 1000000; - const expectedDataTypeValues = { - "alias": "max", - "value": maximumValue - }; + await umbracoApi.dataType.createDefaultNumericDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterMaximumValue(maximumValue.toString()); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'max', maximumValue)).toBeTruthy(); }); test('can update step size value', async ({umbracoApi, umbracoUi}) => { // Arrange const stepSizeValue = 5; - const expectedDataTypeValues = { - "alias": "step", - "value": stepSizeValue - }; + await umbracoApi.dataType.createDefaultNumericDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterStepSizeValue(stepSizeValue.toString()); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'step', stepSizeValue)).toBeTruthy(); }); +// Skip this test as currently this setting is removed. test.skip('can allow decimals', async ({umbracoApi, umbracoUi}) => { // Arrange - const expectedDataTypeValues = { - "alias": "allowDecimals", - "value": true - }; + await umbracoApi.dataType.createDefaultNumericDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.clickAllowDecimalsToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'allowDecimals', true)).toBeTruthy(); }); // TODO: Remove skip when the front-end is ready. Currently you still can update the minimum greater than the maximum. -test.skip('cannot update the minimum greater than the maximum', async ({umbracoUi}) => { +test.skip('cannot update the minimum greater than the maximum', async ({umbracoApi, umbracoUi}) => { // Arrange const minimumValue = 5; const maximumValue = 2; + await umbracoApi.dataType.createDefaultNumericDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterMinimumValue(minimumValue.toString()); @@ -99,3 +92,21 @@ test.skip('cannot update the minimum greater than the maximum', async ({umbracoU // Assert await umbracoUi.dataType.isErrorNotificationVisible(); }); + +test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(dataTypeName); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.numericSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.numericSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(dataTypeDefaultData.values).toEqual([]); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'min')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'max')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'step')).toBeFalsy(); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Radiobox.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Radiobox.spec.ts index 38c0cfdf8d..192dc71ff3 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Radiobox.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Radiobox.spec.ts @@ -1,36 +1,26 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'Radiobox'; -let dataTypeDefaultData = null; -let dataTypeData = null; +const editorAlias = 'Umbraco.RadioButtonList'; +const editorUiAlias = 'Umb.PropertyEditorUi.RadioButtonList'; +const customDataTypeName = 'Custom Radiobox'; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can add option', async ({umbracoApi, umbracoUi}) => { // Arrange const optionName = 'Test option'; - const expectedDataTypeValues = [ - { - "alias": "items", - "value": [optionName] - } - ]; - // Remove all existing options - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = []; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + await umbracoApi.dataType.createRadioboxDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.clickAddOptionButton(); @@ -38,30 +28,41 @@ test('can add option', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'items', [optionName])).toBeTruthy(); }); test('can remove option', async ({umbracoApi, umbracoUi}) => { // Arrange const removedOptionName = 'Removed Option'; - const removedOptionValues = [ - { - "alias": "items", - "value": [removedOptionName] - } - ]; - // Remove all existing options and add an option to remove - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - dataTypeData.values = removedOptionValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(dataTypeName); + const customDataType = 'Custom Radiobox'; + await umbracoApi.dataType.createRadioboxDataType(customDataType, [removedOptionName]); + await umbracoUi.dataType.goToDataType(customDataType); // Act await umbracoUi.dataType.removeOptionByName(removedOptionName); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toEqual([]); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataType, 'items', [removedOptionName])).toBeFalsy(); + + // Clean + await umbracoApi.dataType.ensureNameNotExists(customDataType); +}); + +test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(dataTypeName); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.radioboxSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.radioboxSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(dataTypeDefaultData.values).toEqual([]); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'items')).toBeFalsy(); }); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/RichTextEditor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/RichTextEditor.spec.ts index 7d03341929..62b8dc3928 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/RichTextEditor.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/RichTextEditor.spec.ts @@ -1,23 +1,77 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; +const dataTypeName = 'Richtext editor'; +const tipTapPropertyEditorName = 'Rich Text Editor [Tiptap] Property Editor UI'; +const tipTapAlias = 'Umbraco.RichText'; +const tipTapUiAlias = 'Umb.PropertyEditorUi.Tiptap'; +const extensionsDefaultValue = [ + "Umb.Tiptap.Embed", + "Umb.Tiptap.Link", + "Umb.Tiptap.Figure", + "Umb.Tiptap.Image", + "Umb.Tiptap.Subscript", + "Umb.Tiptap.Superscript", + "Umb.Tiptap.Table", + "Umb.Tiptap.Underline", + "Umb.Tiptap.TextAlign", + "Umb.Tiptap.MediaUpload" +]; + +const toolbarDefaultValue = [ + [ + [ + "Umb.Tiptap.Toolbar.SourceEditor" + ], + [ + "Umb.Tiptap.Toolbar.Bold", + "Umb.Tiptap.Toolbar.Italic", + "Umb.Tiptap.Toolbar.Underline" + ], + [ + "Umb.Tiptap.Toolbar.TextAlignLeft", + "Umb.Tiptap.Toolbar.TextAlignCenter", + "Umb.Tiptap.Toolbar.TextAlignRight" + ], + [ + "Umb.Tiptap.Toolbar.BulletList", + "Umb.Tiptap.Toolbar.OrderedList" + ], + [ + "Umb.Tiptap.Toolbar.Blockquote", + "Umb.Tiptap.Toolbar.HorizontalRule" + ], + [ + "Umb.Tiptap.Toolbar.Link", + "Umb.Tiptap.Toolbar.Unlink" + ], + [ + "Umb.Tiptap.Toolbar.MediaPicker", + "Umb.Tiptap.Toolbar.EmbeddedMedia" + ] + ] +]; + test('tiptap is the default property editor in rich text editor', async ({umbracoApi, umbracoUi}) => { // Arrange - const dataTypeName = 'Richtext editor'; - const tipTapPropertyEditorName = 'Rich Text Editor [Tiptap] Property Editor UI'; - const tipTapAlias = 'Umbraco.RichText'; - const tipTapUiAlias = 'Umb.PropertyEditorUi.Tiptap'; await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); // Act await umbracoUi.dataType.goToDataType(dataTypeName); - // Assert + // Assert + //await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.tipTapSettings); + //await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.tipTapSettings); await umbracoUi.dataType.doesPropertyEditorHaveName(tipTapPropertyEditorName); - await umbracoUi.dataType.doesPropertyEditorHaveSchemaAlias(tipTapAlias); - await umbracoUi.dataType.doesPropertyEditorHaveAlias(tipTapUiAlias); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(tipTapAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(tipTapUiAlias); const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); expect(dataTypeData.editorAlias).toBe(tipTapAlias); expect(dataTypeData.editorUiAlias).toBe(tipTapUiAlias); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'maxImageSize', 500)).toBeTruthy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'overlaySize', 'medium')).toBeTruthy(); + expect(await umbracoApi.dataType.doesTiptapExtensionsItemsMatchCount(dataTypeName, extensionsDefaultValue.length)).toBeTruthy(); + expect(await umbracoApi.dataType.doesTiptapExtensionsHaveItems(dataTypeName, extensionsDefaultValue)).toBeTruthy(); + expect(await umbracoApi.dataType.doesTiptapToolbarHaveItems(dataTypeName, toolbarDefaultValue)).toBeTruthy(); }); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tags.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tags.spec.ts index 2424b4d9d6..9dbf6b013e 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tags.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tags.spec.ts @@ -1,53 +1,63 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'Tags'; -let dataTypeDefaultData = null; -let dataTypeData = null; +const editorAlias = 'Umbraco.Tags'; +const editorUiAlias = 'Umb.PropertyEditorUi.Tags'; +const customDataTypeName = 'Custom Tags'; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - await umbracoUi.dataType.goToDataType(dataTypeName); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can update define a tag group', async ({umbracoApi, umbracoUi}) => { // Arrange const tagGroup = 'testTagGroup'; - const expectedDataTypeValues = { - "alias": "group", - "value": tagGroup - }; + await umbracoApi.dataType.createDefaultTagsDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterDefineTagGroupValue(tagGroup); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'group', tagGroup)).toBeTruthy(); }); test('can select storage type', async ({umbracoApi, umbracoUi}) => { // Arrange const storageType = 'Csv'; - const expectedDataTypeValues = { - "alias": "storageType", - "value": storageType - }; + await umbracoApi.dataType.createDefaultTagsDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.selectStorageTypeOption(storageType); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'storageType', storageType)).toBeTruthy(); +}); + +test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(dataTypeName); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.tagsSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.tagsSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'group', 'default', dataTypeDefaultData)).toBeTruthy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'storageType', 'Json', dataTypeDefaultData)).toBeTruthy(); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textarea.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textarea.spec.ts index 20d9a183dd..07309ea5af 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textarea.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textarea.spec.ts @@ -1,55 +1,64 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'Textarea'; -let dataTypeDefaultData = null; -let dataTypeData = null; +const editorAlias = 'Umbraco.TextArea'; +const editorUiAlias = 'Umb.PropertyEditorUi.TextArea'; +const customDataTypeName = 'Custom Textarea'; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - await umbracoUi.dataType.goToDataType(dataTypeName); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can update maximum allowed characters value', async ({umbracoApi, umbracoUi}) => { // Arrange const maxCharsValue = 126; - const expectedDataTypeValues = { - "alias": "maxChars", - "value": maxCharsValue - }; + await umbracoApi.dataType.createTextareaDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterMaximumAllowedCharactersValue(maxCharsValue.toString()); await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'maxChars', maxCharsValue)).toBeTruthy(); }); test('can update number of rows value', async ({umbracoApi, umbracoUi}) => { // Arrange const numberOfRowsValue = 9; - const expectedDataTypeValues = { - "alias": "rows", - "value": numberOfRowsValue - }; + await umbracoApi.dataType.createTextareaDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterNumberOfRowsValue(numberOfRowsValue.toString()); await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'rows', numberOfRowsValue)).toBeTruthy(); +}); + +test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(dataTypeName); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.textareaSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.textareaSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(dataTypeDefaultData.values).toEqual([]); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'maxChars')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'rows')).toBeFalsy(); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textstring.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textstring.spec.ts index db6e54242d..24a8574002 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textstring.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Textstring.spec.ts @@ -1,36 +1,49 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'Textstring'; -let dataTypeDefaultData = null; -let dataTypeData = null; +const editorAlias = 'Umbraco.TextBox'; +const editorUiAlias = 'Umb.PropertyEditorUi.TextBox'; +const customDataTypeName = 'Custom Textstring'; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - await umbracoUi.dataType.goToDataType(dataTypeName); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can update maximum allowed characters value', async ({umbracoApi, umbracoUi}) => { // Arrange const maxCharsValue = 126; - const expectedDataTypeValues = { - "alias": "maxChars", - "value": maxCharsValue - }; + await umbracoApi.dataType.createTextstringDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.enterMaximumAllowedCharactersValue(maxCharsValue.toString()); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'maxChars', maxCharsValue)).toBeTruthy(); +}); + +// Remove fixme when the front-end is ready. The "Input type" should be removed. +test.fixme('the default configuration is correct', async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(dataTypeName); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.textstringSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.textstringSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(dataTypeDefaultData.values).toEqual([]); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'maxChars')).toBeFalsy(); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts index 590df30a44..a6257ce9b8 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts @@ -1,4 +1,4 @@ -import {NotificationConstantHelper, test} from "@umbraco/playwright-testhelpers"; +import {ConstantHelper, NotificationConstantHelper, test} from "@umbraco/playwright-testhelpers"; import {expect} from "@playwright/test"; const tinyMCEName = 'TestTinyMCE'; @@ -19,6 +19,22 @@ test('can create a rich text editor with tinyMCE', {tag: '@smoke'}, async ({umbr const tinyMCEFilterKeyword = 'Rich Text Editor'; const tinyMCEAlias = 'Umbraco.RichText'; const tinyMCEUiAlias = 'Umb.PropertyEditorUi.TinyMCE'; + const toolbarValue = [ + "styles", + "bold", + "italic", + "alignleft", + "aligncenter", + "alignright", + "bullist", + "numlist", + "outdent", + "indent", + "sourcecode", + "link", + "umbmediapicker", + "umbembeddialog" + ]; // Act await umbracoUi.dataType.clickActionsMenuAtRoot(); @@ -32,9 +48,17 @@ test('can create a rich text editor with tinyMCE', {tag: '@smoke'}, async ({umbr // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.dataType.doesNameExist(tinyMCEName)).toBeTruthy(); + // Verify the default configuration + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.tinyMCESettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.tinyMCESettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(tinyMCEAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(tinyMCEUiAlias); const dataTypeData = await umbracoApi.dataType.getByName(tinyMCEName); expect(dataTypeData.editorAlias).toBe(tinyMCEAlias); expect(dataTypeData.editorUiAlias).toBe(tinyMCEUiAlias); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'maxImageSize', 500)).toBeTruthy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'mode', 'Classic')).toBeTruthy(); + expect(await umbracoApi.dataType.doesTinyMCEToolbarHaveItems(tinyMCEName, toolbarValue)).toBeTruthy(); }); test('can rename a rich text editor with tinyMCE', async ({umbracoApi, umbracoUi}) => { @@ -89,12 +113,7 @@ test('can enable toolbar options', async ({umbracoApi, umbracoUi}) => { test('can add stylesheet', async ({umbracoApi, umbracoUi}) => { // Arrange const stylesheetName = 'StylesheetForDataType.css'; - await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); const stylesheetPath = await umbracoApi.stylesheet.createDefaultStylesheet(stylesheetName); - const expectedTinyMCEValues = { - "alias": "stylesheets", - "value": [stylesheetPath] - }; await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); await umbracoUi.dataType.goToDataType(tinyMCEName); @@ -104,8 +123,7 @@ test('can add stylesheet', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName); - expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'stylesheets', [stylesheetPath])).toBeTruthy(); // Clean await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); @@ -115,13 +133,6 @@ test('can add dimensions', async ({umbracoApi, umbracoUi}) => { // Arrange const width = 100; const height = 10; - const expectedTinyMCEValues = { - "alias": "dimensions", - "value": { - "width": width, - "height": height - } - }; await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); await umbracoUi.dataType.goToDataType(tinyMCEName); @@ -131,17 +142,12 @@ test('can add dimensions', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName); - expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues); + expect(await umbracoApi.dataType.doesRTEHaveDimensions(tinyMCEName, width, height)).toBeTruthy(); }); test('can update maximum size for inserted images', async ({umbracoApi, umbracoUi}) => { // Arrange const maximumSize = 300; - const expectedTinyMCEValues = { - "alias": "maxImageSize", - "value": maximumSize - }; await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); await umbracoUi.dataType.goToDataType(tinyMCEName); @@ -151,17 +157,12 @@ test('can update maximum size for inserted images', async ({umbracoApi, umbracoU // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName); - expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'maxImageSize', maximumSize)).toBeTruthy(); }); test('can enable inline editing mode', async ({umbracoApi, umbracoUi}) => { // Arrange const mode = 'Inline'; - const expectedTinyMCEValues = { - "alias": "mode", - "value": mode - }; await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); await umbracoUi.dataType.goToDataType(tinyMCEName); @@ -171,23 +172,14 @@ test('can enable inline editing mode', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName); - expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'mode', mode)).toBeTruthy(); }); test('can add an available block', async ({umbracoApi, umbracoUi}) => { // Arrange const elementTypeName = 'TestElementType'; - await umbracoApi.documentType.ensureNameNotExists(elementTypeName); const elementTypeId = await umbracoApi.documentType.createEmptyElementType(elementTypeName); - const expectedTinyMCEValues = { - alias: "blocks", - value: [ - { - contentElementTypeKey: elementTypeId, - } - ] - }; + await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); await umbracoUi.dataType.goToDataType(tinyMCEName); @@ -197,8 +189,7 @@ test('can add an available block', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName); - expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues); + expect(await umbracoApi.dataType.doesRTEContainBlocks(tinyMCEName, [elementTypeId])).toBeTruthy(); // Clean await umbracoApi.documentType.ensureNameNotExists(elementTypeName); @@ -207,10 +198,6 @@ test('can add an available block', async ({umbracoApi, umbracoUi}) => { test('can select overlay size', async ({umbracoApi, umbracoUi}) => { // Arrange const overlaySizeValue = 'large'; - const expectedTinyMCEValues = { - "alias": "overlaySize", - "value": overlaySizeValue - }; await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); await umbracoUi.dataType.goToDataType(tinyMCEName); @@ -220,16 +207,11 @@ test('can select overlay size', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName); - expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'overlaySize', overlaySizeValue)).toBeTruthy(); }); test('can enable hide label', async ({umbracoApi, umbracoUi}) => { // Arrange - const expectedTinyMCEValues = { - "alias": "hideLabel", - "value": true - }; await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); await umbracoUi.dataType.goToDataType(tinyMCEName); @@ -239,19 +221,13 @@ test('can enable hide label', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName); - expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'hideLabel', true)).toBeTruthy(); }); test('can add image upload folder', async ({umbracoApi, umbracoUi}) => { // Arrange const mediaFolderName = 'TestMediaFolder'; - await umbracoApi.media.ensureNameNotExists(mediaFolderName); const mediaFolderId = await umbracoApi.media.createDefaultMediaFolder(mediaFolderName); - const expectedTinyMCEValues = { - "alias": "mediaParentId", - "value": mediaFolderId - }; await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); await umbracoUi.dataType.goToDataType(tinyMCEName); @@ -261,8 +237,7 @@ test('can add image upload folder', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName); - expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'mediaParentId', mediaFolderId)).toBeTruthy(); // Clean await umbracoApi.media.ensureNameNotExists(mediaFolderName); @@ -270,10 +245,6 @@ test('can add image upload folder', async ({umbracoApi, umbracoUi}) => { test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => { // Arrange - const expectedTinyMCEValues = { - "alias": "ignoreUserStartNodes", - "value": true - }; await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); await umbracoUi.dataType.goToDataType(tinyMCEName); @@ -283,6 +254,5 @@ test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tinyMCEData = await umbracoApi.dataType.getByName(tinyMCEName); - expect(tinyMCEData.values).toContainEqual(expectedTinyMCEValues); -}); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'ignoreUserStartNodes', true)).toBeTruthy(); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts index d999675f0e..650859289e 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts @@ -71,13 +71,6 @@ test('can add dimensions', async ({umbracoApi, umbracoUi}) => { // Arrange const width = 100; const height = 10; - const expectedTiptapValues = { - "alias": "dimensions", - "value": { - "width": width, - "height": height - } - }; await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName); await umbracoUi.dataType.goToDataType(tipTapName); @@ -87,17 +80,12 @@ test('can add dimensions', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tiptapData = await umbracoApi.dataType.getByName(tipTapName); - expect(tiptapData.values).toContainEqual(expectedTiptapValues); +expect(await umbracoApi.dataType.doesRTEHaveDimensions(tipTapName, width, height)).toBeTruthy(); }); test('can update maximum size for inserted images', async ({umbracoApi, umbracoUi}) => { // Arrange const maximumSize = 300; - const expectedTiptapValues = { - "alias": "maxImageSize", - "value": maximumSize - }; await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName); await umbracoUi.dataType.goToDataType(tipTapName); @@ -107,17 +95,12 @@ test('can update maximum size for inserted images', async ({umbracoApi, umbracoU // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tiptapData = await umbracoApi.dataType.getByName(tipTapName); - expect(tiptapData.values).toContainEqual(expectedTiptapValues); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tipTapName, 'maxImageSize', maximumSize)).toBeTruthy(); }); test('can select overlay size', async ({umbracoApi, umbracoUi}) => { // Arrange const overlaySizeValue = 'large'; - const expectedTiptapValues = { - "alias": "overlaySize", - "value": overlaySizeValue - }; await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName); await umbracoUi.dataType.goToDataType(tipTapName); @@ -127,23 +110,13 @@ test('can select overlay size', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tiptapData = await umbracoApi.dataType.getByName(tipTapName); - expect(tiptapData.values).toContainEqual(expectedTiptapValues); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tipTapName, 'overlaySize', overlaySizeValue)).toBeTruthy(); }); test('can add an available block', async ({umbracoApi, umbracoUi}) => { // Arrange const elementTypeName = 'TestElementType'; - await umbracoApi.documentType.ensureNameNotExists(elementTypeName); const elementTypeId = await umbracoApi.documentType.createEmptyElementType(elementTypeName); - const expectedTiptapValues = { - alias: "blocks", - value: [ - { - contentElementTypeKey: elementTypeId, - } - ] - }; await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName); await umbracoUi.dataType.goToDataType(tipTapName); @@ -153,8 +126,7 @@ test('can add an available block', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tiptapData = await umbracoApi.dataType.getByName(tipTapName); - expect(tiptapData.values).toContainEqual(expectedTiptapValues); + expect(await umbracoApi.dataType.doesRTEContainBlocks(tipTapName, [elementTypeId])).toBeTruthy(); // Clean await umbracoApi.documentType.ensureNameNotExists(elementTypeName); @@ -163,12 +135,7 @@ test('can add an available block', async ({umbracoApi, umbracoUi}) => { test('can add image upload folder', async ({umbracoApi, umbracoUi}) => { // Arrange const mediaFolderName = 'TestMediaFolder'; - await umbracoApi.media.ensureNameNotExists(mediaFolderName); const mediaFolderId = await umbracoApi.media.createDefaultMediaFolder(mediaFolderName); - const expectedTiptapValues = { - "alias": "mediaParentId", - "value": mediaFolderId - }; await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName); await umbracoUi.dataType.goToDataType(tipTapName); @@ -178,8 +145,7 @@ test('can add image upload folder', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tiptapData = await umbracoApi.dataType.getByName(tipTapName); - expect(tiptapData.values).toContainEqual(expectedTiptapValues); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tipTapName, 'mediaParentId', mediaFolderId)).toBeTruthy(); // Clean await umbracoApi.media.ensureNameNotExists(mediaFolderName); @@ -187,10 +153,6 @@ test('can add image upload folder', async ({umbracoApi, umbracoUi}) => { test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => { // Arrange - const expectedTiptapValues = { - "alias": "ignoreUserStartNodes", - "value": true - }; await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName); await umbracoUi.dataType.goToDataType(tipTapName); @@ -200,8 +162,7 @@ test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const tipTapData = await umbracoApi.dataType.getByName(tipTapName); - expect(tipTapData.values).toContainEqual(expectedTiptapValues); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(tipTapName, 'ignoreUserStartNodes', true)).toBeTruthy(); }); test('can delete toolbar group', async ({umbracoApi, umbracoUi}) => { diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TrueFalse.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TrueFalse.spec.ts index 3c8abcf0f2..3803a2c852 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TrueFalse.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TrueFalse.spec.ts @@ -1,87 +1,94 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'True/false'; -let dataTypeDefaultData = null; -let dataTypeData = null; +const editorAlias = 'Umbraco.TrueFalse'; +const editorUiAlias = 'Umb.PropertyEditorUi.Toggle'; +const customDataTypeName = 'Custom TrueFalse'; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); - await umbracoUi.dataType.goToDataType(dataTypeName); - dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test.afterEach(async ({umbracoApi}) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); test('can update preset value state', async ({umbracoApi, umbracoUi}) => { // Arrange - const expectedDataTypeValues = { - "alias": "default", - "value": true - }; + await umbracoApi.dataType.createDefaultTrueFalseDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); // Act await umbracoUi.dataType.clickPresetValueToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'default', true)).toBeTruthy(); }); test('can update show toggle labels', async ({umbracoApi, umbracoUi}) => { // Arrange - const expectedDataTypeValues = { - "alias": "showLabels", - "value": true - }; - + await umbracoApi.dataType.createDefaultTrueFalseDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); + // Act await umbracoUi.dataType.clickShowToggleLabelsToggle(); await umbracoUi.dataType.clickSaveButton(); // Assert - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'showLabels', true)).toBeTruthy(); }); test('can update label on', async ({umbracoApi, umbracoUi}) => { // Arrange const labelOnValue = 'Test Label On'; - const expectedDataTypeValues = { - "alias": "labelOn", - "value": labelOnValue - }; - + await umbracoApi.dataType.createDefaultTrueFalseDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); + // Act await umbracoUi.dataType.enterLabelOnValue(labelOnValue); await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'labelOn', labelOnValue)).toBeTruthy(); }); test('can update label off', async ({umbracoApi, umbracoUi}) => { // Arrange const labelOffValue = 'Test Label Off'; - const expectedDataTypeValues = { - "alias": "labelOff", - "value": labelOffValue - }; - + await umbracoApi.dataType.createDefaultTrueFalseDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); + // Act await umbracoUi.dataType.enterLabelOffValue(labelOffValue); await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'labelOff', labelOffValue)).toBeTruthy(); }); + +test('the default configuration is correct', async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(dataTypeName); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.trueFalseSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.trueFalseSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + const dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + expect(dataTypeDefaultData.editorAlias).toBe(editorAlias); + expect(dataTypeDefaultData.editorUiAlias).toBe(editorUiAlias); + expect(dataTypeDefaultData.values).toEqual([]); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'default')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'showLabels')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'labelOn')).toBeFalsy(); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(dataTypeName, 'labelOff')).toBeFalsy(); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Upload.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Upload.spec.ts deleted file mode 100644 index f3eab5c438..0000000000 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Upload.spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { test } from "@umbraco/playwright-testhelpers"; -import { expect } from "@playwright/test"; - -const uploadTypes = ['Upload Article', 'Upload Audio', 'Upload File', 'Upload Vector Graphics', 'Upload Video']; -for (const uploadType of uploadTypes) { - test.describe(`${uploadType} tests`, () => { - let dataTypeDefaultData = null; - let dataTypeData = null; - - test.beforeEach(async ({ umbracoUi, umbracoApi }) => { - await umbracoUi.goToBackOffice(); - await umbracoUi.dataType.goToSettingsTreeItem("Data Types"); - dataTypeDefaultData = await umbracoApi.dataType.getByName(uploadType); - }); - - test.afterEach(async ({ umbracoApi }) => { - if (dataTypeDefaultData !== null) { - await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); - } - }); - - test('can add accepted file extension', async ({ umbracoApi, umbracoUi }) => { - // Arrange - const fileExtensionValue = 'zip'; - const expectedDataTypeValues = [ - { - "alias": "fileExtensions", - "value": [fileExtensionValue] - } - ]; - // Remove all existing accepted file extensions - dataTypeData = await umbracoApi.dataType.getByName(uploadType); - dataTypeData.values = []; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(uploadType); - - // Act - await umbracoUi.waitForTimeout(500); - await umbracoUi.dataType.clickAddAcceptedFileExtensionsButton(); - await umbracoUi.dataType.enterAcceptedFileExtensions(fileExtensionValue); - await umbracoUi.waitForTimeout(500); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - await umbracoUi.waitForTimeout(500); - dataTypeData = await umbracoApi.dataType.getByName(uploadType); - expect(dataTypeData.values).toEqual(expectedDataTypeValues); - }); - - test('can remove accepted file extension', async ({ umbracoApi, umbracoUi }) => { - // Arrange - const removedFileExtensionValue = "bat"; - const removedFileExtensionsValues = [ - { - "alias": "fileExtensions", - "value": [removedFileExtensionValue] - } - ]; - // Remove all existing accepted file extensions and add an file extension to remove - dataTypeData = await umbracoApi.dataType.getByName(uploadType); - dataTypeData.values = removedFileExtensionsValues; - await umbracoApi.dataType.update(dataTypeData.id, dataTypeData); - await umbracoUi.dataType.goToDataType(uploadType); - - // Act - await umbracoUi.dataType.removeAcceptedFileExtensionsByValue(removedFileExtensionValue); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); - dataTypeData = await umbracoApi.dataType.getByName(uploadType); - expect(dataTypeData.values).toEqual([]); - }); - }); -} diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/UploadField.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/UploadField.spec.ts new file mode 100644 index 0000000000..6832691e55 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/UploadField.spec.ts @@ -0,0 +1,73 @@ +import { ConstantHelper, NotificationConstantHelper, test } from "@umbraco/playwright-testhelpers"; +import { expect } from "@playwright/test"; + +const uploadTypes = [ + {type: 'Upload Article', fileExtensions: ['pdf', 'docx', 'doc']}, + {type: 'Upload Audio', fileExtensions: ['mp3', 'weba', 'oga', 'opus']}, + {type: 'Upload File', fileExtensions: []}, + {type: 'Upload Vector Graphics', fileExtensions: ['svg']}, + {type: 'Upload Video', fileExtensions: ['mp4', 'webm', 'ogv']} +]; +const customDataTypeName = 'Custom Upload Field'; +const editorAlias = 'Umbraco.UploadField'; +const editorUiAlias = 'Umb.PropertyEditorUi.UploadField'; + +test.beforeEach(async ({ umbracoUi }) => { + await umbracoUi.goToBackOffice(); + await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); + +test('can add accepted file extension', async ({ umbracoApi, umbracoUi }) => { + // Arrange + const fileExtensionValue = 'zip'; + await umbracoApi.dataType.createUploadDataType(customDataTypeName); + await umbracoUi.dataType.goToDataType(customDataTypeName); + + // Act + await umbracoUi.dataType.clickAddAcceptedFileExtensionsButton(); + await umbracoUi.dataType.enterAcceptedFileExtensions(fileExtensionValue); + await umbracoUi.dataType.clickSaveButton(); + + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.dataType.doesDataTypeHaveValue(customDataTypeName, 'fileExtensions', [fileExtensionValue])).toBeTruthy(); +}); + +test('can remove accepted file extension', async ({ umbracoApi, umbracoUi }) => { + // Arrange + const removedFileExtensionValue = "bat"; + await umbracoApi.dataType.createUploadDataType(customDataTypeName, [removedFileExtensionValue]); + await umbracoUi.dataType.goToDataType(customDataTypeName); + + // Act + await umbracoUi.dataType.removeAcceptedFileExtensionsByValue(removedFileExtensionValue); + await umbracoUi.dataType.clickSaveButton(); + + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + const customDataTypeData = await umbracoApi.dataType.getByName(customDataTypeName); + expect(customDataTypeData.values).toEqual([]); +}); + +for (const uploadType of uploadTypes) { + test(`the default configuration of ${uploadType.type} is correct`, async ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.dataType.goToDataType(uploadType.type); + + // Assert + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.uploadSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.uploadSettings); + await umbracoUi.dataType.doesPropertyEditorHaveAlias(editorAlias); + await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(editorUiAlias); + if (uploadType.fileExtensions.length > 0) { + expect(await umbracoApi.dataType.doesDataTypeHaveValue(uploadType.type, 'fileExtensions', uploadType.fileExtensions)).toBeTruthy(); + } else { + const dataTypeDefaultData = await umbracoApi.dataType.getByName(uploadType.type); + expect(dataTypeDefaultData.values).toEqual([]); + } + }); +} diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts index 5daad8769b..bd645179ee 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts @@ -9,7 +9,7 @@ const colorValue = {label: "Test Label", value: "038c33"}; let dataTypeId = null; test.beforeEach(async ({umbracoApi}) => { - dataTypeId = await umbracoApi.dataType.createApprovedColorDataTypeWithOneItem(customDataTypeName, colorValue.label, colorValue.value); + dataTypeId = await umbracoApi.dataType.createDefaultApprovedColorDataTypeWithOneItem(customDataTypeName, colorValue.label, colorValue.value); }); test.afterEach(async ({umbracoApi}) => { From a71ebe1902c78dbf7cbefdca041043dd8d84f215 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 27 Mar 2025 11:11:37 +0100 Subject: [PATCH 14/23] V15: Improve the dropzone for Image Cropper (#18838) * feat: uses the umb-dropzone-input to render the dropzone * feat: loads in the blob url rather than reading the file into memory AND appends the server url * chore: lit 3 compat * feat: uses the umb-dropzone-input to render the dropzone * Revert "feat: uses the umb-dropzone-input to render the dropzone" This reverts commit bc1a6ae7df2e3230a132ce1a3756c7b2348647f9. * feat: creates an object url directly from the File rather than the Blob * feat: revokes the file data url from object storage * feat: revokes object url on disconnect --- .../image-cropper-field.element.ts | 57 ++++++---- .../image-cropper-preview.element.ts | 6 +- .../input-image-cropper.element.ts | 100 +++++++----------- 3 files changed, 81 insertions(+), 82 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts index 5ba36c22b8..b8d403ba09 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts @@ -1,3 +1,5 @@ +import type { UmbImageCropChangeEvent } from './crop-change.event.js'; +import type { UmbFocalPointChangeEvent } from './focalpoint-change.event.js'; import type { UmbImageCropperElement } from './image-cropper.element.js'; import type { UmbImageCropperCrop, @@ -5,15 +7,14 @@ import type { UmbImageCropperFocalPoint, UmbImageCropperPropertyEditorValue, } from './types.js'; -import type { UmbImageCropChangeEvent } from './crop-change.event.js'; -import type { UmbFocalPointChangeEvent } from './focalpoint-change.event.js'; import { css, customElement, html, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; -import './image-cropper.element.js'; import './image-cropper-focus-setter.element.js'; import './image-cropper-preview.element.js'; +import './image-cropper.element.js'; @customElement('umb-image-cropper-field') export class UmbInputImageCropperFieldElement extends UmbLitElement { @@ -46,7 +47,19 @@ export class UmbInputImageCropperFieldElement extends UmbLitElement { currentCrop?: UmbImageCropperCrop; @property({ attribute: false }) - file?: File; + set file(file: File | undefined) { + this.#file = file; + if (file) { + this.fileDataUrl = URL.createObjectURL(file); + } else if (this.fileDataUrl) { + URL.revokeObjectURL(this.fileDataUrl); + this.fileDataUrl = undefined; + } + } + get file() { + return this.#file; + } + #file?: File; @property() fileDataUrl?: string; @@ -60,25 +73,29 @@ export class UmbInputImageCropperFieldElement extends UmbLitElement { @state() src = ''; - get source() { - if (this.fileDataUrl) return this.fileDataUrl; - if (this.src) return this.src; - return ''; + @state() + private _serverUrl = ''; + + get source(): string { + if (this.src) { + return `${this._serverUrl}${this.src}`; + } + + return this.fileDataUrl ?? ''; } - override updated(changedProperties: Map) { - super.updated(changedProperties); + constructor() { + super(); - if (changedProperties.has('file')) { - if (this.file) { - const reader = new FileReader(); - reader.onload = (event) => { - this.fileDataUrl = event.target?.result as string; - }; - reader.readAsDataURL(this.file); - } else { - this.fileDataUrl = undefined; - } + this.consumeContext(UMB_APP_CONTEXT, (context) => { + this._serverUrl = context.getServerUrl(); + }); + } + + override disconnectedCallback(): void { + super.disconnectedCallback(); + if (this.fileDataUrl) { + URL.revokeObjectURL(this.fileDataUrl); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-preview.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-preview.element.ts index a092f0868c..d203a1af7e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-preview.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/image-cropper-preview.element.ts @@ -18,13 +18,13 @@ export class UmbImageCropperPreviewElement extends UmbLitElement { label?: string; @property({ attribute: false }) - get focalPoint() { - return this.#focalPoint; - } set focalPoint(value) { this.#focalPoint = value; this.#onFocalPointUpdated(); } + get focalPoint() { + return this.#focalPoint; + } #focalPoint: UmbImageCropperFocalPoint = { left: 0.5, top: 0.5 }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts index b90bd8dd20..1bf0a945c4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts @@ -1,21 +1,26 @@ import type { UmbImageCropperPropertyEditorValue } from './types.js'; import type { UmbInputImageCropperFieldElement } from './image-cropper-field.element.js'; -import { html, customElement, property, query, state, css, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui'; -import { UmbId } from '@umbraco-cms/backoffice/id'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTemporaryFileManager } from '@umbraco-cms/backoffice/temporary-file'; +import { css, customElement, html, ifDefined, property, state } from '@umbraco-cms/backoffice/external/lit'; import { assignToFrozenObject } from '@umbraco-cms/backoffice/observable-api'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbFileDropzoneItemStatus, UmbInputDropzoneDashedStyles } from '@umbraco-cms/backoffice/dropzone'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbTemporaryFileConfigRepository } from '@umbraco-cms/backoffice/temporary-file'; import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; +import type { + UmbDropzoneChangeEvent, + UmbInputDropzoneElement, + UmbUploadableItem, +} from '@umbraco-cms/backoffice/dropzone'; -import './image-cropper.element.js'; +import './image-cropper-field.element.js'; import './image-cropper-focus-setter.element.js'; import './image-cropper-preview.element.js'; -import './image-cropper-field.element.js'; +import './image-cropper.element.js'; const DefaultFocalPoint = { left: 0.5, top: 0.5 }; -const DefaultValue = { +const DefaultValue: UmbImageCropperPropertyEditorValue = { temporaryFileId: null, src: '', crops: [], @@ -28,9 +33,6 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin< typeof UmbLitElement, undefined >(UmbLitElement, undefined) { - @query('#dropzone') - private _dropzone?: UUIFileDropzoneElement; - /** * Sets the input to required, meaning validation will fail if the value is empty. * @type {boolean} @@ -45,10 +47,7 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin< crops: UmbImageCropperPropertyEditorValue['crops'] = []; @state() - file?: File; - - @state() - fileUnique?: string; + private _file?: UmbUploadableItem; @state() private _accept?: string; @@ -56,7 +55,7 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin< @state() private _loading = true; - #manager = new UmbTemporaryFileManager(this); + #config = new UmbTemporaryFileConfigRepository(this); constructor() { super(); @@ -76,9 +75,9 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin< } async #observeAcceptedFileTypes() { - const config = await this.#manager.getConfiguration(); + await this.#config.initialized; this.observe( - config.part('imageFileTypes'), + this.#config.part('imageFileTypes'), (imageFileTypes) => { this._accept = imageFileTypes.join(','); this._loading = false; @@ -87,34 +86,27 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin< ); } - #onUpload(e: UUIFileDropzoneEvent) { - const file = e.detail.files[0]; - if (!file) return; - const unique = UmbId.new(); + #onUpload(e: UmbDropzoneChangeEvent) { + e.stopImmediatePropagation(); - this.file = file; - this.fileUnique = unique; + const target = e.target as UmbInputDropzoneElement; + const file = target.value?.[0]; - this.value = assignToFrozenObject(this.value ?? DefaultValue, { temporaryFileId: unique }); + if (file?.status !== UmbFileDropzoneItemStatus.COMPLETE) return; - this.#manager?.uploadOne({ temporaryUnique: unique, file }); + this._file = file; + + this.value = assignToFrozenObject(this.value ?? DefaultValue, { + temporaryFileId: file.temporaryFile?.temporaryUnique, + }); this.dispatchEvent(new UmbChangeEvent()); } - #onBrowse(e: Event) { - if (!this._dropzone) return; - e.stopImmediatePropagation(); - this._dropzone.browse(); - } - #onRemove = () => { this.value = undefined; - if (this.fileUnique) { - this.#manager?.removeOne(this.fileUnique); - } - this.fileUnique = undefined; - this.file = undefined; + this._file?.temporaryFile?.abortController?.abort(); + this._file = undefined; this.dispatchEvent(new UmbChangeEvent()); }; @@ -144,7 +136,7 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin< return html`
`; } - if (this.value?.src || this.file) { + if (this.value?.src || this._file) { return this.#renderImageCropper(); } @@ -153,14 +145,11 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin< #renderDropzone() { return html` - - - + disable-folder-upload + @change="${this.#onUpload}"> `; } @@ -184,31 +173,24 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin< } #renderImageCropper() { - return html` + return html` ${this.localize.term('content_uploadClear')} `; } - static override styles = [ + static override readonly styles = [ + UmbTextStyles, + UmbInputDropzoneDashedStyles, css` #loader { display: flex; justify-content: center; } - - uui-file-dropzone { - position: relative; - display: block; - } - uui-file-dropzone::after { - content: ''; - position: absolute; - inset: 0; - cursor: pointer; - border: 1px dashed var(--uui-color-divider-emphasis); - } `, ]; } From 164868f32a21e9e8a50f3e1bd1c36e94a9fa62ea Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 27 Mar 2025 11:22:03 +0100 Subject: [PATCH 15/23] V15: Improve the dropzone for Upload Field (#18840) * feat: maps up the CANCELLED status * feat: uses the new dropzone input to render the dropzone * feat: adds support for differing server urls * chore: avoids a breaking change by storing the temporary file --- .../media/dropzone/dropzone-manager.class.ts | 12 +- .../input-upload-field.element.ts | 263 +++++------------- 2 files changed, 82 insertions(+), 193 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts index 52d4636371..64c6bfb6ca 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts @@ -70,7 +70,6 @@ export class UmbDropzoneManager extends UmbControllerBase { } /** - * @param isAllowed * @deprecated Not used anymore; this method will be removed in Umbraco 17. */ public setIsFoldersAllowed(isAllowed: boolean) { @@ -128,7 +127,9 @@ export class UmbDropzoneManager extends UmbControllerBase { const uploaded = await this.#tempFileManager.uploadOne(item.temporaryFile); // Update progress - if (uploaded.status === TemporaryFileStatus.SUCCESS) { + if (uploaded.status === TemporaryFileStatus.CANCELLED) { + this.#updateStatus(item, UmbFileDropzoneItemStatus.CANCELLED); + } else if (uploaded.status === TemporaryFileStatus.SUCCESS) { this.#updateStatus(item, UmbFileDropzoneItemStatus.COMPLETE); } else { this.#updateStatus(item, UmbFileDropzoneItemStatus.ERROR); @@ -226,7 +227,8 @@ export class UmbDropzoneManager extends UmbControllerBase { async #handleFile(item: UmbUploadableFile, mediaTypeUnique: string) { // Upload the file as a temporary file and update progress. - const temporaryFile = await this.#uploadAsTemporaryFile(item); + const temporaryFile = await this.#tempFileManager.uploadOne(item.temporaryFile); + if (temporaryFile.status === TemporaryFileStatus.CANCELLED) { this.#updateStatus(item, UmbFileDropzoneItemStatus.CANCELLED); return; @@ -257,10 +259,6 @@ export class UmbDropzoneManager extends UmbControllerBase { } } - #uploadAsTemporaryFile(item: UmbUploadableFile) { - return this.#tempFileManager.uploadOne(item.temporaryFile); - } - // Media types async #getMediaTypeOptions(item: UmbUploadableItem): Promise> { // Check the parent which children media types are allowed diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts index 7c7cd25841..3ada651c56 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts @@ -1,33 +1,28 @@ import type { MediaValueType } from '../../property-editors/upload-field/types.js'; import type { ManifestFileUploadPreview } from './file-upload-preview.extension.js'; import { getMimeTypeFromExtension } from './utils.js'; -import { - css, - html, - nothing, - ifDefined, - customElement, - property, - query, - state, - when, -} from '@umbraco-cms/backoffice/external/lit'; -import { formatBytes, stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { css, customElement, html, ifDefined, nothing, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbExtensionsManifestInitializer } from '@umbraco-cms/backoffice/extension-api'; -import { UmbId } from '@umbraco-cms/backoffice/id'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbFileDropzoneItemStatus, UmbInputDropzoneDashedStyles } from '@umbraco-cms/backoffice/dropzone'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTemporaryFileManager, TemporaryFileStatus } from '@umbraco-cms/backoffice/temporary-file'; -import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; +import type { + UmbDropzoneChangeEvent, + UmbInputDropzoneElement, + UmbUploadableFile, +} from '@umbraco-cms/backoffice/dropzone'; import type { UmbTemporaryFileModel } from '@umbraco-cms/backoffice/temporary-file'; -import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-input-upload-field') export class UmbInputUploadFieldElement extends UmbLitElement { - @property({ type: Object }) + @property({ type: Object, attribute: false }) set value(value: MediaValueType) { this.#src = value?.src ?? ''; + this.#setPreviewAlias(); } get value(): MediaValueType { return { @@ -42,39 +37,43 @@ export class UmbInputUploadFieldElement extends UmbLitElement { * @type {Array} * @default */ - @property({ type: Array }) - set allowedFileExtensions(value: Array) { - this.#setExtensions(value); - } - get allowedFileExtensions(): Array | undefined { - return this._extensions; - } + @property({ + type: Array, + attribute: 'allowed-file-extensions', + converter(value) { + if (typeof value === 'string') { + return value.split(',').map((ext) => ext.trim()); + } + return value; + }, + }) + allowedFileExtensions?: Array; @state() public temporaryFile?: UmbTemporaryFileModel; - @state() - private _progress = 0; - @state() private _extensions?: string[]; @state() private _previewAlias?: string; - @query('#dropzone') - private _dropzone?: UUIFileDropzoneElement; - - #manager = new UmbTemporaryFileManager(this); + @state() + private _serverUrl = ''; #manifests: Array = []; - override updated(changedProperties: PropertyValueMap | Map) { - super.updated(changedProperties); + constructor() { + super(); - if (changedProperties.has('value') && changedProperties.get('value')?.src !== this.value.src) { - this.#setPreviewAlias(); - } + this.consumeContext(UMB_APP_CONTEXT, (context) => { + this._serverUrl = context.getServerUrl(); + }); + } + + override disconnectedCallback(): void { + super.disconnectedCallback(); + this.#clearObjectUrl(); } async #getManifests() { @@ -87,15 +86,6 @@ export class UmbInputUploadFieldElement extends UmbLitElement { return this.#manifests; } - #setExtensions(extensions: Array) { - if (!extensions?.length) { - this._extensions = undefined; - return; - } - // TODO: The dropzone uui component does not support file extensions without a dot. Remove this when it does. - this._extensions = extensions?.map((extension) => `.${extension}`); - } - async #setPreviewAlias(): Promise { this._previewAlias = await this.#getPreviewElementAlias(); } @@ -151,47 +141,22 @@ export class UmbInputUploadFieldElement extends UmbLitElement { return getMimeTypeFromExtension('.' + extension); } - async #onUpload(e: UUIFileDropzoneEvent) { - try { - //Property Editor for Upload field will always only have one file. - this.temporaryFile = { - temporaryUnique: UmbId.new(), - status: TemporaryFileStatus.WAITING, - file: e.detail.files[0], - onProgress: (p) => { - this._progress = Math.ceil(p); - }, - abortController: new AbortController(), - }; - - const uploaded = await this.#manager.uploadOne(this.temporaryFile); - - if (uploaded.status === TemporaryFileStatus.SUCCESS) { - this.temporaryFile.status = TemporaryFileStatus.SUCCESS; - - const blobUrl = URL.createObjectURL(this.temporaryFile.file); - this.value = { src: blobUrl }; - - this.dispatchEvent(new UmbChangeEvent()); - } else { - this.temporaryFile.status = TemporaryFileStatus.ERROR; - this.requestUpdate('temporaryFile'); - } - } catch { - // If we still have a temporary file, set it to error. - if (this.temporaryFile) { - this.temporaryFile.status = TemporaryFileStatus.ERROR; - this.requestUpdate('temporaryFile'); - } - - // If the error was caused by the upload being aborted, do not show an error message. - } - } - - #handleBrowse(e: Event) { - if (!this._dropzone) return; + async #onUpload(e: UmbDropzoneChangeEvent) { e.stopImmediatePropagation(); - this._dropzone.browse(); + + const target = e.target as UmbInputDropzoneElement; + const file = target.value?.[0]; + + if (file?.status !== UmbFileDropzoneItemStatus.COMPLETE) return; + + this.temporaryFile = (file as UmbUploadableFile).temporaryFile; + + this.#clearObjectUrl(); + + const blobUrl = URL.createObjectURL(this.temporaryFile.file); + this.value = { src: blobUrl }; + + this.dispatchEvent(new UmbChangeEvent()); } override render() { @@ -199,69 +164,28 @@ export class UmbInputUploadFieldElement extends UmbLitElement { return this.#renderDropzone(); } - return html` - ${this.temporaryFile ? this.#renderUploader() : nothing} - ${this.value.src && this._previewAlias ? this.#renderFile(this.value.src) : nothing} - `; + if (this.value?.src && this._previewAlias) { + return this.#renderFile(this.value.src); + } + + return nothing; } #renderDropzone() { return html` - - - - `; - } - - #renderUploader() { - if (!this.temporaryFile) return nothing; - - return html` -
-
- ${when( - this.temporaryFile.status === TemporaryFileStatus.SUCCESS, - () => html``, - )} - ${when( - this.temporaryFile.status === TemporaryFileStatus.ERROR, - () => html``, - )} -
-
-
${this.temporaryFile.file.name}
-
${formatBytes(this.temporaryFile.file.size, { decimals: 2 })}: ${this._progress}%
- ${when( - this.temporaryFile.status === TemporaryFileStatus.WAITING, - () => html`
`, - )} - ${when( - this.temporaryFile.status === TemporaryFileStatus.ERROR, - () => html`
An error occured
`, - )} -
-
- ${when( - this.temporaryFile.status === TemporaryFileStatus.WAITING, - () => html` - - ${this.localize.term('general_cancel')} - - `, - () => this.#renderButtonRemove(), - )} -
-
+ disable-folder-upload + accept=${ifDefined(this._extensions?.join(','))} + @change=${this.#onUpload}> `; } #renderFile(src: string) { + if (!src.startsWith('blob:')) { + src = this._serverUrl + src; + } + return html`
@@ -288,13 +212,25 @@ export class UmbInputUploadFieldElement extends UmbLitElement { // If the upload promise happens to be in progress, cancel it. this.temporaryFile?.abortController?.abort(); + this.#clearObjectUrl(); + this.value = { src: undefined }; this.temporaryFile = undefined; - this._progress = 0; this.dispatchEvent(new UmbChangeEvent()); } + /** + * If there is a former File, revoke the object URL. + */ + #clearObjectUrl(): void { + if (this.value?.src?.startsWith('blob:')) { + URL.revokeObjectURL(this.value.src); + } + } + static override readonly styles = [ + UmbTextStyles, + UmbInputDropzoneDashedStyles, css` :host { position: relative; @@ -323,51 +259,6 @@ export class UmbInputUploadFieldElement extends UmbLitElement { width: fit-content; max-width: 100%; } - - #temporaryFile { - display: grid; - grid-template-columns: auto auto auto; - width: fit-content; - max-width: 100%; - margin: var(--uui-size-layout-1) 0; - padding: var(--uui-size-space-3); - border: 1px dashed var(--uui-color-divider-emphasis); - } - - #fileIcon, - #fileActions { - place-self: center center; - padding: 0 var(--uui-size-layout-1); - } - - #fileName { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-size: var(--uui-size-5); - } - - #fileSize { - font-size: var(--uui-font-size-small); - color: var(--uui-color-text-alt); - } - - #error { - color: var(--uui-color-danger); - } - - uui-file-dropzone { - position: relative; - display: block; - padding: 3px; /** Dropzone background is blurry and covers slightly into other elements. Hack to avoid this */ - } - uui-file-dropzone::after { - content: ''; - position: absolute; - inset: 0; - cursor: pointer; - border: 1px dashed var(--uui-color-divider-emphasis); - } `, ]; } From 9761ef899b6744525056e286ce1dc92c4ca23c0b Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 27 Mar 2025 11:33:01 +0100 Subject: [PATCH 16/23] Populate parent key on move and copy notifications (#18837) * Populate parent key on move and copy notifications. * Remove forgotten fixme --------- Co-authored-by: nikolajlauridsen --- src/Umbraco.Core/Services/ContentService.cs | 29 ++++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index c76e89381a..9b42428938 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -2621,8 +2622,8 @@ public class ContentService : RepositoryService, IContentService throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback } - // FIXME: Use MoveEventInfo that also takes a parent key when implementing move with parentKey. - var moveEventInfo = new MoveEventInfo(content, content.Path, parentId); + TryGetParentKey(parentId, out Guid? parentKey); + var moveEventInfo = new MoveEventInfo(content, content.Path, parentId, parentKey); var movingNotification = new ContentMovingNotification(moveEventInfo, eventMessages); if (scope.Notifications.PublishCancelable(movingNotification)) @@ -2652,9 +2653,12 @@ public class ContentService : RepositoryService, IContentService new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages)); // changes - // FIXME: Use MoveEventInfo that also takes a parent key when implementing move with parentKey. MoveEventInfo[] moveInfo = moves - .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) + .Select(x => + { + TryGetParentKey(x.Item1.ParentId, out Guid? itemParentKey); + return new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId, itemParentKey); + }) .ToArray(); scope.Notifications.Publish( @@ -2834,8 +2838,8 @@ public class ContentService : RepositoryService, IContentService using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - // FIXME: Pass parent key in constructor too when proper Copy method is implemented - if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(content, copy, parentId, eventMessages))) + TryGetParentKey(parentId, out Guid? parentKey); + if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(content, copy, parentId, parentKey, eventMessages))) { scope.Complete(); return null; @@ -2907,8 +2911,7 @@ public class ContentService : RepositoryService, IContentService IContent descendantCopy = descendant.DeepCloneWithResetIdentities(); descendantCopy.ParentId = parentId; - // FIXME: Pass parent key in constructor too when proper Copy method is implemented - if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(descendant, descendantCopy, parentId, eventMessages))) + if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(descendant, descendantCopy, parentId, parentKey, eventMessages))) { continue; } @@ -2945,8 +2948,7 @@ public class ContentService : RepositoryService, IContentService new ContentTreeChangeNotification(copy, TreeChangeTypes.RefreshBranch, eventMessages)); foreach (Tuple x in CollectionsMarshal.AsSpan(copies)) { - // FIXME: Pass parent key in constructor too when proper Copy method is implemented - scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId, relateToOriginal, eventMessages)); + scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId, parentKey, relateToOriginal, eventMessages)); } Audit(AuditType.Copy, userId, content.Id); @@ -2957,6 +2959,13 @@ public class ContentService : RepositoryService, IContentService return copy; } + private bool TryGetParentKey(int parentId, [NotNullWhen(true)] out Guid? parentKey) + { + Attempt parentKeyAttempt = _idKeyMap.GetKeyForId(parentId, UmbracoObjectTypes.Document); + parentKey = parentKeyAttempt.Success ? parentKeyAttempt.Result : null; + return parentKeyAttempt.Success; + } + /// /// Sends an to Publication, which executes handlers and events for the 'Send to Publication' /// action. From 47e09efec645d9bab63ce999f84da80c2221f466 Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Thu, 27 Mar 2025 12:39:09 +0000 Subject: [PATCH 17/23] Fixes Dropdown property-editor validation (#18845) --- .../property-editor-ui-dropdown.element.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts index 9be8531819..00060a0a99 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dropdown/property-editor-ui-dropdown.element.ts @@ -1,4 +1,5 @@ import { css, customElement, html, map, nothing, property, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; import { UUISelectElement } from '@umbraco-cms/backoffice/external/uui'; @@ -6,8 +7,7 @@ import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/property-editor'; -import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import type { UmbInputDropdownListElement } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-dropdown @@ -30,7 +30,7 @@ export class UmbPropertyEditorUIDropdownElement @property({ type: Array }) public override set value(value: Array | string | undefined) { - this.#selection = Array.isArray(value) ? value : value ? [value] : []; + this.#selection = this.#ensureValueIsArray(value); } public override get value(): Array | undefined { return this.#selection; @@ -97,7 +97,11 @@ export class UmbPropertyEditorUIDropdownElement } } - #onChange(event: UUISelectEvent) { + #ensureValueIsArray(value: Array | string | null | undefined): Array { + return Array.isArray(value) ? value : value ? [value] : []; + } + + #onChange(event: CustomEvent & { target: UmbInputDropdownListElement }) { const value = event.target.value as string; this.#setValue(value ? [value] : []); } @@ -110,6 +114,8 @@ export class UmbPropertyEditorUIDropdownElement #setValue(value: Array | string | null | undefined) { if (!value) return; + const selection = this.#ensureValueIsArray(value); + this._options.forEach((item) => (item.selected = selection.includes(item.value))); this.value = value; this.dispatchEvent(new UmbChangeEvent()); } From 52bf6bc41237b1a19e0316afe1f80f1595e7aa4e Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 27 Mar 2025 15:20:36 +0100 Subject: [PATCH 18/23] feat: disables the internal dropzone if multiple=false and an upload is in progress (#18847) you need to clear the files/queue before trying to upload something else, unless multiple=true --- .../input-dropzone/input-dropzone.element.ts | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.element.ts index 8515f2f673..5754ba38a8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/components/input-dropzone/input-dropzone.element.ts @@ -74,6 +74,16 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin 0); + } + constructor() { super(); @@ -107,7 +117,7 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin @@ -132,7 +142,6 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin Date: Fri, 28 Mar 2025 08:49:38 +0100 Subject: [PATCH 19/23] Render Property "vary by segment"-toggle (#18813) * render toggle for vary by segment * use property-layout for appearance * simplify labels * remove duplicates * render tag for vary by segment --- .../src/assets/lang/en-us.ts | 10 ---- .../src/assets/lang/en.ts | 4 +- ...ent-type-design-editor-property.element.ts | 5 ++ ...roperty-workspace-view-settings.element.ts | 55 ++++++++++++------- 4 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts index bf7d91a548..df5bd8c8dc 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts @@ -1705,16 +1705,6 @@ export default { compositionUsageHeading: 'Where is this composition used?', compositionUsageSpecification: 'This composition is currently used in the composition of the following\n Content Types:\n ', - variantsHeading: 'Allow variations', - cultureVariantHeading: 'Allow vary by culture', - segmentVariantHeading: 'Allow segmentation', - cultureVariantLabel: 'Vary by culture', - segmentVariantLabel: 'Vary by segments', - variantsDescription: 'Allow editors to create content of this type in different languages.', - cultureVariantDescription: 'Allow editors to create content of different languages.', - segmentVariantDescription: 'Allow editors to create segments of this content.', - allowVaryByCulture: 'Allow varying by culture', - allowVaryBySegment: 'Allow segmentation', elementType: 'Element Type', elementHeading: 'Is an Element Type', elementDescription: diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index 4f29dcb7f1..39f7edc0ac 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -1735,11 +1735,11 @@ export default { compositionUsageHeading: 'Where is this composition used?', compositionUsageSpecification: 'This composition is currently used in the composition of the following\n Content Types:\n ', - variantsHeading: 'Allow variations', + variantsHeading: 'Variation', cultureVariantHeading: 'Allow vary by culture', segmentVariantHeading: 'Allow segmentation', cultureVariantLabel: 'Vary by culture', - segmentVariantLabel: 'Vary by segments', + segmentVariantLabel: 'Vary by segment', variantsDescription: 'Allow editors to create content of this type in different languages.', cultureVariantDescription: 'Allow editors to create content of different languages.', segmentVariantDescription: 'Allow editors to create segments of this content.', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts index 63ef5a5567..5b8cf05294 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts @@ -299,6 +299,11 @@ export class UmbContentTypeDesignEditorPropertyElement extends UmbLitElement { ${this.localize.term('contentTypeEditor_cultureVariantLabel')} ` : nothing} + ${this.property.variesBySegment + ? html` + ${this.localize.term('contentTypeEditor_segmentVariantLabel')} + ` + : nothing} ${this.property.appearance?.labelOnTop == true ? html` ${this.localize.term('contentTypeEditor_displaySettingsLabelOnTop')} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts index 56db8cbf59..8c069f12d1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts @@ -203,9 +203,12 @@ export class UmbPropertyTypeWorkspaceViewSettingsElement extends UmbLitElement i #onVaryByCultureChange(event: UUIBooleanInputEvent) { const variesByCulture = event.target.checked; - this.updateValue({ - variesByCulture, - }); + this.updateValue({ variesByCulture }); + } + + #onVaryBySegmentChange(event: UUIBooleanInputEvent) { + const variesBySegment = event.target.checked; + this.updateValue({ variesBySegment }); } override render() { @@ -267,14 +270,11 @@ export class UmbPropertyTypeWorkspaceViewSettingsElement extends UmbLitElement i

${this.#renderCustomValidation()}
-
${this.#renderVariationControls()} -
- - Appearance - -
${this.#renderAlignLeftIcon()} ${this.#renderAlignTopIcon()}
-
+ +
${this.#renderAlignLeftIcon()} ${this.#renderAlignTopIcon()}
+
+ ${this.#renderMemberTypeOptions()} `; @@ -405,18 +405,35 @@ export class UmbPropertyTypeWorkspaceViewSettingsElement extends UmbLitElement i #renderVariationControls() { return this._contentTypeVariesByCulture || this._contentTypeVariesBySegment - ? html`
- Allow variations - ${this._contentTypeVariesByCulture ? this.#renderVaryByCulture() : ''} -
-
` + ? html` + + ${this._contentTypeVariesByCulture ? this.#renderVaryByCulture() : nothing} + ${this._contentTypeVariesBySegment ? this.#renderVaryBySegment() : nothing} + + ` : ''; } + #renderVaryByCulture() { - return html` `; + return html` +
+ +
+ `; + } + + #renderVaryBySegment() { + return html` +
+ +
+ `; } static override styles = [ From 685a05827e797c22d2daff00fb5a6b8a944b1b8b Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 28 Mar 2025 11:16:03 +0100 Subject: [PATCH 20/23] Adds MemberTwoFactorLoginService. (#18810) --- .../DependencyInjection/UmbracoBuilder.cs | 1 + .../Services/IMemberTwoFactorLoginService.cs | 37 ++++++++++ .../Services/ITwoFactorLoginService.cs | 6 +- .../Services/MemberTwoFactorLoginService.cs | 72 +++++++++++++++++++ .../Services/UserTwoFactorLoginService.cs | 6 +- 5 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs create mode 100644 src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 3d36c67d3a..24cc907339 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -422,6 +422,7 @@ namespace Umbraco.Cms.Core.DependencyInjection // Two factor providers Services.AddUnique(); Services.AddUnique(); + Services.AddUnique(); // Add Query services Services.AddUnique(); diff --git a/src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs b/src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs new file mode 100644 index 0000000000..3a7ec99c8e --- /dev/null +++ b/src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs @@ -0,0 +1,37 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +/// +/// A member specific Two factor service, that ensures the member exists before doing the job. +/// +public interface IMemberTwoFactorLoginService +{ + /// + /// Disables a specific two factor provider on a specific member. + /// + Task> DisableAsync(Guid memberKey, string providerName); + + /// + /// Gets the two factor providers on a specific member. + /// + Task, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid memberKey); + + /// + /// The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by + /// the provider. + /// + Task> GetSetupInfoAsync(Guid memberKey, string providerName); + + /// + /// Validates and Saves. + /// + Task> ValidateAndSaveAsync(string providerName, Guid memberKey, string modelSecret, string modelCode); + + /// + /// Disables 2FA with Code. + /// + Task> DisableByCodeAsync(string providerName, Guid memberKey, string code); +} diff --git a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs index a5ad0d84e8..01558376e3 100644 --- a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs +++ b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs @@ -29,7 +29,7 @@ public interface ITwoFactorLoginService : IService /// The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by /// the provider. /// - [Obsolete("Use IUserTwoFactorLoginService.GetSetupInfoAsync. This will be removed in Umbraco 15.")] + [Obsolete("Use IUserTwoFactorLoginService.GetSetupInfoAsync or IMemberTwoFactorLoginService.GetSetupInfoAsync. Scheduled for removal in Umbraco 16.")] Task GetSetupInfoAsync(Guid userOrMemberKey, string providerName); /// @@ -60,13 +60,13 @@ public interface ITwoFactorLoginService : IService /// /// Disables 2FA with Code. /// - [Obsolete("Use IUserTwoFactorLoginService.DisableByCodeAsync. This will be removed in Umbraco 15.")] + [Obsolete("Use IUserTwoFactorLoginService.DisableByCodeAsync or IMemberTwoFactorLoginService.DisableByCodeAsync. Scheduled for removal in Umbraco 16.")] Task DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code); /// /// Validates and Saves. /// - [Obsolete("Use IUserTwoFactorLoginService.ValidateAndSaveAsync. This will be removed in Umbraco 15.")] + [Obsolete("Use IUserTwoFactorLoginService.ValidateAndSaveAsync or IMemberTwoFactorLoginService.ValidateAndSaveAsync. Scheduled for removal in Umbraco 16.")] Task ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code); } diff --git a/src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs b/src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs new file mode 100644 index 0000000000..b21558b834 --- /dev/null +++ b/src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs @@ -0,0 +1,72 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +/// +internal class MemberTwoFactorLoginService : TwoFactorLoginServiceBase, IMemberTwoFactorLoginService +{ + private readonly IMemberService _memberService; + + public MemberTwoFactorLoginService( + ITwoFactorLoginService twoFactorLoginService, + IEnumerable twoFactorSetupGenerators, + IMemberService memberService, + ICoreScopeProvider scopeProvider) + : base(twoFactorLoginService, twoFactorSetupGenerators, scopeProvider) => + _memberService = memberService; + + /// + public override async Task> DisableAsync(Guid memberKey, string providerName) + { + IMember? member = _memberService.GetByKey(memberKey); + + if (member is null) + { + return Attempt.Fail(TwoFactorOperationStatus.UserNotFound); + } + + return await base.DisableAsync(memberKey, providerName); + } + + /// + public override async Task, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid memberKey) + { + IMember? member = _memberService.GetByKey(memberKey); + + if (member is null) + { + return Attempt.FailWithStatus(TwoFactorOperationStatus.UserNotFound, Enumerable.Empty()); + } + + return await base.GetProviderNamesAsync(memberKey); + } + + /// + public override async Task> GetSetupInfoAsync(Guid memberKey, string providerName) + { + IMember? member = _memberService.GetByKey(memberKey); + + if (member is null) + { + return Attempt.FailWithStatus(TwoFactorOperationStatus.UserNotFound, new NoopSetupTwoFactorModel()); + } + + return await base.GetSetupInfoAsync(memberKey, providerName); + } + + /// + public override async Task> ValidateAndSaveAsync(string providerName, Guid memberKey, string secret, string code) + { + IMember? member = _memberService.GetByKey(memberKey); + + if (member is null) + { + return Attempt.Fail(TwoFactorOperationStatus.UserNotFound); + } + + return await base.ValidateAndSaveAsync(providerName, memberKey, secret, code); + } +} diff --git a/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs b/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs index 89241d31f2..10a9de140f 100644 --- a/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs +++ b/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs @@ -32,7 +32,7 @@ internal class UserTwoFactorLoginService : TwoFactorLoginServiceBase, IUserTwoFa return await base.DisableAsync(userKey, providerName); } - /// + /// public override async Task, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid userKey) { IUser? user = await _userService.GetAsync(userKey); @@ -45,7 +45,7 @@ internal class UserTwoFactorLoginService : TwoFactorLoginServiceBase, IUserTwoFa return await base.GetProviderNamesAsync(userKey); } - /// + /// public override async Task> GetSetupInfoAsync(Guid userKey, string providerName) { IUser? user = await _userService.GetAsync(userKey); @@ -58,7 +58,7 @@ internal class UserTwoFactorLoginService : TwoFactorLoginServiceBase, IUserTwoFa return await base.GetSetupInfoAsync(userKey, providerName); } - /// + /// public override async Task> ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code) { IUser? user = await _userService.GetAsync(userKey); From aca908c549173d388e7fb49848768d7f016b7f47 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 28 Mar 2025 11:16:03 +0100 Subject: [PATCH 21/23] Adds MemberTwoFactorLoginService. (#18810) --- .../DependencyInjection/UmbracoBuilder.cs | 1 + .../Services/IMemberTwoFactorLoginService.cs | 37 ++++++++++ .../Services/ITwoFactorLoginService.cs | 6 +- .../Services/MemberTwoFactorLoginService.cs | 72 +++++++++++++++++++ .../Services/UserTwoFactorLoginService.cs | 6 +- 5 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs create mode 100644 src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 3d36c67d3a..24cc907339 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -422,6 +422,7 @@ namespace Umbraco.Cms.Core.DependencyInjection // Two factor providers Services.AddUnique(); Services.AddUnique(); + Services.AddUnique(); // Add Query services Services.AddUnique(); diff --git a/src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs b/src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs new file mode 100644 index 0000000000..3a7ec99c8e --- /dev/null +++ b/src/Umbraco.Core/Services/IMemberTwoFactorLoginService.cs @@ -0,0 +1,37 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +/// +/// A member specific Two factor service, that ensures the member exists before doing the job. +/// +public interface IMemberTwoFactorLoginService +{ + /// + /// Disables a specific two factor provider on a specific member. + /// + Task> DisableAsync(Guid memberKey, string providerName); + + /// + /// Gets the two factor providers on a specific member. + /// + Task, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid memberKey); + + /// + /// The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by + /// the provider. + /// + Task> GetSetupInfoAsync(Guid memberKey, string providerName); + + /// + /// Validates and Saves. + /// + Task> ValidateAndSaveAsync(string providerName, Guid memberKey, string modelSecret, string modelCode); + + /// + /// Disables 2FA with Code. + /// + Task> DisableByCodeAsync(string providerName, Guid memberKey, string code); +} diff --git a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs index a5ad0d84e8..01558376e3 100644 --- a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs +++ b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs @@ -29,7 +29,7 @@ public interface ITwoFactorLoginService : IService /// The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by /// the provider. /// - [Obsolete("Use IUserTwoFactorLoginService.GetSetupInfoAsync. This will be removed in Umbraco 15.")] + [Obsolete("Use IUserTwoFactorLoginService.GetSetupInfoAsync or IMemberTwoFactorLoginService.GetSetupInfoAsync. Scheduled for removal in Umbraco 16.")] Task GetSetupInfoAsync(Guid userOrMemberKey, string providerName); /// @@ -60,13 +60,13 @@ public interface ITwoFactorLoginService : IService /// /// Disables 2FA with Code. /// - [Obsolete("Use IUserTwoFactorLoginService.DisableByCodeAsync. This will be removed in Umbraco 15.")] + [Obsolete("Use IUserTwoFactorLoginService.DisableByCodeAsync or IMemberTwoFactorLoginService.DisableByCodeAsync. Scheduled for removal in Umbraco 16.")] Task DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code); /// /// Validates and Saves. /// - [Obsolete("Use IUserTwoFactorLoginService.ValidateAndSaveAsync. This will be removed in Umbraco 15.")] + [Obsolete("Use IUserTwoFactorLoginService.ValidateAndSaveAsync or IMemberTwoFactorLoginService.ValidateAndSaveAsync. Scheduled for removal in Umbraco 16.")] Task ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code); } diff --git a/src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs b/src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs new file mode 100644 index 0000000000..b21558b834 --- /dev/null +++ b/src/Umbraco.Core/Services/MemberTwoFactorLoginService.cs @@ -0,0 +1,72 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +/// +internal class MemberTwoFactorLoginService : TwoFactorLoginServiceBase, IMemberTwoFactorLoginService +{ + private readonly IMemberService _memberService; + + public MemberTwoFactorLoginService( + ITwoFactorLoginService twoFactorLoginService, + IEnumerable twoFactorSetupGenerators, + IMemberService memberService, + ICoreScopeProvider scopeProvider) + : base(twoFactorLoginService, twoFactorSetupGenerators, scopeProvider) => + _memberService = memberService; + + /// + public override async Task> DisableAsync(Guid memberKey, string providerName) + { + IMember? member = _memberService.GetByKey(memberKey); + + if (member is null) + { + return Attempt.Fail(TwoFactorOperationStatus.UserNotFound); + } + + return await base.DisableAsync(memberKey, providerName); + } + + /// + public override async Task, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid memberKey) + { + IMember? member = _memberService.GetByKey(memberKey); + + if (member is null) + { + return Attempt.FailWithStatus(TwoFactorOperationStatus.UserNotFound, Enumerable.Empty()); + } + + return await base.GetProviderNamesAsync(memberKey); + } + + /// + public override async Task> GetSetupInfoAsync(Guid memberKey, string providerName) + { + IMember? member = _memberService.GetByKey(memberKey); + + if (member is null) + { + return Attempt.FailWithStatus(TwoFactorOperationStatus.UserNotFound, new NoopSetupTwoFactorModel()); + } + + return await base.GetSetupInfoAsync(memberKey, providerName); + } + + /// + public override async Task> ValidateAndSaveAsync(string providerName, Guid memberKey, string secret, string code) + { + IMember? member = _memberService.GetByKey(memberKey); + + if (member is null) + { + return Attempt.Fail(TwoFactorOperationStatus.UserNotFound); + } + + return await base.ValidateAndSaveAsync(providerName, memberKey, secret, code); + } +} diff --git a/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs b/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs index 89241d31f2..10a9de140f 100644 --- a/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs +++ b/src/Umbraco.Core/Services/UserTwoFactorLoginService.cs @@ -32,7 +32,7 @@ internal class UserTwoFactorLoginService : TwoFactorLoginServiceBase, IUserTwoFa return await base.DisableAsync(userKey, providerName); } - /// + /// public override async Task, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid userKey) { IUser? user = await _userService.GetAsync(userKey); @@ -45,7 +45,7 @@ internal class UserTwoFactorLoginService : TwoFactorLoginServiceBase, IUserTwoFa return await base.GetProviderNamesAsync(userKey); } - /// + /// public override async Task> GetSetupInfoAsync(Guid userKey, string providerName) { IUser? user = await _userService.GetAsync(userKey); @@ -58,7 +58,7 @@ internal class UserTwoFactorLoginService : TwoFactorLoginServiceBase, IUserTwoFa return await base.GetSetupInfoAsync(userKey, providerName); } - /// + /// public override async Task> ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code) { IUser? user = await _userService.GetAsync(userKey); From 6dc4eca607f934dfd15c77cb5a98199b9a508397 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 28 Mar 2025 11:24:52 +0100 Subject: [PATCH 22/23] Fixes null reference exception triggered when configuring to hide disabled users in the backoffice (#18823) * Fixes null reference exception triggered when configuring to hide disabled users in the backoffice. * Updated integration test suppressions. --- src/Umbraco.Core/Services/UserService.cs | 11 ++++---- .../CompatibilitySuppressions.xml | 7 +++++ .../Services/UserServiceCrudTests.Filter.cs | 26 ++++++++++++------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 659a98ea07..c392011eca 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -1338,7 +1338,6 @@ internal partial class UserService : RepositoryService, IUserService includedUserGroupAliases = userGroupKeyConversionAttempt.Result.ToArray(); } - if (mergedFilter.NameFilters is not null) { foreach (var nameFilter in mergedFilter.NameFilters) @@ -1357,17 +1356,19 @@ internal partial class UserService : RepositoryService, IUserService } else { - includeUserStates = new HashSet(filter.IncludeUserStates!); - includeUserStates.IntersectWith(baseFilter.IncludeUserStates); + includeUserStates = new HashSet(baseFilter.IncludeUserStates); + if (filter.IncludeUserStates is not null && filter.IncludeUserStates.Contains(UserState.All) is false) + { + includeUserStates.IntersectWith(filter.IncludeUserStates); + } // This means that we've only chosen to include a user state that is not allowed, so we'll return an empty result - if(includeUserStates.Count == 0) + if (includeUserStates.Count == 0) { return Attempt.SucceedWithStatus(UserOperationStatus.Success, new PagedModel()); } } - PaginationHelper.ConvertSkipTakeToPaging(skip, take, out long pageNumber, out int pageSize); Expression> orderByExpression = GetOrderByExpression(orderBy); diff --git a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml index 918b3ea4fe..7cf519f60f 100644 --- a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml +++ b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml @@ -78,6 +78,13 @@ lib/net9.0/Umbraco.Tests.Integration.dll true + + CP0002 + M:Umbraco.Cms.Tests.Integration.Umbraco.Core.Services.UserServiceCrudTests.Cannot_Request_Disabled_If_Hidden(Umbraco.Cms.Core.Models.Membership.UserState) + lib/net9.0/Umbraco.Tests.Integration.dll + lib/net9.0/Umbraco.Tests.Integration.dll + true + CP0002 M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.ContentPublishingServiceTests.Publish_Branch_Does_Not_Publish_Unpublished_Children_Unless_Explicitly_Instructed_To(System.Boolean) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Filter.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Filter.cs index 71b557d4d2..58d79bb2a2 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Filter.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Filter.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; @@ -10,12 +10,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; public partial class UserServiceCrudTests { - [Test] - [TestCase(UserState.Disabled)] - [TestCase(UserState.All)] - public async Task Cannot_Request_Disabled_If_Hidden(UserState includeState) + [TestCase(null, 1)] // Requesting no filter, will just get the admin user but not the created and disabled one. + // - verifies fix for https://github.com/umbraco/Umbraco-CMS/issues/18812 + [TestCase(UserState.Inactive, 1)] // Requesting inactive, will just get the admin user but not the created and disabled one. + [TestCase(UserState.Disabled, 0)] // Requesting disabled, won't get any as admin user isn't disabled and, whilst the created one is, disabled users are hidden. + [TestCase(UserState.All, 1)] // Requesting all, will just get the admin user but not the created and disabled one. + public async Task Cannot_Request_Disabled_If_Hidden(UserState? includeState, int expectedCount) { - var userService = CreateUserService(new SecuritySettings {HideDisabledUsersInBackOffice = true}); + var userService = CreateUserService(new SecuritySettings { HideDisabledUsersInBackOffice = true }); var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var createModel = new UserCreateModel @@ -23,21 +25,25 @@ public partial class UserServiceCrudTests UserName = "editor@mail.com", Email = "editor@mail.com", Name = "Editor", - UserGroupKeys = new HashSet { editorGroup.Key } + UserGroupKeys = new HashSet { editorGroup.Key }, }; var createAttempt = await userService.CreateAsync(Constants.Security.SuperUserKey, createModel, true); Assert.IsTrue(createAttempt.Success); var disableStatus = - await userService.DisableAsync(Constants.Security.SuperUserKey, new HashSet{ createAttempt.Result.CreatedUser!.Key }); + await userService.DisableAsync(Constants.Security.SuperUserKey, new HashSet { createAttempt.Result.CreatedUser!.Key }); Assert.AreEqual(UserOperationStatus.Success, disableStatus); - var filter = new UserFilter {IncludeUserStates = new HashSet {includeState}}; + var filter = new UserFilter(); + if (includeState.HasValue) + { + filter.IncludeUserStates = new HashSet { includeState.Value }; + } var filterAttempt = await userService.FilterAsync(Constants.Security.SuperUserKey, filter, 0, 1000); Assert.IsTrue(filterAttempt.Success); - Assert.AreEqual(0, filterAttempt.Result.Items.Count()); + Assert.AreEqual(expectedCount, filterAttempt.Result.Items.Count()); } [Test] From 6f63837dbccef643407c7dd4ec6f6c4536858187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 28 Mar 2025 11:49:07 +0100 Subject: [PATCH 23/23] make load more 100% wide (#18821) --- .../packages/core/tree/default/default-tree.element.ts | 10 ++++++++-- .../documents/documents/tree/document-tree.element.ts | 5 ++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/default/default-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/default/default-tree.element.ts index 3ad8bf25c8..cd1cd41961 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/default/default-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/default/default-tree.element.ts @@ -9,7 +9,7 @@ import type { UmbTreeExpansionModel } from '../expansion-manager/types.js'; import type { UmbDefaultTreeContext } from './default-tree.context.js'; import { UMB_TREE_CONTEXT } from './default-tree.context-token.js'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; -import { html, nothing, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { html, nothing, customElement, property, state, repeat, css } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @customElement('umb-default-tree') @@ -171,8 +171,14 @@ export class UmbDefaultTreeElement extends UmbLitElement { return nothing; } - return html` `; + return html` `; } + + static override styles = css` + #load-more { + width: 100%; + } + `; } export default UmbDefaultTreeElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.element.ts index fd02f52a8d..a57478d279 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.element.ts @@ -1,14 +1,13 @@ import { customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbDefaultTreeElement } from '@umbraco-cms/backoffice/tree'; -const elementName = 'umb-document-tree'; -@customElement(elementName) +@customElement('umb-document-tree') export class UmbDocumentTreeElement extends UmbDefaultTreeElement {} export { UmbDocumentTreeElement as element }; declare global { interface HTMLElementTagNameMap { - [elementName]: UmbDocumentTreeElement; + 'umb-document-tree': UmbDocumentTreeElement; } }