From e1692f867f462fc2f6ebfab16fdc61a8c3f44058 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Fri, 26 Apr 2024 04:22:44 +0200 Subject: [PATCH 1/5] Skips failing test (#16148) --- .../DefaultConfig/Settings/PartialView/PartialView.spec.ts | 6 ++++-- .../tests/DefaultConfig/Settings/Template/Templates.spec.ts | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts index d493fa44c9..d917cf2648 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts @@ -194,7 +194,8 @@ test.describe('Partial View tests', () => { expect(updatedPartialView.content).toBe(expectedTemplateContent); }); - test('can insert dictionary item into a partial view', async ({umbracoApi, umbracoUi}) => { + // TODO: Remove skip when the front-end is ready. Currently the returned items count is not updated after choosing the root content. + test.skip('can insert dictionary item into a partial view', async ({umbracoApi, umbracoUi}) => { // Arrange await umbracoApi.partialView.create(partialViewFileName, defaultPartialViewContent, '/'); expect(await umbracoApi.partialView.doesExist(partialViewFileName)).toBeTruthy(); @@ -215,7 +216,8 @@ test.describe('Partial View tests', () => { expect(partialViewData.content).toBe(partialViewContent); }); - test('can insert value into a partial view', async ({umbracoApi, umbracoUi}) => { + // TODO: Update the value of the System Field in the testHelpers. There has been changes to the SystemField Name. + test.skip('can insert value into a partial view', async ({umbracoApi, umbracoUi}) => { // Arrange await umbracoApi.partialView.create(partialViewFileName, defaultPartialViewContent, '/'); expect(await umbracoApi.partialView.doesExist(partialViewFileName)).toBeTruthy(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Template/Templates.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Template/Templates.spec.ts index f6bb6ecae1..bff04f7bd2 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Template/Templates.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Template/Templates.spec.ts @@ -325,7 +325,8 @@ test.describe('Template tests', () => { expect(templateData.content).toBe(templateContent); }); - test('can insert value into a template', async ({umbracoApi, umbracoUi}) => { + // TODO: Update the value of the System Field in the testHelpers. There has been changes to the SystemField Name. + test.skip('can insert value into a template', async ({umbracoApi, umbracoUi}) => { // Arrange await umbracoApi.template.createDefaultTemplate(templateName); const systemFieldValue = 'createDate'; From fcbfecd28e8d83e38ea3a6e206db92a2e4bb2c3a Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Fri, 26 Apr 2024 08:29:50 +0200 Subject: [PATCH 2/5] Reverse boolean check, to not fail if valid (#16153) --- .../PropertyEditors/TemporaryFileUploadValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/TemporaryFileUploadValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/TemporaryFileUploadValidator.cs index 4aa7d1a211..d53fe22068 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/TemporaryFileUploadValidator.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/TemporaryFileUploadValidator.cs @@ -56,7 +56,7 @@ internal class TemporaryFileUploadValidator : IValueValidator } ContentSettings contentSettings = _getContentSettings(); - if (contentSettings.IsFileAllowedForUpload(extension) || (_validateFileType != null && _validateFileType(extension, dataTypeConfiguration) == false)) + if (contentSettings.IsFileAllowedForUpload(extension) is false || (_validateFileType != null && _validateFileType(extension, dataTypeConfiguration) == false)) { yield return new ValidationResult( $"The file type for file name \"{temporaryFile.FileName}\" is not valid for upload", From e296c173ccb72daa89632c77451979f7a2352f82 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 26 Apr 2024 08:32:42 +0200 Subject: [PATCH 3/5] V14: add authorized logout callback path (#16152) * add option to SecuritySettings.cs to allow the developer to set an authorized logout callback path in line with the login callback path * allow clients using the "Umbraco back-office access" descriptor to return back to either the login path or the logout path --- .../Security/BackOfficeApplicationManager.cs | 8 +++++--- .../Configuration/Models/SecuritySettings.cs | 12 +++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Security/BackOfficeApplicationManager.cs b/src/Umbraco.Cms.Api.Management/Security/BackOfficeApplicationManager.cs index 2bdc791d34..455d70fcba 100644 --- a/src/Umbraco.Cms.Api.Management/Security/BackOfficeApplicationManager.cs +++ b/src/Umbraco.Cms.Api.Management/Security/BackOfficeApplicationManager.cs @@ -16,6 +16,7 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB private readonly IRuntimeState _runtimeState; private readonly Uri? _backOfficeHost; private readonly string _authorizeCallbackPathName; + private readonly string _authorizeCallbackLogoutPathName; public BackOfficeApplicationManager( IOpenIddictApplicationManager applicationManager, @@ -28,6 +29,7 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB _runtimeState = runtimeState; _backOfficeHost = securitySettings.Value.BackOfficeHost; _authorizeCallbackPathName = securitySettings.Value.AuthorizeCallbackPathName; + _authorizeCallbackLogoutPathName = securitySettings.Value.AuthorizeCallbackLogoutPathName; } public async Task EnsureBackOfficeApplicationAsync(Uri backOfficeUrl, CancellationToken cancellationToken = default) @@ -112,7 +114,7 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB PostLogoutRedirectUris = { CallbackUrl(_authorizeCallbackPathName), - CallbackUrl($"{_authorizeCallbackPathName.EnsureEndsWith("/")}logout") + CallbackUrl(_authorizeCallbackLogoutPathName), }, Permissions = { @@ -122,8 +124,8 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB OpenIddictConstants.Permissions.Endpoints.Revocation, OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, OpenIddictConstants.Permissions.GrantTypes.RefreshToken, - OpenIddictConstants.Permissions.ResponseTypes.Code - } + OpenIddictConstants.Permissions.ResponseTypes.Code, + }, }; } diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index 80a9b38d4f..118481b338 100644 --- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs @@ -26,6 +26,7 @@ public class SecuritySettings internal const int StaticMemberDefaultLockoutTimeInMinutes = 30 * 24 * 60; internal const int StaticUserDefaultLockoutTimeInMinutes = 30 * 24 * 60; internal const string StaticAuthorizeCallbackPathName = "/umbraco"; + internal const string StaticAuthorizeCallbackLogoutPathName = "/umbraco/logout"; internal const string StaticAuthorizeCallbackErrorPathName = "/umbraco/error"; /// @@ -113,11 +114,20 @@ public class SecuritySettings public Uri? BackOfficeHost { get; set; } /// - /// The path to use for authorization callback. Will be appended to the BackOfficeHost. + /// Gets or sets the path to use for authorization callback. Will be appended to the BackOfficeHost. /// [DefaultValue(StaticAuthorizeCallbackPathName)] public string AuthorizeCallbackPathName { get; set; } = StaticAuthorizeCallbackPathName; + /// + /// Gets or sets the path to use for authorization callback logout. Will be appended to the BackOfficeHost. + /// + [DefaultValue(StaticAuthorizeCallbackLogoutPathName)] + public string AuthorizeCallbackLogoutPathName { get; set; } = StaticAuthorizeCallbackLogoutPathName; + + /// + /// Gets or sets the path to use for authorization callback error. Will be appended to the BackOfficeHost. + /// [DefaultValue(StaticAuthorizeCallbackErrorPathName)] public string AuthorizeCallbackErrorPathName { get; set; } = StaticAuthorizeCallbackErrorPathName; } From 16a1f429851e1022b4e391698be40e1edabc7903 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 26 Apr 2024 10:43:33 +0200 Subject: [PATCH 4/5] Translate member group picker property editor value to V14 compatible format (and back again) (#16150) --- .../MemberGroupPickerPropertyEditor.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/Umbraco.Core/PropertyEditors/MemberGroupPickerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/MemberGroupPickerPropertyEditor.cs index 141d4140d7..f22c52b159 100644 --- a/src/Umbraco.Core/PropertyEditors/MemberGroupPickerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MemberGroupPickerPropertyEditor.cs @@ -1,3 +1,11 @@ +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; + namespace Umbraco.Cms.Core.PropertyEditors; [DataEditor( @@ -9,4 +17,65 @@ public class MemberGroupPickerPropertyEditor : DataEditor public MemberGroupPickerPropertyEditor(IDataValueEditorFactory dataValueEditorFactory) : base(dataValueEditorFactory) => SupportsReadOnly = true; + + protected override IDataValueEditor CreateValueEditor() => + DataValueEditorFactory.Create(Attribute!); + + private class MemberGroupPickerPropertyValueEditor : DataValueEditor + { + private readonly IMemberGroupService _memberGroupService; + + public MemberGroupPickerPropertyValueEditor( + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute, + IMemberGroupService memberGroupService) + : base(shortStringHelper, jsonSerializer, ioHelper, attribute) + => _memberGroupService = memberGroupService; + + public override object? ToEditor(IProperty property, string? culture = null, string? segment = null) + { + // the stored value is a CSV of member group integer IDs - need to transform them into the corresponding member group keys + var value = base.ToEditor(property, culture, segment); + if (value is not string stringValue || stringValue.IsNullOrWhiteSpace()) + { + return value; + } + + var memberGroupIds = stringValue + .Split(Constants.CharArrays.Comma) + .Select(memberGroupIdStringValue => + int.TryParse(memberGroupIdStringValue, out int memberId) ? memberId : -1) + .Where(id => id > 0) + .ToArray(); + + IEnumerable memberGroups = _memberGroupService.GetByIdsAsync(memberGroupIds).GetAwaiter().GetResult(); + return string.Join(',', memberGroups.Select(group => group.Key)); + } + + public override object? FromEditor(ContentPropertyData editorValue, object? currentValue) + { + // the editor value is a CSV of member group keys - need to store a CSV of the corresponding member group integer IDs + if (editorValue.Value is not string stringValue) + { + return null; + } + + Guid[] memberGroupKeys = stringValue + .Split(Constants.CharArrays.Comma) + .Select(memberGroupKeyStringValue => Guid.TryParse(memberGroupKeyStringValue, out Guid memberGroupKey) + ? memberGroupKey + : Guid.Empty) + .Where(memberGroupKey => memberGroupKey != Guid.Empty) + .ToArray(); + + IMemberGroup[] memberGroups = memberGroupKeys + .Select(memberGroupKey => _memberGroupService.GetAsync(memberGroupKey).GetAwaiter().GetResult()) + .WhereNotNull() + .ToArray(); + + return string.Join(',', memberGroups.Select(memberGroup => memberGroup.Id)); + } + } } From 9236d6a3b9eb9c8f10176be5ad0cdc2984497b6c Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 29 Apr 2024 07:07:15 +0200 Subject: [PATCH 5/5] V14 QA added acceptance tests for the media section (#16039) * Added media tests * Bumped versions * Bumped version of testhelpers * Cleaned up * Added additional media tests * Bumped version of playwright and testHelpers --- .../package-lock.json | 46 ++-- .../Umbraco.Tests.AcceptanceTest/package.json | 4 +- .../tests/DefaultConfig/Media/Media.spec.ts | 258 ++++++++++++++++++ 3 files changed, 290 insertions(+), 18 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index ff3d87c6c5..a981d8c7e5 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.5", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.38", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.40", "camelize": "^1.0.0", "dotenv": "^16.3.1", "faker": "^4.1.0", @@ -17,7 +17,7 @@ "xhr2": "^0.2.1" }, "devDependencies": { - "@playwright/test": "^1.38", + "@playwright/test": "^1.43", "@types/node": "^20.9.0", "del": "^6.0.0", "ncp": "^2.0.0", @@ -87,12 +87,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", - "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz", + "integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==", "dev": true, "dependencies": { - "playwright": "1.40.1" + "playwright": "1.43.1" }, "bin": { "playwright": "cli.js" @@ -146,9 +146,9 @@ "integrity": "sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==" }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "2.0.0-beta.38", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.38.tgz", - "integrity": "sha512-OkPTSmohkC9+6A+qfwkUFYRD4guR5hPXWMHtsPtvLyolm7PXvH5gb3XF5n88Jsv+7YM+IqIxQHkXmPaVmgIyrA==", + "version": "2.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.40.tgz", + "integrity": "sha512-rjGwdUv1itphmpmsqzy6OjAArZYhM/4bEd/RVb6536BeUNBCOYVqj/PvW9oRv8G2SRJH53XKiefro+btNXy/xw==", "dependencies": { "@umbraco/json-models-builders": "2.0.5", "camelize": "^1.0.0", @@ -425,6 +425,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -741,12 +755,12 @@ } }, "node_modules/playwright": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", - "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz", + "integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==", "dev": true, "dependencies": { - "playwright-core": "1.40.1" + "playwright-core": "1.43.1" }, "bin": { "playwright": "cli.js" @@ -759,9 +773,9 @@ } }, "node_modules/playwright-core": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", - "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz", + "integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==", "dev": true, "bin": { "playwright-core": "cli.js" diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 74bedb0ec9..3139c2d673 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -10,7 +10,7 @@ "createTest": "node createTest.js" }, "devDependencies": { - "@playwright/test": "^1.38", + "@playwright/test": "^1.43", "@types/node": "^20.9.0", "del": "^6.0.0", "ncp": "^2.0.0", @@ -21,7 +21,7 @@ }, "dependencies": { "@umbraco/json-models-builders": "^2.0.5", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.38", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.40", "camelize": "^1.0.0", "dotenv": "^16.3.1", "faker": "^4.1.0", diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts new file mode 100644 index 0000000000..25a9f96a91 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts @@ -0,0 +1,258 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from '@playwright/test'; + +test.describe('Media tests', () => { + const mediaFileName = 'TestMediaFile'; + const folderName = 'TestFolder'; + const mediaTypeName = 'File'; + + test.beforeEach(async ({umbracoUi, umbracoApi}) => { + await umbracoApi.media.ensureNameNotExists(mediaFileName); + await umbracoUi.goToBackOffice(); + }); + + test.afterEach(async ({umbracoApi}) => { + await umbracoApi.media.ensureNameNotExists(mediaFileName); + }); + + //TODO: It is currently possible to create an empty mediaFile, should not be possible + test.skip('can not create a empty media file', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + + // Act + await umbracoUi.media.clickCreateMediaItemButton(); + await umbracoUi.media.clickMediaTypeWithNameButton(mediaTypeName); + await umbracoUi.media.enterMediaItemName(mediaFileName); + await umbracoUi.media.clickSaveButton(); + + // Assert + await umbracoUi.media.isErrorNotificationVisible(); + await umbracoUi.media.isTreeItemVisible(mediaFileName, false); + expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); + }); + + test('can rename a media file', async ({umbracoApi, umbracoUi}) => { + // Arrange + const wrongMediaFileName = 'NotACorrectName'; + await umbracoApi.media.ensureNameNotExists(wrongMediaFileName); + await umbracoApi.media.createDefaultMedia(wrongMediaFileName, mediaTypeName); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + + // Arrange + await umbracoUi.media.clickLabelWithName(wrongMediaFileName, true); + await umbracoUi.media.enterMediaItemName(mediaFileName); + await umbracoUi.media.clickSaveButton(); + + // Assert + await umbracoUi.media.isSuccessNotificationVisible(); + await umbracoUi.media.isTreeItemVisible(mediaFileName); + expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeTruthy(); + }); + + // The File type is skipped because there are frontend issues with the mediaType + const mediaFileTypes = [ + {fileName: 'Article', filePath: 'Article.pdf'}, + {fileName: 'Audio', filePath: 'Audio.mp3'}, + // {fileName: 'File', filePath: 'File.txt'}, + {fileName: 'Image', filePath: 'Umbraco.png'}, + {fileName: 'Vector Graphics (SVG)', filePath: 'VectorGraphics.svg'}, + {fileName: 'Video', filePath: 'Video.mp4'} + ]; + + for (const mediaFileType of mediaFileTypes) { + test(`can create a media file with the ${mediaFileType.fileName} type`, async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.media.ensureNameNotExists(mediaFileType.fileName); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + + // Act + await umbracoUi.media.clickCreateMediaItemButton(); + await umbracoUi.media.clickMediaTypeWithNameButton(mediaFileType.fileName); + await umbracoUi.media.enterMediaItemName(mediaFileType.fileName); + await umbracoUi.media.changeFileTypeWithFileChooser('./fixtures/mediaLibrary/' + mediaFileType.filePath); + await umbracoUi.media.clickSaveButton(); + + // Assert + await umbracoUi.media.isSuccessNotificationVisible(); + await umbracoUi.media.isTreeItemVisible(mediaFileType.fileName); + expect(await umbracoApi.media.doesNameExist(mediaFileType.fileName)).toBeTruthy(); + + // Clean + await umbracoApi.media.ensureNameNotExists(mediaFileType.fileName); + }); + } + + test('can delete a media file', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.media.createDefaultMedia(mediaFileName, mediaTypeName); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + await umbracoApi.media.doesNameExist(mediaFileName); + + // Act + await umbracoUi.media.deleteMediaItem(mediaFileName); + + // Assert + await umbracoUi.media.isSuccessNotificationVisible(); + await umbracoUi.media.isTreeItemVisible(mediaFileName, false); + expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); + }); + + test('can create a folder', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.media.ensureNameNotExists(folderName); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + + // Act + await umbracoUi.media.clickCreateMediaItemButton(); + await umbracoUi.media.clickMediaTypeWithNameButton('Folder'); + await umbracoUi.media.enterMediaItemName(folderName); + await umbracoUi.media.clickSaveButton(); + + // Assert + await umbracoUi.media.isSuccessNotificationVisible(); + await umbracoUi.media.isTreeItemVisible(folderName); + expect(await umbracoApi.media.doesNameExist(folderName)).toBeTruthy(); + + // Clean + await umbracoApi.media.ensureNameNotExists(folderName); + }); + + test('can delete a folder', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.media.ensureNameNotExists(folderName); + await umbracoApi.media.createDefaultMediaFolder(folderName); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + await umbracoApi.media.doesNameExist(folderName); + + // Act + await umbracoUi.media.clickActionsMenuForName(folderName); + await umbracoUi.media.clickDeleteButton(); + await umbracoUi.media.clickConfirmToDeleteButton(); + + // Assert + await umbracoUi.media.isTreeItemVisible(folderName, false); + expect(await umbracoApi.media.doesNameExist(folderName)).toBeFalsy(); + }); + + test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { + // Arrange + const parentFolderName = 'ParentFolder'; + await umbracoApi.media.ensureNameNotExists(parentFolderName); + await umbracoApi.media.createDefaultMediaFolder(parentFolderName); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + + // Act + await umbracoUi.media.clickActionsMenuForName(parentFolderName); + await umbracoUi.media.clickCreateModalButton(); + await umbracoUi.media.clickExactLinkWithName('Folder'); + await umbracoUi.media.enterMediaItemName(folderName); + await umbracoUi.media.clickSaveButton(); + + // Assert + await umbracoUi.media.isSuccessNotificationVisible(); + await umbracoUi.media.isTreeItemVisible(parentFolderName); + await umbracoUi.media.clickMediaCaretButtonForName(parentFolderName); + await umbracoUi.media.isTreeItemVisible(folderName); + + // Clean + await umbracoApi.media.ensureNameNotExists(parentFolderName); + }); + + test('can search for a media file', async ({umbracoApi, umbracoUi}) => { + // Arrange + const secondMediaFile = 'SecondMediaFile'; + await umbracoApi.media.ensureNameNotExists(secondMediaFile); + await umbracoApi.media.createDefaultMedia(mediaFileName, mediaTypeName); + await umbracoApi.media.createDefaultMedia(secondMediaFile, mediaTypeName); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + + // Act + await umbracoUi.media.searchForMediaItemByName(mediaFileName); + + // Assert + await umbracoUi.media.doesMediaCardsContainAmount(1); + await umbracoUi.media.doesMediaCardContainText(mediaFileName); + + // Clean + await umbracoApi.media.ensureNameNotExists(secondMediaFile); + }); + + test('can trash a media item', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.media.emptyRecycleBin(); + await umbracoApi.media.createDefaultMedia(mediaFileName, mediaTypeName); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + await umbracoApi.media.doesNameExist(mediaFileName); + + // Act + await umbracoUi.media.clickActionsMenuForName(mediaFileName); + await umbracoUi.media.clickTrashButton(); + await umbracoUi.media.clickConfirmTrashButton(); + + // Assert + await umbracoUi.media.isMediaItemVisibleInRecycleBin(mediaFileName); + expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); + expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeTruthy(); + + // Clean + await umbracoApi.media.emptyRecycleBin(); + }); + + test('can restore a media item from the recycle bin', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.media.emptyRecycleBin(); + await umbracoApi.media.createDefaultMedia(mediaFileName, mediaTypeName); + await umbracoApi.media.trashMediaItem(mediaFileName); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + + // Act + await umbracoUi.media.reloadRecycleBin(); + await umbracoUi.media.restoreMediaItem(mediaFileName); + + // Assert + await umbracoUi.media.isMediaItemVisibleInRecycleBin(mediaFileName, false); + await umbracoUi.media.isTreeItemVisible(mediaFileName); + expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeTruthy(); + expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeFalsy(); + + // Clean + await umbracoApi.media.emptyRecycleBin(); + }); + + // TODO: unskip when the frontend is ready. Currently you are unable to delete a media item from the recycle bin. You have to empty the recycle bin. + test.skip('can delete a media item from the recycle bin', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.media.emptyRecycleBin(); + await umbracoApi.media.createDefaultMedia(mediaFileName, mediaTypeName); + await umbracoApi.media.trashMediaItem(mediaFileName); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + + // Act + await umbracoUi.media.isMediaItemVisibleInRecycleBin(mediaFileName); + await umbracoUi.media.deleteMediaItem(mediaFileName); + + // Assert + await umbracoUi.media.isMediaItemVisibleInRecycleBin(mediaFileName); + expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); + expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeFalsy(); + }); + + test('can empty the recycle bin', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.media.emptyRecycleBin(); + await umbracoApi.media.createDefaultMedia(mediaFileName, mediaTypeName); + await umbracoApi.media.trashMediaItem(mediaFileName); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + + // Act + await umbracoUi.media.isMediaItemVisibleInRecycleBin(mediaFileName); + await umbracoUi.media.clickEmptyRecycleBinButton(); + await umbracoUi.media.clickConfirmEmptyRecycleBinButton(); + + // Assert + await umbracoUi.media.isMediaItemVisibleInRecycleBin(mediaFileName, false); + expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); + expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeFalsy(); + }); +});